781 lines
32 KiB
Python
781 lines
32 KiB
Python
# vim: set fileencoding=utf-8
|
|
from typing import Dict, List, Optional, Tuple
|
|
from typing_extensions import Final
|
|
|
|
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: str = "DanceDanceRevolution X2"
|
|
version: int = VersionConstants.DDR_X2
|
|
|
|
GAME_STYLE_SINGLE: Final[int] = 0
|
|
GAME_STYLE_DOUBLE: Final[int] = 1
|
|
GAME_STYLE_VERSUS: Final[int] = 2
|
|
|
|
GAME_RANK_AAA: Final[int] = 1
|
|
GAME_RANK_AA: Final[int] = 2
|
|
GAME_RANK_A: Final[int] = 3
|
|
GAME_RANK_B: Final[int] = 4
|
|
GAME_RANK_C: Final[int] = 5
|
|
GAME_RANK_D: Final[int] = 6
|
|
GAME_RANK_E: Final[int] = 7
|
|
|
|
GAME_CHART_SINGLE_BEGINNER: Final[int] = 0
|
|
GAME_CHART_SINGLE_BASIC: Final[int] = 1
|
|
GAME_CHART_SINGLE_DIFFICULT: Final[int] = 2
|
|
GAME_CHART_SINGLE_EXPERT: Final[int] = 3
|
|
GAME_CHART_SINGLE_CHALLENGE: Final[int] = 4
|
|
GAME_CHART_DOUBLE_BASIC: Final[int] = 5
|
|
GAME_CHART_DOUBLE_DIFFICULT: Final[int] = 6
|
|
GAME_CHART_DOUBLE_EXPERT: Final[int] = 7
|
|
GAME_CHART_DOUBLE_CHALLENGE: Final[int] = 8
|
|
|
|
GAME_HALO_NONE: Final[int] = 0
|
|
GAME_HALO_FULL_COMBO: Final[int] = 1
|
|
GAME_HALO_PERFECT_COMBO: Final[int] = 2
|
|
GAME_HALO_MARVELOUS_COMBO: Final[int] = 3
|
|
|
|
GAME_MAX_SONGS: Final[int] = 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", self.get_machine_region())))
|
|
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", self.get_machine_region())))
|
|
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", self.get_machine_region())))
|
|
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 = oldprofile.clone()
|
|
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
|