From ab7e62d45517e4f9a67545bc4f941942fec75038 Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Thu, 9 Sep 2021 01:03:23 +0000 Subject: [PATCH] Add event and song unlock options to Eclale, document event unlocks, enable stamp rally. --- bemani/backend/popn/eclale.py | 144 +++++++++++++++++++++++++++++----- bemani/client/popn/eclale.py | 10 ++- 2 files changed, 130 insertions(+), 24 deletions(-) diff --git a/bemani/backend/popn/eclale.py b/bemani/backend/popn/eclale.py index 214e54a..1111702 100644 --- a/bemani/backend/popn/eclale.py +++ b/bemani/backend/popn/eclale.py @@ -1,6 +1,6 @@ # vim: set fileencoding=utf-8 import binascii -from typing import Dict, List +from typing import Any, Dict, List from typing_extensions import Final from bemani.backend.popn.base import PopnMusicBase @@ -41,37 +41,119 @@ class PopnMusicEclale(PopnMusicBase): def previous_version(self) -> PopnMusicBase: return PopnMusicLapistoria(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': '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 11', + 12: 'Phase 12', + 13: 'Phase 13', + 14: 'Phase 14', + 15: 'Phase 15', + 16: 'Phase MAX', + } + }, + { + 'name': 'Additional Music Unlock Phase', + 'tip': 'Additional music unlock phase for all players.', + 'category': 'game_config', + 'setting': 'music_sub_phase', + 'values': { + 0: 'No additional unlocks', + 1: 'Phase 1', + 2: 'Phase 2', + 3: 'Phase MAX', + }, + }, + ], + 'bools': [ + { + 'name': 'Enable Starmaker Event', + 'tip': 'Enable Starmaker event as well as song shop.', + 'category': 'game_config', + 'setting': 'starmaker_enable', + }, + { + 'name': 'Force Song Unlock', + 'tip': 'Force unlock all songs.', + 'category': 'game_config', + 'setting': 'force_unlock_songs', + }, + ], + } + def __construct_common_info(self, root: Node) -> None: - # Event phases - # TODO: Hook event mode settings up to the front end. + game_config = self.get_game_config() + music_phase = game_config.get_int('music_phase') + music_sub_phase = game_config.get_int('music_sub_phase') + + # Event phases. Eclale seems to be so basic that there is no way to disable/enable + # the starmaker event. It is just baked into the game. phases = { - # Unknown event (0-16) - 0: 16, + # Music open phase (0-16). + # The following songs are unlocked when the phase is at or above the number specified: + # 1 - 1470, 1471, 1472 + # 2 - 1447, 1450, 1454, 1457 + # 3 - 1477, 1475, 1483 + # 4 - 1473 + # 5 - 1480, 1479, 1481 + # 6 - 1494, 1495 + # 7 - 1490, 1491 + # 8 - 1489 + # 9 - 1502, 1503, 1504, 1505, 1506, 1507 + # 10 - 1492 + # 11 - 1508, 1509, 1510, 1511 + # 12 - 1518 + # 13 - 1530 + # 14 - 1543 + # 15 - 1544 + # 16 - 1548 + 0: music_phase, # Unknown event (0-3) 1: 3, # Unknown event (0-1) 2: 1, # Unknown event (0-2) 3: 2, - # Unknown event (0-1) + # Something to do with favorites folder and the favorites button on the 10key (0-1) 4: 1, - # Unknown event (0-2) - 5: 2, + # Looks like something to do with stamp cards, enabled with 1 (0-2) + 5: 1, # Unknown event (0-1) 6: 1, # Unknown event (0-4) 7: 4, - # Unknown event (0-3) - 8: 3, + # Unlock a few more songs (1: 1496, 2: 1474, 3: 1531) (0-3) + 8: music_sub_phase, # Unknown event (0-4) 9: 4, # Unknown event (0-4) 10: 4, - # Unknown event (0-1) + # Unknown event, maybe something to do with song categories? (0-1) 11: 1, - # Possibly net taisen related? (0-1) + # Enable Net Taisen, including win/loss sort option on music select (0-1) 12: 1, - # Unknown event (0-4) + # Enable local and server-side matching when selecting a song (0-4) 13: 4, } @@ -81,13 +163,14 @@ class PopnMusicEclale(PopnMusicBase): phase.add_child(Node.s16('event_id', phaseid)) phase.add_child(Node.s16('phase', phases[phaseid])) - for areaid in range(1, 50): - area = Node.void('area') - root.add_child(area) - area.add_child(Node.s16('area_id', areaid)) - area.add_child(Node.u64('end_date', 0)) - area.add_child(Node.s16('medal_id', areaid)) - area.add_child(Node.bool('is_limit', False)) + if game_config.get_bool('starmaker_enable'): + for areaid in range(51): + area = Node.void('area') + root.add_child(area) + area.add_child(Node.s16('area_id', areaid)) + area.add_child(Node.u64('end_date', 0)) + area.add_child(Node.s16('medal_id', areaid)) + area.add_child(Node.bool('is_limit', False)) # Calculate most popular characters profiles = self.data.remote.user.get_all_profiles(self.game, self.version) @@ -619,6 +702,17 @@ class PopnMusicEclale(PopnMusicBase): custom_cate.add_child(Node.s8('friend_no', -1)) custom_cate.add_child(Node.s8('score_flg', -1)) + 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)) + # Set up achievements achievements = self.data.local.user.get_achievements(self.game, self.version, userid) for achievement in achievements: @@ -629,10 +723,15 @@ class PopnMusicEclale(PopnMusicBase): item = Node.void('item') root.add_child(item) + # Type is the type of unlock/item. Type 0 is song unlock in Eclale. # 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 8 and 0. Might be # what chart is available? + if game_config.get_bool('force_unlock_songs') and itemtype == 0: + # We already sent song unlocks in the force unlock section above. + continue + item.add_child(Node.u8('type', itemtype)) item.add_child(Node.u16('id', achievement.id)) item.add_child(Node.u16('param', param)) @@ -775,6 +874,7 @@ class PopnMusicEclale(PopnMusicBase): newprofile.replace_int('stamp_cnt', stamp.child_value('cnt')) # Extract achievements + game_config = self.get_game_config() for node in request.children: if node.name == 'item': itemid = node.child_value('id') @@ -782,6 +882,10 @@ class PopnMusicEclale(PopnMusicBase): param = node.child_value('param') is_new = node.child_value('is_new') + 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/client/popn/eclale.py b/bemani/client/popn/eclale.py index 3d8c012..2aa187b 100644 --- a/bemani/client/popn/eclale.py +++ b/bemani/client/popn/eclale.py @@ -41,10 +41,12 @@ class PopnMusicEclaleClient(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") + + # The following only need to exist if starmaker is enabled. + # 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") def verify_info23_common(self, loc: str) -> None: call = self.call_node()