def __handle_ghostload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: ghostid = requestdata.child_value('ghostid') ghost = self.data.local.music.get_score_by_key(self.game, self.music_version, ghostid) if ghost is None: return userid, score = ghost profile = self.get_profile(userid) if profile is None: return if 'ghost' not in score.data: return ghostdata = Node.void('ghostdata') response.add_child(ghostdata) ghostdata.add_child(Node.s32('code', profile.get_int('extid'))) ghostdata.add_child(Node.u32('mcode', score.id)) ghostdata.add_child( Node.u8('notetype', self.db_to_game_chart(score.chart))) ghostdata.add_child(Node.s32('ghostsize', len(score.data['ghost']))) ghostdata.add_child(Node.string('ghost', score.data['ghost']))
def __add_event_info(self, request: Node) -> None: events: Dict[int, int] = {} for (eventid, phase) in events.items(): data = Node.void('data') request.add_child(data) data.add_child(Node.s32('type', -1)) data.add_child(Node.s32('value', -1))
def _add_event_info(self, root: Node) -> None: event_ctrl = Node.void('event_ctrl') root.add_child(event_ctrl) # Contains zero or more nodes like: # <data> # <type __type="s32">any</type> # <index __type="s32">any</phase> # <value __type="s32">any</phase> # <value2 __type="s32">any</phase> # <start_time __type="s32">any</phase> # <end_time __type="s32">any</phase> # </data> item_lock_ctrl = Node.void('item_lock_ctrl') root.add_child(item_lock_ctrl)
def _add_event_info(self, root: Node) -> None: event_ctrl = Node.void('event_ctrl') root.add_child(event_ctrl) # Contains zero or more nodes like: # <data> # <type __type="s32">any</type> # <index __type="s32">any</phase> # <value __type="s32">any</phase> # <value2 __type="s32">any</phase> # <start_time __type="s32">any</phase> # <end_time __type="s32">any</phase> # </data> item_lock_ctrl = Node.void('item_lock_ctrl') root.add_child(item_lock_ctrl) # Contains zero or more nodes like: # <item> # <type __type="u8">any</type> # <id __type="u16">any</id> # <param __type="u16">0-3</param> # </item> mycourse_ctrl = Node.void('mycourse_ctrl') root.add_child(mycourse_ctrl) songs = { song.id for song in self.data.local.music.get_all_songs( self.game, self.music_version) } for song in songs: data = Node.void('data') mycourse_ctrl.add_child(data) data.add_child(Node.s16('mycourse_id', 1)) data.add_child(Node.s32('type', 0)) data.add_child(Node.s32('music_id', song))
def __handle_usernew(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: if userid is None: raise Exception('Expecting valid UserID to create new profile!') machine = self.data.local.machine.get_machine(self.config['machine']['pcbid']) self.put_profile(userid, ValidatedDict({ 'lid': machine.id, })) profile = self.get_profile(userid) response.add_child(Node.string('seq', ID.format_extid(profile.get_int('extid')))) response.add_child(Node.s32('code', profile.get_int('extid'))) response.add_child(Node.string('shoparea', ''))
def _add_shop_score(self, root: Node) -> None: shop_score = Node.void('shop_score') root.add_child(shop_score) today = Node.void('today') shop_score.add_child(today) yesterday = Node.void('yesterday') shop_score.add_child(yesterday) all_profiles = self.data.local.user.get_all_profiles(self.game, self.version) all_attempts = self.data.local.music.get_all_attempts(self.game, self.version, timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY)) machine = self.data.local.machine.get_machine(self.config['machine']['pcbid']) if machine.arcade is not None: lids = [ machine.id for machine in self.data.local.machine.get_all_machines(machine.arcade) ] else: lids = [machine.id] relevant_profiles = [ profile for profile in all_profiles if profile[1].get_int('lid', -1) in lids ] for (rootnode, timeoffset) in [ (today, 0), (yesterday, Time.SECONDS_IN_DAY), ]: # Grab all attempts made in the relevant day relevant_attempts = [ attempt for attempt in all_attempts if ( attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset) and attempt[1].timestamp <= (Time.end_of_today() - timeoffset) ) ] # Calculate scores based on attempt scores_by_user: Dict[UserID, Dict[int, Dict[int, Attempt]]] = {} for (userid, attempt) in relevant_attempts: if userid not in scores_by_user: scores_by_user[userid] = {} if attempt.id not in scores_by_user[userid]: scores_by_user[userid][attempt.id] = {} if attempt.chart not in scores_by_user[userid][attempt.id]: # No high score for this yet, just use this attempt scores_by_user[userid][attempt.id][attempt.chart] = attempt else: # If this attempt is better than the stored one, replace it if scores_by_user[userid][attempt.id][attempt.chart].points < attempt.points: scores_by_user[userid][attempt.id][attempt.chart] = attempt # Calculate points earned by user in the day points_by_user: Dict[UserID, int] = {} for userid in scores_by_user: points_by_user[userid] = 0 for mid in scores_by_user[userid]: for chart in scores_by_user[userid][mid]: points_by_user[userid] = points_by_user[userid] + scores_by_user[userid][mid][chart].points # Output that day's earned points for (userid, profile) in relevant_profiles: data = Node.void('data') rootnode.add_child(data) data.add_child(Node.s16('day_id', int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY))) data.add_child(Node.s32('user_id', profile.get_int('extid'))) data.add_child(Node.s16('icon_id', profile.get_dict('config').get_int('icon_id'))) data.add_child(Node.s16('point', min(points_by_user.get(userid, 0), 32767))) data.add_child(Node.s32('update_time', Time.now())) data.add_child(Node.string('name', profile.get_str('name'))) rootnode.add_child(Node.s32('time', Time.beginning_of_today() - timeoffset))
def __construct_common_info(self, root: Node) -> None: # Event phases phases = { # Unknown event (0-16) 0: 16, # Unknown event (0-3) 1: 3, # Unknown event (0-1) 2: 1, # Unknown event (0-2) 3: 2, # Unknown event (0-1) 4: 1, # Unknown event (0-2) 5: 2, # Unknown event (0-1) 6: 1, # Unknown event (0-4) 7: 4, # Unknown event (0-3) 8: 3, # Unknown event (0-4) 9: 4, # Unknown event (0-4) 10: 4, # Unknown event (0-1) 11: 1, # Possibly global event matching related? (0-1) 12: 1, # Unknown event (0-4) 13: 4, } 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 areaid in range(1, 50): area = Node.void('area') root.add_child(area) area.add_child(Node.s16('area_id', areaid)) area.add_child(Node.u64('end_date', 0)) area.add_child(Node.s16('medal_id', areaid)) area.add_child(Node.bool('is_limit', False)) # Calculate most popular characters profiles = self.data.remote.user.get_all_profiles( self.game, self.version) charas: Dict[int, int] = {} for (userid, profile) in profiles: chara = profile.get_int('chara', -1) if chara <= 0: continue if chara not in charas: charas[chara] = 1 else: charas[chara] = charas[chara] + 1 # Order a typle by most popular character to least popular character charamap = sorted( [(c, charas[c]) for c in charas], key=lambda c: c[1], reverse=True, ) # Output the top 20 of them rank = 1 for (charaid, usecount) in charamap[:20]: popular = Node.void('popular') root.add_child(popular) popular.add_child(Node.s16('rank', rank)) popular.add_child(Node.s16('chara_num', charaid)) rank = rank + 1 # Output the hit chart for (songid, plays) in self.data.local.music.get_hit_chart( self.game, self.music_version, 500): popular_music = Node.void('popular_music') root.add_child(popular_music) popular_music.add_child(Node.s16('music_num', songid)) # Output goods prices for goodsid in range(1, 421): if goodsid >= 1 and goodsid <= 80: price = 60 elif goodsid >= 81 and goodsid <= 120: price = 250 elif goodsid >= 121 and goodsid <= 142: price = 500 elif goodsid >= 143 and goodsid <= 300: price = 100 elif goodsid >= 301 and goodsid <= 420: price = 150 else: raise Exception('Invalid goods ID!') goods = Node.void('goods') root.add_child(goods) goods.add_child(Node.s16('goods_id', goodsid)) goods.add_child(Node.s32('price', price)) goods.add_child(Node.s16('goods_type', 0))
def __handle_inheritance(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: response.add_child(Node.s32('InheritanceStatus', 0))
def __handle_rivalload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: data = Node.void('data') response.add_child(data) data.add_child( Node.s32('recordtype', requestdata.child_value('loadflag'))) thismachine = self.data.local.machine.get_machine( self.config['machine']['pcbid']) machines_by_id: Dict[int, Optional[Machine]] = { thismachine.id: thismachine } loadkind = requestdata.child_value('loadflag') profiles_by_userid: Dict[UserID, ValidatedDict] = {} def get_machine(lid: int) -> Optional[Machine]: if lid not in machines_by_id: pcbid = self.data.local.machine.from_machine_id(lid) if pcbid is None: machines_by_id[lid] = None return None machine = self.data.local.machine.get_machine(pcbid) if machine is None: machines_by_id[lid] = None return None machines_by_id[lid] = machine return machines_by_id[lid] if loadkind == self.GAME_RIVAL_TYPE_WORLD: # Just load all scores for this network scores = self.data.remote.music.get_all_records( self.game, self.music_version) elif loadkind == self.GAME_RIVAL_TYPE_AREA: if thismachine.arcade is not None: match_arcade = thismachine.arcade match_machine = None else: match_arcade = None match_machine = thismachine.id # Load up all scores by any user registered on a machine in the same arcade profiles = self.data.local.user.get_all_profiles( self.game, self.version) userids: List[UserID] = [] for userid, profiledata in profiles: profiles_by_userid[userid] = profiledata # If we have an arcade to match, see if this user's location matches the arcade. # If we don't, just match lid directly if match_arcade is not None: theirmachine = get_machine(profiledata.get_int('lid')) if theirmachine is not None and theirmachine.arcade == match_arcade: userids.append(userid) elif match_machine is not None: if profiledata.get_int('lid') == match_machine: userids.append(userid) # Load all scores for users in the area scores = self.data.local.music.get_all_records(self.game, self.music_version, userlist=userids) elif loadkind == self.GAME_RIVAL_TYPE_MACHINE: # Load up all scores and filter them by those earned at this location scores = self.data.local.music.get_all_records( self.game, self.music_version, locationlist=[thismachine.id]) elif loadkind in [ self.GAME_RIVAL_TYPE_RIVAL1, self.GAME_RIVAL_TYPE_RIVAL2, self.GAME_RIVAL_TYPE_RIVAL3, ]: # Load up this user's highscores, format the way the below code expects it extid = requestdata.child_value('ddrcode') otherid = self.data.remote.user.from_extid(self.game, self.version, extid) userscores = self.data.remote.music.get_scores( self.game, self.music_version, otherid) scores = [(otherid, score) for score in userscores] else: # Nothing here scores = [] missing_users = [ userid for (userid, _) in scores if userid not in profiles_by_userid ] for (userid, profile) in self.get_any_profiles(missing_users): profiles_by_userid[userid] = profile for userid, score in scores: if profiles_by_userid.get(userid) is None: raise Exception( f'Logic error, couldn\'t find any profile for {userid}') profiledata = profiles_by_userid[userid] record = Node.void('record') data.add_child(record) record.add_child(Node.u32('mcode', score.id)) record.add_child( Node.u8('notetype', self.db_to_game_chart(score.chart))) record.add_child( Node.u8('rank', self.db_to_game_rank(score.data.get_int('rank')))) record.add_child( Node.u8('clearkind', self.db_to_game_halo(score.data.get_int('halo')))) record.add_child(Node.u8('flagdata', 0)) record.add_child(Node.string('name', profiledata.get_str('name'))) record.add_child(Node.s32('area', profiledata.get_int('area', 58))) record.add_child(Node.s32('code', profiledata.get_int('extid'))) record.add_child(Node.s32('score', score.points)) record.add_child(Node.s32('ghostid', score.key))
def __handle_userload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: has_profile: bool = False achievements: List[Achievement] = [] scores: List[Score] = [] if userid is not None: has_profile = self.has_profile(userid) achievements = self.data.local.user.get_achievements( self.game, self.version, userid) scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) # Place scores into an arrangement for easier distribution to Ace. scores_by_mcode: Dict[int, List[Optional[Score]]] = {} for score in scores: if score.id not in scores_by_mcode: scores_by_mcode[score.id] = [None] * 9 scores_by_mcode[score.id][self.db_to_game_chart( score.chart)] = score # First, set new flag response.add_child(Node.bool('is_new', not has_profile)) # Now, return the scores to Ace for mcode in scores_by_mcode: music = Node.void('music') response.add_child(music) music.add_child(Node.u32('mcode', mcode)) scores_that_matter = scores_by_mcode[mcode] while scores_that_matter[-1] is None: scores_that_matter = scores_that_matter[:-1] for score in scores_that_matter: note = Node.void('note') music.add_child(note) if score is None: note.add_child(Node.u16('count', 0)) note.add_child(Node.u8('rank', 0)) note.add_child(Node.u8('clearkind', 0)) note.add_child(Node.s32('score', 0)) note.add_child(Node.s32('ghostid', 0)) else: note.add_child(Node.u16('count', score.plays)) note.add_child( Node.u8( 'rank', self.db_to_game_rank(score.data.get_int('rank')))) note.add_child( Node.u8( 'clearkind', self.db_to_game_halo(score.data.get_int('halo')))) note.add_child(Node.s32('score', score.points)) note.add_child(Node.s32('ghostid', score.key)) # Active event settings activeevents = [ 1, 3, 5, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, ] # Event reward settings rewards = { '30': { 999: 5, } } # Now handle event progress and activation. events = { ach.id: ach.data for ach in achievements if ach.type == '9999' } progress = [ach for ach in achievements if ach.type != '9999'] # Make sure we always send a babylon's adventure save event or the game won't send progress babylon_included = False for evtprogress in progress: if evtprogress.id == 999 and evtprogress.type == '30': babylon_included = True break if not babylon_included: progress.append( Achievement( 999, '30', None, { 'completed': False, 'progress': 0, }, )) for event in activeevents: # Get completion data playerstats = events.get(event, ValidatedDict({'completed': False})) # Return the data eventdata = Node.void('eventdata') response.add_child(eventdata) eventdata.add_child(Node.u32('eventid', event)) eventdata.add_child(Node.s32('eventtype', 9999)) eventdata.add_child(Node.u32('eventno', 0)) eventdata.add_child(Node.s64('condition', 0)) eventdata.add_child(Node.u32('reward', 0)) eventdata.add_child( Node.s32('comptime', 1 if playerstats.get_bool('completed') else 0)) eventdata.add_child(Node.s64('savedata', 0)) for evtprogress in progress: # Babylon's adventure progres and anything else the game sends eventdata = Node.void('eventdata') response.add_child(eventdata) eventdata.add_child(Node.u32('eventid', evtprogress.id)) eventdata.add_child(Node.s32('eventtype', int(evtprogress.type))) eventdata.add_child(Node.u32('eventno', 0)) eventdata.add_child(Node.s64('condition', 0)) eventdata.add_child( Node.u32('reward', rewards.get(evtprogress.type, {}).get(evtprogress.id))) eventdata.add_child( Node.s32('comptime', 1 if evtprogress.data.get_bool('completed') else 0)) eventdata.add_child( Node.s64('savedata', evtprogress.data.get_int('progress')))