From c91a3f2ea7b4d665baeea1e81b1f2f5846f01d6b Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Sun, 13 Nov 2022 01:20:51 +0000 Subject: [PATCH] Move emblem unlocks to i tems, correctly allow earned JBOX emblem parts to show up on front end. --- bemani/backend/jubeat/base.py | 65 ++++++++++++++++++++- bemani/backend/jubeat/clan.py | 94 ++++++++++++++++-------------- bemani/backend/jubeat/festo.py | 100 ++++++++++++++++++-------------- bemani/backend/jubeat/prop.py | 68 ++++++++++++---------- bemani/backend/jubeat/qubell.py | 76 ++++++++++++++---------- 5 files changed, 254 insertions(+), 149 deletions(-) diff --git a/bemani/backend/jubeat/base.py b/bemani/backend/jubeat/base.py index 5eb2508..1c910d3 100644 --- a/bemani/backend/jubeat/base.py +++ b/bemani/backend/jubeat/base.py @@ -1,5 +1,6 @@ # vim: set fileencoding=utf-8 -from typing import Dict, List, Optional +import random +from typing import Dict, Iterable, List, Optional, Set, Tuple from typing_extensions import Final from bemani.backend.base import Base @@ -293,3 +294,65 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): raised, timestamp=timestamp, ) + + def default_select_jbox(self) -> Set[int]: + gameitems = self.data.local.game.get_items(self.game, self.version) + default_main: Set[int] = set() + + for gameitem in gameitems: + if gameitem.type == "emblem": + if ( + gameitem.data.get_int("layer") == 2 + and gameitem.data.get_int("rarity") == 1 + ): + default_main.add(gameitem.id) + + return default_main + + def random_select_jbox(self, owned_emblems: Set[int]) -> Tuple[int, int]: + gameitems = self.data.local.game.get_items(self.game, self.version) + normalemblems: Set[int] = set() + premiumemblems: Set[int] = set() + for gameitem in gameitems: + if gameitem.type == "emblem": + if gameitem.id in owned_emblems: + # We don't want to give out random emblems that are already owned. + continue + + if gameitem.data.get_int("rarity") in {1, 2, 3}: + normalemblems.add(gameitem.id) + if gameitem.data.get_int("rarity") in {4, 5}: + premiumemblems.add(gameitem.id) + + # Default to some emblems in case the catalog is not available. + normalindex = 2 + premiumindex = 1 + if normalemblems: + normalindex = random.sample(normalemblems, 1)[0] + if premiumemblems: + premiumindex = random.sample(premiumemblems, 1)[0] + + return normalindex, premiumindex + + def calculate_owned_items(self, item_list: List[int]) -> Set[int]: + owned_items: Set[int] = set() + + for index in range(len(item_list) * 32): + offset = 1 << (index % 32) + bucket = index // 32 + + if (item_list[bucket] & offset) != 0: + owned_items.add(index) + + return owned_items + + def create_owned_items(self, items: Iterable[int], size: int) -> List[int]: + items_list = [0] * size + + for index in items: + offset = 1 << (index % 32) + bucket = index // 32 + + items_list[bucket] |= offset + + return items_list diff --git a/bemani/backend/jubeat/clan.py b/bemani/backend/jubeat/clan.py index 959087a..bdce316 100644 --- a/bemani/backend/jubeat/clan.py +++ b/bemani/backend/jubeat/clan.py @@ -1401,9 +1401,18 @@ class JubeatClan( # Grab unlock progress item = player.child("item") if item is not None: - profile.replace_int_array( - "emblem_list", 96, item.child_value("emblem_list") + owned_emblems = self.calculate_owned_items( + item.child_value("emblem_list") ) + for index in owned_emblems: + self.data.local.user.put_achievement( + self.game, + self.version, + userid, + index, + "emblem", + {}, + ) # jbox stuff jbox = player.child("jbox") @@ -1540,6 +1549,28 @@ class JubeatClan( data = Node.void("data") root.add_child(data) + # Calculate all of our achievement-backed entities. + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) + event_completion: Dict[int, bool] = {} + course_completion: Dict[int, ValidatedDict] = {} + owned_emblems: Set[int] = set() + for achievement in achievements: + if achievement.type == "event": + event_completion[achievement.id] = achievement.data.get_bool( + "is_completed" + ) + elif achievement.type == "course": + course_completion[achievement.id] = achievement.data + elif achievement.type == "emblem": + owned_emblems.add(achievement.id) + + # Make sure we grant ownership of default main parts. + default_emblems = self.default_select_jbox() + owned_emblems.update(default_emblems) + default_main = next(iter(default_emblems)) if default_emblems else 0 + # Jubeat Clan appears to allow full event overrides per-player data.add_child(self.__get_global_info()) @@ -1608,13 +1639,16 @@ class JubeatClan( settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) - settings.add_child( - Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) - ) settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) + # Hack to make the default emblem appear properly. + partslist = lastdict.get_int_array("emblem", 5, [0, default_main, 0, 0, 0]) + if partslist[1] == 0: + partslist[1] = default_main + settings.add_child(Node.s16_array("emblem", partslist)) + # Secret unlocks item = Node.void("item") player.add_child(item) @@ -1649,9 +1683,7 @@ class JubeatClan( ) ) item.add_child( - Node.s32_array( - "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) - ) + Node.s32_array("emblem_list", self.create_owned_items(owned_emblems, 96)) ) item.add_child( Node.s32_array( @@ -1766,19 +1798,6 @@ class JubeatClan( # Player status for events event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements( - self.game, self.version, userid - ) - event_completion: Dict[int, bool] = {} - course_completion: Dict[int, ValidatedDict] = {} - for achievement in achievements: - if achievement.type == "event": - event_completion[achievement.id] = achievement.data.get_bool( - "is_completed" - ) - if achievement.type == "course": - course_completion[achievement.id] = achievement.data - for eventid, eventdata in self.EVENTS.items(): # There are two significant bits here, bit 0 and bit 1, I think the first # one is whether the event is started, second is if its finished? @@ -1809,24 +1828,7 @@ class JubeatClan( # Calculate a random index for normal and premium to give to player # as a gatcha. - gameitems = self.data.local.game.get_items(self.game, self.version) - normalemblems: Set[int] = set() - premiumemblems: Set[int] = set() - for gameitem in gameitems: - if gameitem.type == "emblem": - if gameitem.data.get_int("rarity") in {1, 2, 3}: - normalemblems.add(gameitem.id) - if gameitem.data.get_int("rarity") in {3, 4, 5}: - premiumemblems.add(gameitem.id) - - # Default to some emblems in case the catalog is not available. - normalindex = 2 - premiumindex = 1 - if normalemblems: - normalindex = random.sample(normalemblems, 1)[0] - if premiumemblems: - premiumindex = random.sample(premiumemblems, 1)[0] - + normalindex, premiumindex = self.random_select_jbox(owned_emblems) normal.add_child(Node.s16("index", normalindex)) premium.add_child(Node.s16("index", premiumindex)) @@ -2114,13 +2116,21 @@ class JubeatClan( newprofile.replace_int_array( "parts_list", 160, item.child_value("parts_list") ) - newprofile.replace_int_array( - "emblem_list", 96, item.child_value("emblem_list") - ) newprofile.replace_int_array( "commu_list", 16, item.child_value("commu_list") ) + owned_emblems = self.calculate_owned_items(item.child_value("emblem_list")) + for index in owned_emblems: + self.data.local.user.put_achievement( + self.game, + self.version, + userid, + index, + "emblem", + {}, + ) + newitem = item.child("new") if newitem is not None: newprofile.replace_int_array( diff --git a/bemani/backend/jubeat/festo.py b/bemani/backend/jubeat/festo.py index 933a41a..1e52a6e 100644 --- a/bemani/backend/jubeat/festo.py +++ b/bemani/backend/jubeat/festo.py @@ -2212,9 +2212,18 @@ class JubeatFesto( # Grab unlock progress item = player.child("item") if item is not None: - profile.replace_int_array( - "emblem_list", 96, item.child_value("emblem_list") + owned_emblems = self.calculate_owned_items( + item.child_value("emblem_list") ) + for index in owned_emblems: + self.data.local.user.put_achievement( + self.game, + self.version, + userid, + index, + "emblem", + {}, + ) # jbox stuff jbox = player.child("jbox") @@ -2417,6 +2426,28 @@ class JubeatFesto( data = Node.void("data") root.add_child(data) + # Calculate all of our achievement-backed entities. + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) + owned_emblems: Set[int] = set() + event_completion: Dict[int, bool] = {} + course_completion: Dict[int, ValidatedDict] = {} + for achievement in achievements: + if achievement.type == "event": + event_completion[achievement.id] = achievement.data.get_bool( + "is_completed" + ) + elif achievement.type == "course": + course_completion[achievement.id] = achievement.data + elif achievement.type == "emblem": + owned_emblems.add(achievement.id) + + # Make sure we grant ownership of default main parts. + default_emblems = self.default_select_jbox() + owned_emblems.update(default_emblems) + default_main = next(iter(default_emblems)) if default_emblems else 0 + # Jubeat Clan appears to allow full event overrides per-player data.add_child(self.__get_global_info()) @@ -2483,13 +2514,16 @@ class JubeatFesto( settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) - settings.add_child( - Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) - ) settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) + # Hack to make the default emblem appear properly. + partslist = lastdict.get_int_array("emblem", 5, [0, default_main, 0, 0, 0]) + if partslist[1] == 0: + partslist[1] = default_main + settings.add_child(Node.s16_array("emblem", partslist)) + # Secret unlocks, the game is too complicated and server-controlled to handle these correctly. item = Node.void("item") player.add_child(item) @@ -2503,6 +2537,8 @@ class JubeatFesto( "secret_list", profile.get_int_array("secret_list", 64, [-1] * 64) ) ) + + # We force unlock all themes, markers, titles, and parts, regardless of what the client ended up earning. item.add_child( Node.s32_array( "theme_list", profile.get_int_array("theme_list", 16, [-1] * 16) @@ -2523,11 +2559,13 @@ class JubeatFesto( "parts_list", profile.get_int_array("parts_list", 160, [-1] * 160) ) ) + + # These get earned by unlocking them through JBOX. item.add_child( - Node.s32_array( - "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) - ) + Node.s32_array("emblem_list", self.create_owned_items(owned_emblems, 96)) ) + + # I got no idea wtf this is, so I'm defaulting it to all on like the above ones. item.add_child( Node.s32_array( "commu_list", profile.get_int_array("commu_list", 16, [-1] * 16) @@ -2640,19 +2678,6 @@ class JubeatFesto( # Player status for events event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements( - self.game, self.version, userid - ) - event_completion: Dict[int, bool] = {} - course_completion: Dict[int, ValidatedDict] = {} - for achievement in achievements: - if achievement.type == "event": - event_completion[achievement.id] = achievement.data.get_bool( - "is_completed" - ) - if achievement.type == "course": - course_completion[achievement.id] = achievement.data - for eventid, eventdata in self.EVENTS.items(): # There are two significant bits here, bit 0 and bit 1, I think the first # one is whether the event is started, second is if its finished? @@ -2683,24 +2708,7 @@ class JubeatFesto( # Calculate a random index for normal and premium to give to player # as a gatcha. - gameitems = self.data.local.game.get_items(self.game, self.version) - normalemblems: Set[int] = set() - premiumemblems: Set[int] = set() - for gameitem in gameitems: - if gameitem.type == "emblem": - if gameitem.data.get_int("rarity") in {1, 2, 3}: - normalemblems.add(gameitem.id) - if gameitem.data.get_int("rarity") in {3, 4, 5}: - premiumemblems.add(gameitem.id) - - # Default to some emblems in case the catalog is not available. - normalindex = 2 - premiumindex = 1 - if normalemblems: - normalindex = random.sample(normalemblems, 1)[0] - if premiumemblems: - premiumindex = random.sample(premiumemblems, 1)[0] - + normalindex, premiumindex = self.random_select_jbox(owned_emblems) normal.add_child(Node.s16("index", normalindex)) premium.add_child(Node.s16("index", premiumindex)) @@ -2940,13 +2948,21 @@ class JubeatFesto( newprofile.replace_int_array( "parts_list", 160, item.child_value("parts_list") ) - newprofile.replace_int_array( - "emblem_list", 96, item.child_value("emblem_list") - ) newprofile.replace_int_array( "commu_list", 16, item.child_value("commu_list") ) + owned_emblems = self.calculate_owned_items(item.child_value("emblem_list")) + for index in owned_emblems: + self.data.local.user.put_achievement( + self.game, + self.version, + userid, + index, + "emblem", + {}, + ) + newitem = item.child("new") if newitem is not None: newprofile.replace_int_array( diff --git a/bemani/backend/jubeat/prop.py b/bemani/backend/jubeat/prop.py index d6c6a37..e995add 100644 --- a/bemani/backend/jubeat/prop.py +++ b/bemani/backend/jubeat/prop.py @@ -896,6 +896,21 @@ class JubeatProp( root = Node.void("gametop") data = Node.void("data") root.add_child(data) + + # Allow figuring out owned emblems. + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) + owned_emblems: Set[int] = set() + for achievement in achievements: + if achievement.type == "emblem": + owned_emblems.add(achievement.id) + + # Make sure we grant ownership of default main parts. + default_emblems = self.default_select_jbox() + owned_emblems.update(default_emblems) + default_main = next(iter(default_emblems)) if default_emblems else 0 + # Jubeat Prop appears to allow full event overrides per-player data.add_child(self.__get_global_info()) @@ -967,13 +982,16 @@ class JubeatProp( settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) - settings.add_child( - Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) - ) settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) + # Hack to make the default emblem appear properly. + partslist = lastdict.get_int_array("emblem", 5, [0, default_main, 0, 0, 0]) + if partslist[1] == 0: + partslist[1] = default_main + settings.add_child(Node.s16_array("emblem", partslist)) + # Secret unlocks item = Node.void("item") player.add_child(item) @@ -1004,9 +1022,7 @@ class JubeatProp( ) ) item.add_child( - Node.s32_array( - "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) - ) + Node.s32_array("emblem_list", self.create_owned_items(owned_emblems, 96)) ) new = Node.void("new") @@ -1045,9 +1061,6 @@ class JubeatProp( # Player status for events event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements( - self.game, self.version, userid - ) for achievement in achievements: if achievement.type == "event": # There are two significant bits here, 0x1 and 0x2, I think the first @@ -1172,24 +1185,7 @@ class JubeatProp( # Calculate a random index for normal and premium to give to player # as a gatcha. - gameitems = self.data.local.game.get_items(self.game, self.version) - normalemblems: Set[int] = set() - premiumemblems: Set[int] = set() - for gameitem in gameitems: - if gameitem.type == "emblem": - if gameitem.data.get_int("rarity") in {1, 2, 3}: - normalemblems.add(gameitem.id) - if gameitem.data.get_int("rarity") in {3, 4, 5}: - premiumemblems.add(gameitem.id) - - # Default to some emblems in case the catalog is not available. - normalindex = 2 - premiumindex = 1 - if normalemblems: - normalindex = random.sample(normalemblems, 1)[0] - if premiumemblems: - premiumindex = random.sample(premiumemblems, 1)[0] - + normalindex, premiumindex = self.random_select_jbox(owned_emblems) normal.add_child(Node.s16("index", normalindex)) premium.add_child(Node.s16("index", premiumindex)) @@ -1220,8 +1216,8 @@ class JubeatProp( # Emblem list stuff? eapass_privilege = Node.void("eapass_privilege") player.add_child(eapass_privilege) - emblem_list = Node.void("emblem_list") - eapass_privilege.add_child(emblem_list) + emblem_node = Node.void("emblem_list") + eapass_privilege.add_child(emblem_node) # Bonus music stuff? bonus_music = Node.void("bonus_music") @@ -1349,9 +1345,17 @@ class JubeatProp( newprofile.replace_int_array( "music_list", 32, item.child_value("music_list") ) - newprofile.replace_int_array( - "emblem_list", 96, item.child_value("emblem_list") - ) + + owned_emblems = self.calculate_owned_items(item.child_value("emblem_list")) + for index in owned_emblems: + self.data.local.user.put_achievement( + self.game, + self.version, + userid, + index, + "emblem", + {}, + ) newitem = item.child("new") if newitem is not None: diff --git a/bemani/backend/jubeat/qubell.py b/bemani/backend/jubeat/qubell.py index 451e738..4d922b2 100644 --- a/bemani/backend/jubeat/qubell.py +++ b/bemani/backend/jubeat/qubell.py @@ -671,9 +671,18 @@ class JubeatQubell( # Grab unlock progress item = player.child("item") if item is not None: - profile.replace_int_array( - "emblem_list", 96, item.child_value("emblem_list") + owned_emblems = self.calculate_owned_items( + item.child_value("emblem_list") ) + for index in owned_emblems: + self.data.local.user.put_achievement( + self.game, + self.version, + userid, + index, + "emblem", + {}, + ) # jbox stuff jbox = player.child("jbox") @@ -705,6 +714,20 @@ class JubeatQubell( root = Node.void("gametop") data = Node.void("data") root.add_child(data) + + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) + owned_emblems: Set[int] = set() + for achievement in achievements: + if achievement.type == "emblem": + owned_emblems.add(achievement.id) + + # Make sure we grant ownership of default main parts. + default_emblems = self.default_select_jbox() + owned_emblems.update(default_emblems) + default_main = next(iter(default_emblems)) if default_emblems else 0 + # Jubeat Prop appears to allow full event overrides per-player data.add_child(self.__get_global_info()) @@ -780,13 +803,16 @@ class JubeatQubell( settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) - settings.add_child( - Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) - ) settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) + # Hack to make the default emblem appear properly. + partslist = lastdict.get_int_array("emblem", 5, [0, default_main, 0, 0, 0]) + if partslist[1] == 0: + partslist[1] = default_main + settings.add_child(Node.s16_array("emblem", partslist)) + # Secret unlocks item = Node.void("item") player.add_child(item) @@ -821,9 +847,7 @@ class JubeatQubell( ) ) item.add_child( - Node.s32_array( - "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) - ) + Node.s32_array("emblem_list", self.create_owned_items(owned_emblems, 96)) ) new = Node.void("new") @@ -935,9 +959,6 @@ class JubeatQubell( # Player status for events event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements( - self.game, self.version, userid - ) for achievement in achievements: if achievement.type == "event": # There are two significant bits here, 0x1 and 0x2, I think the first @@ -966,24 +987,7 @@ class JubeatQubell( # Calculate a random index for normal and premium to give to player # as a gatcha. - gameitems = self.data.local.game.get_items(self.game, self.version) - normalemblems: Set[int] = set() - premiumemblems: Set[int] = set() - for gameitem in gameitems: - if gameitem.type == "emblem": - if gameitem.data.get_int("rarity") in {1, 2, 3}: - normalemblems.add(gameitem.id) - if gameitem.data.get_int("rarity") in {3, 4, 5}: - premiumemblems.add(gameitem.id) - - # Default to some emblems in case the catalog is not available. - normalindex = 2 - premiumindex = 1 - if normalemblems: - normalindex = random.sample(normalemblems, 1)[0] - if premiumemblems: - premiumindex = random.sample(premiumemblems, 1)[0] - + normalindex, premiumindex = self.random_select_jbox(owned_emblems) normal.add_child(Node.s16("index", normalindex)) premium.add_child(Node.s16("index", premiumindex)) @@ -1331,9 +1335,17 @@ class JubeatQubell( newprofile.replace_int_array( "parts_list", 160, item.child_value("parts_list") ) - newprofile.replace_int_array( - "emblem_list", 96, item.child_value("emblem_list") - ) + + owned_emblems = self.calculate_owned_items(item.child_value("emblem_list")) + for index in owned_emblems: + self.data.local.user.put_achievement( + self.game, + self.version, + userid, + index, + "emblem", + {}, + ) newitem = item.child("new") if newitem is not None: