782 lines
32 KiB
Python
782 lines
32 KiB
Python
# vim: set fileencoding=utf-8
|
|
import copy
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
from bemani.backend.ddr.base import DDRBase
|
|
from bemani.backend.ddr.stubs import DDRX
|
|
from bemani.backend.ddr.common import (
|
|
DDRGameFriendHandler,
|
|
DDRGameLockHandler,
|
|
DDRGameLoadCourseHandler,
|
|
DDRGameLoadHandler,
|
|
DDRGameLogHandler,
|
|
DDRGameMessageHandler,
|
|
DDRGameNewHandler,
|
|
DDRGameOldHandler,
|
|
DDRGameRankingHandler,
|
|
DDRGameSaveCourseHandler,
|
|
DDRGameSaveHandler,
|
|
DDRGameScoreHandler,
|
|
DDRGameShopHandler,
|
|
DDRGameTraceHandler,
|
|
)
|
|
from bemani.common import Time, VersionConstants, Profile, intish
|
|
from bemani.data import Score, UserID
|
|
from bemani.protocol import Node
|
|
|
|
|
|
class DDRX2(
|
|
DDRGameFriendHandler,
|
|
DDRGameLockHandler,
|
|
DDRGameLoadCourseHandler,
|
|
DDRGameLoadHandler,
|
|
DDRGameLogHandler,
|
|
DDRGameMessageHandler,
|
|
DDRGameOldHandler,
|
|
DDRGameNewHandler,
|
|
DDRGameRankingHandler,
|
|
DDRGameSaveCourseHandler,
|
|
DDRGameSaveHandler,
|
|
DDRGameScoreHandler,
|
|
DDRGameShopHandler,
|
|
DDRGameTraceHandler,
|
|
DDRBase,
|
|
):
|
|
|
|
name = 'DanceDanceRevolution X2'
|
|
version = VersionConstants.DDR_X2
|
|
|
|
GAME_STYLE_SINGLE = 0
|
|
GAME_STYLE_DOUBLE = 1
|
|
GAME_STYLE_VERSUS = 2
|
|
|
|
GAME_RANK_AAA = 1
|
|
GAME_RANK_AA = 2
|
|
GAME_RANK_A = 3
|
|
GAME_RANK_B = 4
|
|
GAME_RANK_C = 5
|
|
GAME_RANK_D = 6
|
|
GAME_RANK_E = 7
|
|
|
|
GAME_CHART_SINGLE_BEGINNER = 0
|
|
GAME_CHART_SINGLE_BASIC = 1
|
|
GAME_CHART_SINGLE_DIFFICULT = 2
|
|
GAME_CHART_SINGLE_EXPERT = 3
|
|
GAME_CHART_SINGLE_CHALLENGE = 4
|
|
GAME_CHART_DOUBLE_BASIC = 5
|
|
GAME_CHART_DOUBLE_DIFFICULT = 6
|
|
GAME_CHART_DOUBLE_EXPERT = 7
|
|
GAME_CHART_DOUBLE_CHALLENGE = 8
|
|
|
|
GAME_HALO_NONE = 0
|
|
GAME_HALO_FULL_COMBO = 1
|
|
GAME_HALO_PERFECT_COMBO = 2
|
|
GAME_HALO_MARVELOUS_COMBO = 3
|
|
|
|
GAME_MAX_SONGS = 600
|
|
|
|
def previous_version(self) -> Optional[DDRBase]:
|
|
return DDRX(self.data, self.config, self.model)
|
|
|
|
def game_to_db_rank(self, game_rank: int) -> int:
|
|
return {
|
|
self.GAME_RANK_AAA: self.RANK_AAA,
|
|
self.GAME_RANK_AA: self.RANK_AA,
|
|
self.GAME_RANK_A: self.RANK_A,
|
|
self.GAME_RANK_B: self.RANK_B,
|
|
self.GAME_RANK_C: self.RANK_C,
|
|
self.GAME_RANK_D: self.RANK_D,
|
|
self.GAME_RANK_E: self.RANK_E,
|
|
}[game_rank]
|
|
|
|
def db_to_game_rank(self, db_rank: int) -> int:
|
|
return {
|
|
self.RANK_AAA: self.GAME_RANK_AAA,
|
|
self.RANK_AA_PLUS: self.GAME_RANK_AA,
|
|
self.RANK_AA: self.GAME_RANK_AA,
|
|
self.RANK_AA_MINUS: self.GAME_RANK_A,
|
|
self.RANK_A_PLUS: self.GAME_RANK_A,
|
|
self.RANK_A: self.GAME_RANK_A,
|
|
self.RANK_A_MINUS: self.GAME_RANK_B,
|
|
self.RANK_B_PLUS: self.GAME_RANK_B,
|
|
self.RANK_B: self.GAME_RANK_B,
|
|
self.RANK_B_MINUS: self.GAME_RANK_C,
|
|
self.RANK_C_PLUS: self.GAME_RANK_C,
|
|
self.RANK_C: self.GAME_RANK_C,
|
|
self.RANK_C_MINUS: self.GAME_RANK_D,
|
|
self.RANK_D_PLUS: self.GAME_RANK_D,
|
|
self.RANK_D: self.GAME_RANK_D,
|
|
self.RANK_E: self.GAME_RANK_E,
|
|
}[db_rank]
|
|
|
|
def game_to_db_chart(self, game_chart: int) -> int:
|
|
return {
|
|
self.GAME_CHART_SINGLE_BEGINNER: self.CHART_SINGLE_BEGINNER,
|
|
self.GAME_CHART_SINGLE_BASIC: self.CHART_SINGLE_BASIC,
|
|
self.GAME_CHART_SINGLE_DIFFICULT: self.CHART_SINGLE_DIFFICULT,
|
|
self.GAME_CHART_SINGLE_EXPERT: self.CHART_SINGLE_EXPERT,
|
|
self.GAME_CHART_SINGLE_CHALLENGE: self.CHART_SINGLE_CHALLENGE,
|
|
self.GAME_CHART_DOUBLE_BASIC: self.CHART_DOUBLE_BASIC,
|
|
self.GAME_CHART_DOUBLE_DIFFICULT: self.CHART_DOUBLE_DIFFICULT,
|
|
self.GAME_CHART_DOUBLE_EXPERT: self.CHART_DOUBLE_EXPERT,
|
|
self.GAME_CHART_DOUBLE_CHALLENGE: self.CHART_DOUBLE_CHALLENGE,
|
|
}[game_chart]
|
|
|
|
def db_to_game_chart(self, db_chart: int) -> int:
|
|
return {
|
|
self.CHART_SINGLE_BEGINNER: self.GAME_CHART_SINGLE_BEGINNER,
|
|
self.CHART_SINGLE_BASIC: self.GAME_CHART_SINGLE_BASIC,
|
|
self.CHART_SINGLE_DIFFICULT: self.GAME_CHART_SINGLE_DIFFICULT,
|
|
self.CHART_SINGLE_EXPERT: self.GAME_CHART_SINGLE_EXPERT,
|
|
self.CHART_SINGLE_CHALLENGE: self.GAME_CHART_SINGLE_CHALLENGE,
|
|
self.CHART_DOUBLE_BASIC: self.GAME_CHART_DOUBLE_BASIC,
|
|
self.CHART_DOUBLE_DIFFICULT: self.GAME_CHART_DOUBLE_DIFFICULT,
|
|
self.CHART_DOUBLE_EXPERT: self.GAME_CHART_DOUBLE_EXPERT,
|
|
self.CHART_DOUBLE_CHALLENGE: self.GAME_CHART_DOUBLE_CHALLENGE,
|
|
}[db_chart]
|
|
|
|
def db_to_game_halo(self, db_halo: int) -> int:
|
|
if db_halo == self.HALO_MARVELOUS_FULL_COMBO:
|
|
combo_type = self.GAME_HALO_MARVELOUS_COMBO
|
|
elif db_halo == self.HALO_PERFECT_FULL_COMBO:
|
|
combo_type = self.GAME_HALO_PERFECT_COMBO
|
|
elif db_halo == self.HALO_GREAT_FULL_COMBO:
|
|
combo_type = self.GAME_HALO_FULL_COMBO
|
|
else:
|
|
combo_type = self.GAME_HALO_NONE
|
|
return combo_type
|
|
|
|
def handle_game_common_request(self, request: Node) -> Node:
|
|
game = Node.void('game')
|
|
for flagid in range(256):
|
|
flag = Node.void('flag')
|
|
game.add_child(flag)
|
|
|
|
flag.set_attribute('id', str(flagid))
|
|
flag.set_attribute('s2', '0')
|
|
flag.set_attribute('s1', '0')
|
|
flag.set_attribute('t', '0')
|
|
|
|
hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS)
|
|
counts_by_reflink = [0] * self.GAME_MAX_SONGS
|
|
for (reflink, plays) in hit_chart:
|
|
if reflink >= 0 and reflink < self.GAME_MAX_SONGS:
|
|
counts_by_reflink[reflink] = plays
|
|
game.add_child(Node.u32_array('cnt_music', counts_by_reflink))
|
|
|
|
return game
|
|
|
|
def handle_game_hiscore_request(self, request: Node) -> Node:
|
|
# This is almost identical to X3 and above, except X3 added a 'code' field
|
|
# that isn't present here. In the interest of correctness, keep a separate
|
|
# implementation here.
|
|
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))
|
|
|
|
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))
|
|
|
|
return game
|
|
|
|
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 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.
|
|
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 format_profile(self, userid: UserID, profile: Profile) -> Node:
|
|
root = Node.void('game')
|
|
|
|
# Look up play stats we bridge to every mix
|
|
play_stats = self.get_play_statistics(userid)
|
|
|
|
# Basic game settings
|
|
root.add_child(Node.string('seq', ''))
|
|
root.add_child(Node.u32('code', profile.extid))
|
|
root.add_child(Node.string('name', profile.get_str('name')))
|
|
root.add_child(Node.u8('area', profile.get_int('area', 51)))
|
|
root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays')))
|
|
root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays')))
|
|
root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess
|
|
root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0')))
|
|
root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1')))
|
|
root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2')))
|
|
root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3')))
|
|
root.add_child(Node.u32('exp', play_stats.get_int('exp')))
|
|
root.add_child(Node.u32('exp_o', profile.get_int('exp_o')))
|
|
root.add_child(Node.u32('star', profile.get_int('star')))
|
|
root.add_child(Node.u32('star_c', profile.get_int('star_c')))
|
|
root.add_child(Node.u8('combo', profile.get_int('combo', 0)))
|
|
root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0)))
|
|
|
|
# Character stuff
|
|
chara = Node.void('chara')
|
|
root.add_child(chara)
|
|
if 'chara' in profile:
|
|
chara.set_attribute('my', str(profile.get_int('chara')))
|
|
|
|
root.add_child(Node.u8_array('chara_opt', profile.get_int_array('chara_opt', 96)))
|
|
|
|
# Drill rankings
|
|
if 'title' in profile:
|
|
title = Node.void('title')
|
|
root.add_child(title)
|
|
titledict = profile.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 profile:
|
|
title_gr = Node.void('title_gr')
|
|
root.add_child(title_gr)
|
|
title_grdict = profile.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')))
|
|
|
|
# Event progrses
|
|
if 'event' in profile:
|
|
event = Node.void('event')
|
|
root.add_child(event)
|
|
event_dict = profile.get_dict('event')
|
|
if 'diff_sum' in event_dict:
|
|
event.set_attribute('diff_sum', str(event_dict.get_int('diff_sum')))
|
|
if 'welcome' in event_dict:
|
|
event.set_attribute('welcome', str(event_dict.get_int('welcome')))
|
|
if 'e_flags' in event_dict:
|
|
event.set_attribute('e_flags', str(event_dict.get_int('e_flags')))
|
|
|
|
if 'e_panel' in profile:
|
|
e_panel = Node.void('e_panel')
|
|
root.add_child(e_panel)
|
|
e_panel_dict = profile.get_dict('e_panel')
|
|
if 'play_id' in e_panel_dict:
|
|
e_panel.set_attribute('play_id', str(e_panel_dict.get_int('play_id')))
|
|
e_panel.add_child(Node.u8_array('cell', e_panel_dict.get_int_array('cell', 24)))
|
|
e_panel.add_child(Node.u8_array('panel_state', e_panel_dict.get_int_array('panel_state', 6)))
|
|
|
|
if 'e_pix' in profile:
|
|
e_pix = Node.void('e_pix')
|
|
root.add_child(e_pix)
|
|
e_pix_dict = profile.get_dict('e_pix')
|
|
if 'max_distance' in e_pix_dict:
|
|
e_pix.set_attribute('max_distance', str(e_pix_dict.get_int('max_distance')))
|
|
if 'max_planet' in e_pix_dict:
|
|
e_pix.set_attribute('max_planet', str(e_pix_dict.get_int('max_planet')))
|
|
if 'total_distance' in e_pix_dict:
|
|
e_pix.set_attribute('total_distance', str(e_pix_dict.get_int('total_distance')))
|
|
if 'total_planet' in e_pix_dict:
|
|
e_pix.set_attribute('total_planet', str(e_pix_dict.get_int('total_planet')))
|
|
if 'border_character' in e_pix_dict:
|
|
e_pix.set_attribute('border_character', str(e_pix_dict.get_int('border_character')))
|
|
if 'border_balloon' in e_pix_dict:
|
|
e_pix.set_attribute('border_balloon', str(e_pix_dict.get_int('border_balloon')))
|
|
if 'border_music_aftr' in e_pix_dict:
|
|
e_pix.set_attribute('border_music_aftr', str(e_pix_dict.get_int('border_music_aftr')))
|
|
if 'border_music_meii' in e_pix_dict:
|
|
e_pix.set_attribute('border_music_meii', str(e_pix_dict.get_int('border_music_meii')))
|
|
if 'border_music_dirt' in e_pix_dict:
|
|
e_pix.set_attribute('border_music_dirt', str(e_pix_dict.get_int('border_music_dirt')))
|
|
if 'flags' in e_pix_dict:
|
|
e_pix.set_attribute('flags', str(e_pix_dict.get_int('flags')))
|
|
|
|
# Calorie mode
|
|
if 'weight' in profile:
|
|
workouts = self.data.local.user.get_time_based_achievements(
|
|
self.game,
|
|
self.version,
|
|
userid,
|
|
achievementtype='workout',
|
|
since=Time.now() - Time.SECONDS_IN_DAY,
|
|
)
|
|
total = sum([w.data.get_int('calories') for w in workouts])
|
|
workout = Node.void('workout')
|
|
root.add_child(workout)
|
|
workout.set_attribute('weight', str(profile.get_int('weight')))
|
|
workout.set_attribute('day', str(total))
|
|
workout.set_attribute('disp', '1')
|
|
|
|
# Last cursor settings
|
|
last = Node.void('last')
|
|
root.add_child(last)
|
|
lastdict = profile.get_dict('last')
|
|
last.set_attribute('fri', str(lastdict.get_int('fri')))
|
|
last.set_attribute('style', str(lastdict.get_int('style')))
|
|
last.set_attribute('mode', str(lastdict.get_int('mode')))
|
|
last.set_attribute('cate', str(lastdict.get_int('cate')))
|
|
last.set_attribute('sort', str(lastdict.get_int('sort')))
|
|
last.set_attribute('mid', str(lastdict.get_int('mid')))
|
|
last.set_attribute('mtype', str(lastdict.get_int('mtype')))
|
|
last.set_attribute('cid', str(lastdict.get_int('cid')))
|
|
last.set_attribute('ctype', str(lastdict.get_int('ctype')))
|
|
last.set_attribute('sid', str(lastdict.get_int('sid')))
|
|
|
|
# Groove gauge level-ups
|
|
gr_s = Node.void('gr_s')
|
|
root.add_child(gr_s)
|
|
index = 1
|
|
for entry in profile.get_int_array('gr_s', 5):
|
|
gr_s.set_attribute(f'gr{index}', str(entry))
|
|
index = index + 1
|
|
|
|
gr_d = Node.void('gr_d')
|
|
root.add_child(gr_d)
|
|
index = 1
|
|
for entry in profile.get_int_array('gr_d', 5):
|
|
gr_d.set_attribute(f'gr{index}', str(entry))
|
|
index = index + 1
|
|
|
|
# Options in menus
|
|
root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16)))
|
|
root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16)))
|
|
|
|
# Unlock flags
|
|
root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256)))
|
|
|
|
# Ranking display?
|
|
root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100)))
|
|
|
|
# Rivals
|
|
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:])
|
|
friend = self.get_profile(link.other_userid)
|
|
play_stats = self.get_play_statistics(link.other_userid)
|
|
if friend is not None:
|
|
friendnode = Node.void('friend')
|
|
root.add_child(friendnode)
|
|
friendnode.set_attribute('pos', str(pos))
|
|
friendnode.set_attribute('vs', '0')
|
|
friendnode.set_attribute('up', '0')
|
|
friendnode.add_child(Node.u32('code', friend.extid))
|
|
friendnode.add_child(Node.string('name', friend.get_str('name')))
|
|
friendnode.add_child(Node.u8('area', friend.get_int('area', 51)))
|
|
friendnode.add_child(Node.u32('exp', play_stats.get_int('exp')))
|
|
friendnode.add_child(Node.u32('star', friend.get_int('star')))
|
|
|
|
# Drill rankings
|
|
if 'title' in friend:
|
|
title = Node.void('title')
|
|
friendnode.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')
|
|
friendnode.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')
|
|
friendnode.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')
|
|
friendnode.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 root
|
|
|
|
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
|
|
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}')
|
|
|
|
# Update last attributes
|
|
lastdict.replace_int('fri', intish(last.attribute('fri')))
|
|
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')))
|
|
newprofile.replace_int_array('chara_opt', 96, request.child_value('chara_opt'))
|
|
|
|
# Track event progress
|
|
event = request.child('event')
|
|
if event is not None:
|
|
event_dict = newprofile.get_dict('event')
|
|
event_dict.replace_int('diff_sum', intish(event.attribute('diff_sum')))
|
|
event_dict.replace_int('e_flags', intish(event.attribute('e_flags')))
|
|
event_dict.replace_int('welcome', intish(event.attribute('welcome')))
|
|
newprofile.replace_dict('event', event_dict)
|
|
|
|
e_panel = request.child('e_panel')
|
|
if e_panel is not None:
|
|
e_panel_dict = newprofile.get_dict('e_panel')
|
|
e_panel_dict.replace_int('play_id', intish(e_panel.attribute('play_id')))
|
|
e_panel_dict.replace_int_array('cell', 24, e_panel.child_value('cell'))
|
|
e_panel_dict.replace_int_array('panel_state', 6, e_panel.child_value('panel_state'))
|
|
newprofile.replace_dict('e_panel', e_panel_dict)
|
|
|
|
e_pix = request.child('e_pix')
|
|
if e_pix is not None:
|
|
e_pix_dict = newprofile.get_dict('e_pix')
|
|
max_distance = e_pix_dict.get_int('max_distance')
|
|
max_planet = e_pix_dict.get_int('max_planet')
|
|
total_distance = e_pix_dict.get_int('total_distance')
|
|
total_planet = e_pix_dict.get_int('total_planet')
|
|
cur_distance = intish(e_pix.attribute('this_distance'))
|
|
cur_planet = intish(e_pix.attribute('this_planet'))
|
|
if cur_distance is not None:
|
|
max_distance = max(max_distance, cur_distance)
|
|
total_distance += cur_distance
|
|
if cur_planet is not None:
|
|
max_planet = max(max_planet, cur_planet)
|
|
total_planet += cur_planet
|
|
|
|
e_pix_dict.replace_int('max_distance', max_distance)
|
|
e_pix_dict.replace_int('max_planet', max_planet)
|
|
e_pix_dict.replace_int('total_distance', total_distance)
|
|
e_pix_dict.replace_int('total_planet', total_planet)
|
|
e_pix_dict.replace_int('flags', intish(e_pix.attribute('flags')))
|
|
newprofile.replace_dict('e_pix', e_pix_dict)
|
|
|
|
# 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],
|
|
{},
|
|
)
|
|
|
|
# Keep track of play statistics
|
|
self.update_play_statistics(userid, play_stats)
|
|
|
|
return newprofile
|