mirror of
https://github.com/DragonMinded/bemaniutils.git
synced 2026-03-21 17:24:33 -05:00
Add code from old fork
This commit is contained in:
parent
df4a9c712d
commit
a77ad50345
7
bemani/backend/bst/__init__.py
Normal file
7
bemani/backend/bst/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from bemani.backend.bst.factory import BSTFactory
|
||||
from bemani.backend.bst.base import BSTBase
|
||||
|
||||
__all__ = [
|
||||
"BSTFactory",
|
||||
"BSTBase",
|
||||
]
|
||||
23
bemani/backend/bst/base.py
Normal file
23
bemani/backend/bst/base.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from bemani.common import Profile
|
||||
from bemani.data.types import UserID
|
||||
from bemani.backend.base import Base
|
||||
from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler
|
||||
from bemani.common import GameConstants
|
||||
from bemani.protocol import Node
|
||||
|
||||
class BSTBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
|
||||
game = GameConstants.BST
|
||||
|
||||
# Helper method that enables events based on the server config
|
||||
def get_events(self) -> Node:
|
||||
return Node.void('event_ctrl')
|
||||
|
||||
def update_score(self, extid, songid, chartid, loc, points, gauge,
|
||||
max_combo, grade, medal, fanta_count, great_count, fine_count, miss_count) -> None:
|
||||
return None
|
||||
|
||||
def unformat_player_profile(self, profile: Node, refid: str, extid: int, userid: UserID) -> Profile:
|
||||
return None
|
||||
|
||||
def format_player_profile(self, profile: Profile, userid: UserID) -> Node:
|
||||
return None
|
||||
402
bemani/backend/bst/beatstream.py
Normal file
402
bemani/backend/bst/beatstream.py
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
import copy
|
||||
import random
|
||||
import struct
|
||||
from typing import Optional, Dict, Any, List, Tuple
|
||||
import time
|
||||
|
||||
from bemani.backend.bst.base import BSTBase
|
||||
|
||||
from bemani.common import Profile, ValidatedDict, VersionConstants, ID, Time
|
||||
from bemani.backend.ess import EventLogHandler
|
||||
from bemani.data import Data, UserID, Score
|
||||
from bemani.protocol import Node
|
||||
|
||||
class Beatstream(EventLogHandler, BSTBase):
|
||||
name = "BeatStream"
|
||||
version = VersionConstants.BEATSTREAM
|
||||
#TODO: Beatstream 1 support
|
||||
|
||||
GRADE_AAA = 1
|
||||
GRADE_AA = 2
|
||||
GRADE_A = 3
|
||||
GRADE_B = 4
|
||||
GRADE_C = 5
|
||||
GRADE_D = 6
|
||||
|
||||
MEDAL_NOPLAY = 0
|
||||
MEDAL_FAILED = 1
|
||||
MEDAL_SAVED = 2
|
||||
MEDAL_CLEAR = 3
|
||||
MEDAL_FC = 4
|
||||
MEDAL_PERFECT = 5
|
||||
|
||||
def get_settings(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
Return all of our front-end modifiably settings.
|
||||
"""
|
||||
return {
|
||||
'bools': [
|
||||
{
|
||||
'name': 'Enable Local Matching',
|
||||
'tip': 'Enable local matching between games.',
|
||||
'category': 'game_config',
|
||||
'setting': 'enable_local_match',
|
||||
},
|
||||
{
|
||||
'name': 'Enable Global Matching',
|
||||
'tip': 'Enable global matching between games.',
|
||||
'category': 'game_config',
|
||||
'setting': 'enable_global_match',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
def get_events(self) -> Node:
|
||||
root = super().get_events()
|
||||
game_config = self.get_game_config()
|
||||
|
||||
# Campaign
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 0))
|
||||
data.add_child(Node.s32('phase', 18))
|
||||
root.add_child(data)
|
||||
|
||||
# Beast crissis
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 1))
|
||||
data.add_child(Node.s32('phase', 4))
|
||||
root.add_child(data)
|
||||
|
||||
# 5th KAC screen on the demo reel
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 1))
|
||||
root.add_child(data)
|
||||
|
||||
# Eamuse app screenshots
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 2))
|
||||
root.add_child(data)
|
||||
|
||||
# Enables continues
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 3))
|
||||
root.add_child(data)
|
||||
|
||||
# Allows 3 stage with paseli
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 4))
|
||||
root.add_child(data)
|
||||
|
||||
# Local matching at start of credit enable
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 7))
|
||||
if game_config.get_bool('enable_local_match'):
|
||||
root.add_child(data)
|
||||
|
||||
# Controlls floor infection on attract screen ONLY
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 8))
|
||||
root.add_child(data)
|
||||
|
||||
# Global matching during song loading enable
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 12))
|
||||
if game_config.get_bool('enable_global_match'):
|
||||
root.add_child(data)
|
||||
|
||||
# Unlocks the bemani rockin fes songs
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 13))
|
||||
root.add_child(data)
|
||||
|
||||
# Enables Illil partner addition
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 16))
|
||||
root.add_child(data)
|
||||
|
||||
# Controls notifs when carding in
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 4))
|
||||
data.add_child(Node.s32('phase', 31))
|
||||
root.add_child(data)
|
||||
|
||||
# Courses
|
||||
# 1 = 1-12, 2 = 13 and 14, 3 = 15, 4 = kami
|
||||
# any other value disables courses
|
||||
# need to set 1-4 to enable all courses
|
||||
for x in range(1,5):
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 7))
|
||||
data.add_child(Node.s32('phase', x))
|
||||
root.add_child(data)
|
||||
|
||||
# Beast Hacker
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 8))
|
||||
data.add_child(Node.s32('phase', 10)) # Phase 1 - 9, plus unused 10th
|
||||
root.add_child(data)
|
||||
|
||||
# First play free
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 1100))
|
||||
data.add_child(Node.s32('phase', 2))
|
||||
root.add_child(data)
|
||||
|
||||
return root
|
||||
|
||||
# Helper method to unformat the player profile into a ValidatedDict for the DB
|
||||
def unformat_player_profile(self, profile: Node) -> Profile:
|
||||
userid = self.data.local.user.from_extid(self.game, self.version, profile.child_value('account/usrid'))
|
||||
ret = Profile(self.game, self.version, profile.child_value('account/rid'), profile.child_value('account/usrid'))
|
||||
|
||||
# Account
|
||||
next_tpc = int(profile.child_value('account/tpc')) + 1
|
||||
ret.replace_int('usrid', int(profile.child_value('account/usrid')))
|
||||
ret.replace_int('tpc', next_tpc)
|
||||
ret.replace_int('dpc', int(profile.child_value('account/dpc')))
|
||||
ret.replace_int('crd', int(profile.child_value('account/crd')))
|
||||
ret.replace_int('brd', int(profile.child_value('account/brd')))
|
||||
ret.replace_int('tdc', int(profile.child_value('account/tdc')))
|
||||
ret.replace_str('lid', profile.child_value('account/lid'))
|
||||
ret.replace_int('ver', int(profile.child_value('account/ver')))
|
||||
|
||||
# Base
|
||||
ret.replace_str('name', profile.child_value('base/name'))
|
||||
ret.replace_int('brnk', int(profile.child_value('base/brnk')))
|
||||
ret.replace_int('bcnum', int(profile.child_value('base/bcnum')))
|
||||
ret.replace_int('lcnum', int(profile.child_value('base/lcnum')))
|
||||
ret.replace_int('volt', int(profile.child_value('base/volt')))
|
||||
ret.replace_int('gold', int(profile.child_value('base/gold')))
|
||||
ret.replace_int('lmid', int(profile.child_value('base/lmid')))
|
||||
ret.replace_int('lgrd', int(profile.child_value('base/lgrd')))
|
||||
ret.replace_int('lsrt', int(profile.child_value('base/lsrt')))
|
||||
ret.replace_int('ltab', int(profile.child_value('base/ltab')))
|
||||
ret.replace_int('splv', int(profile.child_value('base/splv')))
|
||||
ret.replace_int('pref', int(profile.child_value('base/pref')))
|
||||
|
||||
# Base2
|
||||
ret.replace_int('lcid', int(profile.child_value('base2/lcid')))
|
||||
ret.replace_int('hat', int(profile.child_value('base2/hat')))
|
||||
|
||||
# Items stored as achievements
|
||||
items = profile.child('item')
|
||||
if items is not None and int(profile.child_value('account/usrid')) != 0:
|
||||
for i in items.children:
|
||||
self.data.local.user.put_achievement(self.game, self.version, userid, i.child_value('id'),
|
||||
f"item_{i.child_value('type')}", {"type": i.child_value('type'), "param": i.child_value('param'),
|
||||
"count": i.child_value('count')})
|
||||
|
||||
# Customize
|
||||
custom = profile.child_value('customize/custom')
|
||||
if custom is not None:
|
||||
customize = []
|
||||
for i in custom:
|
||||
customize.append(i)
|
||||
ret.replace_int_array('custom', 16, custom)
|
||||
|
||||
# Tips
|
||||
ret.replace_int('last_tips', profile.child_value('tips/last_tips'))
|
||||
|
||||
# Beast hacker
|
||||
hacker = profile.child("hacker")
|
||||
for x in hacker.children:
|
||||
self.data.local.user.put_achievement(self.game, self.version, userid, x.child_value("id"), "hacker", {
|
||||
"state0": x.child_value("state0"), "state1": x.child_value("state1"), "state2": x.child_value("state2"),
|
||||
"state3": x.child_value("state3"), "state4": x.child_value("state4")
|
||||
})
|
||||
return ret
|
||||
|
||||
def handle_info_stagedata_write_request(self, request: Node) -> Node:
|
||||
userid = request.child_value('user_id')
|
||||
musicid = request.child_value('select_music_id')
|
||||
chartid = request.child_value('select_grade')
|
||||
location = self.get_machine_id()
|
||||
points = request.child_value('result_score')
|
||||
gauge = request.child_value('result_clear_gauge')
|
||||
max_combo = request.child_value('result_max_combo')
|
||||
grade = request.child_value('result_grade')
|
||||
medal = request.child_value('result_medal')
|
||||
fantastic_count = request.child_value('result_fanta')
|
||||
great_count = request.child_value('result_great')
|
||||
fine_count = request.child_value('result_fine')
|
||||
miss_count = request.child_value('result_miss')
|
||||
|
||||
self.update_score(userid, musicid, chartid, location, points, gauge,
|
||||
max_combo, grade, medal, fantastic_count, great_count, fine_count, miss_count)
|
||||
|
||||
return Node.void('player2')
|
||||
|
||||
def handle_pcb_boot_request(self, request: Node) -> Node:
|
||||
machine = self.data.local.machine.get_machine(self.config['machine']['pcbid'])
|
||||
arcade = self.data.local.machine.get_arcade(machine.arcade)
|
||||
pcb2 = Node.void('pcb')
|
||||
pcb2.set_attribute('status', '0')
|
||||
sinfo = Node.void('sinfo')
|
||||
pcb2.add_child(sinfo)
|
||||
|
||||
sinfo_nm = Node.string('nm', arcade.name)
|
||||
sinfo_cl_enbl = Node.bool('cl_enbl', False)
|
||||
sinfo_cl_h = Node.u8('cl_h', 0)
|
||||
sinfo_cl_m = Node.u8('cl_m', 0)
|
||||
sinfo_shop_flag = Node.bool('shop_flag', True)
|
||||
|
||||
sinfo.add_child(sinfo_nm)
|
||||
sinfo.add_child(sinfo_cl_enbl)
|
||||
sinfo.add_child(sinfo_cl_h)
|
||||
sinfo.add_child(sinfo_cl_m)
|
||||
sinfo.add_child(sinfo_shop_flag)
|
||||
|
||||
return pcb2
|
||||
|
||||
def handle_info_common_request(self, request: Node) -> Node:
|
||||
info2 = Node.void('info')
|
||||
info2.set_attribute('status', '0')
|
||||
|
||||
event_ctrl = self.get_events()
|
||||
info2.add_child(event_ctrl)
|
||||
|
||||
return info2
|
||||
|
||||
# Called after settings_write, not sure what it does
|
||||
def handle_info_music_count_read_request(self, request: Node) -> Node:
|
||||
info2 = Node.void('info2')
|
||||
record = Node.void('record')
|
||||
record.add_child(Node.void('rec'))
|
||||
record.add_child(Node.void('rate'))
|
||||
info2.add_child(record)
|
||||
return info2
|
||||
|
||||
# Called after music_count_read. Might have something to do with song popularity?
|
||||
def handle_info_music_ranking_read_request(self, Request: Node) -> Node:
|
||||
info2 = Node.void('info2')
|
||||
ranking = Node.void('ranking')
|
||||
ranking.add_child(Node.void('count'))
|
||||
info2.add_child(ranking)
|
||||
return info2
|
||||
|
||||
# First call when somebody cards in, returns the status of a few crossover events
|
||||
def handle_player_start_request(self, request: Node) -> Node:
|
||||
userid = self.data.local.user.from_refid(self.game, self.version, request.child_value('rid'))
|
||||
profile = self.data.local.user.get_profile(self.game, self.version, userid)
|
||||
player = Node.void('player')
|
||||
|
||||
plytime = Node.s32('plyid', 0)
|
||||
if profile is not None:
|
||||
plytime = Node.s32('plyid', profile.get_int("plyid", 1))
|
||||
|
||||
player.add_child(plytime)
|
||||
|
||||
start_time = Node.u64('start_time', Time.now() * 1000)
|
||||
player.add_child(start_time)
|
||||
|
||||
return player
|
||||
|
||||
def handle_lobby_entry_request(self, request: Node) -> Node:
|
||||
lobby2 = Node.void('lobby2')
|
||||
lobby2.add_child(Node.s32('interval', 120))
|
||||
lobby2.add_child(Node.s32('interval_p', 120))
|
||||
|
||||
global_ip = "".join(str(e) + "." for e in request.child_value('e/ga'))[:-1],
|
||||
local_ip = "".join(str(e) + "." for e in request.child_value('e/la'))[:-1],
|
||||
session = self.data.local.lobby.get_play_session_info_by_ip(self.game, self.version, global_ip, local_ip)
|
||||
userid = 0
|
||||
requested_lobby_id = request.child_value('e/eid')
|
||||
lobby = None
|
||||
|
||||
if userid is not None:
|
||||
userid = session.get_int("userid")
|
||||
|
||||
if requested_lobby_id > 0:
|
||||
# Get the detales of the requested lobby
|
||||
lobby = self.data.local.lobby.get_lobby_by_id(self.game, self.version, requested_lobby_id)
|
||||
|
||||
if lobby is None:
|
||||
# Make a new lobby
|
||||
self.data.local.lobby.put_lobby(
|
||||
self.game,
|
||||
self.version,
|
||||
userid,
|
||||
{
|
||||
'ver': request.child_value('e/ver'),
|
||||
'mid': request.child_value('e/mid'),
|
||||
'rest': request.child_value('e/rest'),
|
||||
'uid': request.child_value('e/uid'),
|
||||
'mmode': request.child_value('e/mmode'),
|
||||
'mg': request.child_value('e/mg'),
|
||||
'mopt': request.child_value('e/mopt'),
|
||||
'lid': request.child_value('e/lid'),
|
||||
'sn': request.child_value('e/sn'),
|
||||
'pref': request.child_value('e/pref'),
|
||||
'eatime': request.child_value('e/eatime'),
|
||||
'ga': request.child_value('e/ga'),
|
||||
'gp': request.child_value('e/gp'),
|
||||
'la': request.child_value('e/la'),
|
||||
}
|
||||
)
|
||||
|
||||
lobby = self.data.local.lobby.get_lobby(self.game, self.version, userid)
|
||||
|
||||
lobby2.add_child(Node.s32('eid', lobby.get_int('id')))
|
||||
e = Node.void('e')
|
||||
lobby2.add_child(e)
|
||||
e.add_child(Node.s32('eid', lobby.get_int('id')))
|
||||
e.add_child(Node.u8('ver', lobby.get_int('ver')))
|
||||
e.add_child(Node.u16('mid', lobby.get_int('mid')))
|
||||
e.add_child(Node.u8('rest', lobby.get_int('rest')))
|
||||
e.add_child(Node.s32('uid', lobby.get_int('mmode')))
|
||||
e.add_child(Node.s32('mmode', lobby.get_int('mmode')))
|
||||
e.add_child(Node.s16('mg', lobby.get_int('mg')))
|
||||
e.add_child(Node.s32('mopt', lobby.get_int('mopt')))
|
||||
e.add_child(Node.string('lid', lobby.get_str('lid')))
|
||||
e.add_child(Node.string('sn', lobby.get_str('sn')))
|
||||
e.add_child(Node.u8('pref', lobby.get_int('pref')))
|
||||
e.add_child(Node.s16('eatime', lobby.get_int('eatime')))
|
||||
e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4)))
|
||||
e.add_child(Node.u16('gp', lobby.get_int('gp')))
|
||||
e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4)))
|
||||
|
||||
return lobby2
|
||||
|
||||
def handle_shop_setting_write_request(self, request: Node) -> Node:
|
||||
shop2 = Node.void('shop2')
|
||||
#TODO: shop settings saving
|
||||
return shop2
|
||||
|
||||
def handle_player_end_request(self, request: Node) -> Node:
|
||||
self.data.local.lobby.destroy_play_session_info(self.game, self.version,
|
||||
self.data.local.user.from_refid(self.game, self.version, request.child_value("rid")))
|
||||
return Node.void('player2')
|
||||
|
||||
# Called either when carding out or creating a new profile
|
||||
def handle_player_write_request(self, request: Node) -> Node:
|
||||
refid = request.child_value('pdata/account/rid')
|
||||
extid = request.child_value('pdata/account/usrid')
|
||||
pdata = request.child('pdata')
|
||||
reply = Node.void('player2')
|
||||
|
||||
profile = self.unformat_player_profile(pdata)
|
||||
userid = self.data.remote.user.from_refid(self.game, self.version, refid) # Get the userid for the refid
|
||||
|
||||
# The game always wants the extid sent back, so we only have to look it up if it's 0
|
||||
if extid == 0:
|
||||
extid = self.data.local.user.get_extid(self.game, self.version, userid) # Get the extid for the profile we just saved
|
||||
profile.replace_int('usrid', extid) # Replace the extid in the profile with the one generated by butils
|
||||
|
||||
self.put_profile(userid, profile) # Save the profile
|
||||
|
||||
node_uid = Node.s32('uid', extid) # Send it back as a node
|
||||
reply.add_child(node_uid)
|
||||
|
||||
return reply
|
||||
|
||||
def handle_info_matching_data_write(self, request: Node) -> Node:
|
||||
return Node.void("info")
|
||||
869
bemani/backend/bst/beatstream2.py
Normal file
869
bemani/backend/bst/beatstream2.py
Normal file
|
|
@ -0,0 +1,869 @@
|
|||
from typing import Dict, Any
|
||||
|
||||
from bemani.common import Profile, ValidatedDict, VersionConstants, ID, Time
|
||||
from bemani.backend.bst.base import BSTBase
|
||||
from bemani.common import VersionConstants, Time
|
||||
from bemani.backend.ess import EventLogHandler
|
||||
from bemani.common.constants import BroadcastConstants, GameConstants, VersionConstants
|
||||
from bemani.common.validateddict import ValidatedDict
|
||||
from bemani.data.types import UserID
|
||||
from bemani.protocol import Node
|
||||
from bemani.data import Song
|
||||
|
||||
class Beatstream2(EventLogHandler, BSTBase):
|
||||
name = "BeatStream アニムトライヴ"
|
||||
version = VersionConstants.BEATSTREAM_2
|
||||
|
||||
GRADE_AAA_RED = 0
|
||||
GRADE_AAA = 1
|
||||
GRADE_AA = 2
|
||||
GRADE_A = 3
|
||||
GRADE_B = 4
|
||||
GRADE_C = 5
|
||||
GRADE_D = 6
|
||||
|
||||
MEDAL_NOPLAY = 0
|
||||
MEDAL_FAILED = 1
|
||||
MEDAL_SAVED = 2
|
||||
MEDAL_CLEAR = 3
|
||||
MEDAL_FC = 4
|
||||
MEDAL_PERFECT = 5
|
||||
|
||||
@classmethod
|
||||
def get_settings(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
Return all of our front-end modifiably settings.
|
||||
"""
|
||||
return {
|
||||
'bools': [
|
||||
{
|
||||
'name': 'Enable Local Matching',
|
||||
'tip': 'Enable local matching between games.',
|
||||
'category': 'game_config',
|
||||
'setting': 'enable_local_match',
|
||||
},
|
||||
{
|
||||
'name': 'Enable Global Matching',
|
||||
'tip': 'Enable global matching between games.',
|
||||
'category': 'game_config',
|
||||
'setting': 'enable_global_match',
|
||||
},
|
||||
{
|
||||
'name': 'Force Unlock Museca Crossover',
|
||||
'tip': 'Gives the Illil avitar to players even if they haven\'t played Museca 1+1/2',
|
||||
'category': 'game_config',
|
||||
'setting': 'force_unlock_museca_xover',
|
||||
},
|
||||
{
|
||||
'name': 'Force Unlock Floor Infection',
|
||||
'tip': 'Force unlocks the Floor Infection Part 20 SDVX 3 crossover songs',
|
||||
'category': 'game_config',
|
||||
'setting': 'force_unlock_sdvx_xover',
|
||||
},
|
||||
{
|
||||
'name': 'Enable Pop\'n Crossover',
|
||||
'tip': 'Enables the BeaSt pop\'n tanabata matsuri event',
|
||||
'category': 'game_config',
|
||||
'setting': 'enable_popn_xover',
|
||||
},
|
||||
{
|
||||
'name': 'Enable REFLEC Crossover',
|
||||
'tip': 'Enables the HAPPY☆SUMMER CAMPAIGN event',
|
||||
'category': 'game_config',
|
||||
'setting': 'enable_reflec_xover',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
def get_events(self) -> Node:
|
||||
root = super().get_events()
|
||||
game_config = self.get_game_config()
|
||||
|
||||
# Campaign
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 0))
|
||||
data.add_child(Node.s32('phase', 18))
|
||||
root.add_child(data)
|
||||
|
||||
# Beast crissis
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 1))
|
||||
data.add_child(Node.s32('phase', 4))
|
||||
root.add_child(data)
|
||||
|
||||
# 5th KAC screen on the demo reel
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 1))
|
||||
root.add_child(data)
|
||||
|
||||
# Eamuse app screenshots
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 2))
|
||||
# Only enable if we have webhooks set up for this game
|
||||
if self.config.webhooks.discord[self.game]:
|
||||
root.add_child(data)
|
||||
|
||||
# Enables continues
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 3))
|
||||
root.add_child(data)
|
||||
|
||||
# Allows 3 stage with paseli
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 4))
|
||||
root.add_child(data)
|
||||
|
||||
# Local matching at start of credit enable
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 7))
|
||||
if game_config.get_bool('enable_local_match'):
|
||||
root.add_child(data)
|
||||
|
||||
# Controlls floor infection on attract screen ONLY
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 8))
|
||||
root.add_child(data)
|
||||
|
||||
# Global matching during song loading enable
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 12))
|
||||
if game_config.get_bool('enable_global_match'):
|
||||
root.add_child(data)
|
||||
|
||||
# Unlocks the bemani rockin fes songs
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 13))
|
||||
root.add_child(data)
|
||||
|
||||
# Enables Illil partner addition if is_play_museca is True
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 3))
|
||||
data.add_child(Node.s32('phase', 16))
|
||||
root.add_child(data)
|
||||
|
||||
# Controls notifs when carding in
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 4))
|
||||
data.add_child(Node.s32('phase', 31))
|
||||
root.add_child(data)
|
||||
|
||||
# Courses
|
||||
# 1 = 1-12, 2 = 13 and 14, 3 = 15, 4 = kami
|
||||
# any other value disables courses
|
||||
# need to set 1-4 to enable all courses
|
||||
for x in range(1,5):
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 7))
|
||||
data.add_child(Node.s32('phase', x))
|
||||
root.add_child(data)
|
||||
|
||||
# Beast Hacker
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 8))
|
||||
data.add_child(Node.s32('phase', 10)) # Phase 1 - 9, plus unused 10th
|
||||
root.add_child(data)
|
||||
|
||||
# First play free
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('type', 1100))
|
||||
data.add_child(Node.s32('phase', 2))
|
||||
root.add_child(data)
|
||||
|
||||
return root
|
||||
|
||||
def update_score(self, extid, songid, chartid, loc, points, gauge,
|
||||
max_combo, grade, medal, fanta_count, great_count, fine_count, miss_count) -> None:
|
||||
butils_userid = self.data.local.user.from_extid(self.game, self.version, extid)
|
||||
if butils_userid is None:
|
||||
return None
|
||||
|
||||
old_score = self.data.local.music.get_score(self.game, self.version, butils_userid, songid, chartid)
|
||||
|
||||
if old_score is not None:
|
||||
new_record = old_score.points < points
|
||||
new_points = max(old_score.points, points)
|
||||
new_data = old_score.data
|
||||
else:
|
||||
new_record = True
|
||||
new_points = points
|
||||
new_data = ValidatedDict()
|
||||
|
||||
new_loc = loc
|
||||
# Only update the location if it's a new high score
|
||||
if not new_record:
|
||||
new_loc = old_score.location
|
||||
|
||||
new_gauge = max(new_data.get_int('gauge'), gauge)
|
||||
new_data.replace_int('gauge', new_gauge)
|
||||
|
||||
new_max_combo = max(new_data.get_int('max_combo'), max_combo)
|
||||
new_data.replace_int('max_combo', new_max_combo)
|
||||
|
||||
# Grades get better as their value decreases
|
||||
if "grade" in new_data:
|
||||
new_grade = min(new_data.get_int('grade'), grade)
|
||||
new_data.replace_int('grade', new_grade)
|
||||
else:
|
||||
new_data.replace_int('grade', grade)
|
||||
|
||||
new_medal = max(new_data.get_int('medal'), medal)
|
||||
new_data.replace_int('medal', new_medal)
|
||||
|
||||
# Only replace notecoutns if we upscored
|
||||
if new_record:
|
||||
new_data.replace_int('fanta_count', fanta_count)
|
||||
new_data.replace_int('great_count', great_count)
|
||||
new_data.replace_int('fine_count', fine_count)
|
||||
new_data.replace_int('miss_count', miss_count)
|
||||
|
||||
data = {
|
||||
"gauge": gauge,
|
||||
"max_combo": max_combo,
|
||||
"grade": grade,
|
||||
"medal" : medal,
|
||||
"fanta_count": fanta_count,
|
||||
"great_count": great_count,
|
||||
"fine_count": fine_count,
|
||||
"miss_count": miss_count
|
||||
}
|
||||
|
||||
self.data.local.music.put_attempt(
|
||||
self.game,
|
||||
self.version,
|
||||
butils_userid,
|
||||
songid,
|
||||
chartid,
|
||||
loc,
|
||||
points,
|
||||
data,
|
||||
new_record,
|
||||
Time.now())
|
||||
|
||||
self.data.local.music.put_score(self.game,
|
||||
self.version,
|
||||
butils_userid,
|
||||
songid,
|
||||
chartid,
|
||||
new_loc,
|
||||
new_points,
|
||||
new_data,
|
||||
new_record,
|
||||
Time.now())
|
||||
|
||||
# Helper method to formay a player profile as a Node that the game will accept
|
||||
def format_player_profile(self, profile: Profile, userid: UserID) -> Node:
|
||||
root = Node.void("player2")
|
||||
pdata = Node.void('pdata')
|
||||
root.add_child(pdata)
|
||||
|
||||
account = Node.void('account')
|
||||
account.add_child(Node.s32('usrid', profile.extid))
|
||||
account.add_child(Node.s32('is_takeover', profile.get_int("is_takeover")))
|
||||
account.add_child(Node.s32('tpc', profile.get_int("tpc")))
|
||||
account.add_child(Node.s32('dpc', profile.get_int("dpc")))
|
||||
account.add_child(Node.s32('crd', profile.get_int("crd")))
|
||||
account.add_child(Node.s32('brd', profile.get_int("brd")))
|
||||
account.add_child(Node.s32('tdc', profile.get_int("tdc")))
|
||||
account.add_child(Node.s32('intrvld', profile.get_int("intrvld")))
|
||||
account.add_child(Node.s16('ver', profile.get_int("ver")))
|
||||
account.add_child(Node.u64('pst', profile.get_int("pst")))
|
||||
account.add_child(Node.u64('st', Time.now() * 1000))
|
||||
account.add_child(Node.bool('ea', profile.get_int("ea", True)))
|
||||
pdata.add_child(account)
|
||||
|
||||
base = Node.void('base')
|
||||
base.add_child(Node.string('name', profile.get_str('name')))
|
||||
base.add_child(Node.s8('brnk', profile.get_int('brnk')))
|
||||
base.add_child(Node.s8('bcnum', profile.get_int('bcnum')))
|
||||
base.add_child(Node.s8('lcnum', profile.get_int('lcnum')))
|
||||
base.add_child(Node.s32('volt', profile.get_int('volt')))
|
||||
base.add_child(Node.s32('gold', profile.get_int('gold')))
|
||||
base.add_child(Node.s32('lmid', profile.get_int('lmid')))
|
||||
base.add_child(Node.s8('lgrd', profile.get_int('lgrd')))
|
||||
base.add_child(Node.s8('lsrt', profile.get_int('lsrt')))
|
||||
base.add_child(Node.s8('ltab', profile.get_int('ltab')))
|
||||
base.add_child(Node.s8('splv', profile.get_int('splv')))
|
||||
base.add_child(Node.s8('pref', profile.get_int('pref')))
|
||||
base.add_child(Node.s32('lcid', profile.get_int('lcid')))
|
||||
base.add_child(Node.s32('hat', profile.get_int('hat')))
|
||||
pdata.add_child(base)
|
||||
|
||||
survey = Node.void('survey')
|
||||
survey.add_child(Node.s8('motivate', 0)) # Needs testing
|
||||
pdata.add_child(survey)
|
||||
|
||||
item = Node.void('item')
|
||||
hacker = Node.void('hacker')
|
||||
course = Node.void('course')
|
||||
play_log = Node.void('play_log')
|
||||
|
||||
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
|
||||
for i in achievements:
|
||||
if i.type[:4] == "item":
|
||||
info = Node.void('info')
|
||||
info.add_child(Node.s32('id', i.id))
|
||||
info.add_child(Node.s32('type', i.data.get_int('type')))
|
||||
info.add_child(Node.s32('param', i.data.get_int('param')))
|
||||
info.add_child(Node.s32('count', i.data.get_int('count')))
|
||||
item.add_child(info)
|
||||
|
||||
elif i.type == "hacker":
|
||||
info = Node.void('info')
|
||||
info.add_child(Node.s32('id', i.id))
|
||||
info.add_child(Node.s8('state0', i.data.get_int("state0")))
|
||||
info.add_child(Node.s8('state1', i.data.get_int("state1")))
|
||||
info.add_child(Node.s8('state2', i.data.get_int("state2")))
|
||||
info.add_child(Node.s8('state3', i.data.get_int("state3")))
|
||||
info.add_child(Node.s8('state4', i.data.get_int("state4")))
|
||||
info.add_child(Node.u64('update_time', i.data.get_int('update_time', Time.now() * 1000))) # update_time is required or the profile will fail
|
||||
hacker.add_child(info)
|
||||
|
||||
elif i.type == "course":
|
||||
info = Node.void('record')
|
||||
info.add_child(Node.s32('course_id', i.data.get_int("course_id")))
|
||||
info.add_child(Node.s32('play', i.data.get_int("clear"))) # Play_id?
|
||||
info.add_child(Node.bool('is_touch', i.data.get_bool("is_touch", False))) # ???
|
||||
info.add_child(Node.s32('clear', i.data.get_int("clear"))) # Not sure what it wants here...
|
||||
info.add_child(Node.s32('gauge', i.data.get_int("gauge")))
|
||||
info.add_child(Node.s32('score', i.data.get_int("score")))
|
||||
info.add_child(Node.s32('grade', i.data.get_int("grade")))
|
||||
info.add_child(Node.s32('medal', i.data.get_int("medal")))
|
||||
info.add_child(Node.s32('combo', i.data.get_int("combo")))
|
||||
course.add_child(info)
|
||||
|
||||
rate = Node.void('rate')
|
||||
rate.add_child(Node.s32('course_id', i.data.get_int("course_id")))
|
||||
rate.add_child(Node.s32('play_count', i.data.get_int("play_count")))
|
||||
rate.add_child(Node.s32('clear_count', i.data.get_int("clear_count")))
|
||||
course.add_child(rate)
|
||||
|
||||
elif i.type == "crysis":
|
||||
crysis = Node.void('crysis')
|
||||
crysis.add_child(Node.s32('id', i.id))
|
||||
crysis.add_child(Node.s8('step', i.data.get_int("step")))
|
||||
crysis.add_child(Node.s32('r_gauge', i.data.get_int("r_gauge")))
|
||||
crysis.add_child(Node.s8('r_state', i.data.get_int("r_state")))
|
||||
play_log.add_child(crysis)
|
||||
|
||||
pdata.add_child(item)
|
||||
pdata.add_child(course)
|
||||
pdata.add_child(hacker)
|
||||
pdata.add_child(play_log)
|
||||
|
||||
customize = Node.void('customize')
|
||||
customize.add_child(Node.u16_array('custom', profile.get_int_array('custom', 16)))
|
||||
pdata.add_child(customize)
|
||||
|
||||
tips = Node.void('tips')
|
||||
tips.add_child(Node.s32('last_tips', profile.get_int('last_tips')))
|
||||
pdata.add_child(tips)
|
||||
|
||||
record = Node.void('record')
|
||||
scores = self.data.local.music.get_scores(self.game, self.version, userid)
|
||||
for i in scores:
|
||||
rec = Node.void('rec')
|
||||
rec.add_child(Node.s32('music_id', i.id))
|
||||
rec.add_child(Node.s32('note_level', i.chart))
|
||||
rec.add_child(Node.s32('play_count', i.plays))
|
||||
rec.add_child(Node.s32('clear_count', i.data.get_int('clear_count')))
|
||||
rec.add_child(Node.s32('best_gauge', i.data.get_int('gauge')))
|
||||
rec.add_child(Node.s32('best_score', i.points))
|
||||
rec.add_child(Node.s32('best_grade', i.data.get_int('grade')))
|
||||
rec.add_child(Node.s32('best_medal', i.data.get_int('medal')))
|
||||
record.add_child(rec)
|
||||
pdata.add_child(record)
|
||||
|
||||
return root
|
||||
|
||||
# Helper method to unformat the player profile into a ValidatedDict for the DB
|
||||
def unformat_player_profile(self, profile: Node, refid: str, extid: int, userid: UserID) -> Profile:
|
||||
ret = Profile(self.game, self.version, refid, extid)
|
||||
|
||||
# Account
|
||||
next_tpc = int(profile.child_value('account/tpc')) + 1
|
||||
ret.replace_int('is_takeover', int(profile.child_value('account/is_takeover')))
|
||||
ret.replace_int('tpc', next_tpc)
|
||||
ret.replace_int('dpc', int(profile.child_value('account/dpc')))
|
||||
ret.replace_int('crd', int(profile.child_value('account/crd')))
|
||||
ret.replace_int('brd', int(profile.child_value('account/brd')))
|
||||
ret.replace_int('tdc', int(profile.child_value('account/tdc')))
|
||||
ret.replace_str('lid', profile.child_value('account/lid'))
|
||||
ret.replace_int('ver', int(profile.child_value('account/ver')))
|
||||
ret.replace_int('st', int(profile.child_value('account/st')))
|
||||
|
||||
# Base
|
||||
ret.replace_str('name', profile.child_value('base/name'))
|
||||
ret.replace_int('brnk', int(profile.child_value('base/brnk')))
|
||||
ret.replace_int('bcnum', int(profile.child_value('base/bcnum')))
|
||||
ret.replace_int('lcnum', int(profile.child_value('base/lcnum')))
|
||||
ret.replace_int('volt', int(profile.child_value('base/volt')))
|
||||
ret.replace_int('gold', int(profile.child_value('base/gold')))
|
||||
ret.replace_int('lmid', int(profile.child_value('base/lmid')))
|
||||
ret.replace_int('lgrd', int(profile.child_value('base/lgrd')))
|
||||
ret.replace_int('lsrt', int(profile.child_value('base/lsrt')))
|
||||
ret.replace_int('ltab', int(profile.child_value('base/ltab')))
|
||||
ret.replace_int('splv', int(profile.child_value('base/splv')))
|
||||
ret.replace_int('pref', int(profile.child_value('base/pref')))
|
||||
ret.replace_int('lcid', int(profile.child_value('base/lcid')))
|
||||
ret.replace_int('hat', int(profile.child_value('base/hat')))
|
||||
|
||||
# Items stored as achievements
|
||||
items = profile.child('item')
|
||||
if items is not None:
|
||||
for i in items.children:
|
||||
self.data.local.user.put_achievement(self.game, self.version, userid, i.child_value('id'),
|
||||
f"item_{i.child_value('type')}", {"type": i.child_value('type'), "param": i.child_value('param'),
|
||||
"count": i.child_value('count')})
|
||||
|
||||
# Customize
|
||||
custom = profile.child_value('customize/custom')
|
||||
if custom is not None:
|
||||
customize = []
|
||||
for i in custom:
|
||||
customize.append(i)
|
||||
ret.replace_int_array('custom', 16, custom)
|
||||
|
||||
# Beast Crysis and multiplayer data
|
||||
play_log = profile.child('play_log')
|
||||
|
||||
if play_log is not None:
|
||||
for i in play_log.children:
|
||||
if i.name == "crysis":
|
||||
self.data.local.user.put_achievement(self.game, self.version, userid, i.child_value('id'),
|
||||
"crysis", {"step": i.child_value('step'), "r_gauge": i.child_value('r_gauge'),
|
||||
"r_state": i.child_value('r_state')})
|
||||
|
||||
elif i.name == "onmatch":
|
||||
pass
|
||||
|
||||
# Tips
|
||||
ret.replace_int('last_tips', profile.child_value('tips/last_tips'))
|
||||
|
||||
# Beast hacker
|
||||
hacker = profile.child("hacker")
|
||||
for x in hacker.children:
|
||||
self.data.local.user.put_achievement(self.game, self.version, userid, x.child_value("id"), "hacker", {
|
||||
"state0": x.child_value("state0"), "state1": x.child_value("state1"), "state2": x.child_value("state2"),
|
||||
"state3": x.child_value("state3"), "state4": x.child_value("state4"), 'update_time': Time.now() * 1000
|
||||
})
|
||||
return ret
|
||||
|
||||
# First call when somebody cards in, returns the status of a few crossover events
|
||||
def handle_player2_start_request(self, request: Node, is_continue: bool = False) -> Node:
|
||||
userid = self.data.local.user.from_refid(self.game, self.version, request.child_value('rid'))
|
||||
player2 = Node.void('player2')
|
||||
play_id = 0
|
||||
game_config = self.get_game_config()
|
||||
|
||||
if userid is not None:
|
||||
if not is_continue:
|
||||
self.data.local.lobby.put_play_session_info(
|
||||
self.game,
|
||||
self.version,
|
||||
userid,
|
||||
# This is needed due to lobby2/entry's lack of user info
|
||||
{ "pcbid": self.config.machine.pcbid }
|
||||
)
|
||||
|
||||
profile = self.data.local.user.get_profile(self.game, self.version, userid)
|
||||
if profile is None:
|
||||
profile = self.data.local.user.get_profile(self.game, self.version - 1, userid)
|
||||
|
||||
if profile is not None:
|
||||
play_id = profile.get_int('tpc') + 1
|
||||
|
||||
# Session stuff, and resend global defaults
|
||||
player2.add_child(Node.s32('plyid', play_id))
|
||||
|
||||
start_time = Node.u64('start_time', Time.now() * 1000)
|
||||
player2.add_child(start_time)
|
||||
|
||||
# Crossover events
|
||||
# HAPPY☆SUMMER CAMPAIGN possibly?
|
||||
reflec_collabo = Node.bool('reflec_collabo', game_config.get_bool("enable_reflec_xover"))
|
||||
player2.add_child(reflec_collabo)
|
||||
|
||||
# BeaSt pop'n tanabata matsuri possibly?
|
||||
pop_collabo = Node.bool('pop_collabo', game_config.get_bool("enable_popn_xover"))
|
||||
player2.add_child(pop_collabo)
|
||||
|
||||
# Floor Infection Part 20
|
||||
floor_infection = Node.void('floor_infection')
|
||||
player2.add_child(floor_infection)
|
||||
fi_event = Node.void('event')
|
||||
floor_infection.add_child(fi_event)
|
||||
fi_event.add_child(Node.s32('infection_id', 20))
|
||||
|
||||
if game_config.get_bool("force_unlock_sdvx_xover"):
|
||||
# TODO: Test if 7 actually is the magic number, given there's only 3 songs
|
||||
fi_event.add_child(Node.s32('music_list', 7))
|
||||
fi_event.add_child(Node.bool('is_complete', True))
|
||||
else:
|
||||
# TODO: Figure out how to track floor infection lol
|
||||
fi_event.add_child(Node.s32('music_list', 7))
|
||||
fi_event.add_child(Node.bool('is_complete', True))
|
||||
|
||||
# If you played Museca 1+1/2 at launch you got rewards in BST and other games
|
||||
museca = Node.void('museca')
|
||||
player2.add_child(museca)
|
||||
|
||||
if game_config.get_bool("enable_popn_xover"):
|
||||
museca.add_child(Node.bool('is_play_museca', True))
|
||||
else:
|
||||
m112_profile = self.data.local.user.get_profile(GameConstants.MUSECA, VersionConstants.MUSECA_1_PLUS, userid)
|
||||
museca.add_child(Node.bool('is_play_museca', m112_profile is not None))
|
||||
|
||||
return player2
|
||||
|
||||
# Called when carding in to get the player profile
|
||||
def handle_player2_read_request(self, request: Node) -> Node:
|
||||
refid = request.child_value('rid')
|
||||
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
|
||||
profile = self.get_profile(userid)
|
||||
return self.format_player_profile(profile, userid)
|
||||
|
||||
# Called either when carding out or creating a new profile
|
||||
def handle_player2_write_request(self, request: Node) -> Node:
|
||||
refid = request.child_value('pdata/account/rid')
|
||||
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
|
||||
extid = self.data.local.user.get_extid(self.game, self.version, userid)
|
||||
pdata = request.child('pdata')
|
||||
reply = Node.void('player2')
|
||||
|
||||
profile = self.unformat_player_profile(pdata, refid, extid, userid)
|
||||
|
||||
self.put_profile(userid, profile) # Save the profile
|
||||
|
||||
node_uid = Node.s32('uid', extid) # Send the extid back as a node
|
||||
reply.add_child(node_uid)
|
||||
|
||||
return reply
|
||||
|
||||
# Called whenever some kind of error happens.
|
||||
def handle_pcb2_error_request(self, request: Node) -> Node:
|
||||
return Node.void('pcb2')
|
||||
|
||||
# BST2 has to be special and have it's own boot method
|
||||
def handle_pcb2_boot_request(self, request: Node) -> Node:
|
||||
shop_id = ID.parse_machine_id(request.child_value('lid'))
|
||||
pcbid = self.data.local.machine.from_machine_id(shop_id)
|
||||
|
||||
if pcbid is not None:
|
||||
machine = self.data.local.machine.get_machine(pcbid)
|
||||
machine_name = machine.name
|
||||
close = machine.data.get_bool('close')
|
||||
hour = machine.data.get_int('hour')
|
||||
minute = machine.data.get_int('minute')
|
||||
else:
|
||||
return None
|
||||
|
||||
pcb2 = Node.void('pcb2')
|
||||
sinfo = Node.void('sinfo')
|
||||
pcb2.add_child(sinfo)
|
||||
|
||||
sinfo.add_child(Node.string('nm', machine_name))
|
||||
sinfo.add_child(Node.bool('cl_enbl', close))
|
||||
sinfo.add_child(Node.u8('cl_h', hour))
|
||||
sinfo.add_child(Node.u8('cl_m', minute))
|
||||
|
||||
return pcb2
|
||||
|
||||
# Send a list of events and phases
|
||||
def handle_info2_common_request(self, request: Node) -> Node:
|
||||
info2 = Node.void('info2')
|
||||
info2.set_attribute('status', '0')
|
||||
|
||||
event_ctrl = self.get_events()
|
||||
info2.add_child(event_ctrl)
|
||||
|
||||
return info2
|
||||
|
||||
# Called when a player registeres a new profile when they have an account
|
||||
def handle_player2_succeed_request(self, request: Node) -> Node:
|
||||
userid = self.data.local.user.from_refid(self.game, self.version, request.child_value('rid'))
|
||||
profile = self.data.local.user.get_profile(self.game, self.version - 1, userid)
|
||||
scores = self.data.local.music.get_scores(self.game, self.version - 1, userid)
|
||||
achievements = self.data.local.user.get_achievements(self.game, self.version - 1, userid)
|
||||
player2 = Node.void('player2')
|
||||
|
||||
play = Node.bool('play', profile.get_int('tpc') > 1 if profile else False)
|
||||
player2.add_child(play)
|
||||
|
||||
data = Node.void('data')
|
||||
player2.add_child(data)
|
||||
|
||||
name = Node.string('name', profile.get_str('name') if profile else "")
|
||||
data.add_child(name)
|
||||
|
||||
record = Node.void('record')
|
||||
hacker = Node.void('hacker')
|
||||
phantom = Node.void('phantom')
|
||||
|
||||
for score in scores:
|
||||
rec = Node.void('rec')
|
||||
rec.add_child(Node.s32('music_id', score.id))
|
||||
rec.add_child(Node.s32('note_level', score.chart))
|
||||
rec.add_child(Node.s32('play_count', score.plays))
|
||||
rec.add_child(Node.s32('clear_count', score.data.get_int('clear_count')))
|
||||
rec.add_child(Node.s32('best_gauge', score.data.get_int('gauge')))
|
||||
rec.add_child(Node.s32('best_score', score.points))
|
||||
rec.add_child(Node.s32('best_grade', score.data.get_int('grade')))
|
||||
rec.add_child(Node.s32('best_medal', score.data.get_int('medal')))
|
||||
record.add_child(rec)
|
||||
|
||||
for ach in achievements:
|
||||
if ach.type == "hacker":
|
||||
info = Node.void('info')
|
||||
info.add_child(Node.s32('id', ach.id))
|
||||
info.add_child(Node.s8('state0', ach.data.get_int("state0")))
|
||||
info.add_child(Node.s8('state1', ach.data.get_int("state1")))
|
||||
info.add_child(Node.s8('state2', ach.data.get_int("state2")))
|
||||
info.add_child(Node.s8('state3', ach.data.get_int("state3")))
|
||||
info.add_child(Node.s8('state4', ach.data.get_int("state4")))
|
||||
info.add_child(Node.u64('update_time', ach.data.get_int('update_time', Time.now() * 1000))) # update_time is required or the profile will fail
|
||||
hacker.add_child(info)
|
||||
|
||||
elif ach.type == "phantom":
|
||||
minfo = Node.void('minfo')
|
||||
minfo.add_child(Node.s32('mid', ach.data.get_int("mid")))
|
||||
minfo.add_child(Node.s64('cbit', ach.data.get_int("cbit")))
|
||||
minfo.add_child(Node.bool('clr', ach.data.get_bool("clr")))
|
||||
phantom.add_child(info)
|
||||
|
||||
player2.add_child(hacker)
|
||||
player2.add_child(record)
|
||||
player2.add_child(phantom)
|
||||
return player2
|
||||
|
||||
# Called during boot
|
||||
def handle_shop2_setting_write_request(self, request: Node) -> Node:
|
||||
shop2 = Node.void('shop2')
|
||||
#TODO: shop settings saving
|
||||
return shop2
|
||||
|
||||
# Called after settings_write, not sure what it does
|
||||
def handle_info2_music_count_read_request(self, request: Node) -> Node:
|
||||
info2 = Node.void('info2')
|
||||
record = Node.void('record')
|
||||
record.add_child(Node.void('rec'))
|
||||
record.add_child(Node.void('rate'))
|
||||
info2.add_child(record)
|
||||
return info2
|
||||
|
||||
# Called after music_count_read. Might have something to do with song popularity?
|
||||
def handle_info2_music_ranking_read_request(self, Request: Node) -> Node:
|
||||
info2 = Node.void('info2')
|
||||
ranking = Node.void('ranking')
|
||||
ranking.add_child(Node.void('count'))
|
||||
info2.add_child(ranking)
|
||||
return info2
|
||||
|
||||
# Called on card out
|
||||
def handle_player2_end_request(self, request: Node) -> Node:
|
||||
self.data.local.lobby.destroy_play_session_info(self.game, self.version,
|
||||
self.data.local.user.from_refid(self.game, self.version, request.child_value("rid")))
|
||||
return Node.void('player2')
|
||||
|
||||
# Called after finishing a song
|
||||
def handle_player2_stagedata_write_request(self, request: Node) -> Node:
|
||||
userid = request.child_value('user_id')
|
||||
musicid = request.child_value('select_music_id')
|
||||
chartid = request.child_value('select_grade')
|
||||
location = self.get_machine_id()
|
||||
points = request.child_value('result_score')
|
||||
gauge = request.child_value('result_clear_gauge')
|
||||
max_combo = request.child_value('result_max_combo')
|
||||
grade = request.child_value('result_grade')
|
||||
medal = request.child_value('result_medal')
|
||||
fantastic_count = request.child_value('result_fanta')
|
||||
great_count = request.child_value('result_great')
|
||||
fine_count = request.child_value('result_fine')
|
||||
miss_count = request.child_value('result_miss')
|
||||
|
||||
self.update_score(userid, musicid, chartid, location, points, gauge,
|
||||
max_combo, grade, medal, fantastic_count, great_count, fine_count, miss_count)
|
||||
|
||||
return Node.void('player2')
|
||||
|
||||
# Called after finishing a song in a course
|
||||
def handle_player2_course_stage_data_write_request(self, request: Node) -> Node:
|
||||
userid = request.child_value('user_id')
|
||||
musicid = request.child_value('select_music_id')
|
||||
chartid = request.child_value('select_grade')
|
||||
location = self.get_machine_id()
|
||||
points = request.child_value('result_score')
|
||||
gauge = request.child_value('result_clear_gauge')
|
||||
max_combo = request.child_value('result_max_combo')
|
||||
grade = request.child_value('result_grade')
|
||||
medal = request.child_value('result_medal')
|
||||
fantastic_count = request.child_value('result_fanta')
|
||||
great_count = request.child_value('result_great')
|
||||
fine_count = request.child_value('result_fine')
|
||||
miss_count = request.child_value('result_miss')
|
||||
|
||||
self.update_score(userid, musicid, chartid, location, points, gauge,
|
||||
max_combo, grade, medal, fantastic_count, great_count, fine_count, miss_count)
|
||||
|
||||
return Node.void('player2')
|
||||
|
||||
# Called after finishing a course
|
||||
def handle_player2_course_data_write_request(self, request: Node) -> Node:
|
||||
return Node.void('player2')
|
||||
|
||||
# Called frequently to see who's playing
|
||||
def handle_lobby2_get_lobby_list_request(self, request: Node) -> Node:
|
||||
lobby2 = Node.void('lobby2')
|
||||
lobby2.add_child(Node.s32('interval_sec', 10))
|
||||
lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version)
|
||||
|
||||
if lobbies is not None:
|
||||
for (user, lobby) in lobbies:
|
||||
e = Node.void('e')
|
||||
lobby2.add_child(e)
|
||||
e.add_child(Node.u16('mid', lobby.get_int('mid')))
|
||||
e.add_child(Node.u64('eatime', lobby.get_int('eatime')))
|
||||
|
||||
return lobby2
|
||||
|
||||
# Called to destroy a lobby after it's use
|
||||
def handle_lobby2_delete_request(self, request: Node) -> Node:
|
||||
self.data.local.lobby.destroy_lobby(request.child_value("eid"))
|
||||
return Node.void('lobby2')
|
||||
|
||||
# Called when matching starts
|
||||
def handle_lobby2_entry_request(self, request: Node) -> Node:
|
||||
lobby2 = Node.void('lobby2')
|
||||
lobby2.add_child(Node.s32('interval', 120))
|
||||
lobby2.add_child(Node.s32('interval_p', 120))
|
||||
lobby = None
|
||||
userid = 0
|
||||
|
||||
sessions = self.data.local.lobby.get_all_play_session_infos(self.game, self.version)
|
||||
requested_lobby_id = request.child_value('e/eid')
|
||||
|
||||
# Beatstream sends 0 identifiable user information, so this is how we have to pull the UserID
|
||||
for usr, sesh in sessions:
|
||||
if sesh.get_str('pcbid') == self.config.machine.pcbid:
|
||||
userid = usr
|
||||
break
|
||||
|
||||
if requested_lobby_id > 0:
|
||||
# Get the detales of the requested lobby
|
||||
lobby = self.data.local.lobby.get_lobby(self.game, self.version, requested_lobby_id)
|
||||
|
||||
else:
|
||||
# Make a new lobby
|
||||
self.data.local.lobby.put_lobby(
|
||||
self.game,
|
||||
self.version,
|
||||
userid,
|
||||
{
|
||||
'ver': request.child_value('e/ver'),
|
||||
'mid': request.child_value('e/mid'),
|
||||
'rest': request.child_value('e/rest'),
|
||||
'uid': request.child_value('e/uid'),
|
||||
'mmode': request.child_value('e/mmode'),
|
||||
'mg': request.child_value('e/mg'),
|
||||
'mopt': request.child_value('e/mopt'),
|
||||
'lid': request.child_value('e/lid'),
|
||||
'sn': request.child_value('e/sn'),
|
||||
'pref': request.child_value('e/pref'),
|
||||
'eatime': request.child_value('e/eatime'),
|
||||
'ga': request.child_value('e/ga'),
|
||||
'gp': request.child_value('e/gp'),
|
||||
'la': request.child_value('e/la'),
|
||||
}
|
||||
)
|
||||
|
||||
# Pull the lobby details back down to get the ID
|
||||
lobby = self.data.local.lobby.get_lobby(self.game, self.version, userid)
|
||||
|
||||
lobby2.add_child(Node.s32('eid', lobby.get_int('id')))
|
||||
e = Node.void('e')
|
||||
lobby2.add_child(e)
|
||||
e.add_child(Node.s32('eid', lobby.get_int('id')))
|
||||
e.add_child(Node.u8('ver', lobby.get_int('ver')))
|
||||
e.add_child(Node.u16('mid', lobby.get_int('mid')))
|
||||
e.add_child(Node.u8('rest', lobby.get_int('rest')))
|
||||
e.add_child(Node.s32('uid', lobby.get_int('mmode')))
|
||||
e.add_child(Node.s32('mmode', lobby.get_int('mmode')))
|
||||
e.add_child(Node.s16('mg', lobby.get_int('mg')))
|
||||
e.add_child(Node.s32('mopt', lobby.get_int('mopt')))
|
||||
e.add_child(Node.string('lid', lobby.get_str('lid')))
|
||||
e.add_child(Node.string('sn', lobby.get_str('sn')))
|
||||
e.add_child(Node.u8('pref', lobby.get_int('pref')))
|
||||
e.add_child(Node.s16('eatime', lobby.get_int('eatime')))
|
||||
e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4)))
|
||||
e.add_child(Node.u16('gp', lobby.get_int('gp')))
|
||||
e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4)))
|
||||
|
||||
return lobby2
|
||||
|
||||
# Called when a player tries to continue another credit
|
||||
def handle_player2_continue_request(self, request: Node) -> Node:
|
||||
return self.handle_player2_start_request(request, True)
|
||||
|
||||
# Called when a user request an eamuse app screenshot
|
||||
def handle_info2_result_image_write_request(self, request: Node) -> Node:
|
||||
song: Song = self.data.local.music.get_song(self.game, self.version, request.child_value("music_id"),
|
||||
request.child_value("music_level"))
|
||||
|
||||
grades = ["Red AAA", "AAA", "AA", "A", "B", "C", "D"]
|
||||
medals = ["No Play", "Failed", "Saved", "Cleared", "Full Combo", "Perfect"]
|
||||
|
||||
card_data = {
|
||||
BroadcastConstants.PLAYER_NAME: request.child_value("player_name"),
|
||||
BroadcastConstants.SONG_NAME: request.child_value("music_title"),
|
||||
BroadcastConstants.ARTIST_NAME: request.child_value("artist_name"),
|
||||
BroadcastConstants.DIFFICULTY: request.child_value("music_level"),
|
||||
BroadcastConstants.DIFFICULTY_LEVEL: song.data.get_int("difficulty"),
|
||||
#BroadcastConstants.BEAST_RANK: request.child_value("beast_rank"),
|
||||
|
||||
BroadcastConstants.SCORE: request.child_value("score"),
|
||||
#BroadcastConstants.BEST_SCORE: request.child_value("best_score"),
|
||||
BroadcastConstants.GAUGE: float(request.child_value("gauge") / 10),
|
||||
BroadcastConstants.MEDAL: medals[request.child_value("medal")],
|
||||
BroadcastConstants.GRADE: grades[request.child_value("grade")],
|
||||
|
||||
BroadcastConstants.MAX_COMBO: request.child_value("max_combo"),
|
||||
#BroadcastConstants.FANTASTIC: request.child_value("fanta"),
|
||||
#BroadcastConstants.GREAT: request.child_value("great"),
|
||||
#BroadcastConstants.FINE: request.child_value("fine"),
|
||||
#BroadcastConstants.MISS: request.child_value("miss"),
|
||||
}
|
||||
|
||||
self.data.triggers.broadcast_score(card_data, self.game, song)
|
||||
return Node.void("info2")
|
||||
|
||||
# Called when matching
|
||||
def handle_player2_matching_data_load_request(self, request: Node) -> Node:
|
||||
root = Node.void('player_matching')
|
||||
data = Node.void('data')
|
||||
data.add_child(Node.s32('id', 0)) # player id?
|
||||
data.add_child(Node.bool('fl', False)) # First Local
|
||||
data.add_child(Node.bool('fo', False)) # First Online
|
||||
root.add_child(root)
|
||||
|
||||
# Called when saving machine settings
|
||||
def handle_shop2_info_write_request(self, request: Node) -> Node:
|
||||
mech = self.data.local.machine.get_machine(self.config.machine.pcbid)
|
||||
if mech is not None:
|
||||
mech.data["close"] = request.child_value('sinfo/cl_enbl')
|
||||
mech.data["hour"] = request.child_value('sinfo/cl_h')
|
||||
mech.data["minute"] = request.child_value('sinfo/cl_m')
|
||||
mech.data["pref"] = request.child_value('sinfo/prf')
|
||||
mech.name = request.child_value('sinfo/nm')
|
||||
mech.game = self.game
|
||||
mech.version = self.version
|
||||
|
||||
self.data.local.machine.put_machine(mech)
|
||||
|
||||
return Node.void("shop2")
|
||||
32
bemani/backend/bst/factory.py
Normal file
32
bemani/backend/bst/factory.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from typing import Dict, Optional, Any
|
||||
|
||||
from bemani.backend.base import Base, Factory
|
||||
from bemani.backend.bst.beatstream2 import Beatstream2
|
||||
from bemani.backend.bst.beatstream import Beatstream
|
||||
from bemani.common import Model
|
||||
from bemani.data import Data
|
||||
|
||||
class BSTFactory(Factory):
|
||||
|
||||
MANAGED_CLASSES = [
|
||||
Beatstream2,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def register_all(cls) -> None:
|
||||
for game in ['NBT']:
|
||||
Base.register(game, BSTFactory)
|
||||
|
||||
@classmethod
|
||||
def create(cls, data: Data, config: Dict[str, Any], model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
|
||||
|
||||
if model.gamecode == 'NBT':
|
||||
if model.version is None:
|
||||
return None
|
||||
if model.version <= 2015121600: # Beatstream 1
|
||||
return Beatstream(data, config, model)
|
||||
if model.version <= 2016111400 and model.version > 2015121600: # Beatstream 2
|
||||
return Beatstream2(data, config, model)
|
||||
|
||||
# Unknown game version
|
||||
return None
|
||||
5
bemani/client/bst/__init__.py
Normal file
5
bemani/client/bst/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from bemani.client.bst.beatstream2 import Beatstream2Client
|
||||
|
||||
__all__ = [
|
||||
"Beatstream2Client",
|
||||
]
|
||||
261
bemani/client/bst/beatstream2.py
Normal file
261
bemani/client/bst/beatstream2.py
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
from typing import Optional
|
||||
|
||||
from bemani.client.base import BaseClient
|
||||
from bemani.common.constants import GameConstants, VersionConstants
|
||||
from bemani.common.time import Time
|
||||
from bemani.common.validateddict import Profile
|
||||
from bemani.data import Song
|
||||
from bemani.protocol import Node
|
||||
|
||||
class Beatstream2Client(BaseClient):
|
||||
name = 'TEST'
|
||||
|
||||
def verify_player2_start(self, refid: str) -> int:
|
||||
call = self.call_node()
|
||||
|
||||
player2 = Node.void("player2")
|
||||
player2.set_attribute('method', 'start')
|
||||
player2.add_child(Node.string("rid", refid))
|
||||
player2.add_child(Node.u8_array("ga", [192, 168, 1, 2]))
|
||||
player2.add_child(Node.u16("gp", 1234))
|
||||
player2.add_child(Node.u8_array("la", [192, 168, 1, 2]))
|
||||
|
||||
call.add_child(player2)
|
||||
|
||||
resp = self.exchange('', call)
|
||||
|
||||
self.assert_path(resp, "response/player2/plyid")
|
||||
|
||||
return resp.child_value("player2/plyid")
|
||||
|
||||
def verify_player2_read(self, refid: str) -> Profile:
|
||||
call = self.call_node()
|
||||
|
||||
player2 = Node.void("player2")
|
||||
player2.set_attribute('method', 'read')
|
||||
player2.add_child(Node.string("rid", refid))
|
||||
player2.add_child(Node.string("lid", "JP-1"))
|
||||
player2.add_child(Node.s16("ver", 0))
|
||||
|
||||
call.add_child(player2)
|
||||
|
||||
resp = self.exchange('', call)
|
||||
|
||||
self.assert_path(resp, "response/player2/pdata/account/usrid")
|
||||
self.assert_path(resp, "response/player2/pdata/base/name")
|
||||
|
||||
profile = resp.child("player2/pdata")
|
||||
|
||||
ret = Profile("bst", 2, refid, profile.child_value('account/usrid'))
|
||||
|
||||
ret.replace_int('usrid', int(profile.child_value('account/usrid')))
|
||||
ret.replace_int('is_takeover', int(profile.child_value('account/is_takeover')))
|
||||
ret.replace_int('tpc', profile.child_value('account/tpc'))
|
||||
ret.replace_int('dpc', int(profile.child_value('account/dpc')))
|
||||
ret.replace_int('crd', int(profile.child_value('account/crd')))
|
||||
ret.replace_int('brd', int(profile.child_value('account/brd')))
|
||||
ret.replace_int('tdc', int(profile.child_value('account/tdc')))
|
||||
ret.replace_str('lid', profile.child_value('account/lid'))
|
||||
ret.replace_int('ver', int(profile.child_value('account/ver')))
|
||||
ret.replace_int('st', int(profile.child_value('account/st')))
|
||||
|
||||
# Base
|
||||
ret.replace_str('name', profile.child_value('base/name'))
|
||||
ret.replace_int('brnk', int(profile.child_value('base/brnk')))
|
||||
ret.replace_int('bcnum', int(profile.child_value('base/bcnum')))
|
||||
ret.replace_int('lcnum', int(profile.child_value('base/lcnum')))
|
||||
ret.replace_int('volt', int(profile.child_value('base/volt')))
|
||||
ret.replace_int('gold', int(profile.child_value('base/gold')))
|
||||
ret.replace_int('lmid', int(profile.child_value('base/lmid')))
|
||||
ret.replace_int('lgrd', int(profile.child_value('base/lgrd')))
|
||||
ret.replace_int('lsrt', int(profile.child_value('base/lsrt')))
|
||||
ret.replace_int('ltab', int(profile.child_value('base/ltab')))
|
||||
ret.replace_int('splv', int(profile.child_value('base/splv')))
|
||||
ret.replace_int('pref', int(profile.child_value('base/pref')))
|
||||
ret.replace_int('lcid', int(profile.child_value('base/lcid')))
|
||||
ret.replace_int('hat', int(profile.child_value('base/hat')))
|
||||
|
||||
custom = profile.child_value('customize/custom')
|
||||
if custom is not None:
|
||||
customize = []
|
||||
for i in custom:
|
||||
customize.append(i)
|
||||
ret.replace_int_array('custom', 16, custom)
|
||||
|
||||
|
||||
ret.replace_int('last_tips', profile.child_value('tips/last_tips'))
|
||||
|
||||
return ret
|
||||
|
||||
def verify_player2_stagedata_write(self, profile: Profile, song: Song, sid: int) -> None:
|
||||
call = self.call_node()
|
||||
player2 = Node.void("player2")
|
||||
player2.set_attribute('method', 'stagedata_write')
|
||||
|
||||
player2.add_child(Node.s32('play_id', sid))
|
||||
player2.add_child(Node.s32('continue_count', 0))
|
||||
player2.add_child(Node.s32('stage_no', 0))
|
||||
player2.add_child(Node.s32('user_id', profile.get_int('usrid')))
|
||||
player2.add_child(Node.string('location_id', "JP-1"))
|
||||
player2.add_child(Node.s32('select_music_id', song.id))
|
||||
player2.add_child(Node.s32('select_grade', song.chart))
|
||||
player2.add_child(Node.s32('result_clear_gauge', 1000))
|
||||
player2.add_child(Node.s32('result_score', 888392))
|
||||
player2.add_child(Node.s32('result_max_combo', 114))
|
||||
player2.add_child(Node.s32('result_grade', 0))
|
||||
player2.add_child(Node.s32('result_medal', 3))
|
||||
player2.add_child(Node.s32('result_fanta', 68))
|
||||
player2.add_child(Node.s32('result_great', 35))
|
||||
player2.add_child(Node.s32('result_fine', 7))
|
||||
player2.add_child(Node.s32('result_miss', 2))
|
||||
|
||||
call.add_child(player2)
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player2/@status")
|
||||
|
||||
def verify_player2_write(self, profile: Profile, sid: int) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
player2 = Node.void("player2")
|
||||
player2.set_attribute('method', 'write')
|
||||
|
||||
pdata = Node.void('pdata')
|
||||
player2.add_child(pdata)
|
||||
|
||||
account = Node.void('account')
|
||||
account.add_child(Node.s32('usrid', profile.get_int("usrid")))
|
||||
account.add_child(Node.s32('is_takeover', profile.get_int("is_takeover")))
|
||||
account.add_child(Node.s32('plyid', sid))
|
||||
account.add_child(Node.s32('continue_cnt', 0))
|
||||
account.add_child(Node.s32('tpc', profile.get_int("tpc")))
|
||||
account.add_child(Node.s32('dpc', profile.get_int("dpc")))
|
||||
account.add_child(Node.s32('crd', profile.get_int("crd")))
|
||||
account.add_child(Node.s32('brd', profile.get_int("brd")))
|
||||
account.add_child(Node.s32('tdc', profile.get_int("tdc")))
|
||||
account.add_child(Node.string('rid', profile.refid))
|
||||
account.add_child(Node.string('lid', "JP-1"))
|
||||
account.add_child(Node.s32('intrvld', profile.get_int("intrvld")))
|
||||
account.add_child(Node.s16('ver', profile.get_int("ver")))
|
||||
account.add_child(Node.u64('pst', profile.get_int("pst")))
|
||||
account.add_child(Node.u64('st', Time.now() * 1000))
|
||||
account.add_child(Node.bool('ea', profile.get_int("ea", True)))
|
||||
pdata.add_child(account)
|
||||
|
||||
base = Node.void('base')
|
||||
base.add_child(Node.string('name', profile.get_str('name')))
|
||||
base.add_child(Node.s8('brnk', profile.get_int('brnk')))
|
||||
base.add_child(Node.s8('bcnum', profile.get_int('bcnum')))
|
||||
base.add_child(Node.s8('lcnum', profile.get_int('lcnum')))
|
||||
base.add_child(Node.s32('volt', profile.get_int('volt')))
|
||||
base.add_child(Node.s32('gold', profile.get_int('gold')))
|
||||
base.add_child(Node.s32('lmid', profile.get_int('lmid')))
|
||||
base.add_child(Node.s8('lgrd', profile.get_int('lgrd')))
|
||||
base.add_child(Node.s8('lsrt', profile.get_int('lsrt')))
|
||||
base.add_child(Node.s8('ltab', profile.get_int('ltab')))
|
||||
base.add_child(Node.s8('splv', profile.get_int('splv')))
|
||||
base.add_child(Node.s8('pref', profile.get_int('pref')))
|
||||
base.add_child(Node.s32('lcid', profile.get_int('lcid')))
|
||||
base.add_child(Node.s32('hat', profile.get_int('hat')))
|
||||
pdata.add_child(base)
|
||||
|
||||
pdata.add_child(Node.void("item"))
|
||||
|
||||
customize = Node.void('customize')
|
||||
customize.add_child(Node.u16_array('custom', profile.get_int_array('custom', 16)))
|
||||
pdata.add_child(customize)
|
||||
|
||||
tips = Node.void('tips')
|
||||
tips.add_child(Node.s32('last_tips', profile.get_int('last_tips')))
|
||||
pdata.add_child(tips)
|
||||
|
||||
pdata.add_child(Node.void("hacker"))
|
||||
|
||||
play_log = Node.void("play_log")
|
||||
|
||||
crisis = Node.void("crysis")
|
||||
crisis.add_child(Node.s32("id", 0))
|
||||
crisis.add_child(Node.s32("stage_no", 0))
|
||||
crisis.add_child(Node.s8("step", 0))
|
||||
crisis.add_child(Node.s32("r_gauge", 95))
|
||||
crisis.add_child(Node.s8("r_state", 0))
|
||||
play_log.add_child(crisis)
|
||||
|
||||
crisis = Node.void("crysis")
|
||||
crisis.add_child(Node.s32("id", 0))
|
||||
crisis.add_child(Node.s32("stage_no", 1))
|
||||
crisis.add_child(Node.s8("step", 1))
|
||||
crisis.add_child(Node.s32("r_gauge", 192))
|
||||
crisis.add_child(Node.s8("r_state", 1))
|
||||
play_log.add_child(crisis)
|
||||
|
||||
crisis = Node.void("crysis")
|
||||
crisis.add_child(Node.s32("id", 0))
|
||||
crisis.add_child(Node.s32("stage_no", 2))
|
||||
crisis.add_child(Node.s8("step", 1))
|
||||
crisis.add_child(Node.s32("r_gauge", 214))
|
||||
crisis.add_child(Node.s8("r_state", 0))
|
||||
play_log.add_child(crisis)
|
||||
|
||||
pdata.add_child(play_log)
|
||||
|
||||
call.add_child(player2)
|
||||
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player2/uid")
|
||||
|
||||
def verify_info2_result_image_write(self, profile: Profile, song: Song, sid: int) -> None:
|
||||
call = self.call_node()
|
||||
info2 = Node.void("info2")
|
||||
info2.set_attribute('method', 'result_image_write')
|
||||
|
||||
info2.add_child(Node.s32("play_id", sid))
|
||||
info2.add_child(Node.s32("continue_no", 0))
|
||||
info2.add_child(Node.s32("stage_no", 0))
|
||||
info2.add_child(Node.string("ref_id", profile.refid))
|
||||
info2.add_child(Node.s32("beast_rank", 0))
|
||||
info2.add_child(Node.string("player_name", profile.get_str("name")))
|
||||
info2.add_child(Node.s32("music_id", song.id))
|
||||
info2.add_child(Node.s32("music_grade", 0))
|
||||
info2.add_child(Node.s32("music_level", song.chart))
|
||||
info2.add_child(Node.string("music_title", song.name))
|
||||
info2.add_child(Node.string("artist_name", song.artist))
|
||||
info2.add_child(Node.s32("gauge", 995))
|
||||
info2.add_child(Node.s32("grade", 0))
|
||||
info2.add_child(Node.s32("score", 820567))
|
||||
info2.add_child(Node.s32("best_score", 0))
|
||||
info2.add_child(Node.s32("medal", 3))
|
||||
info2.add_child(Node.bool("is_new_record", False))
|
||||
info2.add_child(Node.s32("fanta", 81))
|
||||
info2.add_child(Node.s32("great", 29))
|
||||
info2.add_child(Node.s32("fine", 23))
|
||||
info2.add_child(Node.s32("miss", 8))
|
||||
|
||||
call.add_child(info2)
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/info2/@status")
|
||||
|
||||
def verify_player2_end(self, refid: str) -> None:
|
||||
call = self.call_node()
|
||||
player2 = Node.void("player2")
|
||||
player2.set_attribute('method', 'end')
|
||||
player2.add_child(Node.string("rid", refid))
|
||||
call.add_child(player2)
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player2/@status")
|
||||
|
||||
def verify(self, cardid: Optional[str]) -> None:
|
||||
# Make sure we can card in properly
|
||||
refid = self.verify_cardmng_inquire(cardid, "query", True)
|
||||
sid = self.verify_player2_start(refid)
|
||||
profile = self.verify_player2_read(refid)
|
||||
|
||||
# Make sure courses, songs, and scorecards save properly
|
||||
test_song = Song(GameConstants.BST, VersionConstants.BEATSTREAM_2,
|
||||
129, 0, "チョコレートスマイル", "ちよこれえとすまいる", "", {})
|
||||
|
||||
self.verify_info2_result_image_write(profile, test_song, sid)
|
||||
self.verify_player2_stagedata_write(profile, test_song, sid)
|
||||
|
||||
# Make sure profile saves properly, and game ends gracefully
|
||||
self.verify_player2_write(profile, sid)
|
||||
self.verify_player2_end(refid)
|
||||
|
|
@ -21,6 +21,7 @@ class GameConstants(Enum):
|
|||
POPN_MUSIC = "pnm"
|
||||
REFLEC_BEAT = "reflec"
|
||||
SDVX = "sdvx"
|
||||
BST = "bst"
|
||||
|
||||
|
||||
class VersionConstants:
|
||||
|
|
@ -142,6 +143,9 @@ class VersionConstants:
|
|||
SDVX_GRAVITY_WARS: Final[int] = 3
|
||||
SDVX_HEAVENLY_HAVEN: Final[int] = 4
|
||||
|
||||
BEATSTREAM: Final[int] = 1
|
||||
BEATSTREAM_2: Final[int] = 2
|
||||
|
||||
|
||||
class APIConstants(Enum):
|
||||
"""
|
||||
|
|
@ -341,6 +345,17 @@ class BroadcastConstants(Enum):
|
|||
COMBO = "Combo"
|
||||
MEDAL = "Medal"
|
||||
|
||||
# Added for BST
|
||||
DIFFICULTY_LEVEL = "Difficulty"
|
||||
BEAST_RANK = "Beast Rank"
|
||||
BEST_SCORE = "Best Score"
|
||||
GAUGE = "Gauge"
|
||||
MAX_COMBO = "Max Combo"
|
||||
FANTASTIC = "Fantastic"
|
||||
GREAT = "Great"
|
||||
FINE = "Fine"
|
||||
MISS = "Miss"
|
||||
|
||||
|
||||
class _RegionConstants:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class Triggers:
|
|||
GameConstants.POPN_MUSIC: "Pop'n Music",
|
||||
GameConstants.REFLEC_BEAT: "Reflec Beat",
|
||||
GameConstants.SDVX: "Sound Voltex",
|
||||
GameConstants.BST: "BeatStream",
|
||||
}.get(game, "Unknown")
|
||||
|
||||
def has_broadcast_destination(self, game: GameConstants) -> bool:
|
||||
|
|
@ -43,7 +44,7 @@ class Triggers:
|
|||
self.broadcast_score_discord(data, game, song)
|
||||
|
||||
def broadcast_score_discord(self, data: Dict[BroadcastConstants, str], game: GameConstants, song: Song) -> None:
|
||||
if game in {GameConstants.IIDX, GameConstants.POPN_MUSIC}:
|
||||
if game in {GameConstants.IIDX, GameConstants.POPN_MUSIC, GameConstants.BST}:
|
||||
now = datetime.now()
|
||||
|
||||
webhook = DiscordWebhook(url=self.config.webhooks.discord[game])
|
||||
|
|
|
|||
|
|
@ -856,6 +856,23 @@ def navigation() -> Dict[str, Any]:
|
|||
"gamecode": GameConstants.SDVX.value,
|
||||
},
|
||||
)
|
||||
|
||||
if GameConstants.BST in g.config.support:
|
||||
bst_entries = []
|
||||
bst_entries.extend([
|
||||
{
|
||||
'label': 'Global Scores',
|
||||
'uri': url_for('bst_pages.viewnetworkscores'),
|
||||
}
|
||||
])
|
||||
pages.append(
|
||||
{
|
||||
'label': 'BeatStream',
|
||||
'entries': bst_entries,
|
||||
'base_uri': app.blueprints['bst_pages'].url_prefix,
|
||||
'gamecode': GameConstants.BST.value,
|
||||
},
|
||||
)
|
||||
|
||||
# Admin pages
|
||||
if user.admin:
|
||||
|
|
|
|||
8
bemani/frontend/bst/__init__.py
Normal file
8
bemani/frontend/bst/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from bemani.frontend.bst.endpoints import bst_pages
|
||||
from bemani.frontend.bst.cache import BSTCache
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BSTCache",
|
||||
"bst_pages",
|
||||
]
|
||||
18
bemani/frontend/bst/bst.py
Normal file
18
bemani/frontend/bst/bst.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import copy
|
||||
from typing import Any, Dict, Iterator, List, Tuple
|
||||
|
||||
from bemani.backend.bst import BSTFactory, BSTBase
|
||||
from bemani.common import Profile, ValidatedDict, GameConstants, VersionConstants
|
||||
from bemani.data import Attempt, Link, RemoteUser, Score, Song, UserID
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
class BSTFrontend(FrontendBase):
|
||||
game: GameConstants = GameConstants.BST
|
||||
|
||||
def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
|
||||
yield from BSTFactory.all_games()
|
||||
|
||||
def update_name(self, profile: Profile, name: str) -> Profile:
|
||||
newprofile = copy.deepcopy(profile)
|
||||
newprofile.replace_str('name', name)
|
||||
return newprofile
|
||||
16
bemani/frontend/bst/cache.py
Normal file
16
bemani/frontend/bst/cache.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from flask_caching import Cache # type: ignore
|
||||
|
||||
from bemani.data import Config, Data
|
||||
from bemani.frontend.app import app
|
||||
from bemani.frontend.bst.bst import BSTFrontend
|
||||
|
||||
class BSTCache:
|
||||
|
||||
@classmethod
|
||||
def preload(cls, data: Data, config: Config) -> None:
|
||||
cache = Cache(app, config={
|
||||
'CACHE_TYPE': 'filesystem',
|
||||
'CACHE_DIR': config.cache_dir,
|
||||
})
|
||||
frontend = BSTFrontend(data, config, cache)
|
||||
frontend.get_all_songs(force_db_load=True)
|
||||
52
bemani/frontend/bst/endpoints.py
Normal file
52
bemani/frontend/bst/endpoints.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
from flask import Blueprint, request, Response, url_for, abort
|
||||
|
||||
from bemani.common import ID, GameConstants
|
||||
from bemani.data import Link, UserID
|
||||
from bemani.frontend.app import loginrequired, jsonify, render_react
|
||||
from bemani.frontend.bst.bst import BSTFrontend
|
||||
from bemani.frontend.templates import templates_location
|
||||
from bemani.frontend.static import static_location
|
||||
from bemani.frontend.types import g
|
||||
|
||||
bst_pages = Blueprint(
|
||||
'bst_pages',
|
||||
__name__,
|
||||
url_prefix=f'/{GameConstants.BST.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
||||
@bst_pages.route("/scores")
|
||||
@loginrequired
|
||||
def viewnetworkscores() -> Response:
|
||||
frontend = BSTFrontend(g.data, g.config, g.cache)
|
||||
network_scores = frontend.get_network_scores(limit=100)
|
||||
if len(network_scores['attempts']) > 10:
|
||||
network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts'])
|
||||
|
||||
return render_react(
|
||||
'Global DDR Scores',
|
||||
'bst/scores.react.js',
|
||||
{
|
||||
'attempts': network_scores['attempts'],
|
||||
'songs': frontend.get_all_songs(),
|
||||
'players': network_scores['players'],
|
||||
'versions': {version: name for (game, version, name) in frontend.all_games()},
|
||||
'shownames': True,
|
||||
'shownewrecords': False,
|
||||
},
|
||||
{
|
||||
'refresh': url_for('bst_pages.listnetworkscores'),
|
||||
'player': url_for('bst_pages.viewplayer', userid=-1),
|
||||
'individual_score': url_for('bst_pages.viewtopscores', musicid=-1),
|
||||
},
|
||||
)
|
||||
|
||||
@bst_pages.route('/scores/list')
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def listnetworkscores() -> Dict[str, Any]:
|
||||
frontend = BSTFrontend(g.data, g.config, g.cache)
|
||||
return frontend.get_network_scores()
|
||||
1
bemani/frontend/static/controllers/bst/scores.react.js
Normal file
1
bemani/frontend/static/controllers/bst/scores.react.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
/*** @jsx React.DOM */
|
||||
|
|
@ -12,6 +12,7 @@ from bemani.backend.sdvx import SoundVoltexFactory
|
|||
from bemani.backend.reflec import ReflecBeatFactory
|
||||
from bemani.backend.museca import MusecaFactory
|
||||
from bemani.backend.mga import MetalGearArcadeFactory
|
||||
from bemani.backend.bst import BSTFactory
|
||||
from bemani.common import GameConstants, cache
|
||||
from bemani.data import Config, Data
|
||||
|
||||
|
|
@ -81,3 +82,5 @@ def register_games(config: Config) -> None:
|
|||
MetalGearArcadeFactory.register_all()
|
||||
if GameConstants.DANCE_EVOLUTION in config.support:
|
||||
DanceEvolutionFactory.register_all()
|
||||
if GameConstants.BST in config.support:
|
||||
BSTFactory.register_all()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from bemani.frontend.sdvx import sdvx_pages
|
|||
from bemani.frontend.reflec import reflec_pages
|
||||
from bemani.frontend.museca import museca_pages
|
||||
from bemani.frontend.danevo import danevo_pages
|
||||
from bemani.frontend.bst import bst_pages
|
||||
from bemani.utils.config import (
|
||||
load_config as base_load_config,
|
||||
instantiate_cache as base_instantiate_cache,
|
||||
|
|
@ -50,6 +51,8 @@ def register_blueprints() -> None:
|
|||
app.register_blueprint(museca_pages)
|
||||
if GameConstants.DANCE_EVOLUTION in config.support:
|
||||
app.register_blueprint(danevo_pages)
|
||||
if GameConstants.BST in config.support:
|
||||
app.register_blueprint(bst_pages)
|
||||
|
||||
|
||||
def register_games() -> None:
|
||||
|
|
|
|||
|
|
@ -6207,6 +6207,168 @@ class ImportDanceEvolution(ImportBase):
|
|||
self.finish_batch()
|
||||
|
||||
|
||||
class ImportBst(ImportBase):
|
||||
def __init__ (
|
||||
self,
|
||||
config: Config,
|
||||
version: str,
|
||||
no_combine: bool,
|
||||
update: bool
|
||||
) -> None:
|
||||
if version in ['1', '2']:
|
||||
actual_version = {
|
||||
'1': VersionConstants.BEATSTREAM,
|
||||
'2': VersionConstants.BEATSTREAM_2,
|
||||
}.get(version, -1)
|
||||
elif version == 'all':
|
||||
actual_version = None
|
||||
|
||||
if actual_version in [
|
||||
None,
|
||||
VersionConstants.BEATSTREAM,
|
||||
VersionConstants.BEATSTREAM_2,
|
||||
]:
|
||||
self.charts = [0, 1, 2, 3]
|
||||
else:
|
||||
raise Exception("Unsupported Beatstream version! Please use one of the following: 1, 2.")
|
||||
super().__init__(config, GameConstants.BST, actual_version, no_combine, update)
|
||||
|
||||
def scrape(self, infile:str) -> List[Dict[str, Any]]:
|
||||
if self.version is None:
|
||||
raise Exception('Can\'t scrape database for \'all\' version!')
|
||||
songs = []
|
||||
with open(infile, encoding='utf_16_le') as musicdb:
|
||||
songs = []
|
||||
reader = csv.reader(musicdb, delimiter='|', quotechar='"')
|
||||
for row in reader:
|
||||
if self.version == VersionConstants.BEATSTREAM:
|
||||
if 'MusicInfoData' in row[0]:
|
||||
continue
|
||||
if 'EOF' in row[0]:
|
||||
continue
|
||||
songid = int(row[0])
|
||||
name = row[1]
|
||||
genre = row[3]
|
||||
bpm_min = float(row[7])
|
||||
bpm_max = float(row[8])
|
||||
light = (row[9])
|
||||
medium = (row[10])
|
||||
beast = (row[11])
|
||||
nightmare = (row[12])
|
||||
artist = row[13]
|
||||
if self.version == VersionConstants.BEATSTREAM_2:
|
||||
if 'MusicInfoData' in row[0]:
|
||||
continue
|
||||
if 'EOF' in row[0]:
|
||||
continue
|
||||
songid = int(row[0])
|
||||
name = row[1]
|
||||
genre = row[4]
|
||||
bpm_min = float(row[8])
|
||||
bpm_max = float(row[9])
|
||||
light = row[10]
|
||||
medium = row[11]
|
||||
beast = row[12]
|
||||
nightmare = row[13]
|
||||
artist = row[14]
|
||||
song = {
|
||||
'id': songid,
|
||||
'title': name,
|
||||
'artist': artist,
|
||||
'genre': genre,
|
||||
'bpm_min': bpm_min,
|
||||
'bpm_max': bpm_max,
|
||||
'difficulty': {
|
||||
'light': light,
|
||||
'medium': medium,
|
||||
'beast': beast,
|
||||
'nightmare': nightmare,
|
||||
},
|
||||
}
|
||||
songs.append(song)
|
||||
return songs
|
||||
|
||||
def lookup(self, server: str, token: str) -> List[Dict[str, Any]]:
|
||||
if self.version is None:
|
||||
raise Exception('Can\'t look up database for \'all\' version!')
|
||||
|
||||
# Grab music info from remote server
|
||||
music = self.remote_music(server, token)
|
||||
songs = music.get_all_songs(self.game, self.version)
|
||||
lut: Dict[int, Dict[str, Any]] = {}
|
||||
chart_map = {
|
||||
0: 'light',
|
||||
1: 'medium',
|
||||
2: 'beast',
|
||||
3: 'nightmare',
|
||||
}
|
||||
|
||||
# Format it the way we expect
|
||||
for song in songs:
|
||||
if song.chart not in chart_map:
|
||||
# Ignore charts on songs we don't support/care about.
|
||||
continue
|
||||
|
||||
if song.id not in lut:
|
||||
lut[song.id] = {
|
||||
'id': song.id,
|
||||
'title': song.name,
|
||||
'artist': song.artist,
|
||||
'genre': song.genre,
|
||||
'bpm_min': song.data.get_int('bpm_min'),
|
||||
'bpm_max': song.data.get_int('bpm_max'),
|
||||
'difficulty': {
|
||||
'light': 0,
|
||||
'medium': 1,
|
||||
'beast': 2,
|
||||
'nightmare': 3,
|
||||
},
|
||||
}
|
||||
lut[song.id]['difficulty'][chart_map[song.chart]] = song.data.get_int('difficulty')
|
||||
|
||||
# Reassemble the data
|
||||
reassembled_songs = [val for _, val in lut.items()]
|
||||
|
||||
|
||||
return reassembled_songs
|
||||
|
||||
def import_music_db(self, songs: List[Dict[str, Any]]) -> None:
|
||||
if self.version is None:
|
||||
raise Exception('Can\'t import database for \'all\' version!')
|
||||
|
||||
chart_map: Dict[int, str] = {
|
||||
0: 'light',
|
||||
1: 'medium',
|
||||
2: 'beast',
|
||||
3: 'nightmare',
|
||||
}
|
||||
for song in songs:
|
||||
songid = song['id']
|
||||
|
||||
self.start_batch()
|
||||
for chart in self.charts:
|
||||
# First, try to find in the DB from another version
|
||||
old_id = self.get_music_id_for_song(songid, chart)
|
||||
|
||||
# First, try to find in the DB from another version
|
||||
if self.no_combine or old_id is None:
|
||||
# Insert original
|
||||
print(f"New entry for {songid} chart {chart}")
|
||||
next_id = self.get_next_music_id()
|
||||
else:
|
||||
# Insert pointing at same ID so scores transfer
|
||||
print(f"Reused entry for {songid} chart {chart}")
|
||||
next_id = old_id
|
||||
data = {
|
||||
'difficulty': song['difficulty'][chart_map[chart]],
|
||||
'bpm_min': song['bpm_min'],
|
||||
'bpm_max': song['bpm_max'],
|
||||
}
|
||||
|
||||
self.insert_music_id_for_song(next_id, songid, chart, song['title'], song['artist'], song['genre'], data)
|
||||
self.finish_batch()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Import Game Music DB")
|
||||
parser.add_argument(
|
||||
|
|
@ -6465,6 +6627,15 @@ def main() -> None:
|
|||
danevo.import_music_db(songs)
|
||||
danevo.close()
|
||||
|
||||
if series == GameConstants.BST:
|
||||
bst = ImportBst(config, args.version, args.no_combine, args.update)
|
||||
if args.csv:
|
||||
songs = bst.scrape(args.csv)
|
||||
else:
|
||||
raise Exception('No musicdb.csv provided! Please provide --csv')
|
||||
bst.import_music_db(songs)
|
||||
bst.close()
|
||||
|
||||
else:
|
||||
raise CLIException("Unsupported game series!")
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ from bemani.client.reflec import (
|
|||
from bemani.client.bishi import TheStarBishiBashiClient
|
||||
from bemani.client.mga import MetalGearArcadeClient
|
||||
from bemani.client.danevo import DanceEvolutionClient
|
||||
from bemani.client.bst.beatstream2 import Beatstream2Client
|
||||
|
||||
|
||||
def get_client(proto: ClientProtocol, pcbid: str, game: str, config: Dict[str, Any]) -> BaseClient:
|
||||
|
|
@ -322,6 +323,12 @@ def get_client(proto: ClientProtocol, pcbid: str, game: str, config: Dict[str, A
|
|||
pcbid,
|
||||
config,
|
||||
)
|
||||
if game == 'beatstream2':
|
||||
return Beatstream2Client(
|
||||
proto,
|
||||
pcbid,
|
||||
config
|
||||
)
|
||||
|
||||
raise Exception(f"Unknown game {game}")
|
||||
|
||||
|
|
@ -559,6 +566,10 @@ def mainloop(
|
|||
"name": "Dance Evolution Arcade",
|
||||
"model": "KDM:J:B:A:2016080100",
|
||||
"avs": "2.15.5 r6251",
|
||||
},'beatstream2': {
|
||||
'name': 'BeatStream 2',
|
||||
'model': 'NBT:J:A:A:2016111400',
|
||||
'avs': '2.16.7 r7487'
|
||||
},
|
||||
}
|
||||
if action == "list":
|
||||
|
|
@ -683,6 +694,7 @@ def main() -> None:
|
|||
"reflec-6": "reflec-volzza2",
|
||||
"mga": "metal-gear-arcade",
|
||||
"danevo": "dance-evolution",
|
||||
'bst2': 'beatstream2',
|
||||
}.get(game, game)
|
||||
|
||||
mainloop(args.address, args.port, args.config, action, game, args.cardid, args.verbose)
|
||||
|
|
|
|||
|
|
@ -49,10 +49,9 @@ server:
|
|||
# Delete this to disable this support.
|
||||
webhooks:
|
||||
discord:
|
||||
iidx:
|
||||
- "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||
pnm:
|
||||
- "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||
iidx: "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||
pnm: "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||
bst: "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||
|
||||
# Assets URLs. These allow for in-game asset rendering on the front end. Delete this to disable asset rendering.
|
||||
assets:
|
||||
|
|
@ -89,6 +88,8 @@ support:
|
|||
reflec: True
|
||||
# SDVX frontend/backend enabled.
|
||||
sdvx: True
|
||||
# Beatstream frontend/backend enable.
|
||||
bst: True
|
||||
|
||||
# Key used to encrypt cookies, should be unique per network instance.
|
||||
secret_key: 'this_is_a_secret_please_change_me'
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user