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
def handle_game_trace_request(self, request: Node) -> Node: # This is almost identical to 2013 and below, except it will never # even try to request course traces, so we fork from common functionality. extid = int(request.attribute('code')) chart = int(request.attribute('type')) 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 # 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'])) return game
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
def handle_game_new_request(self, request: Node) -> Node: refid = request.attribute('refid') name = request.attribute('name') loc = ID.parse_machine_id(request.attribute('locid')) self.new_profile_by_refid(refid, name, loc) root = Node.void('game') return root
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
def handle_game_load_m_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) if userid is not None: scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) else: scores = [] sortedscores: Dict[int, Dict[int, Score]] = {} for score in scores: if score.id not in sortedscores: sortedscores[score.id] = {} sortedscores[score.id][score.chart] = score game = Node.void('game') for song in sortedscores: music = Node.void('music') game.add_child(music) music.set_attribute('reclink', str(song)) for chart in sortedscores[song]: score = sortedscores[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.u32('score', score.points)) typenode.add_child(Node.u16('count', score.plays)) typenode.add_child(Node.u8('rank', gamerank)) typenode.add_child(Node.u8('combo_type', combo_type)) # The game optionally receives hard, life8, life4, risky, assist_clear, normal_clear # u8 values too, and saves music scores with these set, but the UI doesn't appear to # do anything with them, so we don't care. return game
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
def handle_game_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'get': # TODO: Hook these up to config so we can change this root = Node.void('game') root.add_child(Node.s32('ir_phase', 0)) root.add_child(Node.s32('music_open_phase', 8)) root.add_child(Node.s32('collabo_phase', 8)) root.add_child(Node.s32('personal_event_phase', 10)) root.add_child(Node.s32('shop_event_phase', 6)) root.add_child(Node.s32('netvs_phase', 0)) root.add_child(Node.s32('card_phase', 9)) root.add_child(Node.s32('other_phase', 9)) root.add_child(Node.s32('local_matching_enable', 1)) root.add_child(Node.s32('n_matching_sec', 60)) root.add_child(Node.s32('l_matching_sec', 60)) root.add_child(Node.s32('is_check_cpu', 0)) root.add_child(Node.s32('week_no', 0)) root.add_child(Node.s16_array('sel_ranking', [-1, -1, -1, -1, -1])) root.add_child(Node.s16_array('up_ranking', [-1, -1, -1, -1, -1])) return root if method == 'active': # Update the name of this cab for admin purposes self.update_machine_name(request.child_value('shop_name')) return Node.void('game') if method == 'taxphase': return Node.void('game') # Invalid method return None
def handle_game_load_request(self, request: Node) -> Node: refid = request.attribute('dataid') root = self.get_profile_by_refid(refid) if root is None: root = Node.void('game') root.set_attribute('none', '1') return root
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
def handle_game_save_m_request(self, request: Node) -> Node: refid = request.attribute('dataid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is None: return Node.void('game') musicid = int(request.attribute('music_id')) chart = int(request.attribute('music_type')) score = int(request.attribute('score')) combo = int(request.attribute('max_chain')) grade = self.__game_to_db_grade(int(request.attribute('score_grade'))) clear_type = self.__game_to_db_clear_type( int(request.attribute('clear_type'))) # Save the score self.update_score( userid, musicid, chart, score, clear_type, grade, combo, ) # No response necessary return Node.void('game')
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
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
def handle_game_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'get': # TODO: Hook these up to config so we can change this root = Node.void('game') root.set_attribute('game_phase', '2') root.set_attribute('psp_phase', '2') return root if method == 'active': # Update the name of this cab for admin purposes self.update_machine_name(request.attribute('shop_name')) return Node.void('game') if method == 'taxphase': return Node.void('game') # Invalid method return None
def handle_game_save_m_request(self, request: Node) -> Node: refid = request.attribute('refid') songid = int(request.attribute('mid')) chart = self.game_to_db_chart(int(request.attribute('mtype'))) # Calculate statistics data = request.child('data') points = int(data.attribute('score')) combo = int(data.attribute('combo')) rank = self.game_to_db_rank(int(data.attribute('rank'))) if points == 1000000: halo = self.HALO_MARVELOUS_FULL_COMBO elif int(data.attribute('perf_fc')) != 0: halo = self.HALO_PERFECT_FULL_COMBO elif int(data.attribute('great_fc')) != 0: halo = self.HALO_GREAT_FULL_COMBO elif int(data.attribute('good_fc')) != 0: halo = self.HALO_GOOD_FULL_COMBO else: halo = self.HALO_NONE trace = request.child_value('trace') # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the # number of plays for that song/chart. userid = self.data.remote.user.from_refid(self.game, self.version, refid) self.update_score( userid, songid, chart, points, rank, halo, combo, trace, ) # No response needed game = Node.void('game') return game
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
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
def handle_pcb22_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'boot': return Node.void('pcb22') elif method == 'error': return Node.void('pcb22') elif method == 'write': # Update the name of this cab for admin purposes self.update_machine_name(request.child_value('pcb_setting/name')) return Node.void('pcb22') # Invalid method return None
def handle_game_load_m_request(self, request: Node) -> Node: refid = request.attribute('dataid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: scores = self.data.remote.music.get_scores(self.game, self.version, userid) else: scores = [] # Organize by song->chart scores_by_id: Dict[int, Dict[int, Score]] = {} for score in scores: if score.id not in scores_by_id: scores_by_id[score.id] = {} scores_by_id[score.id][score.chart] = score # Output to the game game = Node.void('game') for songid in scores_by_id: music = Node.void('music') game.add_child(music) music.set_attribute('music_id', str(songid)) for chart in scores_by_id[songid]: typenode = Node.void('type') music.add_child(typenode) typenode.set_attribute('type_id', str(chart)) score = scores_by_id[songid][chart] typenode.set_attribute('score', str(score.points)) typenode.set_attribute('cnt', str(score.plays)) typenode.set_attribute( 'clear_type', str( self.__db_to_game_clear_type( score.data.get_int('clear_type')))) typenode.set_attribute( 'score_grade', str(self.__db_to_game_grade(score.data.get_int('grade')))) return game
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
def handle_playerdata_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'expire': return Node.void('playerdata') elif method == 'logout': return Node.void('playerdata') elif method == 'get': modelstring = request.attribute('model') refid = request.attribute('ref_id') root = self.get_profile_by_refid( refid, self.NEW_PROFILE_ONLY if modelstring is None else self.OLD_PROFILE_ONLY, ) if root is None: root = Node.void('playerdata') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'new': refid = request.attribute('ref_id') name = request.attribute('name') root = self.new_profile_by_refid(refid, name) if root is None: root = Node.void('playerdata') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'set': refid = request.attribute('ref_id') root = Node.void('playerdata') if refid is None: return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: return root oldprofile = self.get_profile(userid) or ValidatedDict() newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: self.put_profile(userid, newprofile) return root # Invalid method return None
def handle_game_save_request(self, request: Node) -> Node: refid = request.attribute('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: oldprofile = self.get_profile(userid) newprofile = self.unformat_profile(userid, request, oldprofile) else: newprofile = None if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) return Node.void('game')
def handle_info22_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'common': # TODO: Hook these up to config so we can change this phases = { # Unknown event 0: 0, # Unknown event 1: 0, # Pop'n Aura, max 10 (remov all aura requirements) 2: 10, # Story 3: 1, # BEMANI ruins Discovery! 4: 0, # Unknown event 5: 0, # Unknown event 6: 0, # Unknown event 7: 0, # Unknown event 8: 0, # Unknown event 9: 0, # Unknown event 10: 0, # Unknown event 11: 0, # Unknown event 12: 0, # Unknown event 13: 0, # Unknown event 14: 0, # Unknown event 15: 0, # Unknown event 16: 0, # Unknown event 17: 0, # Unknown event 18: 0, # Unknown event 19: 0, } stories = list(range(173)) root = Node.void('info22') for phaseid in phases: phase = Node.void('phase') root.add_child(phase) phase.add_child(Node.s16('event_id', phaseid)) phase.add_child(Node.s16('phase', phases[phaseid])) for storyid in stories: story = Node.void('story') root.add_child(story) story.add_child(Node.u32('story_id', storyid)) story.add_child(Node.bool('is_limited', False)) story.add_child(Node.u64('limit_date', 0)) return root # Invalid method return None
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
def handle_player22_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'read': refid = request.child_value('ref_id') # Pop'n Music 22 doesn't send a modelstring to load old profiles, # it just expects us to know. So always look for old profiles in # Pop'n 22 land. root = self.get_profile_by_refid(refid, self.OLD_PROFILE_FALLTHROUGH) if root is None: root = Node.void('player22') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'new': refid = request.child_value('ref_id') name = request.child_value('name') root = self.new_profile_by_refid(refid, name) if root is None: root = Node.void('player22') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'start': return Node.void('player22') elif method == 'logout': return Node.void('player22') elif method == 'write': refid = request.child_value('ref_id') root = Node.void('player22') if refid is None: return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: return root oldprofile = self.get_profile(userid) or ValidatedDict() newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: self.put_profile(userid, newprofile) return root elif method == 'conversion': refid = request.child_value('ref_id') name = request.child_value('name') chara = request.child_value('chara') root = self.new_profile_by_refid(refid, name, chara) if root is None: root = Node.void('playerdata') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'write_music': refid = request.child_value('ref_id') root = Node.void('player22') if refid is None: return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: return root songid = request.child_value('music_num') chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, }[request.child_value('sheet_num')] medal = request.child_value('clearmedal') points = request.child_value('score') combo = request.child_value('combo') stats = { 'cool': request.child_value('cool'), 'great': request.child_value('great'), 'good': request.child_value('good'), 'bad': request.child_value('bad') } medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED, self.GAME_PLAY_MEDAL_STAR_FAILED: self.PLAY_MEDAL_STAR_FAILED, self.GAME_PLAY_MEDAL_EASY_CLEAR: self.PLAY_MEDAL_EASY_CLEAR, self.GAME_PLAY_MEDAL_CIRCLE_CLEARED: self.PLAY_MEDAL_CIRCLE_CLEARED, self.GAME_PLAY_MEDAL_DIAMOND_CLEARED: self.PLAY_MEDAL_DIAMOND_CLEARED, self.GAME_PLAY_MEDAL_STAR_CLEARED: self.PLAY_MEDAL_STAR_CLEARED, self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO: self.PLAY_MEDAL_CIRCLE_FULL_COMBO, self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO: self.PLAY_MEDAL_DIAMOND_FULL_COMBO, self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] self.update_score(userid, songid, chart, points, medal, combo=combo, stats=stats) return root # Invalid method return None
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) play_stats = self.get_play_statistics(userid) # Grab last node and accessories so we can make decisions based on type last = request.child('last') lastdict = newprofile.get_dict('last') mode = int(last.attribute('mode')) style = int(last.attribute('style')) is_dp = style == self.GAME_STYLE_DOUBLE # Drill rankings title = request.child('title') title_gr = request.child('title_gr') titledict = newprofile.get_dict('title') title_grdict = newprofile.get_dict('title_gr') # Groove radar level ups gr = request.child('gr') # Set the correct values depending on if we're single or double play if is_dp: play_stats.increment_int('double_plays') if gr is not None: newprofile.replace_int_array( 'gr_d', 5, [ intish(gr.attribute('gr1')), intish(gr.attribute('gr2')), intish(gr.attribute('gr3')), intish(gr.attribute('gr4')), intish(gr.attribute('gr5')), ], ) if title is not None: titledict.replace_int('d', title.value) newprofile.replace_dict('title', titledict) if title_gr is not None: title_grdict.replace_int('d', title.value) newprofile.replace_dict('title_gr', title_grdict) else: play_stats.increment_int('single_plays') if gr is not None: newprofile.replace_int_array( 'gr_s', 5, [ intish(gr.attribute('gr1')), intish(gr.attribute('gr2')), intish(gr.attribute('gr3')), intish(gr.attribute('gr4')), intish(gr.attribute('gr5')), ], ) if title is not None: titledict.replace_int('s', title.value) newprofile.replace_dict('title', titledict) if title_gr is not None: title_grdict.replace_int('s', title.value) newprofile.replace_dict('title_gr', title_grdict) play_stats.increment_int(f'cnt_m{mode}') # Result stars result_star = request.child('result_star') if result_star is not None: newprofile.replace_int_array( 'result_stars', 9, [ intish(result_star.attribute('slot1')), intish(result_star.attribute('slot2')), intish(result_star.attribute('slot3')), intish(result_star.attribute('slot4')), intish(result_star.attribute('slot5')), intish(result_star.attribute('slot6')), intish(result_star.attribute('slot7')), intish(result_star.attribute('slot8')), intish(result_star.attribute('slot9')), ], ) # Target stuff target = request.child('target') if target is not None: newprofile.replace_int('target_flag', intish(target.attribute('flag'))) newprofile.replace_int('target_setnum', intish(target.attribute('setnum'))) # Update last attributes lastdict.replace_int('rival1', intish(last.attribute('rival1'))) lastdict.replace_int('rival2', intish(last.attribute('rival2'))) lastdict.replace_int('rival3', intish(last.attribute('rival3'))) lastdict.replace_int('style', intish(last.attribute('style'))) lastdict.replace_int('mode', intish(last.attribute('mode'))) lastdict.replace_int('cate', intish(last.attribute('cate'))) lastdict.replace_int('sort', intish(last.attribute('sort'))) lastdict.replace_int('mid', intish(last.attribute('mid'))) lastdict.replace_int('mtype', intish(last.attribute('mtype'))) lastdict.replace_int('cid', intish(last.attribute('cid'))) lastdict.replace_int('ctype', intish(last.attribute('ctype'))) lastdict.replace_int('sid', intish(last.attribute('sid'))) newprofile.replace_dict('last', lastdict) # Grab character options chara = request.child('chara') if chara is not None: newprofile.replace_int('chara', intish(chara.attribute('my'))) chara_opt = request.child('chara_opt') if chara_opt is not None: # A bug in old versions of AVS returns the wrong number for set newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96]) # Options opt = request.child('opt') if opt is not None: # A bug in old versions of AVS returns the wrong number for set newprofile.replace_int_array('opt', 16, opt.value[:16]) # Experience and stars exp = request.child_value('exp') if exp is not None: play_stats.replace_int('exp', play_stats.get_int('exp') + exp) star = request.child_value('star') if star is not None: newprofile.replace_int('star', newprofile.get_int('star') + star) star_c = request.child_value('star_c') if star_c is not None: newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp) # Update game flags for child in request.children: if child.name != 'flag': continue try: value = int(child.attribute('data')) offset = int(child.attribute('no')) except ValueError: continue flags = newprofile.get_int_array('flag', 256, [1] * 256) if offset < 0 or offset >= len(flags): continue flags[offset] = value newprofile.replace_int_array('flag', 256, flags) # Workout mode support newweight = -1 oldweight = newprofile.get_int('weight') for child in request.children: if child.name != 'weight': continue newweight = child.value if newweight < 0: newweight = oldweight # Either update or unset the weight depending on the game if newweight == 0: # Weight is unset or we declined to use this feature, remove from profile if 'weight' in newprofile: del newprofile['weight'] else: # Weight has been set or previously retrieved, we should save calories newprofile.replace_int('weight', newweight) total = 0 for child in request.children: if child.name != 'calory': continue total += child.value self.data.local.user.put_time_based_achievement( self.game, self.version, userid, 0, 'workout', { 'calories': total, 'weight': newweight, }, ) # Look up old friends oldfriends: List[Optional[UserID]] = [None] * 10 links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: if link.type[:7] != 'friend_': continue pos = int(link.type[7:]) oldfriends[pos] = link.other_userid # Save any rivals that were added/removed/changed newfriends = oldfriends[:] for child in request.children: if child.name != 'friend': continue code = int(child.attribute('code')) pos = int(child.attribute('pos')) if pos >= 0 and pos < 10: if code == 0: # We cleared this friend newfriends[pos] = None else: # Try looking up the userid newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code) # Diff the set of links to determine updates for i in range(10): if newfriends[i] == oldfriends[i]: continue if newfriends[i] is None: # Kill the rival in this location self.data.local.user.destroy_link( self.game, self.version, userid, f'friend_{i}', oldfriends[i], ) elif oldfriends[i] is None: # Add rival in this location self.data.local.user.put_link( self.game, self.version, userid, f'friend_{i}', newfriends[i], {}, ) else: # Changed the rival here, kill the old one, add the new one self.data.local.user.destroy_link( self.game, self.version, userid, f'friend_{i}', oldfriends[i], ) self.data.local.user.put_link( self.game, self.version, userid, f'friend_{i}', newfriends[i], {}, ) # Play area counter shop_area = int(request.attribute('shop_area')) if shop_area >= 0 and shop_area < 55: areas = newprofile.get_int_array('play_area', 55) areas[shop_area] = areas[shop_area] + 1 newprofile.replace_int_array('play_area', 55, areas) # Keep track of play statistics self.update_play_statistics(userid, play_stats) return newprofile
def handle_game_save_m_request(self, request: Node) -> Node: refid = request.attribute('refid') songid = int(request.attribute('mid')) chart = self.game_to_db_chart(int(request.attribute('mtype'))) userid = self.data.remote.user.from_refid(self.game, self.version, refid) # Calculate statistics data = request.child('data') playmode = int(data.attribute('playmode')) if playmode == self.GAME_PLAY_MODE_2NDMIX: if userid is not None: # These are special cased and treated like achievements points = int(data.attribute('score_2nd')) combo = int(data.attribute('combo_2nd')) rank = int(data.attribute('rank_2nd')) # Grab the old 2nd mix score existingscore = self.data.local.user.get_achievement( self.game, self.music_version, userid, (songid * 100) + chart, '2ndmix', ) if existingscore is not None: highscore = points > existingscore.get_int('points') plays = existingscore.get_int('plays', 0) + 1 points = max(points, existingscore.get_int('points')) if not highscore: combo = existingscore.get_int('combo', combo) rank = existingscore.get_int('rank', rank) else: plays = 1 self.data.local.user.put_achievement( self.game, self.music_version, userid, (songid * 100) + chart, '2ndmix', { 'points': points, 'combo': combo, 'rank': rank, 'plays': plays, }, ) else: points = int(data.attribute('score')) combo = int(data.attribute('combo')) rank = self.game_to_db_rank(int(data.attribute('rank'))) if int(data.attribute('full')) == 0: halo = self.HALO_NONE elif int(data.attribute('perf')) == 0: halo = self.HALO_GREAT_FULL_COMBO elif points < 1000000: halo = self.HALO_PERFECT_FULL_COMBO else: halo = self.HALO_MARVELOUS_FULL_COMBO trace = request.child_value('trace') # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the # number of plays for that song/chart. self.update_score( userid, songid, chart, points, rank, halo, combo, trace, ) # No response needed game = Node.void('game') return game
def handle_game_load_m_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) if userid is not None: scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) old_scores = [ score for score in self.data.local.user.get_achievements(self.game, self.music_version, userid) if score.type == '2ndmix' ] else: scores = [] old_scores = [] sortedscores: Dict[int, Dict[int, Dict[str, Union[Score, Achievement]]]] = {} for score in scores: if score.id not in sortedscores: sortedscores[score.id] = {} if score.chart not in sortedscores[score.id]: sortedscores[score.id][score.chart] = {} sortedscores[score.id][score.chart]['score'] = score for oldscore in old_scores: songid = int(oldscore.id / 100) chart = int(oldscore.id % 100) if songid not in sortedscores: sortedscores[songid] = {} if chart not in sortedscores[songid]: sortedscores[songid][chart] = {} sortedscores[songid][chart]['oldscore'] = oldscore game = Node.void('game') for song in sortedscores: music = Node.void('music') game.add_child(music) music.set_attribute('reclink', str(song)) for chart in sortedscores[song]: try: gamechart = self.db_to_game_chart(chart) except KeyError: # Don't support this chart in this game continue scoredict = sortedscores[song][chart] if 'score' in scoredict: # We played the normal version of this song gamerank = self.db_to_game_rank(scoredict['score'].data.get_int('rank')) combo_type = self.db_to_game_halo(scoredict['score'].data.get_int('halo')) points = scoredict['score'].points # type: ignore plays = scoredict['score'].plays # type: ignore else: # We only played 2nd mix version of this song gamerank = 0 combo_type = self.GAME_HALO_NONE points = 0 plays = 0 if 'oldscore' in scoredict: # We played the 2nd mix version of this song oldpoints = scoredict['oldscore'].data.get_int('points') oldrank = scoredict['oldscore'].data.get_int('rank') oldplays = scoredict['oldscore'].data.get_int('plays') else: oldpoints = 0 oldrank = 0 oldplays = 0 typenode = Node.void('type') music.add_child(typenode) typenode.set_attribute('diff', str(gamechart)) typenode.add_child(Node.u32('score', points)) typenode.add_child(Node.u16('count', plays)) typenode.add_child(Node.u8('rank', gamerank)) typenode.add_child(Node.u8('combo_type', combo_type)) typenode.add_child(Node.u32('score_2nd', oldpoints)) typenode.add_child(Node.u8('rank_2nd', oldrank)) typenode.add_child(Node.u16('cnt_2nd', oldplays)) return game
def handle_eacoin_request(self, request: Node) -> Optional[Node]: """ Handle PASELI requests. The game will check out a session at the beginning of the game, make PASELI purchases against that session, and then close it ad the end of of a game. This handler ensures that this works for all games. """ method = request.attribute('method') if not self.config['paseli']['enabled']: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") root = Node.void('eacoin') root.set_attribute('status', str(Status.NOT_ALLOWED)) return root if method == 'checkin': root = Node.void('eacoin') cardid = request.child_value('cardid') pin = request.child_value('passwd') if cardid is None or pin is None: # Refuse to return anything print("Invalid eacoin checkin request, missing cardid or pin") root.set_attribute('status', str(Status.NO_PROFILE)) return root userid = self.data.local.user.from_cardid(cardid) if userid is None: # Refuse to do anything print("No user for eacoin checkin request") root.set_attribute('status', str(Status.NO_PROFILE)) return root valid = self.data.local.user.validate_pin(userid, pin) if not valid: # Refuse to do anything print("User entered invalid pin for eacoin checkin request") root.set_attribute('status', str(Status.INVALID_PIN)) return root session = self.data.local.user.create_session(userid) if self.config['paseli']['infinite']: balance = PASELIHandler.INFINITE_PASELI_AMOUNT else: if self.config['machine']['arcade'] is None: # There's no arcade for this machine, but infinite is not # enabled, so there's no way to find a balance. balance = 0 else: balance = self.data.local.user.get_balance(userid, self.config['machine']['arcade']) root.add_child(Node.s16('sequence', 0)) root.add_child(Node.u8('acstatus', 0)) root.add_child(Node.string('acid', 'DUMMY_ID')) root.add_child(Node.string('acname', 'DUMMY_NAME')) root.add_child(Node.s32('balance', balance)) root.add_child(Node.string('sessid', session)) return root if method == 'opcheckin': root = Node.void('eacoin') passwd = request.child_value('passwd') if passwd is None: # Refuse to return anything print("Invalid eacoin checkin request, missing passwd") root.set_attribute('status', str(Status.NO_PROFILE)) return root if self.config['machine']['arcade'] is None: # Machine doesn't belong to an arcade print("Machine doesn't belong to an arcade") root.set_attribute('status', str(Status.NO_PROFILE)) return root arcade = self.data.local.machine.get_arcade(self.config['machine']['arcade']) if arcade is None: # Refuse to do anything print("No arcade for operator checkin request") root.set_attribute('status', str(Status.NO_PROFILE)) return root if arcade.pin != passwd: # Refuse to do anything print("User entered invalid pin for operator checkin request") root.set_attribute('status', str(Status.INVALID_PIN)) return root session = self.data.local.machine.create_session(arcade.id) root.add_child(Node.string('sessid', session)) return root elif method == 'consume': def make_resp(status: int, balance: int) -> Node: root = Node.void('eacoin') root.add_child(Node.u8('acstatus', status)) root.add_child(Node.u8('autocharge', 0)) root.add_child(Node.s32('balance', balance)) return root session = request.child_value('sessid') payment = request.child_value('payment') service = request.child_value('service') details = request.child_value('detail') if session is None or payment is None: # Refuse to do anything print("Invalid eacoin consume request, missing sessid or payment") return make_resp(2, 0) userid = self.data.local.user.from_session(session) if userid is None: # Refuse to do anything print("Invalid session for eacoin consume request") return make_resp(2, 0) if self.config['paseli']['infinite']: balance = PASELIHandler.INFINITE_PASELI_AMOUNT - payment else: if self.config['machine']['arcade'] is None: # There's no arcade for this machine, but infinite is not # enabled, so there's no way to find a balance, assume failed # consume payment. balance = None else: # Look up the new balance based on this delta. If there isn't enough, # we will end up returning None here and exit without performing. balance = self.data.local.user.update_balance(userid, self.config['machine']['arcade'], -payment) if balance is None: print("Not enough balance for eacoin consume request") return make_resp(1, self.data.local.user.get_balance(userid, self.config['machine']['arcade'])) else: self.data.local.network.put_event( 'paseli_transaction', { 'delta': -payment, 'balance': balance, 'service': -service, 'reason': details, 'pcbid': self.config['machine']['pcbid'], }, userid=userid, arcadeid=self.config['machine']['arcade'], ) return make_resp(0, balance) elif method == 'getlog': root = Node.void('eacoin') sessid = request.child_value('sessid') logtype = request.child_value('logtype') target = request.child_value('target') limit = request.child_value('perpage') offset = request.child_value('offset') # Try to determine whether its a user or an arcade session userid = self.data.local.user.from_session(sessid) if userid is None: arcadeid = self.data.local.machine.from_session(sessid) else: arcadeid = None # Bail out if we don't have any idea what session this is if userid is None and arcadeid is None: print("Unable to determine session type") return root # If we're a user session, also look up the current arcade # so we display only entries that happened on this arcade. if userid is not None: arcade = self.data.local.machine.get_arcade(self.config['machine']['arcade']) if arcade is None: print("Machine doesn't belong to an arcade") return root arcadeid = arcade.id # Now, look up all transactions for this specific group events = self.data.local.network.get_events( userid=userid, arcadeid=arcadeid, event='paseli_transaction', ) # Further filter it down to the current PCBID events = [event for event in events if event.data.get('pcbid') == target] # Grab the end of day today as a timestamp end_of_today = Time.end_of_today() time_format = '%Y-%m-%d %H:%M:%S' date_format = '%Y-%m-%d' # Set up common structure lognode = Node.void(logtype) topic = Node.void('topic') lognode.add_child(topic) summary = Node.void('summary') lognode.add_child(summary) # Display what day we are summed to topic.add_child(Node.string('sumdate', Time.format(Time.now(), date_format))) if logtype == 'last7days': # We show today in the today total, last 7 days prior in the week total beginning_of_today = end_of_today - Time.SECONDS_IN_DAY end_of_week = beginning_of_today beginning_of_week = end_of_week - Time.SECONDS_IN_WEEK topic.add_child(Node.string('sumfrom', Time.format(beginning_of_week, date_format))) topic.add_child(Node.string('sumto', Time.format(end_of_week, date_format))) today_total = sum([ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_today and event.timestamp < end_of_today ]) today_total = sum([ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_today and event.timestamp < end_of_today ]) week_txns = [ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_week and event.timestamp < end_of_week ] week_total = sum(week_txns) if len(week_txns) > 0: week_avg = int(sum(week_txns) / len(week_txns)) else: week_avg = 0 # We display the totals for each day starting with yesterday and up through 7 days prior. # Index starts at 0 = yesterday, 1 = the day before, etc... items = [] for days in range(0, 7): end_of_day = end_of_week - (days * Time.SECONDS_IN_DAY) start_of_day = end_of_day - Time.SECONDS_IN_DAY items.append(sum([ -event.data.get_int('delta') for event in events if event.timestamp >= start_of_day and event.timestamp < end_of_day ])) topic.add_child(Node.s32('today', today_total)) topic.add_child(Node.s32('average', week_avg)) topic.add_child(Node.s32('total', week_total)) summary.add_child(Node.s32_array('items', items)) if logtype == 'last52weeks': # Start one week back, since the operator can look at last7days for newer stuff. beginning_of_today = end_of_today - Time.SECONDS_IN_DAY end_of_52_weeks = beginning_of_today - Time.SECONDS_IN_WEEK topic.add_child(Node.string('sumfrom', Time.format(end_of_52_weeks - (52 * Time.SECONDS_IN_WEEK), date_format))) topic.add_child(Node.string('sumto', Time.format(end_of_52_weeks, date_format))) # We index backwards, where index 0 = the first week back, 1 = the next week back after that, etc... items = [] for weeks in range(0, 52): end_of_range = end_of_52_weeks - (weeks * Time.SECONDS_IN_WEEK) beginning_of_range = end_of_range - Time.SECONDS_IN_WEEK items.append(sum([ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_range and event.timestamp < end_of_range ])) summary.add_child(Node.s32_array('items', items)) if logtype == 'eachday': start_ts = Time.now() end_ts = Time.now() weekdays = [0] * 7 for event in events: event_day = Time.days_into_week(event.timestamp) weekdays[event_day] = weekdays[event_day] - event.data.get_int('delta') if event.timestamp < start_ts: start_ts = event.timestamp topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format))) topic.add_child(Node.string('sumto', Time.format(end_ts, date_format))) summary.add_child(Node.s32_array('items', weekdays)) if logtype == 'eachhour': start_ts = Time.now() end_ts = Time.now() hours = [0] * 24 for event in events: event_hour = int((event.timestamp % Time.SECONDS_IN_DAY) / Time.SECONDS_IN_HOUR) hours[event_hour] = hours[event_hour] - event.data.get_int('delta') if event.timestamp < start_ts: start_ts = event.timestamp topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format))) topic.add_child(Node.string('sumto', Time.format(end_ts, date_format))) summary.add_child(Node.s32_array('items', hours)) if logtype == 'detail': history = Node.void('history') lognode.add_child(history) # Respect details paging if offset is not None: events = events[offset:] if limit is not None: events = events[:limit] # Output the details themselves for event in events: card_no = '' if event.userid is not None: user = self.data.local.user.get_user(event.userid) if user is not None: cards = self.data.local.user.get_cards(user.id) if len(cards) > 0: card_no = CardCipher.encode(cards[0]) item = Node.void('item') history.add_child(item) item.add_child(Node.string('date', Time.format(event.timestamp, time_format))) item.add_child(Node.s32('consume', -event.data.get_int('delta'))) item.add_child(Node.s32('service', -event.data.get_int('service'))) item.add_child(Node.string('cardtype', '')) item.add_child(Node.string('cardno', ' ' * self.paseli_padding + card_no)) item.add_child(Node.string('title', '')) item.add_child(Node.string('systemid', '')) if logtype == 'lastmonths': year, month, _ = Time.todays_date() this_month = Time.timestamp_from_date(year, month) last_month = Time.timestamp_from_date(year, month - 1) month_before = Time.timestamp_from_date(year, month - 2) topic.add_child(Node.string('sumfrom', Time.format(month_before, date_format))) topic.add_child(Node.string('sumto', Time.format(this_month, date_format))) for (start, end) in [(month_before, last_month), (last_month, this_month)]: year, month, _ = Time.date_from_timestamp(start) items = [] for day in range(0, 31): begin_ts = start + (day * Time.SECONDS_IN_DAY) end_ts = begin_ts + Time.SECONDS_IN_DAY if begin_ts >= end: # Passed the end of this month items.append(0) else: # Sum up all the txns for this day items.append(sum([ -event.data.get_int('delta') for event in events if event.timestamp >= begin_ts and event.timestamp < end_ts ])) item = Node.void('item') summary.add_child(item) item.add_child(Node.s32('year', year)) item.add_child(Node.s32('month', month)) item.add_child(Node.s32_array('items', items)) root.add_child(Node.u8('processing', 0)) root.add_child(lognode) return root elif method == 'opchpass': root = Node.void('eacoin') oldpass = request.child_value('passwd') newpass = request.child_value('newpasswd') if oldpass is None or newpass is None: # Refuse to return anything print("Invalid eacoin pass change request, missing passwd") root.set_attribute('status', str(Status.NO_PROFILE)) return root if self.config['machine']['arcade'] is None: # Machine doesn't belong to an arcade print("Machine doesn't belong to an arcade") root.set_attribute('status', str(Status.NO_PROFILE)) return root arcade = self.data.local.machine.get_arcade(self.config['machine']['arcade']) if arcade is None: # Refuse to do anything print("No arcade for operator pass change request") root.set_attribute('status', str(Status.NO_PROFILE)) return root if arcade.pin != oldpass: # Refuse to do anything print("User entered invalid pin for operator pass change request") root.set_attribute('status', str(Status.INVALID_PIN)) return root arcade.pin = newpass self.data.local.machine.put_arcade(arcade) return root elif method == 'checkout': session = request.child_value('sessid') if session is not None: # Destroy the session so it can't be used for any other purchases self.data.local.user.destroy_session(session) root = Node.void('eacoin') return root elif method == 'opcheckout': session = request.child_value('sessid') if session is not None: # Destroy the session so it can't be used for any other purchases self.data.local.machine.destroy_session(session) root = Node.void('eacoin') return root # Invalid method return None
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) # Extract the playmode, important for scores later playmode = int(request.attribute('play_mode')) newprofile.replace_int('play_mode', playmode) # Extract profile options newprofile.replace_int('chara', int(request.attribute('chara_num'))) if 'option' in request.attributes: newprofile.replace_int('option', int(request.attribute('option'))) if 'last_play_flag' in request.attributes: newprofile.replace_int('last_play_flag', int(request.attribute('last_play_flag'))) if 'medal_and_friend' in request.attributes: newprofile.replace_int('medal_and_friend', int(request.attribute('medal_and_friend'))) if 'music_num' in request.attributes: newprofile.replace_int('music', int(request.attribute('music_num'))) if 'sheet_num' in request.attributes: newprofile.replace_int('sheet', int(request.attribute('sheet_num'))) if 'category_num' in request.attributes: newprofile.replace_int('category', int(request.attribute('category_num'))) # Keep track of play statistics self.update_play_statistics(userid) # Extract scores for node in request.children: if node.name == 'music': songid = int(node.attribute('music_num')) chart = int(node.attribute('sheet_num')) points = int(node.attribute('score')) data = int(node.attribute('data')) # We never save battle scores if chart in [ self.GAME_CHART_TYPE_BATTLE_NORMAL, self.GAME_CHART_TYPE_BATTLE_HYPER, ]: continue # Arrange order to be compatible with future mixes if playmode == self.GAME_PLAY_MODE_CHO_CHALLENGE: if chart in [ self.GAME_CHART_TYPE_5_BUTTON, self.GAME_CHART_TYPE_ENJOY_5_BUTTON, self.GAME_CHART_TYPE_ENJOY_9_BUTTON, ]: # We don't save 5 button for cho scores, or enjoy modes continue chart = { self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, }[chart] else: chart = { self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_OLD_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_OLD_HYPER, self.GAME_CHART_TYPE_5_BUTTON: self.CHART_TYPE_5_BUTTON, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_OLD_EX, self.GAME_CHART_TYPE_ENJOY_5_BUTTON: self.CHART_TYPE_ENJOY_5_BUTTON, self.GAME_CHART_TYPE_ENJOY_9_BUTTON: self.CHART_TYPE_ENJOY_9_BUTTON, }[chart] # Extract play flags shift = { self.CHART_TYPE_5_BUTTON: 4, self.CHART_TYPE_OLD_NORMAL: 0, self.CHART_TYPE_OLD_HYPER: 2, self.CHART_TYPE_OLD_EX: 6, self.CHART_TYPE_NORMAL: 0, self.CHART_TYPE_HYPER: 2, self.CHART_TYPE_EX: 6, self.CHART_TYPE_ENJOY_5_BUTTON: 9, self.CHART_TYPE_ENJOY_9_BUTTON: 8, }[chart] if chart in [ self.CHART_TYPE_ENJOY_5_BUTTON, self.CHART_TYPE_ENJOY_9_BUTTON, ]: # We only store cleared or not played for enjoy mode mask = 0x1 else: # We store all data for regular charts mask = 0x3 # Grab flags, map to medals in DB. Choose lowest one for each so # a newer pop'n can still improve scores and medals. flags = (data >> shift) & mask medal = { self.GAME_PLAY_FLAG_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, self.GAME_PLAY_FLAG_CLEARED: self.PLAY_MEDAL_CIRCLE_CLEARED, self.GAME_PLAY_FLAG_FULL_COMBO: self.PLAY_MEDAL_CIRCLE_FULL_COMBO, self.GAME_PLAY_FLAG_PERFECT_COMBO: self.PLAY_MEDAL_PERFECT, }[flags] self.update_score(userid, songid, chart, points, medal) return newprofile