diff --git a/bemani/backend/popn/common.py b/bemani/backend/popn/common.py index 9c370f5..5f30a0d 100644 --- a/bemani/backend/popn/common.py +++ b/bemani/backend/popn/common.py @@ -119,11 +119,18 @@ class PopnMusicModernBase(PopnMusicBase, ABC): return Node.void('pcb24') @abstractmethod - def get_phases(self) -> Dict[int, int]: - ... + def get_common_config(self) -> Tuple[Dict[int, int], bool]: + """ + Return a tuple of configuration options for sending the common node back + to the client. The first parameter is a dictionary whose keys are event + IDs and values are the event phase number. The second parameter is a bool + representing whether or not to send areas. + """ def __construct_common_info(self, root: Node) -> None: - for phaseid, phase_value in self.get_phases().items(): + phases, send_areas = self.get_common_config() + + for phaseid, phase_value in phases.items(): phase = Node.void('phase') root.add_child(phase) phase.add_child(Node.s16('event_id', phaseid)) @@ -199,13 +206,14 @@ class PopnMusicModernBase(PopnMusicBase, ABC): subnode.add_child(Node.u8('clear_type', ach.data.get_int('clear_type'))) subnode.add_child(Node.u8('clear_rank', ach.data.get_int('clear_rank'))) - for area_id in range(0, 16): - area = Node.void('area') - root.add_child(area) - area.add_child(Node.s16('area_id', area_id)) - area.add_child(Node.u64('end_date', 0)) - area.add_child(Node.s16('medal_id', area_id)) - area.add_child(Node.bool('is_limit', False)) + if send_areas: + for area_id in range(1, 16): + area = Node.void('area') + root.add_child(area) + area.add_child(Node.s16('area_id', area_id)) + area.add_child(Node.u64('end_date', 0)) + area.add_child(Node.s16('medal_id', area_id)) + area.add_child(Node.bool('is_limit', False)) for choco_id in range(0, 5): choco = Node.void('choco') @@ -958,6 +966,18 @@ class PopnMusicModernBase(PopnMusicBase, ABC): if 'navi_points' in profile: navi_data.add_child(Node.s32_array('raisePoint', profile.get_int_array('navi_points', 5))) + game_config = self.get_game_config() + if game_config.get_bool('force_unlock_songs'): + songs = self.data.local.music.get_all_songs(self.game, self.version) + for song in songs: + item = Node.void('item') + root.add_child(item) + item.add_child(Node.u8('type', 0)) + item.add_child(Node.u16('id', song.id)) + item.add_child(Node.u16('param', 15)) + item.add_child(Node.bool('is_new', False)) + item.add_child(Node.u64('get_time', 0)) + # Set up achievements achievements = self.data.local.user.get_achievements(self.game, self.version, userid) for achievement in achievements: @@ -967,8 +987,6 @@ class PopnMusicModernBase(PopnMusicBase, ABC): is_new = achievement.data.get_bool('is_new') get_time = achievement.data.get_int('get_time') - item = Node.void('item') - root.add_child(item) # Item type can be 0-6 inclusive and is the type of the unlock/item. # Item 0 is music unlocks. In this case, the id is the song ID according # to the game. Unclear what the param is supposed to be, but i've seen @@ -982,6 +1000,12 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # 4: 1 # 5: 1 # 6: 60 + if game_config.get_bool('force_unlock_songs') and itemtype == 0: + # We already sent song unlocks in the force unlock section above. + continue + + item = Node.void('item') + root.add_child(item) item.add_child(Node.u8('type', itemtype)) item.add_child(Node.u16('id', achievement.id)) item.add_child(Node.u16('param', param)) @@ -1223,6 +1247,7 @@ class PopnMusicModernBase(PopnMusicBase, ABC): ) # Extract achievements + game_config = self.get_game_config() for node in request.children: if node.name == 'item': itemid = node.child_value('id') @@ -1231,6 +1256,10 @@ class PopnMusicModernBase(PopnMusicBase, ABC): is_new = node.child_value('is_new') get_time = node.child_value('get_time') + if game_config.get_bool('force_unlock_songs') and itemtype == 0: + # If we enabled force song unlocks, don't save songs to the profile. + continue + self.data.local.user.put_achievement( self.game, self.version, diff --git a/bemani/backend/popn/eclale.py b/bemani/backend/popn/eclale.py index dae7be8..7cddd21 100644 --- a/bemani/backend/popn/eclale.py +++ b/bemani/backend/popn/eclale.py @@ -164,7 +164,7 @@ class PopnMusicEclale(PopnMusicBase): phase.add_child(Node.s16('phase', phases[phaseid])) if game_config.get_bool('starmaker_enable'): - for areaid in range(51): + for areaid in range(1, 51): area = Node.void('area') root.add_child(area) area.add_child(Node.s16('area_id', areaid)) diff --git a/bemani/backend/popn/peace.py b/bemani/backend/popn/peace.py index 4b8a155..dfb3dc9 100644 --- a/bemani/backend/popn/peace.py +++ b/bemani/backend/popn/peace.py @@ -1,5 +1,5 @@ # vim: set fileencoding=utf-8 -from typing import Dict +from typing import Dict, Tuple from bemani.backend.popn.base import PopnMusicBase from bemani.backend.popn.common import PopnMusicModernBase @@ -18,58 +18,61 @@ class PopnMusicPeace(PopnMusicModernBase): def previous_version(self) -> PopnMusicBase: return PopnMusicUsaNeko(self.data, self.config, self.model) - def get_phases(self) -> Dict[int, int]: + def get_common_config(self) -> Tuple[Dict[int, int], bool]: # Event phases # TODO: Hook event mode settings up to the front end. - return { - # Default song phase availability (0-23) - 0: 23, - # Unknown event (0-2) - 1: 2, - # Unknown event (0-2) - 2: 2, - # Unknown event (0-4) - 3: 4, - # Unknown event (0-1) - 4: 1, - # Enable Net Taisen, including win/loss display on song select (0-1) - 5: 1, - # Enable NAVI-kun shunkyoku toujou, allows song 1608 to be unlocked (0-1) - 6: 1, - # Unknown event (0-1) - 7: 1, - # Unknown event (0-2) - 8: 2, - # Daily Mission (0-2) - 9: 2, - # NAVI-kun Song phase availability (0-30) - 10: 30, - # Unknown event (0-1) - 11: 1, - # Unknown event (0-2) - 12: 2, - # Enable Pop'n Peace preview song (0-1) - 13: 1, - # Unknown event (0-39) - 14: 39, - # Unknown event (0-2) - 15: 2, - # Unknown event (0-3) - 16: 3, - # Unknown event (0-8) - 17: 8, - # Unknown event (0-1) - 28: 1, - # Unknown event (0-1) - 19: 1, - # Unknown event (0-13) - 20: 13, - # Pop'n event archive song phase availability (0-20) - 21: 20, - # Unknown event (0-2) - 22: 2, - # Unknown event (0-1) - 23: 1, - # Unknown event (0-1) - 24: 1, - } + return ( + { + # Default song phase availability (0-23) + 0: 23, + # Unknown event (0-2) + 1: 2, + # Unknown event (0-2) + 2: 2, + # Unknown event (0-4) + 3: 4, + # Unknown event (0-1) + 4: 1, + # Enable Net Taisen, including win/loss display on song select (0-1) + 5: 1, + # Enable NAVI-kun shunkyoku toujou, allows song 1608 to be unlocked (0-1) + 6: 1, + # Unknown event (0-1) + 7: 1, + # Unknown event (0-2) + 8: 2, + # Daily Mission (0-2) + 9: 2, + # NAVI-kun Song phase availability (0-30) + 10: 30, + # Unknown event (0-1) + 11: 1, + # Unknown event (0-2) + 12: 2, + # Enable Pop'n Peace preview song (0-1) + 13: 1, + # Unknown event (0-39) + 14: 39, + # Unknown event (0-2) + 15: 2, + # Unknown event (0-3) + 16: 3, + # Unknown event (0-8) + 17: 8, + # Unknown event (0-1) + 28: 1, + # Unknown event (0-1) + 19: 1, + # Unknown event (0-13) + 20: 13, + # Pop'n event archive song phase availability (0-20) + 21: 20, + # Unknown event (0-2) + 22: 2, + # Unknown event (0-1) + 23: 1, + # Unknown event (0-1) + 24: 1, + }, + False, + ) diff --git a/bemani/backend/popn/usaneko.py b/bemani/backend/popn/usaneko.py index aa691fc..b76a713 100644 --- a/bemani/backend/popn/usaneko.py +++ b/bemani/backend/popn/usaneko.py @@ -1,5 +1,5 @@ # vim: set fileencoding=utf-8 -from typing import Dict +from typing import Any, Dict, Tuple from bemani.backend.popn.base import PopnMusicBase from bemani.backend.popn.common import PopnMusicModernBase @@ -18,36 +18,147 @@ class PopnMusicUsaNeko(PopnMusicModernBase): def previous_version(self) -> PopnMusicBase: return PopnMusicEclale(self.data, self.config, self.model) - def get_phases(self) -> Dict[int, int]: - # Event phases - # TODO: Hook event mode settings up to the front end. + @classmethod + def get_settings(cls) -> Dict[str, Any]: + """ + Return all of our front-end modifiably settings. + """ return { - # Default song phase availability (0-11) - 0: 11, - # Unknown event (0-2) - 1: 2, - # Unknown event (0-2) - 2: 2, - # Unknown event (0-4) - 3: 4, - # Unknown event (0-1) - 4: 1, - # Enable Net Taisen, including win/loss display on song select (0-1) - 5: 1, - # Enable NAVI-kun shunkyoku toujou, allows song 1608 to be unlocked (0-1) - 6: 1, - # Unknown event (0-1) - 7: 1, - # Unknown event (0-2) - 8: 2, - # Daily Mission (0-2) - 9: 2, - # NAVI-kun Song phase availability (0-15) - 10: 15, - # Unknown event (0-1) - 11: 1, - # Unknown event (0-2) - 12: 2, - # Enable Pop'n Peace preview song (0-1) - 13: 1, + 'ints': [ + { + 'name': 'Music Open Phase', + 'tip': 'Default music phase for all players.', + 'category': 'game_config', + 'setting': 'music_phase', + 'values': { + 0: 'No music unlocks', + 1: 'Phase 1', + 2: 'Phase 2', + 3: 'Phase 3', + 4: 'Phase 4', + 5: 'Phase 5', + 6: 'Phase 6', + 7: 'Phase 7', + 8: 'Phase 8', + 9: 'Phase 9', + 10: 'Phase 10', + 11: 'Phase MAX', + } + }, + { + 'name': 'Active Event', + 'tip': 'Active event for all players.', + 'category': 'game_config', + 'setting': 'active_event', + 'values': { + 0: 'No event', + 1: 'NAVI-Kun event', + 2: 'Daily Mission event', + }, + }, + { + 'name': 'NAVI-Kun Event Phase', + 'tip': 'NAVI-Kun event phase for all players.', + 'category': 'game_config', + 'setting': 'navikun_phase', + 'values': { + 0: 'Phase 1', + 1: 'Phase 2', + 2: 'Phase 3', + 3: 'Phase 4', + 4: 'Phase 5', + 5: 'Phase 6', + 6: 'Phase 7', + 7: 'Phase 8', + 8: 'Phase 9', + 9: 'Phase 10', + 10: 'Phase 11', + 11: 'Phase 12', + 12: 'Phase 13', + 13: 'Phase 14', + 14: 'Phase 15', + 15: 'Phase MAX', + }, + }, + ], + 'bools': [ + { + 'name': 'Force Song Unlock', + 'tip': 'Force unlock all songs.', + 'category': 'game_config', + 'setting': 'force_unlock_songs', + }, + ], } + + def get_common_config(self) -> Tuple[Dict[int, int], bool]: + game_config = self.get_game_config() + music_phase = game_config.get_int('music_phase') + active_event = game_config.get_int('active_event') + navikun_phase = game_config.get_int('navikun_phase') + + navikun_enabled = active_event == 1 + daily_mission_enabled = active_event == 2 + + # Event phases + return ( + { + # Default song phase availability (0-11) + # The following songs are unlocked when the phase is at or above the number specified: + # 1 - 1589, 1590, 1591 + # 2 - 1594, 1595 + # 3 - 1596, 1597 + # 4 - 1593 + # 5 - 1602 + # 6 - 1604 + # 7 - 1629, 1630, 1631, 1633, 1641, 1642, 1643, 1644, 1645, 1646, 1647 + # 8 - 1632 + # 9 - 1651 + # 10 - 1679, 1680, 1681 + # 11 - 1669, 1670, 1669, 1670 + 0: music_phase, + # Unknown event (0-2) + 1: 2, + # Unknown event (0-2) + 2: 2, + # Unknown event (0-4) + 3: 4, + # Unknown event (0-1) + 4: 1, + # Enable Net Taisen, including win/loss display on song select (0-1) + 5: 1, + # Enable NAVI-kun shunkyoku toujou, allows song 1608 to be unlocked (0-1) + 6: 1, + # Unknown event (0-1) + 7: 1, + # Unknown event (0-2) + 8: 2, + # Daily Mission (0-2) + 9: 2 if daily_mission_enabled else 0, + # NAVI-kun Song phase availability (0-15) + # The following songs are unlocked when the phase is at or above the number specified: + # 1 - 1553 + # 2 - 1577, 1576, 1569 + # 3 - 1575, 1557 + # 4 - 1567 + # 5 - 1587, 1588, 1585 + # 6 - 1586 + # 7 - 1601, 1600, 1599 + # 8 - 1603 + # 9 - 1606, 1607, 1605 + # 10 - 1610, 1611, 1612 + # 11 - 1616, 1613, 1614, 1615 + # 12 - 1619, 1618, 1620, 1617 + # 13 - 1624, 1621, 1623, 1622 + # 14 - 1627, 1626, 1625 + # 15 - 1628 + 10: navikun_phase, + # Unknown event (0-1) + 11: 1, + # Unknown event (0-2) + 12: 2, + # Enable Pop'n Peace preview song (1703) (0-1) + 13: 1, + }, + navikun_enabled, + ) diff --git a/bemani/client/popn/usaneko.py b/bemani/client/popn/usaneko.py index f9b0f97..86d2def 100644 --- a/bemani/client/popn/usaneko.py +++ b/bemani/client/popn/usaneko.py @@ -41,10 +41,13 @@ class PopnMusicUsaNekoClient(BaseClient): def __verify_common(self, root: str, resp: Node) -> None: self.assert_path(resp, f"response/{root}/phase/event_id") self.assert_path(resp, f"response/{root}/phase/phase") - self.assert_path(resp, f"response/{root}/area/area_id") - self.assert_path(resp, f"response/{root}/area/end_date") - self.assert_path(resp, f"response/{root}/area/medal_id") - self.assert_path(resp, f"response/{root}/area/is_limit") + + # Area stuff is only necessary if navikun event is active. + # self.assert_path(resp, f"response/{root}/area/area_id") + # self.assert_path(resp, f"response/{root}/area/end_date") + # self.assert_path(resp, f"response/{root}/area/medal_id") + # self.assert_path(resp, f"response/{root}/area/is_limit") + self.assert_path(resp, f"response/{root}/choco/choco_id") self.assert_path(resp, f"response/{root}/choco/param") self.assert_path(resp, f"response/{root}/goods/item_id")