diff --git a/TODO.md b/TODO.md index 618c7bb..f776609 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ Things that I have not gotten around to doing. - IIDX favorites viewer and editor for frontend. The data is all available in profile, but the interface was never built. - DDR calorie tracker and workout stats. Again, the data is all available but the interface was never built. - - Rivals for several games. Pop'n and Jubeat come to mind as games that I never supported rivals for, but have support in-game. + - Rivals support for Pop'n Music series. - Lobby for all games except Reflec Beat. Reflec is the only game with lobby support right now, but this should be fairly easy to add to other games since the backend infra is all there. Correct global IP detection is even built-in to the server and passed forward if you are using a proxy. - Prettify the frontend. Its a bit utilitarian right now, aside from some minor color flare. - Make the frontend work better on mobile. It works well enough, but it could be a much better experience. diff --git a/bemani/backend/jubeat/clan.py b/bemani/backend/jubeat/clan.py index b859d8b..6362478 100644 --- a/bemani/backend/jubeat/clan.py +++ b/bemani/backend/jubeat/clan.py @@ -1262,10 +1262,37 @@ class JubeatClan( new.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list_new', 16, [-1] * 16))) new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 16, [-1] * 16))) - # No rival support, yet. + # Add rivals to profile. rivallist = Node.void('rivallist') player.add_child(rivallist) - rivallist.set_attribute('count', '0') + + links = self.data.local.user.get_links(self.game, self.version, userid) + rivalcount = 0 + for link in links: + if link.type != 'rival': + continue + + rprofile = self.get_profile(link.other_userid) + if rprofile is None: + continue + + rival = Node.void('rival') + rivallist.add_child(rival) + rival.add_child(Node.s32('jid', rprofile.get_int('extid'))) + rival.add_child(Node.string('name', rprofile.get_str('name'))) + + # This looks like a carry-over from prop's career and isn't displayed. + career = Node.void('career') + rival.add_child(career) + career.add_child(Node.s16('level', 1)) + + # Lazy way of keeping track of rivals, since we can only have 3 + # or the game with throw up. + rivalcount += 1 + if rivalcount >= 3: + break + + rivallist.set_attribute('count', str(rivalcount)) lab_edit_seq = Node.void('lab_edit_seq') player.add_child(lab_edit_seq) diff --git a/bemani/backend/jubeat/prop.py b/bemani/backend/jubeat/prop.py index cb025cd..63a783f 100644 --- a/bemani/backend/jubeat/prop.py +++ b/bemani/backend/jubeat/prop.py @@ -923,10 +923,43 @@ class JubeatProp( news.add_child(Node.s16('checked', 0)) news.add_child(Node.u32('checked_flag', 0)) - # No rival support, yet. + # Add rivals to profile. rivallist = Node.void('rivallist') player.add_child(rivallist) - rivallist.set_attribute('count', '0') + + links = self.data.local.user.get_links(self.game, self.version, userid) + rivalcount = 0 + for link in links: + if link.type != 'rival': + continue + + rprofile = self.get_profile(link.other_userid) + if rprofile is None: + continue + + rival = Node.void('rival') + rivallist.add_child(rival) + rival.add_child(Node.s32('jid', rprofile.get_int('extid'))) + rival.add_child(Node.string('name', rprofile.get_str('name'))) + + rcareerdict = rprofile.get_dict('career') + career = Node.void('career') + rival.add_child(career) + career.add_child(Node.s16('level', rcareerdict.get_int('level', 1))) + + league = Node.void('league') + rival.add_child(league) + league.add_child(Node.bool('is_first_play', rprofile.get_bool('league_is_first_play', True))) + league.add_child(Node.s32('class', rprofile.get_int('league_class', 1))) + league.add_child(Node.s32('subclass', rprofile.get_int('league_subclass', 5))) + + # Lazy way of keeping track of rivals, since we can only have 3 + # or the game with throw up. + rivalcount += 1 + if rivalcount >= 3: + break + + rivallist.set_attribute('count', str(rivalcount)) # Nothing in life is free, WTF? free_first_play = Node.void('free_first_play') diff --git a/bemani/backend/jubeat/qubell.py b/bemani/backend/jubeat/qubell.py index c6f5730..f28151a 100644 --- a/bemani/backend/jubeat/qubell.py +++ b/bemani/backend/jubeat/qubell.py @@ -585,10 +585,37 @@ class JubeatQubell( new.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list_new', 16, [-1] * 16))) new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 16, [-1] * 16))) - # No rival support, yet. + # Add rivals to profile. rivallist = Node.void('rivallist') player.add_child(rivallist) - rivallist.set_attribute('count', '0') + + links = self.data.local.user.get_links(self.game, self.version, userid) + rivalcount = 0 + for link in links: + if link.type != 'rival': + continue + + rprofile = self.get_profile(link.other_userid) + if rprofile is None: + continue + + rival = Node.void('rival') + rivallist.add_child(rival) + rival.add_child(Node.s32('jid', rprofile.get_int('extid'))) + rival.add_child(Node.string('name', rprofile.get_str('name'))) + + # This looks like a carry-over from prop's career and isn't displayed. + career = Node.void('career') + rival.add_child(career) + career.add_child(Node.s16('level', 1)) + + # Lazy way of keeping track of rivals, since we can only have 3 + # or the game with throw up. + rivalcount += 1 + if rivalcount >= 3: + break + + rivallist.set_attribute('count', str(rivalcount)) lab_edit_seq = Node.void('lab_edit_seq') player.add_child(lab_edit_seq) diff --git a/bemani/backend/jubeat/saucer.py b/bemani/backend/jubeat/saucer.py index 99c0b09..3f3deec 100644 --- a/bemani/backend/jubeat/saucer.py +++ b/bemani/backend/jubeat/saucer.py @@ -140,6 +140,16 @@ class JubeatSaucer( root.set_attribute('status', str(Status.NO_PROFILE)) return root + def handle_gametop_get_rival_mdata_request(self, request: Node) -> Node: + data = request.child('data') + player = data.child('player') + extid = player.child_value('rival') + root = self.get_scores_by_extid(extid) + if root is None: + root = Node.void('gametop') + root.set_attribute('status', str(Status.NO_PROFILE)) + return root + def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('gametop') data = Node.void('data') @@ -249,10 +259,37 @@ class JubeatSaucer( player.add_child(news) news.add_child(Node.s16('checked', 0)) - # No rival support, yet. + # Add rivals to profile. rivallist = Node.void('rivallist') player.add_child(rivallist) - rivallist.set_attribute('count', '0') + + links = self.data.local.user.get_links(self.game, self.version, userid) + rivalcount = 0 + for link in links: + if link.type != 'rival': + continue + + rprofile = self.get_profile(link.other_userid) + if rprofile is None: + continue + + rival = Node.void('rival') + rivallist.add_child(rival) + rival.add_child(Node.s32('jid', rprofile.get_int('extid'))) + rival.add_child(Node.string('name', rprofile.get_str('name'))) + + # Lazy way of keeping track of rivals, since we can only have 4 + # or the game with throw up. At least, I think Fulfill can have + # 4 instead of the 3 found in newer versions, given the size of + # the array that it loads the values in. However, to keep things + # simple, I only supported three here. + rivalcount += 1 + if rivalcount >= 3: + break + + rivallist.set_attribute('count', str(rivalcount)) + + # Unclear what this is. Looks related to Jubeat lab. mylist = Node.void('mylist') player.add_child(mylist) mylist.set_attribute('count', '0') @@ -616,6 +653,7 @@ class JubeatSaucer( root.add_child(datanode) player = Node.void('player') datanode.add_child(player) + player.add_child(Node.s32('jid', profile.get_int('extid'))) playdata = Node.void('playdata') player.add_child(playdata) playdata.set_attribute('count', str(len(scores))) diff --git a/bemani/backend/jubeat/saucerfulfill.py b/bemani/backend/jubeat/saucerfulfill.py index 935b07c..a9493cb 100644 --- a/bemani/backend/jubeat/saucerfulfill.py +++ b/bemani/backend/jubeat/saucerfulfill.py @@ -271,6 +271,16 @@ class JubeatSaucerFulfill( root.set_attribute('status', str(Status.NO_PROFILE)) return root + def handle_gametop_get_rival_mdata_request(self, request: Node) -> Node: + data = request.child('data') + player = data.child('player') + extid = player.child_value('rival') + root = self.get_scores_by_extid(extid) + if root is None: + root = Node.void('gametop') + root.set_attribute('status', str(Status.NO_PROFILE)) + return root + def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('gametop') data = Node.void('data') @@ -433,10 +443,35 @@ class JubeatSaucerFulfill( player.add_child(news) news.add_child(Node.s16('checked', 0)) - # No rival support, yet. + # Add rivals to profile. rivallist = Node.void('rivallist') player.add_child(rivallist) - rivallist.set_attribute('count', '0') + + links = self.data.local.user.get_links(self.game, self.version, userid) + rivalcount = 0 + for link in links: + if link.type != 'rival': + continue + + rprofile = self.get_profile(link.other_userid) + if rprofile is None: + continue + + rival = Node.void('rival') + rivallist.add_child(rival) + rival.add_child(Node.s32('jid', rprofile.get_int('extid'))) + rival.add_child(Node.string('name', rprofile.get_str('name'))) + + # Lazy way of keeping track of rivals, since we can only have 4 + # or the game with throw up. At least, I think Fulfill can have + # 4 instead of the 3 found in newer versions, given the size of + # the array that it loads the values in. However, to keep things + # simple, I only supported three here. + rivalcount += 1 + if rivalcount >= 3: + break + + rivallist.set_attribute('count', str(rivalcount)) # Full combo daily challenge. entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') @@ -715,6 +750,7 @@ class JubeatSaucerFulfill( root.add_child(datanode) player = Node.void('player') datanode.add_child(player) + player.add_child(Node.s32('jid', profile.get_int('extid'))) playdata = Node.void('playdata') player.add_child(playdata) playdata.set_attribute('count', str(len(scores)))