diff --git a/bemani/backend/base.py b/bemani/backend/base.py index a034f35..2cbcf45 100644 --- a/bemani/backend/base.py +++ b/bemani/backend/base.py @@ -3,7 +3,7 @@ import traceback from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type from bemani.common import Model, ValidatedDict, Profile, PlayStatistics, GameConstants, Time -from bemani.data import Config, Data, UserID, RemoteUser +from bemani.data import Config, Data, Machine, UserID, RemoteUser class ProfileCreationException(Exception): @@ -391,18 +391,27 @@ class Base(ABC): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) return machine.id + def get_machine(self) -> Machine: + return self.data.local.machine.get_machine(self.config.machine.pcbid) + def update_machine_name(self, newname: Optional[str]) -> None: if newname is None: return - machine = self.data.local.machine.get_machine(self.config.machine.pcbid) + machine = self.get_machine() machine.name = newname self.data.local.machine.put_machine(machine) def update_machine_data(self, newdata: Dict[str, Any]) -> None: - machine = self.data.local.machine.get_machine(self.config.machine.pcbid) + machine = self.get_machine() machine.data.update(newdata) self.data.local.machine.put_machine(machine) + def update_machine(self, newmachine: Machine) -> None: + machine = self.data.local.machine.get_machine(self.config.machine.pcbid) + machine.name = newmachine.name + machine.data = newmachine.data + self.data.local.machine.put_machine(machine) + def get_game_config(self) -> ValidatedDict: machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: diff --git a/bemani/backend/popn/fantasia.py b/bemani/backend/popn/fantasia.py index 9a8f3f0..3aae863 100644 --- a/bemani/backend/popn/fantasia.py +++ b/bemani/backend/popn/fantasia.py @@ -1,6 +1,6 @@ # vim: set fileencoding=utf-8 import copy -from typing import Dict, List +from typing import Any, Dict, List from bemani.backend.popn.base import PopnMusicBase from bemani.backend.popn.tunestreet import PopnMusicTuneStreet @@ -46,6 +46,41 @@ class PopnMusicFantasia(PopnMusicBase): def previous_version(self) -> PopnMusicBase: return PopnMusicTuneStreet(self.data, self.config, self.model) + @classmethod + def get_settings(cls) -> Dict[str, Any]: + """ + Return all of our front-end modifiably settings. + """ + return { + 'ints': [ + { + 'name': 'Game Phase', + 'tip': 'Game unlock phase for all players.', + 'category': 'game_config', + 'setting': 'game_phase', + 'values': { + 0: 'NO PHASE', + 1: 'SECRET DATA RELEASE', + 2: 'MAX: ALL DATA RELEASE', + } + }, + { + 'name': 'Soreyuke! pop\'n quest Event Phase', + 'tip': 'Event phase for all players.', + 'category': 'game_config', + 'setting': 'event_phase', + 'values': { + 0: 'No event', + 1: 'Phase 1', + 2: 'Phase 2', + 3: 'Phase 3', + 4: 'Phase 4', + 5: 'Phase MAX', + } + }, + ] + } + def __format_medal_for_score(self, score: Score) -> int: medal = { self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, @@ -109,6 +144,7 @@ class PopnMusicFantasia(PopnMusicBase): player_card.add_child(Node.s32('get_frame', player_card_dict.get_int('get_frame'))) player_card.add_child(Node.s32('get_base', player_card_dict.get_int('get_base'))) player_card.add_child(Node.s32_array('get_seal', player_card_dict.get_int_array('get_seal', 2))) + player_card.add_child(Node.s8('is_open', 1)) # Player card EX section player_card_ex = Node.void('player_card_ex') @@ -201,7 +237,6 @@ class PopnMusicFantasia(PopnMusicBase): netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) netvs.add_child(Node.s8_array('jewelry', [0] * 15)) for dialog in [0, 1, 2, 3, 4, 5]: - # TODO: Configure this, maybe? netvs.add_child(Node.string('dialog', f'dialog#{dialog}')) sp_data = Node.void('sp_data') @@ -213,17 +248,18 @@ class PopnMusicFantasia(PopnMusicBase): reflec_data.add_child(Node.s8_array('reflec', profile.get_int_array('reflec', 2))) # Navigate section - navigate_dict = profile.get_dict('navigate') - navigate = Node.void('navigate') - root.add_child(navigate) - navigate.add_child(Node.s8('genre', navigate_dict.get_int('genre'))) - navigate.add_child(Node.s8('image', navigate_dict.get_int('image'))) - navigate.add_child(Node.s8('level', navigate_dict.get_int('level'))) - navigate.add_child(Node.s8('ojama', navigate_dict.get_int('ojama'))) - navigate.add_child(Node.s16('limit_num', navigate_dict.get_int('limit_num'))) - navigate.add_child(Node.s8('button', navigate_dict.get_int('button'))) - navigate.add_child(Node.s8('life', navigate_dict.get_int('life'))) - navigate.add_child(Node.s16('progress', navigate_dict.get_int('progress'))) + for i in range(3): + navigate_dict = profile.get_dict(f'navigate_{i}') + navigate = Node.void('navigate') + root.add_child(navigate) + navigate.add_child(Node.s8('genre', navigate_dict.get_int('genre', -1))) + navigate.add_child(Node.s8('image', navigate_dict.get_int('image', -1))) + navigate.add_child(Node.s8('level', navigate_dict.get_int('level', -1))) + navigate.add_child(Node.s8('ojama', navigate_dict.get_int('ojama', -1))) + navigate.add_child(Node.s16('limit_num', navigate_dict.get_int('limit_num', -1))) + navigate.add_child(Node.s8('button', navigate_dict.get_int('button', -1))) + navigate.add_child(Node.s8('life', navigate_dict.get_int('life', -1))) + navigate.add_child(Node.s16('progress', navigate_dict.get_int('progress', -1))) return root @@ -238,6 +274,7 @@ class PopnMusicFantasia(PopnMusicBase): root.add_child(Node.u8('season', 0)) clear_medal = [0] * self.GAME_MAX_MUSIC_ID + hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8) scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: @@ -254,8 +291,21 @@ class PopnMusicFantasia(PopnMusicBase): continue clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score) + hiscore_index = (score.id * 4) + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER_POSITION, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX_POSITION, + }[score.chart] + hiscore_byte_pos = int((hiscore_index * 17) / 8) + hiscore_bit_pos = int((hiscore_index * 17) % 8) + hiscore_value = score.points << hiscore_bit_pos + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF) + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF) + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF) root.add_child(Node.u16_array('clear_medal', clear_medal)) + root.add_child(Node.binary('hiscore', bytes(hiscore_array))) return root @@ -317,18 +367,23 @@ class PopnMusicFantasia(PopnMusicBase): newprofile.replace_dict('player_card', player_card_dict) # Extract navigate stuff - navigate_dict = newprofile.get_dict('navigate') - navigate = request.child('navigate') - if navigate is not None: - navigate_dict.replace_int('genre', navigate.child_value('genre')) - navigate_dict.replace_int('image', navigate.child_value('image')) - navigate_dict.replace_int('level', navigate.child_value('level')) - navigate_dict.replace_int('ojama', navigate.child_value('ojama')) - navigate_dict.replace_int('limit_num', navigate.child_value('limit_num')) - navigate_dict.replace_int('button', navigate.child_value('button')) - navigate_dict.replace_int('life', navigate.child_value('life')) - navigate_dict.replace_int('progress', navigate.child_value('progress')) - newprofile.replace_dict('navigate', navigate_dict) + nav_id = 0 + for navigate in request.children: + if navigate.name == 'navigate': + navigate_dict = newprofile.get_dict(f'navigate_{nav_id}') + navigate_dict.replace_int('genre', navigate.child_value('genre')) + navigate_dict.replace_int('image', navigate.child_value('image')) + navigate_dict.replace_int('level', navigate.child_value('level')) + navigate_dict.replace_int('ojama', navigate.child_value('ojama')) + navigate_dict.replace_int('limit_num', navigate.child_value('limit_num')) + navigate_dict.replace_int('button', navigate.child_value('button')) + navigate_dict.replace_int('life', navigate.child_value('life')) + navigate_dict.replace_int('progress', navigate.child_value('progress')) + newprofile.replace_dict(f'navigate_{nav_id}', navigate_dict) + nav_id += 1 + + if nav_id >= 3: + break # Extract scores for node in request.children: @@ -397,22 +452,61 @@ class PopnMusicFantasia(PopnMusicBase): def handle_playerdata_set_request(self, request: Node) -> Node: refid = request.attribute('ref_id') + machine = self.get_machine() root = Node.void('playerdata') - root.add_child(Node.s8('pref', -1)) + root.add_child(Node.s8('pref', machine.data.get_int('pref', -1))) + if refid is None: + root.add_child(Node.string('name', '')) + root.add_child(Node.s16('chara', -1)) + root.add_child(Node.u8('frame', 0)) + root.add_child(Node.u8('base', 0)) + root.add_child(Node.u8('seal_1', 0)) + root.add_child(Node.u8('seal_2', 0)) + root.add_child(Node.u8('title_1', 0)) + root.add_child(Node.u8('title_2', 0)) + root.add_child(Node.s16('recommend_1', -1)) + root.add_child(Node.s16('recommend_2', -1)) + root.add_child(Node.s16('recommend_3', -1)) + root.add_child(Node.string('message', '')) return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: + root.add_child(Node.string('name', '')) + root.add_child(Node.s16('chara', -1)) + root.add_child(Node.u8('frame', 0)) + root.add_child(Node.u8('base', 0)) + root.add_child(Node.u8('seal_1', 0)) + root.add_child(Node.u8('seal_2', 0)) + root.add_child(Node.u8('title_1', 0)) + root.add_child(Node.u8('title_2', 0)) + root.add_child(Node.s16('recommend_1', -1)) + root.add_child(Node.s16('recommend_2', -1)) + root.add_child(Node.s16('recommend_3', -1)) + root.add_child(Node.string('message', '')) return root oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: + player_card_dict = newprofile.get_dict('player_card') + self.put_profile(userid, newprofile) - root.add_child(Node.string('name', newprofile['name'])) + root.add_child(Node.string('name', newprofile.get_str('name', 'なし'))) + root.add_child(Node.s16('chara', newprofile.get_int('chara', -1))) + root.add_child(Node.u8('frame', player_card_dict.get_int('frame'))) + root.add_child(Node.u8('base', player_card_dict.get_int('base'))) + root.add_child(Node.u8('seal_1', player_card_dict.get_int_array('seal', 2)[0])) + root.add_child(Node.u8('seal_2', player_card_dict.get_int_array('seal', 2)[1])) + root.add_child(Node.u8('title_1', player_card_dict.get_int_array('title', 2, [0, 1])[0])) + root.add_child(Node.u8('title_2', player_card_dict.get_int_array('title', 2, [0, 1])[1])) + root.add_child(Node.s16('recommend_1', -1)) + root.add_child(Node.s16('recommend_2', -1)) + root.add_child(Node.s16('recommend_3', -1)) + root.add_child(Node.string('message', '')) return root @@ -492,38 +586,48 @@ class PopnMusicFantasia(PopnMusicBase): friend.add_child(Node.u16_array('clear_medal', clear_medal)) friend.add_child(Node.binary('hiscore', hiscore)) + # Note that if we ever support internet ranking mode, there's an 'ir_hiscore' node here as well. + + # Also note that if we support lobbies, there's a few extra nodes in each friend for current lobby + # that they're in as well as previous lobby logs. + return root def handle_game_get_request(self, request: Node) -> Node: - # TODO: Hook these up to config so we can change this + game_config = self.get_game_config() + game_phase = game_config.get_int('game_phase') + event_phase = game_config.get_int('event_phase') + root = Node.void('game') - root.add_child(Node.s32('game_phase', 2)) + root.add_child(Node.s32('game_phase', game_phase)) root.add_child(Node.s32('ir_phase', 0)) - root.add_child(Node.s32('event_phase', 5)) - root.add_child(Node.s32('netvs_phase', 0)) + root.add_child(Node.s32('event_phase', event_phase)) + root.add_child(Node.s32('netvs_phase', 0)) # Net taisen mode, we don't support lobbies. root.add_child(Node.s32('card_phase', 6)) root.add_child(Node.s32('illust_phase', 2)) - root.add_child(Node.s32('psp_phase', 5)) + root.add_child(Node.s32('psp_phase', 5)) # Unlock songs from Pop'n Music Portable. root.add_child(Node.s32('other_phase', 1)) root.add_child(Node.s32('jubeat_phase', 1)) root.add_child(Node.s32('public_phase', 3)) root.add_child(Node.s32('kac_phase', 2)) - root.add_child(Node.s32('local_matching', 1)) + root.add_child(Node.s32('local_matching_enable', 1)) root.add_child(Node.s32('n_matching_sec', 60)) root.add_child(Node.s32('l_matching_sec', 60)) root.add_child(Node.s32('is_check_cpu', 0)) root.add_child(Node.s32('week_no', 0)) - root.add_child(Node.s32_array('ng_illust', [0] * 10)) + root.add_child(Node.s32('team_day', 0)) + root.add_child(Node.s32_array('ng_illust', [-1] * 64)) root.add_child(Node.s16_array('sel_ranking', [-1] * 10)) root.add_child(Node.s16_array('up_ranking', [-1] * 10)) + return root def handle_game_active_request(self, request: Node) -> Node: - # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('shop_name')) - return Node.void('game') - - def handle_game_taxphase_request(self, request: Node) -> Node: + # Update the name of this cab for admin purposes. Also store the prefecture. + machine = self.get_machine() + machine.name = request.child_value('shop_name') or machine.name + machine.data.replace_int('pref', request.child_value('pref')) + self.update_machine(machine) return Node.void('game') def handle_lobby_request(self, request: Node) -> Node: diff --git a/bemani/client/popn/fantasia.py b/bemani/client/popn/fantasia.py index acff3a2..1daf3c5 100644 --- a/bemani/client/popn/fantasia.py +++ b/bemani/client/popn/fantasia.py @@ -84,7 +84,7 @@ class PopnMusicFantasiaClient(BaseClient): 'jubeat_phase', 'public_phase', 'kac_phase', - 'local_matching', + 'local_matching_enable', 'n_matching_sec', 'l_matching_sec', 'is_check_cpu', @@ -101,22 +101,18 @@ class PopnMusicFantasiaClient(BaseClient): up_ranking = resp.child('game').child('up_ranking') ng_illust = resp.child('game').child('ng_illust') - for nodepair in [ - ('sel_ranking', sel_ranking, 's16'), - ('up_ranking', up_ranking, 's16'), - ('ng_illust', ng_illust, 's32'), + for name, node, dtype, length in [ + ('sel_ranking', sel_ranking, 's16', 10), + ('up_ranking', up_ranking, 's16', 10), + ('ng_illust', ng_illust, 's32', 64), ]: - name = nodepair[0] - node = nodepair[1] - dtype = nodepair[2] - if node is None: raise Exception(f'Missing node \'{name}\' in response!') if node.data_type != dtype: raise Exception(f'Node \'{name}\' has wrong data type!') if not node.is_array: raise Exception(f'Node \'{name}\' is not array!') - if len(node.value) != 10: + if len(node.value) != length: raise Exception(f'Node \'{name}\' is wrong array length!') def verify_playerdata_get(self, ref_id: str, msg_type: str) -> Optional[Dict[str, Any]]: