from typing import Dict, Optional, Tuple from bemani.backend.ddr.base import DDRBase from bemani.common import Time, ValidatedDict, intish from bemani.data import Score, UserID from bemani.protocol import Node class DDRGameShopHandler(DDRBase): def handle_game_shop_request(self, request: Node) -> Node: self.update_machine_name(request.attribute('name')) game = Node.void('game') game.set_attribute('stop', '0') return game class DDRGameLogHandler(DDRBase): def handle_game_log_request(self, request: Node) -> Node: return Node.void('game') class DDRGameMessageHandler(DDRBase): def handle_game_message_request(self, request: Node) -> Node: return Node.void('game') class DDRGameRankingHandler(DDRBase): def handle_game_ranking_request(self, request: Node) -> Node: # Ranking request, unknown what its for return Node.void('game') class DDRGameLockHandler(DDRBase): def handle_game_lock_request(self, request: Node) -> Node: game = Node.void('game') game.set_attribute('now_login', '0') return game class DDRGameTaxInfoHandler(DDRBase): def handle_game_tax_info_request(self, request: Node) -> Node: game = Node.void('game') tax_info = Node.void('tax_info') game.add_child(tax_info) tax_info.set_attribute('tax_phase', '0') return game class DDRGameRecorderHandler(DDRBase): def handle_game_recorder_request(self, request: Node) -> Node: return Node.void('game') class DDRGameHiscoreHandler(DDRBase): def handle_game_hiscore_request(self, request: Node) -> Node: records = self.data.remote.music.get_all_records(self.game, self.music_version) sortedrecords: Dict[int, Dict[int, Tuple[UserID, Score]]] = {} missing_profiles = [] for (userid, score) in records: if score.id not in sortedrecords: sortedrecords[score.id] = {} sortedrecords[score.id][score.chart] = (userid, score) missing_profiles.append(userid) users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_profiles)} game = Node.void('game') for song in sortedrecords: music = Node.void('music') game.add_child(music) music.set_attribute('reclink_num', str(song)) for chart in sortedrecords[song]: userid, score = sortedrecords[song][chart] try: gamechart = self.db_to_game_chart(chart) except KeyError: # Don't support this chart in this game continue gamerank = self.db_to_game_rank(score.data.get_int('rank')) combo_type = self.db_to_game_halo(score.data.get_int('halo')) typenode = Node.void('type') music.add_child(typenode) typenode.set_attribute('diff', str(gamechart)) typenode.add_child(Node.string('name', users[userid].get_str('name'))) typenode.add_child(Node.u32('score', score.points)) typenode.add_child(Node.u16('area', users[userid].get_int('area', 51))) typenode.add_child(Node.u8('rank', gamerank)) typenode.add_child(Node.u8('combo_type', combo_type)) typenode.add_child(Node.u32('code', users[userid].get_int('extid'))) return game class DDRGameAreaHiscoreHandler(DDRBase): def handle_game_area_hiscore_request(self, request: Node) -> Node: shop_area = int(request.attribute('shop_area')) # First, get all users that are in the current shop's area area_users = { uid: prof for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) if prof.get_int('area', 51) == shop_area } # Second, look up records belonging only to those users records = self.data.local.music.get_all_records(self.game, self.music_version, userlist=list(area_users.keys())) # Now, do the same lazy thing as 'hiscore' because I don't want # to think about how to change this knowing that we only pulled # up area records. area_records: Dict[int, Dict[int, Tuple[UserID, Score]]] = {} for (userid, score) in records: if score.id not in area_records: area_records[score.id] = {} area_records[score.id][score.chart] = (userid, score) game = Node.void('game') for song in area_records: music = Node.void('music') game.add_child(music) music.set_attribute('reclink_num', str(song)) for chart in area_records[song]: userid, score = area_records[song][chart] if area_users[userid].get_int('area', 51) != shop_area: # Don't return this, this user isn't in this area continue try: gamechart = self.db_to_game_chart(chart) except KeyError: # Don't support this chart in this game continue gamerank = self.db_to_game_rank(score.data.get_int('rank')) combo_type = self.db_to_game_halo(score.data.get_int('halo')) typenode = Node.void('type') music.add_child(typenode) typenode.set_attribute('diff', str(gamechart)) typenode.add_child(Node.string('name', area_users[userid].get_str('name'))) typenode.add_child(Node.u32('score', score.points)) typenode.add_child(Node.u16('area', area_users[userid].get_int('area', 51))) typenode.add_child(Node.u8('rank', gamerank)) typenode.add_child(Node.u8('combo_type', combo_type)) typenode.add_child(Node.u32('code', area_users[userid].get_int('extid'))) return game class DDRGameScoreHandler(DDRBase): def handle_game_score_request(self, request: Node) -> Node: refid = request.attribute('refid') songid = int(request.attribute('mid')) chart = self.game_to_db_chart(int(request.attribute('type'))) userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: attempts = self.data.local.music.get_all_attempts( self.game, self.music_version, userid, songid=songid, songchart=chart, limit=5, ) recentscores = [attempt.points for (_, attempt) in attempts] else: recentscores = [] # Always pad to five, so we ensure that we return all the scores while len(recentscores) < 5: recentscores.append(0) # Return the most recent five scores game = Node.void('game') for i in range(len(recentscores)): game.set_attribute(f'sc{i + 1}', str(recentscores[i])) return game class DDRGameTraceHandler(DDRBase): def handle_game_trace_request(self, request: Node) -> Node: extid = int(request.attribute('code')) chart = int(request.attribute('type')) cid = intish(request.attribute('cid')) mid = intish(request.attribute('mid')) # Base packet is just game, if we find something we add to it game = Node.void('game') # Rival trace loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: # Nothing to load return game if mid is not None: # Load trace from song score songscore = self.data.remote.music.get_score( self.game, self.music_version, userid, mid, self.game_to_db_chart(chart), ) if songscore is not None and 'trace' in songscore.data: game.add_child(Node.u32('size', len(songscore.data['trace']))) game.add_child(Node.u8_array('trace', songscore.data['trace'])) elif cid is not None: # Load trace from achievement coursescore = self.data.local.user.get_achievement( self.game, self.version, userid, (cid * 4) + chart, 'course', ) if coursescore is not None and 'trace' in coursescore: game.add_child(Node.u32('size', len(coursescore['trace']))) game.add_child(Node.u8_array('trace', coursescore['trace'])) # Nothing found, return empty return game class DDRGameLoadHandler(DDRBase): def handle_game_load_request(self, request: Node) -> Node: refid = request.attribute('refid') profile = self.get_profile_by_refid(refid) if profile is not None: return profile game = Node.void('game') game.set_attribute('none', '0') return game class DDRGameLoadDailyHandler(DDRBase): def handle_game_load_daily_request(self, request: Node) -> Node: extid = intish(request.attribute('code')) refid = request.attribute('refid') game = Node.void('game') profiledict = None if extid is not None: # Rival daily loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) else: # Self daily loading userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: profiledict = self.get_profile(userid) if profiledict is not None: play_stats = self.get_play_statistics(userid) # Day play counts last_play_date = play_stats.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if ( last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2] ): today_count = play_stats.get_int('today_plays', 0) else: today_count = 0 daycount = Node.void('daycount') game.add_child(daycount) daycount.set_attribute('playcount', str(today_count)) # Daily combo stuff, unclear how this works dailycombo = Node.void('dailycombo') game.add_child(dailycombo) dailycombo.set_attribute('daily_combo', str(0)) dailycombo.set_attribute('daily_combo_lv', str(0)) return game class DDRGameOldHandler(DDRBase): def handle_game_old_request(self, request: Node) -> Node: refid = request.attribute('refid') game = Node.void('game') userid = self.data.remote.user.from_refid(self.game, self.version, refid) previous_version: Optional[DDRBase] = None oldprofile: Optional[ValidatedDict] = None if userid is not None: previous_version = self.previous_version() if previous_version is not None: oldprofile = previous_version.get_profile(userid) if oldprofile is not None: game.set_attribute('name', oldprofile.get_str('name')) game.set_attribute('area', str(oldprofile.get_int('area', 51))) return game class DDRGameNewHandler(DDRBase): def handle_game_new_request(self, request: Node) -> Node: refid = request.attribute('refid') area = int(request.attribute('area')) name = request.attribute('name').strip() # Create a new profile for this user! self.new_profile_by_refid(refid, name, area) # No response needed game = Node.void('game') return game class DDRGameSaveHandler(DDRBase): def handle_game_save_request(self, request: Node) -> Node: refid = request.attribute('refid') self.put_profile_by_refid(refid, request) # No response needed game = Node.void('game') return game class DDRGameFriendHandler(DDRBase): def handle_game_friend_request(self, request: Node) -> Node: extid = intish(request.attribute('code')) userid = None friend = None if extid is not None: # Rival score loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: friend = self.get_profile(userid) play_stats = self.get_play_statistics(userid) if friend is None: # Return an empty node to tell the game we don't have a player here game = Node.void('game') return game game = Node.void('game') game.set_attribute('data', '1') game.add_child(Node.u32('code', friend.get_int('extid'))) game.add_child(Node.string('name', friend.get_str('name'))) game.add_child(Node.u8('area', friend.get_int('area', 51))) game.add_child(Node.u32('exp', play_stats.get_int('exp'))) game.add_child(Node.u32('star', friend.get_int('star'))) # Drill rankings if 'title' in friend: title = Node.void('title') game.add_child(title) titledict = friend.get_dict('title') if 't' in titledict: title.set_attribute('t', str(titledict.get_int('t'))) if 's' in titledict: title.set_attribute('s', str(titledict.get_int('s'))) if 'd' in titledict: title.set_attribute('d', str(titledict.get_int('d'))) if 'title_gr' in friend: title_gr = Node.void('title_gr') game.add_child(title_gr) title_grdict = friend.get_dict('title_gr') if 't' in title_grdict: title_gr.set_attribute('t', str(title_grdict.get_int('t'))) if 's' in title_grdict: title_gr.set_attribute('s', str(title_grdict.get_int('s'))) if 'd' in title_grdict: title_gr.set_attribute('d', str(title_grdict.get_int('d'))) # Groove gauge level-ups gr_s = Node.void('gr_s') game.add_child(gr_s) index = 1 for entry in friend.get_int_array('gr_s', 5): gr_s.set_attribute(f'gr{index}', str(entry)) index = index + 1 gr_d = Node.void('gr_d') game.add_child(gr_d) index = 1 for entry in friend.get_int_array('gr_d', 5): gr_d.set_attribute(f'gr{index}', str(entry)) index = index + 1 return game class DDRGameLoadCourseHandler(DDRBase): def handle_game_load_c_request(self, request: Node) -> Node: extid = intish(request.attribute('code')) refid = request.attribute('refid') if extid is not None: # Rival score loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) else: # Self score loading userid = self.data.remote.user.from_refid(self.game, self.version, refid) coursedata = [0] * 3200 if userid is not None: for course in self.data.local.user.get_achievements(self.game, self.version, userid): if course.type != 'course': continue # Grab course ID and chart (kinda pointless because we add it right back up # below, but it is more documented/readable this way. courseid = int(course.id / 4) coursechart = course.id % 4 # Populate course data index = ((courseid * 4) + coursechart) * 8 if index >= 0 and index <= (len(coursedata) - 8): coursedata[index + 0] = int(course.data.get_int('score') / 10000) coursedata[index + 1] = course.data.get_int('score') % 10000 coursedata[index + 2] = course.data.get_int('combo') coursedata[index + 3] = self.db_to_game_rank(course.data.get_int('rank')) coursedata[index + 5] = course.data.get_int('stage') coursedata[index + 6] = course.data.get_int('combo_type') game = Node.void('game') game.add_child(Node.u16_array('course', coursedata)) return game class DDRGameSaveCourseHandler(DDRBase): def handle_game_save_c_request(self, request: Node) -> Node: refid = request.attribute('refid') courseid = int(request.attribute('cid')) chart = int(request.attribute('ctype')) userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Calculate statistics data = request.child('data') points = int(data.attribute('score')) combo = int(data.attribute('combo')) combo_type = int(data.attribute('combo_type')) stage = int(data.attribute('stage')) rank = self.game_to_db_rank(int(data.attribute('rank'))) trace = request.child_value('trace') # Grab the old course score oldcourse = self.data.local.user.get_achievement( self.game, self.version, userid, (courseid * 4) + chart, 'course', ) if oldcourse is not None: highscore = points > oldcourse.get_int('score') points = max(points, oldcourse.get_int('score')) combo = max(combo, oldcourse.get_int('combo')) stage = max(stage, oldcourse.get_int('stage')) rank = max(rank, oldcourse.get_int('rank')) combo_type = max(combo_type, oldcourse.get_int('combo_type')) if not highscore: # Don't overwrite the ghost for a non-highscore trace = oldcourse.get_int_array('trace', len(trace)) self.data.local.user.put_achievement( self.game, self.version, userid, (courseid * 4) + chart, 'course', { 'score': points, 'combo': combo, 'stage': stage, 'rank': rank, 'combo_type': combo_type, 'trace': trace, }, ) # No response needed game = Node.void('game') return game