mirror of
https://github.com/DragonMinded/bemaniutils.git
synced 2026-04-26 10:07:00 -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"
|
POPN_MUSIC = "pnm"
|
||||||
REFLEC_BEAT = "reflec"
|
REFLEC_BEAT = "reflec"
|
||||||
SDVX = "sdvx"
|
SDVX = "sdvx"
|
||||||
|
BST = "bst"
|
||||||
|
|
||||||
|
|
||||||
class VersionConstants:
|
class VersionConstants:
|
||||||
|
|
@ -142,6 +143,9 @@ class VersionConstants:
|
||||||
SDVX_GRAVITY_WARS: Final[int] = 3
|
SDVX_GRAVITY_WARS: Final[int] = 3
|
||||||
SDVX_HEAVENLY_HAVEN: Final[int] = 4
|
SDVX_HEAVENLY_HAVEN: Final[int] = 4
|
||||||
|
|
||||||
|
BEATSTREAM: Final[int] = 1
|
||||||
|
BEATSTREAM_2: Final[int] = 2
|
||||||
|
|
||||||
|
|
||||||
class APIConstants(Enum):
|
class APIConstants(Enum):
|
||||||
"""
|
"""
|
||||||
|
|
@ -341,6 +345,17 @@ class BroadcastConstants(Enum):
|
||||||
COMBO = "Combo"
|
COMBO = "Combo"
|
||||||
MEDAL = "Medal"
|
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:
|
class _RegionConstants:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ class Triggers:
|
||||||
GameConstants.POPN_MUSIC: "Pop'n Music",
|
GameConstants.POPN_MUSIC: "Pop'n Music",
|
||||||
GameConstants.REFLEC_BEAT: "Reflec Beat",
|
GameConstants.REFLEC_BEAT: "Reflec Beat",
|
||||||
GameConstants.SDVX: "Sound Voltex",
|
GameConstants.SDVX: "Sound Voltex",
|
||||||
|
GameConstants.BST: "BeatStream",
|
||||||
}.get(game, "Unknown")
|
}.get(game, "Unknown")
|
||||||
|
|
||||||
def has_broadcast_destination(self, game: GameConstants) -> bool:
|
def has_broadcast_destination(self, game: GameConstants) -> bool:
|
||||||
|
|
@ -43,7 +44,7 @@ class Triggers:
|
||||||
self.broadcast_score_discord(data, game, song)
|
self.broadcast_score_discord(data, game, song)
|
||||||
|
|
||||||
def broadcast_score_discord(self, data: Dict[BroadcastConstants, str], game: GameConstants, song: Song) -> None:
|
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()
|
now = datetime.now()
|
||||||
|
|
||||||
webhook = DiscordWebhook(url=self.config.webhooks.discord[game])
|
webhook = DiscordWebhook(url=self.config.webhooks.discord[game])
|
||||||
|
|
|
||||||
|
|
@ -856,6 +856,23 @@ def navigation() -> Dict[str, Any]:
|
||||||
"gamecode": GameConstants.SDVX.value,
|
"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
|
# Admin pages
|
||||||
if user.admin:
|
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.reflec import ReflecBeatFactory
|
||||||
from bemani.backend.museca import MusecaFactory
|
from bemani.backend.museca import MusecaFactory
|
||||||
from bemani.backend.mga import MetalGearArcadeFactory
|
from bemani.backend.mga import MetalGearArcadeFactory
|
||||||
|
from bemani.backend.bst import BSTFactory
|
||||||
from bemani.common import GameConstants, cache
|
from bemani.common import GameConstants, cache
|
||||||
from bemani.data import Config, Data
|
from bemani.data import Config, Data
|
||||||
|
|
||||||
|
|
@ -81,3 +82,5 @@ def register_games(config: Config) -> None:
|
||||||
MetalGearArcadeFactory.register_all()
|
MetalGearArcadeFactory.register_all()
|
||||||
if GameConstants.DANCE_EVOLUTION in config.support:
|
if GameConstants.DANCE_EVOLUTION in config.support:
|
||||||
DanceEvolutionFactory.register_all()
|
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.reflec import reflec_pages
|
||||||
from bemani.frontend.museca import museca_pages
|
from bemani.frontend.museca import museca_pages
|
||||||
from bemani.frontend.danevo import danevo_pages
|
from bemani.frontend.danevo import danevo_pages
|
||||||
|
from bemani.frontend.bst import bst_pages
|
||||||
from bemani.utils.config import (
|
from bemani.utils.config import (
|
||||||
load_config as base_load_config,
|
load_config as base_load_config,
|
||||||
instantiate_cache as base_instantiate_cache,
|
instantiate_cache as base_instantiate_cache,
|
||||||
|
|
@ -50,6 +51,8 @@ def register_blueprints() -> None:
|
||||||
app.register_blueprint(museca_pages)
|
app.register_blueprint(museca_pages)
|
||||||
if GameConstants.DANCE_EVOLUTION in config.support:
|
if GameConstants.DANCE_EVOLUTION in config.support:
|
||||||
app.register_blueprint(danevo_pages)
|
app.register_blueprint(danevo_pages)
|
||||||
|
if GameConstants.BST in config.support:
|
||||||
|
app.register_blueprint(bst_pages)
|
||||||
|
|
||||||
|
|
||||||
def register_games() -> None:
|
def register_games() -> None:
|
||||||
|
|
|
||||||
|
|
@ -6207,6 +6207,168 @@ class ImportDanceEvolution(ImportBase):
|
||||||
self.finish_batch()
|
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:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser(description="Import Game Music DB")
|
parser = argparse.ArgumentParser(description="Import Game Music DB")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|
@ -6465,6 +6627,15 @@ def main() -> None:
|
||||||
danevo.import_music_db(songs)
|
danevo.import_music_db(songs)
|
||||||
danevo.close()
|
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:
|
else:
|
||||||
raise CLIException("Unsupported game series!")
|
raise CLIException("Unsupported game series!")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ from bemani.client.reflec import (
|
||||||
from bemani.client.bishi import TheStarBishiBashiClient
|
from bemani.client.bishi import TheStarBishiBashiClient
|
||||||
from bemani.client.mga import MetalGearArcadeClient
|
from bemani.client.mga import MetalGearArcadeClient
|
||||||
from bemani.client.danevo import DanceEvolutionClient
|
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:
|
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,
|
pcbid,
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
|
if game == 'beatstream2':
|
||||||
|
return Beatstream2Client(
|
||||||
|
proto,
|
||||||
|
pcbid,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
|
||||||
raise Exception(f"Unknown game {game}")
|
raise Exception(f"Unknown game {game}")
|
||||||
|
|
||||||
|
|
@ -559,6 +566,10 @@ def mainloop(
|
||||||
"name": "Dance Evolution Arcade",
|
"name": "Dance Evolution Arcade",
|
||||||
"model": "KDM:J:B:A:2016080100",
|
"model": "KDM:J:B:A:2016080100",
|
||||||
"avs": "2.15.5 r6251",
|
"avs": "2.15.5 r6251",
|
||||||
|
},'beatstream2': {
|
||||||
|
'name': 'BeatStream 2',
|
||||||
|
'model': 'NBT:J:A:A:2016111400',
|
||||||
|
'avs': '2.16.7 r7487'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if action == "list":
|
if action == "list":
|
||||||
|
|
@ -683,6 +694,7 @@ def main() -> None:
|
||||||
"reflec-6": "reflec-volzza2",
|
"reflec-6": "reflec-volzza2",
|
||||||
"mga": "metal-gear-arcade",
|
"mga": "metal-gear-arcade",
|
||||||
"danevo": "dance-evolution",
|
"danevo": "dance-evolution",
|
||||||
|
'bst2': 'beatstream2',
|
||||||
}.get(game, game)
|
}.get(game, game)
|
||||||
|
|
||||||
mainloop(args.address, args.port, args.config, action, game, args.cardid, args.verbose)
|
mainloop(args.address, args.port, args.config, action, game, args.cardid, args.verbose)
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,9 @@ server:
|
||||||
# Delete this to disable this support.
|
# Delete this to disable this support.
|
||||||
webhooks:
|
webhooks:
|
||||||
discord:
|
discord:
|
||||||
iidx:
|
iidx: "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||||
- "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
pnm: "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||||
pnm:
|
bst: "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||||
- "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 URLs. These allow for in-game asset rendering on the front end. Delete this to disable asset rendering.
|
||||||
assets:
|
assets:
|
||||||
|
|
@ -89,6 +88,8 @@ support:
|
||||||
reflec: True
|
reflec: True
|
||||||
# SDVX frontend/backend enabled.
|
# SDVX frontend/backend enabled.
|
||||||
sdvx: True
|
sdvx: True
|
||||||
|
# Beatstream frontend/backend enable.
|
||||||
|
bst: True
|
||||||
|
|
||||||
# Key used to encrypt cookies, should be unique per network instance.
|
# Key used to encrypt cookies, should be unique per network instance.
|
||||||
secret_key: 'this_is_a_secret_please_change_me'
|
secret_key: 'this_is_a_secret_please_change_me'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user