1191 lines
51 KiB
Python
1191 lines
51 KiB
Python
# vim: set fileencoding=utf-8
|
|
import random
|
|
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
from typing_extensions import Final
|
|
|
|
from bemani.backend.base import Status
|
|
from bemani.backend.jubeat.base import JubeatBase
|
|
from bemani.backend.jubeat.common import (
|
|
JubeatDemodataGetHitchartHandler,
|
|
JubeatDemodataGetNewsHandler,
|
|
JubeatGamendRegisterHandler,
|
|
JubeatGametopGetMeetingHandler,
|
|
JubeatLobbyCheckHandler,
|
|
JubeatLoggerReportHandler,
|
|
)
|
|
from bemani.backend.jubeat.prop import JubeatProp
|
|
|
|
from bemani.common import Profile, ValidatedDict, VersionConstants
|
|
from bemani.data import Data, Score, Song, UserID
|
|
from bemani.protocol import Node
|
|
|
|
|
|
class JubeatQubell(
|
|
JubeatDemodataGetHitchartHandler,
|
|
JubeatDemodataGetNewsHandler,
|
|
JubeatGamendRegisterHandler,
|
|
JubeatGametopGetMeetingHandler,
|
|
JubeatLobbyCheckHandler,
|
|
JubeatLoggerReportHandler,
|
|
JubeatBase,
|
|
):
|
|
|
|
name: str = 'Jubeat Qubell'
|
|
version: int = VersionConstants.JUBEAT_QUBELL
|
|
|
|
JBOX_EMBLEM_NORMAL: Final[int] = 1
|
|
JBOX_EMBLEM_PREMIUM: Final[int] = 2
|
|
|
|
ENABLE_GARNET: Final[bool] = False
|
|
|
|
EVENTS: Dict[int, Dict[str, bool]] = {
|
|
5: {
|
|
'enabled': False,
|
|
},
|
|
6: {
|
|
'enabled': False,
|
|
},
|
|
15: {
|
|
'enabled': False,
|
|
},
|
|
19: {
|
|
'enabled': False,
|
|
},
|
|
}
|
|
|
|
def previous_version(self) -> Optional[JubeatBase]:
|
|
return JubeatProp(self.data, self.config, self.model)
|
|
|
|
@classmethod
|
|
def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]:
|
|
"""
|
|
Insert daily FC challenges into the DB.
|
|
"""
|
|
events = []
|
|
if data.local.network.should_schedule(cls.game, cls.version, 'fc_challenge', 'daily'):
|
|
# Generate a new list of two FC challenge songs.
|
|
start_time, end_time = data.local.network.get_schedule_duration('daily')
|
|
all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version))
|
|
if len(all_songs) >= 2:
|
|
daily_songs = random.sample(all_songs, 2)
|
|
data.local.game.put_time_sensitive_settings(
|
|
cls.game,
|
|
cls.version,
|
|
'fc_challenge',
|
|
{
|
|
'start_time': start_time,
|
|
'end_time': end_time,
|
|
'today': daily_songs[0],
|
|
'whim': daily_songs[1],
|
|
},
|
|
)
|
|
events.append((
|
|
'jubeat_fc_challenge_charts',
|
|
{
|
|
'version': cls.version,
|
|
'today': daily_songs[0],
|
|
'whim': daily_songs[1],
|
|
},
|
|
))
|
|
|
|
# Mark that we did some actual work here.
|
|
data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily')
|
|
return events
|
|
|
|
def __get_global_info(self) -> Node:
|
|
info = Node.void('info')
|
|
|
|
# Event info. Valid event IDs are 5, 6, 15, 19
|
|
event_info = Node.void('event_info')
|
|
info.add_child(event_info)
|
|
for event in self.EVENTS:
|
|
evt = Node.void('event')
|
|
event_info.add_child(evt)
|
|
evt.set_attribute('type', str(event))
|
|
evt.add_child(Node.u8('state', 0x1 if self.EVENTS[event]['enabled'] else 0x0))
|
|
|
|
# Each of the following two sections should have zero or more child nodes (no
|
|
# particular name) which look like the following:
|
|
# <node>
|
|
# <id __type="s32">songid</id>
|
|
# <stime __type="str">start time?</stime>
|
|
# <etime __type="str">end time?</etime>
|
|
# </node>
|
|
# Share music?
|
|
share_music = Node.void('share_music')
|
|
info.add_child(share_music)
|
|
|
|
# Bonus music?
|
|
bonus_music = Node.void('bonus_music')
|
|
info.add_child(bonus_music)
|
|
|
|
# Some sort of music DB whitelist
|
|
info.add_child(Node.s32_array(
|
|
'white_music_list',
|
|
[
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
],
|
|
))
|
|
|
|
info.add_child(Node.s32_array(
|
|
'white_marker_list',
|
|
[
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
],
|
|
))
|
|
|
|
info.add_child(Node.s32_array(
|
|
'white_theme_list',
|
|
[
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
],
|
|
))
|
|
|
|
info.add_child(Node.s32_array(
|
|
'open_music_list',
|
|
[
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
],
|
|
))
|
|
|
|
info.add_child(Node.s32_array(
|
|
'shareable_music_list',
|
|
[
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
-1, -1, -1, -1,
|
|
],
|
|
))
|
|
|
|
jbox = Node.void('jbox')
|
|
info.add_child(jbox)
|
|
jbox.add_child(Node.s32('point', 0))
|
|
emblem = Node.void('emblem')
|
|
jbox.add_child(emblem)
|
|
normal = Node.void('normal')
|
|
emblem.add_child(normal)
|
|
premium = Node.void('premium')
|
|
emblem.add_child(premium)
|
|
normal.add_child(Node.s16('index', 2))
|
|
premium.add_child(Node.s16('index', 1))
|
|
|
|
born = Node.void('born')
|
|
info.add_child(born)
|
|
born.add_child(Node.s8('status', 0))
|
|
born.add_child(Node.s16('year', 0))
|
|
|
|
digdig = Node.void('digdig')
|
|
info.add_child(digdig)
|
|
stage_list = Node.void('stage_list')
|
|
digdig.add_child(stage_list)
|
|
# Stage numbers are between 1 and 13 inclusive.
|
|
for i in range(1, 14):
|
|
stage = Node.void('stage')
|
|
stage_list.add_child(stage)
|
|
stage.set_attribute('number', str(i))
|
|
stage.add_child(Node.u8('state', 0x1))
|
|
|
|
# Collection list values should look like:
|
|
# <rating>
|
|
# <id __type="s32">songid</id>
|
|
# <stime __type="str">start time?</stime>
|
|
# <etime __type="str">end time?</etime>
|
|
# </node>
|
|
collection = Node.void('collection')
|
|
info.add_child(collection)
|
|
collection.add_child(Node.void('rating_s'))
|
|
|
|
# Additional digdig nodes that aren't the main event
|
|
generic_dig = Node.void('generic_dig')
|
|
info.add_child(generic_dig)
|
|
map_list = Node.void('map_list')
|
|
generic_dig.add_child(map_list)
|
|
# DigDig nodes here have the following format:
|
|
# <map release_code='somecode' dataversion='someversion' id='someid' client_map_kind='somekind'>
|
|
# <stime __type="str">start time?</stime>
|
|
# <etime __type="str">end time?</etime>
|
|
# <stage_list>
|
|
# <!-- stage number is between 1 and 14 inclusive here -->
|
|
# <stage number='number'>
|
|
# <need_point __type="s32">point</need_point>
|
|
# <need_norma_num __type="s32">norma num</need_point>
|
|
# <norma_list>
|
|
# <norma>
|
|
# <point __type="s32">point</point>
|
|
# <!-- between 1 and 15 inclusive -->
|
|
# <type __type="s32">type</type>
|
|
# <music_id __type="s32">music_id</music_id>
|
|
# <seq_difficulty __type="s32">seq_difficulty</seq_difficulty>
|
|
# <rating __type="s32">rating</rating>
|
|
# <stage_level __type="s32">stage_level</stage_level>
|
|
# <goal __type="s32">goal</goalpoint>
|
|
# <title_line_list>
|
|
# <!-- there can be up to 4 of these -->
|
|
# <title type="str">32 bytes long title</title>
|
|
# </title_line_list>
|
|
# </norma>
|
|
# </norma_list>
|
|
# <item_list>
|
|
# <item>
|
|
# <point __type="s32">point</point>
|
|
# <!-- between 1 and 7 inclusive -->
|
|
# <type __type="s32">type</type>
|
|
# <music_id __type="s32">music_id</music_id>
|
|
# <bonus_tune_point __type="s32">bonus_tune_point</bonus_tune_point>
|
|
# <title_id __type="s32">title_id</title_id>
|
|
# <marker_id __type="s32">marker_id</marker_id>
|
|
# <background_id __type="s32">background_id</background_id>
|
|
# </item>
|
|
# </item_list>
|
|
# <!-- unlock challenge -->
|
|
# <unlock>
|
|
# <tune_list>
|
|
# <!-- between 1 and 3 inclusive -->
|
|
# <tune no='number'>
|
|
# <seq_list>
|
|
# <seq>
|
|
# <music_id __type="s32">music_id</music_id>
|
|
# <seq_difficulty __type="s32">seq_difficulty</seq_difficulty>
|
|
# <!-- flags are optional -->
|
|
# <flags __type="str">SECRET|empty</flags>
|
|
# </seq>
|
|
# </seq_list>
|
|
# </tune>
|
|
# </tune_list>
|
|
# <flags __type="str">RISKY|empty</flags>
|
|
# <!-- requirements for normal clear -->
|
|
# <clear>
|
|
# <type __type="s32">type</type>
|
|
# <!-- between 0 and 8 inclusive -->
|
|
# <rating __type="s32">rating</rating>
|
|
# <score __type="s32">score</score>
|
|
# <ex_option>
|
|
# <is_hard __type="bool">true/false</is_hard>
|
|
# <!-- between 0 and 6 inclusive -->
|
|
# <hazard_type __type="s32">type</hazard_type>
|
|
# </ex_option>
|
|
# </clear>
|
|
# <!-- requirements for gold clear -->
|
|
# <gold>
|
|
# <type __type="s32">type</type>
|
|
# <!-- between 0 and 8 inclusive -->
|
|
# <rating __type="s32">rating</rating>
|
|
# <score __type="s32">score</score>
|
|
# <ex_option>
|
|
# <is_hard __type="bool">true/false</is_hard>
|
|
# <!-- between 0 and 6 inclusive -->
|
|
# <hazard_type __type="s32">type</hazard_type>
|
|
# </ex_option>
|
|
# </gold>
|
|
# </unlock>
|
|
# <serif_list>
|
|
# <!-- kind should be between 1 and 6 inclusive -->
|
|
# <serif kind='kind'>
|
|
# <line_list>
|
|
# <!-- there can be up to four of these -->
|
|
# <line __type="str">64 byte string</str>
|
|
# </line_list>
|
|
# </serif>
|
|
# </serif_list>
|
|
# <bgm_list>
|
|
# <!-- kind can be between 1 and 2 inclusive -->
|
|
# <bgm kind='kind' id='id' />
|
|
# </bgm_list>
|
|
# <se_list>
|
|
# <!-- kind can be between 1 and 20 inclusive -->
|
|
# <se kind='kind' id='id' />
|
|
# </se_list>
|
|
# <!-- optional node -->
|
|
# <tex_number __type="s32">unknown</tex_number>
|
|
# </stage>
|
|
# </stage_list>
|
|
# </map>
|
|
|
|
expert_option = Node.void('expert_option')
|
|
info.add_child(expert_option)
|
|
expert_option.add_child(Node.bool('is_available', True))
|
|
|
|
tsumtsum = Node.void('tsumtsum')
|
|
info.add_child(tsumtsum)
|
|
tsumtsum.add_child(Node.bool('is_available', True))
|
|
|
|
nagatanien = Node.void('nagatanien')
|
|
info.add_child(nagatanien)
|
|
nagatanien.add_child(Node.bool('is_available', True))
|
|
|
|
all_music_matching = Node.void('all_music_matching')
|
|
info.add_child(all_music_matching)
|
|
all_music_matching.add_child(Node.bool('is_available', True))
|
|
|
|
question_list = Node.void('question_list')
|
|
info.add_child(question_list)
|
|
|
|
return info
|
|
|
|
def handle_shopinfo_regist_request(self, request: Node) -> Node:
|
|
# Update the name of this cab for admin purposes
|
|
self.update_machine_name(request.child_value('shop/name'))
|
|
|
|
shopinfo = Node.void('shopinfo')
|
|
|
|
data = Node.void('data')
|
|
shopinfo.add_child(data)
|
|
data.add_child(Node.u32('cabid', 1))
|
|
data.add_child(Node.string('locationid', 'nowhere'))
|
|
data.add_child(Node.u8('tax_phase', 1))
|
|
|
|
facility = Node.void('facility')
|
|
data.add_child(facility)
|
|
facility.add_child(Node.u32('exist', 1))
|
|
|
|
data.add_child(self.__get_global_info())
|
|
|
|
return shopinfo
|
|
|
|
def handle_recommend_get_recommend_request(self, request: Node) -> Node:
|
|
recommend = Node.void('recommend')
|
|
data = Node.void('data')
|
|
recommend.add_child(data)
|
|
|
|
player = Node.void('player')
|
|
data.add_child(player)
|
|
music_list = Node.void('music_list')
|
|
player.add_child(music_list)
|
|
|
|
# TODO: Might be a way to figure out who plays what song and then offer
|
|
# recommendations based on that. There should be 12 songs returned here.
|
|
recommended_songs: List[Song] = []
|
|
for i, song in enumerate(recommended_songs):
|
|
music = Node.void('music')
|
|
music_list.add_child(music)
|
|
music.set_attribute('order', str(i))
|
|
music.add_child(Node.s32('music_id', song.id))
|
|
music.add_child(Node.s8('seq', song.chart))
|
|
|
|
return recommend
|
|
|
|
def handle_gametop_regist_request(self, request: Node) -> Node:
|
|
data = request.child('data')
|
|
player = data.child('player')
|
|
refid = player.child_value('refid')
|
|
name = player.child_value('name')
|
|
root = self.new_profile_by_refid(refid, name)
|
|
return root
|
|
|
|
def handle_gametop_get_pdata_request(self, request: Node) -> Node:
|
|
data = request.child('data')
|
|
player = data.child('player')
|
|
refid = player.child_value('refid')
|
|
root = self.get_profile_by_refid(refid)
|
|
if root is None:
|
|
root = Node.void('gametop')
|
|
root.set_attribute('status', str(Status.NO_PROFILE))
|
|
return root
|
|
|
|
def handle_gametop_get_mdata_request(self, request: Node) -> Node:
|
|
data = request.child('data')
|
|
player = data.child('player')
|
|
extid = player.child_value('jid')
|
|
mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason
|
|
if mdata_ver != 1:
|
|
root = Node.void('gametop')
|
|
datanode = Node.void('data')
|
|
root.add_child(datanode)
|
|
player = Node.void('player')
|
|
datanode.add_child(player)
|
|
player.add_child(Node.s32('jid', extid))
|
|
playdata = Node.void('mdata_list')
|
|
player.add_child(playdata)
|
|
return root
|
|
root = self.get_scores_by_extid(extid)
|
|
if root is None:
|
|
root = Node.void('gametop')
|
|
root.set_attribute('status', str(Status.NO_PROFILE))
|
|
return root
|
|
|
|
def handle_gametop_get_info_request(self, request: Node) -> Node:
|
|
root = Node.void('gametop')
|
|
data = Node.void('data')
|
|
root.add_child(data)
|
|
data.add_child(self.__get_global_info())
|
|
|
|
return root
|
|
|
|
def handle_gameend_final_request(self, request: Node) -> Node:
|
|
data = request.child('data')
|
|
player = data.child('player')
|
|
|
|
if player is not None:
|
|
refid = player.child_value('refid')
|
|
else:
|
|
refid = None
|
|
|
|
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:
|
|
profile = self.get_profile(userid)
|
|
|
|
# Grab unlock progress
|
|
item = player.child('item')
|
|
if item is not None:
|
|
profile.replace_int_array('emblem_list', 96, item.child_value('emblem_list'))
|
|
|
|
# jbox stuff
|
|
jbox = player.child('jbox')
|
|
jboxdict = profile.get_dict('jbox')
|
|
if jbox is not None:
|
|
jboxdict.replace_int('point', jbox.child_value('point'))
|
|
emblemtype = jbox.child_value('emblem/type')
|
|
index = jbox.child_value('emblem/index')
|
|
if emblemtype == self.JBOX_EMBLEM_NORMAL:
|
|
jboxdict.replace_int('normal_index', index)
|
|
elif emblemtype == self.JBOX_EMBLEM_PREMIUM:
|
|
jboxdict.replace_int('premium_index', index)
|
|
profile.replace_dict('jbox', jboxdict)
|
|
|
|
# Born stuff
|
|
born = player.child('born')
|
|
if born is not None:
|
|
profile.replace_int('born_status', born.child_value('status'))
|
|
profile.replace_int('born_year', born.child_value('year'))
|
|
else:
|
|
profile = None
|
|
|
|
if userid is not None and profile is not None:
|
|
self.put_profile(userid, profile)
|
|
|
|
return Node.void('gameend')
|
|
|
|
def format_profile(self, userid: UserID, profile: Profile) -> Node:
|
|
root = Node.void('gametop')
|
|
data = Node.void('data')
|
|
root.add_child(data)
|
|
# Jubeat Prop appears to allow full event overrides per-player
|
|
data.add_child(self.__get_global_info())
|
|
|
|
player = Node.void('player')
|
|
data.add_child(player)
|
|
|
|
# Some server node
|
|
server = Node.void('server')
|
|
player.add_child(server)
|
|
|
|
# Basic profile info
|
|
player.add_child(Node.string('name', profile.get_str('name', 'なし')))
|
|
player.add_child(Node.s32('jid', profile.extid))
|
|
|
|
# Miscelaneous crap
|
|
player.add_child(Node.s32('session_id', 1))
|
|
player.add_child(Node.u64('event_flag', 0))
|
|
|
|
# Player info and statistics
|
|
info = Node.void('info')
|
|
player.add_child(info)
|
|
info.add_child(Node.s16('jubility', profile.get_int('jubility')))
|
|
info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday')))
|
|
info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt')))
|
|
info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt')))
|
|
info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt')))
|
|
info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt')))
|
|
info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt')))
|
|
info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt')))
|
|
info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt')))
|
|
info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt')))
|
|
info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt')))
|
|
info.add_child(Node.s32('bonus_tune_points', profile.get_int('bonus_tune_points')))
|
|
info.add_child(Node.bool('is_bonus_tune_played', profile.get_bool('is_bonus_tune_played')))
|
|
|
|
# Looks to be set to true when there's an old profile, stops tutorial from
|
|
# happening on first load.
|
|
info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved')))
|
|
|
|
# Not saved, but loaded
|
|
info.add_child(Node.s32('mtg_entry_cnt', 123))
|
|
info.add_child(Node.s32('mtg_hold_cnt', 456))
|
|
info.add_child(Node.u8('mtg_result', 10))
|
|
|
|
# Last played data, for showing cursor and such
|
|
lastdict = profile.get_dict('last')
|
|
last = Node.void('last')
|
|
player.add_child(last)
|
|
last.add_child(Node.s64('play_time', lastdict.get_int('play_time')))
|
|
last.add_child(Node.string('shopname', lastdict.get_str('shopname')))
|
|
last.add_child(Node.string('areaname', lastdict.get_str('areaname')))
|
|
last.add_child(Node.s32('music_id', lastdict.get_int('music_id')))
|
|
last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id')))
|
|
last.add_child(Node.s8('sort', lastdict.get_int('sort')))
|
|
last.add_child(Node.s8('category', lastdict.get_int('category')))
|
|
last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option')))
|
|
last.add_child(Node.s32('dig_select', lastdict.get_int('dig_select')))
|
|
|
|
settings = Node.void('settings')
|
|
last.add_child(settings)
|
|
settings.add_child(Node.s8('marker', lastdict.get_int('marker')))
|
|
settings.add_child(Node.s8('theme', lastdict.get_int('theme')))
|
|
settings.add_child(Node.s16('title', lastdict.get_int('title')))
|
|
settings.add_child(Node.s16('parts', lastdict.get_int('parts')))
|
|
settings.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort')))
|
|
settings.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp')))
|
|
settings.add_child(Node.s16_array('emblem', lastdict.get_int_array('emblem', 5)))
|
|
settings.add_child(Node.s8('matching', lastdict.get_int('matching')))
|
|
settings.add_child(Node.s8('hazard', lastdict.get_int('hazard')))
|
|
settings.add_child(Node.s8('hard', lastdict.get_int('hard')))
|
|
|
|
# Secret unlocks
|
|
item = Node.void('item')
|
|
player.add_child(item)
|
|
item.add_child(Node.s32_array('music_list', profile.get_int_array('music_list', 64, [-1] * 64)))
|
|
item.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list', 64, [-1] * 64)))
|
|
item.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list', 16, [-1] * 16)))
|
|
item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 16, [-1] * 16)))
|
|
item.add_child(Node.s32_array('title_list', profile.get_int_array('title_list', 160, [-1] * 160)))
|
|
item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 160, [-1] * 160)))
|
|
item.add_child(Node.s32_array('emblem_list', profile.get_int_array('emblem_list', 96, [-1] * 96)))
|
|
|
|
new = Node.void('new')
|
|
item.add_child(new)
|
|
new.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list_new', 64, [-1] * 64)))
|
|
new.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list_new', 16, [-1] * 16)))
|
|
new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 16, [-1] * 16)))
|
|
|
|
# Add rivals to profile.
|
|
rivallist = Node.void('rivallist')
|
|
player.add_child(rivallist)
|
|
|
|
links = self.data.local.user.get_links(self.game, self.version, userid)
|
|
rivalcount = 0
|
|
for link in links:
|
|
if link.type != 'rival':
|
|
continue
|
|
|
|
rprofile = self.get_profile(link.other_userid)
|
|
if rprofile is None:
|
|
continue
|
|
|
|
rival = Node.void('rival')
|
|
rivallist.add_child(rival)
|
|
rival.add_child(Node.s32('jid', rprofile.extid))
|
|
rival.add_child(Node.string('name', rprofile.get_str('name')))
|
|
|
|
# This looks like a carry-over from prop's career and isn't displayed.
|
|
career = Node.void('career')
|
|
rival.add_child(career)
|
|
career.add_child(Node.s16('level', 1))
|
|
|
|
# Lazy way of keeping track of rivals, since we can only have 3
|
|
# or the game with throw up.
|
|
rivalcount += 1
|
|
if rivalcount >= 3:
|
|
break
|
|
|
|
rivallist.set_attribute('count', str(rivalcount))
|
|
|
|
lab_edit_seq = Node.void('lab_edit_seq')
|
|
player.add_child(lab_edit_seq)
|
|
lab_edit_seq.set_attribute('count', '0')
|
|
|
|
# Full combo challenge
|
|
entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge')
|
|
if entry is None:
|
|
entry = ValidatedDict()
|
|
|
|
# Figure out if we've played these songs
|
|
start_time, end_time = self.data.local.network.get_schedule_duration('daily')
|
|
today_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('today', -1), timelimit=start_time)
|
|
whim_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('whim', -1), timelimit=start_time)
|
|
|
|
fc_challenge = Node.void('fc_challenge')
|
|
player.add_child(fc_challenge)
|
|
today = Node.void('today')
|
|
fc_challenge.add_child(today)
|
|
today.add_child(Node.s32('music_id', entry.get_int('today', -1)))
|
|
today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0))
|
|
whim = Node.void('whim')
|
|
fc_challenge.add_child(whim)
|
|
whim.add_child(Node.s32('music_id', entry.get_int('whim', -1)))
|
|
whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0))
|
|
|
|
# No news, ever.
|
|
news = Node.void('news')
|
|
player.add_child(news)
|
|
news.add_child(Node.s16('checked', 0))
|
|
news.add_child(Node.u32('checked_flag', 0))
|
|
|
|
# Sane defaults for unknown/who cares nodes
|
|
history = Node.void('history')
|
|
player.add_child(history)
|
|
history.set_attribute('count', '0')
|
|
free_first_play = Node.void('free_first_play')
|
|
player.add_child(free_first_play)
|
|
free_first_play.add_child(Node.bool('is_available', False))
|
|
navi = Node.void('navi')
|
|
player.add_child(navi)
|
|
navi.add_child(Node.u64('flag', profile.get_int('navi_flag')))
|
|
|
|
# Player status for events
|
|
event_info = Node.void('event_info')
|
|
player.add_child(event_info)
|
|
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
|
|
for achievement in achievements:
|
|
if achievement.type == 'event':
|
|
# There are two significant bits here, 0x1 and 0x2, I think the first
|
|
# one is whether the event is started, second is if its finished?
|
|
event = Node.void('event')
|
|
event_info.add_child(event)
|
|
event.set_attribute('type', str(achievement.id))
|
|
|
|
state = 0x0
|
|
state = state + 0x2 if achievement.data.get_bool('is_completed') else 0x0
|
|
event.add_child(Node.u8('state', state))
|
|
|
|
# JBox stuff
|
|
jbox = Node.void('jbox')
|
|
jboxdict = profile.get_dict('jbox')
|
|
player.add_child(jbox)
|
|
jbox.add_child(Node.s32('point', jboxdict.get_int('point')))
|
|
emblem = Node.void('emblem')
|
|
jbox.add_child(emblem)
|
|
normal = Node.void('normal')
|
|
emblem.add_child(normal)
|
|
premium = Node.void('premium')
|
|
emblem.add_child(premium)
|
|
|
|
# Calculate a random index for normal and premium to give to player
|
|
# as a gatcha.
|
|
gameitems = self.data.local.game.get_items(self.game, self.version)
|
|
normalemblems: Set[int] = set()
|
|
premiumemblems: Set[int] = set()
|
|
for gameitem in gameitems:
|
|
if gameitem.type == 'emblem':
|
|
if gameitem.data.get_int('rarity') in {1, 2, 3}:
|
|
normalemblems.add(gameitem.id)
|
|
if gameitem.data.get_int('rarity') in {3, 4, 5}:
|
|
premiumemblems.add(gameitem.id)
|
|
|
|
# Default to some emblems in case the catalog is not available.
|
|
normalindex = 2
|
|
premiumindex = 1
|
|
if normalemblems:
|
|
normalindex = random.sample(normalemblems, 1)[0]
|
|
if premiumemblems:
|
|
premiumindex = random.sample(premiumemblems, 1)[0]
|
|
|
|
normal.add_child(Node.s16('index', normalindex))
|
|
premium.add_child(Node.s16('index', premiumindex))
|
|
|
|
# Digdig stuff
|
|
digdig = Node.void('digdig')
|
|
digdigdict = profile.get_dict('digdig')
|
|
eternaldict = digdigdict.get_dict('eternal')
|
|
olddict = digdigdict.get_dict('old')
|
|
player.add_child(digdig)
|
|
digdig.add_child(Node.u64('flag', digdigdict.get_int('flag')))
|
|
|
|
# Emerald main stages
|
|
main = Node.void('main')
|
|
digdig.add_child(main)
|
|
stage = Node.void('stage')
|
|
main.add_child(stage)
|
|
stage.set_attribute('number', str(digdigdict.get_int('stage_number', 1)))
|
|
stage.add_child(Node.s32('point', digdigdict.get_int('point')))
|
|
stage.add_child(Node.s32_array('param', digdigdict.get_int_array('param', 12, [0] * 12)))
|
|
|
|
# Emerald eternal stages
|
|
eternal = Node.void('eternal')
|
|
digdig.add_child(eternal)
|
|
eternal.add_child(Node.s32('ratio', 1))
|
|
eternal.add_child(Node.s64('used_point', eternaldict.get_int('used_point')))
|
|
eternal.add_child(Node.s64('point', eternaldict.get_int('point')))
|
|
eternal.add_child(Node.s64('excavated_point', eternaldict.get_int('excavated_point')))
|
|
cube = Node.void('cube')
|
|
eternal.add_child(cube)
|
|
cube.add_child(Node.s8_array('state', eternaldict.get_int_array('state', 12, [0] * 12)))
|
|
item = Node.void('item')
|
|
cube.add_child(item)
|
|
item.add_child(Node.s32_array('kind', eternaldict.get_int_array('item_kind', 12, [0] * 12)))
|
|
item.add_child(Node.s32_array('value', eternaldict.get_int_array('item_value', 12, [0] * 12)))
|
|
norma = Node.void('norma')
|
|
cube.add_child(norma)
|
|
norma.add_child(Node.s64_array('till_time', [0] * 12))
|
|
norma.add_child(Node.s32_array('kind', eternaldict.get_int_array('norma_kind', 12, [0] * 12)))
|
|
norma.add_child(Node.s32_array('value', eternaldict.get_int_array('norma_value', 12, [0] * 12)))
|
|
norma.add_child(Node.s32_array('param', eternaldict.get_int_array('norma_param', 12, [0] * 12)))
|
|
|
|
if self.ENABLE_GARNET:
|
|
# Garnet
|
|
old = Node.void('old')
|
|
digdig.add_child(old)
|
|
old.add_child(Node.s32('need_point', olddict.get_int('need_point')))
|
|
old.add_child(Node.s32('point', olddict.get_int('point')))
|
|
old.add_child(Node.s32_array('excavated_point', olddict.get_int_array('excavated_point', 5, [0] * 5)))
|
|
old.add_child(Node.s32_array('excavated', olddict.get_int_array('excavated', 5, [0] * 5)))
|
|
old.add_child(Node.s32_array('param', olddict.get_int_array('param', 5, [0] * 5)))
|
|
# This should have a bunch of sub-nodes with the following format. Note that only
|
|
# the first ten nodes are saved even if more are read. Presumably this is the list
|
|
# of old songs we are allowing the player to unlock? Doesn't matter, we're disabling
|
|
# Garnet anyway.:
|
|
# <music>
|
|
# <music_id __type="s32">id</music_id>
|
|
# </music>
|
|
old.add_child(Node.void('music_list'))
|
|
|
|
# Unlock event, turns on unlock challenge for a particular stage.
|
|
unlock = Node.void('unlock')
|
|
player.add_child(unlock)
|
|
main = Node.void('main')
|
|
unlock.add_child(main)
|
|
stage_list = Node.void('stage_list')
|
|
main.add_child(stage_list)
|
|
# Stage numbers are between 1 and 13 inclusive.
|
|
for i in range(1, 14):
|
|
stage_flags = self.data.local.user.get_achievement(self.game, self.version, userid, i, 'stage')
|
|
if stage_flags is None:
|
|
stage_flags = ValidatedDict()
|
|
|
|
stage = Node.void('stage')
|
|
stage_list.add_child(stage)
|
|
stage.set_attribute('number', str(i))
|
|
stage.add_child(Node.u8('state', stage_flags.get_int('state')))
|
|
|
|
# DigDig event for server-controlled cubes (basically anything not Garnet or Emerald)
|
|
generic_dig = Node.void('generic_dig')
|
|
player.add_child(generic_dig)
|
|
map_list = Node.void('map_list')
|
|
generic_dig.add_child(map_list)
|
|
# Map list consists of up to 9 of the following structures:
|
|
# <map id="id of map as defined in info node above">
|
|
# <point __type="s32">points</point>
|
|
# <used_point __type="s32">points</used_point>
|
|
# <stage_num __type="s32">stage</stage_num>
|
|
# <stage_list>
|
|
# <stage number="number matching a stage number from info node above:"">
|
|
# <norma>
|
|
# <param __type="s32">0 0 0 0 0 0 0 0 0 0 0 0</param>
|
|
# </norma>
|
|
# <unlock>
|
|
# <state __type="u8">0</state>
|
|
# </unlock>
|
|
# </stage>
|
|
# </stage_list>
|
|
# </map>
|
|
|
|
# New Music stuff
|
|
new_music = Node.void('new_music')
|
|
player.add_child(new_music)
|
|
|
|
# Gift list, maybe from other players?
|
|
gift_list = Node.void('gift_list')
|
|
player.add_child(gift_list)
|
|
# If we had gifts, they look like this:
|
|
# <gift reason="??" kind="??">
|
|
# <id __type="s32">??</id>
|
|
# </gift>
|
|
|
|
# Birthday event?
|
|
born = Node.void('born')
|
|
player.add_child(born)
|
|
born.add_child(Node.s8('status', profile.get_int('born_status')))
|
|
born.add_child(Node.s16('year', profile.get_int('born_year')))
|
|
|
|
# More crap
|
|
question_list = Node.void('question_list')
|
|
player.add_child(question_list)
|
|
|
|
return root
|
|
|
|
def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node:
|
|
|
|
root = Node.void('gametop')
|
|
datanode = Node.void('data')
|
|
root.add_child(datanode)
|
|
player = Node.void('player')
|
|
datanode.add_child(player)
|
|
player.add_child(Node.s32('jid', profile.extid))
|
|
playdata = Node.void('mdata_list')
|
|
player.add_child(playdata)
|
|
|
|
music = ValidatedDict()
|
|
for score in scores:
|
|
data = music.get_dict(str(score.id))
|
|
play_cnt = data.get_int_array('play_cnt', 3)
|
|
clear_cnt = data.get_int_array('clear_cnt', 3)
|
|
clear_flags = data.get_int_array('clear_flags', 3)
|
|
fc_cnt = data.get_int_array('fc_cnt', 3)
|
|
ex_cnt = data.get_int_array('ex_cnt', 3)
|
|
points = data.get_int_array('points', 3)
|
|
|
|
# Replace data for this chart type
|
|
play_cnt[score.chart] = score.plays
|
|
clear_cnt[score.chart] = score.data.get_int('clear_count')
|
|
fc_cnt[score.chart] = score.data.get_int('full_combo_count')
|
|
ex_cnt[score.chart] = score.data.get_int('excellent_count')
|
|
points[score.chart] = score.points
|
|
|
|
# Format the clear flags
|
|
clear_flags[score.chart] = self.GAME_FLAG_BIT_PLAYED
|
|
if score.data.get_int('clear_count') > 0:
|
|
clear_flags[score.chart] |= self.GAME_FLAG_BIT_CLEARED
|
|
if score.data.get_int('full_combo_count') > 0:
|
|
clear_flags[score.chart] |= self.GAME_FLAG_BIT_FULL_COMBO
|
|
if score.data.get_int('excellent_count') > 0:
|
|
clear_flags[score.chart] |= self.GAME_FLAG_BIT_EXCELLENT
|
|
|
|
# Save chart data back
|
|
data.replace_int_array('play_cnt', 3, play_cnt)
|
|
data.replace_int_array('clear_cnt', 3, clear_cnt)
|
|
data.replace_int_array('clear_flags', 3, clear_flags)
|
|
data.replace_int_array('fc_cnt', 3, fc_cnt)
|
|
data.replace_int_array('ex_cnt', 3, ex_cnt)
|
|
data.replace_int_array('points', 3, points)
|
|
|
|
# Update the ghost (untyped)
|
|
ghost = data.get('ghost', [None, None, None])
|
|
ghost[score.chart] = score.data.get('ghost')
|
|
data['ghost'] = ghost
|
|
|
|
# Save it back
|
|
music.replace_dict(str(score.id), data)
|
|
|
|
for scoreid in music:
|
|
scoredata = music[scoreid]
|
|
musicdata = Node.void('musicdata')
|
|
playdata.add_child(musicdata)
|
|
|
|
musicdata.set_attribute('music_id', scoreid)
|
|
musicdata.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('play_cnt', 3)))
|
|
musicdata.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('clear_cnt', 3)))
|
|
musicdata.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('fc_cnt', 3)))
|
|
musicdata.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('ex_cnt', 3)))
|
|
musicdata.add_child(Node.s32_array('score', scoredata.get_int_array('points', 3)))
|
|
musicdata.add_child(Node.s8_array('clear', scoredata.get_int_array('clear_flags', 3)))
|
|
|
|
ghosts = scoredata.get('ghost', [None, None, None])
|
|
for i in range(len(ghosts)):
|
|
ghost = ghosts[i]
|
|
if ghost is None:
|
|
continue
|
|
|
|
bar = Node.u8_array('bar', ghost)
|
|
musicdata.add_child(bar)
|
|
bar.set_attribute('seq', str(i))
|
|
|
|
return root
|
|
|
|
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
|
|
newprofile = oldprofile.clone()
|
|
newprofile.replace_bool('saved', True)
|
|
data = request.child('data')
|
|
|
|
# Grab system information
|
|
sysinfo = data.child('info')
|
|
|
|
# Grab player information
|
|
player = data.child('player')
|
|
|
|
# Grab result information
|
|
result = data.child('result')
|
|
|
|
# Grab last information. Lots of this will be filled in while grabbing scores
|
|
last = newprofile.get_dict('last')
|
|
if sysinfo is not None:
|
|
last.replace_int('play_time', sysinfo.child_value('time_gameend'))
|
|
last.replace_str('shopname', sysinfo.child_value('shopname'))
|
|
last.replace_str('areaname', sysinfo.child_value('areaname'))
|
|
|
|
# Grab player info for echoing back
|
|
info = player.child('info')
|
|
if info is not None:
|
|
newprofile.replace_int('jubility', info.child_value('jubility'))
|
|
newprofile.replace_int('jubility_yday', info.child_value('jubility_yday'))
|
|
newprofile.replace_int('tune_cnt', info.child_value('tune_cnt'))
|
|
newprofile.replace_int('save_cnt', info.child_value('save_cnt'))
|
|
newprofile.replace_int('saved_cnt', info.child_value('saved_cnt'))
|
|
newprofile.replace_int('fc_cnt', info.child_value('fc_cnt'))
|
|
newprofile.replace_int('ex_cnt', info.child_value('ex_cnt'))
|
|
newprofile.replace_int('clear_cnt', info.child_value('clear_cnt'))
|
|
newprofile.replace_int('match_cnt', info.child_value('match_cnt'))
|
|
newprofile.replace_int('beat_cnt', info.child_value('beat_cnt'))
|
|
newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt'))
|
|
|
|
newprofile.replace_int('bonus_tune_points', info.child_value('bonus_tune_points'))
|
|
newprofile.replace_bool('is_bonus_tune_played', info.child_value('is_bonus_tune_played'))
|
|
|
|
# Grab last settings
|
|
lastnode = player.child('last')
|
|
if lastnode is not None:
|
|
last.replace_int('expert_option', lastnode.child_value('expert_option'))
|
|
last.replace_int('dig_select', lastnode.child_value('dig_select'))
|
|
last.replace_int('sort', lastnode.child_value('sort'))
|
|
last.replace_int('category', lastnode.child_value('category'))
|
|
|
|
settings = lastnode.child('settings')
|
|
if settings is not None:
|
|
last.replace_int('matching', settings.child_value('matching'))
|
|
last.replace_int('hazard', settings.child_value('hazard'))
|
|
last.replace_int('hard', settings.child_value('hard'))
|
|
last.replace_int('marker', settings.child_value('marker'))
|
|
last.replace_int('theme', settings.child_value('theme'))
|
|
last.replace_int('title', settings.child_value('title'))
|
|
last.replace_int('parts', settings.child_value('parts'))
|
|
last.replace_int('rank_sort', settings.child_value('rank_sort'))
|
|
last.replace_int('combo_disp', settings.child_value('combo_disp'))
|
|
last.replace_int_array('emblem', 5, settings.child_value('emblem'))
|
|
|
|
# Grab unlock progress
|
|
item = player.child('item')
|
|
if item is not None:
|
|
newprofile.replace_int_array('music_list', 64, item.child_value('music_list'))
|
|
newprofile.replace_int_array('secret_list', 64, item.child_value('secret_list'))
|
|
newprofile.replace_int_array('theme_list', 16, item.child_value('theme_list'))
|
|
newprofile.replace_int_array('marker_list', 16, item.child_value('marker_list'))
|
|
newprofile.replace_int_array('title_list', 160, item.child_value('title_list'))
|
|
newprofile.replace_int_array('parts_list', 160, item.child_value('parts_list'))
|
|
newprofile.replace_int_array('emblem_list', 96, item.child_value('emblem_list'))
|
|
|
|
newitem = item.child('new')
|
|
if newitem is not None:
|
|
newprofile.replace_int_array('secret_list_new', 64, newitem.child_value('secret_list'))
|
|
newprofile.replace_int_array('theme_list_new', 16, newitem.child_value('theme_list'))
|
|
newprofile.replace_int_array('marker_list_new', 16, newitem.child_value('marker_list'))
|
|
|
|
# jbox stuff
|
|
jbox = player.child('jbox')
|
|
jboxdict = newprofile.get_dict('jbox')
|
|
if jbox is not None:
|
|
jboxdict.replace_int('point', jbox.child_value('point'))
|
|
emblemtype = jbox.child_value('emblem/type')
|
|
index = jbox.child_value('emblem/index')
|
|
if emblemtype == self.JBOX_EMBLEM_NORMAL:
|
|
jboxdict.replace_int('normal_index', index)
|
|
elif emblemtype == self.JBOX_EMBLEM_PREMIUM:
|
|
jboxdict.replace_int('premium_index', index)
|
|
newprofile.replace_dict('jbox', jboxdict)
|
|
|
|
# event stuff
|
|
event_info = player.child('event_info')
|
|
if event_info is not None:
|
|
for child in event_info.children:
|
|
try:
|
|
eventid = int(child.attribute('type'))
|
|
except TypeError:
|
|
# Event is empty
|
|
continue
|
|
is_completed = child.child_value('is_completed')
|
|
|
|
# Figure out if we should update the rating/scores or not
|
|
oldevent = self.data.local.user.get_achievement(
|
|
self.game,
|
|
self.version,
|
|
userid,
|
|
eventid,
|
|
'event',
|
|
)
|
|
|
|
if oldevent is None:
|
|
# Create a new event structure for this
|
|
oldevent = ValidatedDict()
|
|
|
|
oldevent.replace_bool('is_completed', is_completed)
|
|
|
|
# Save it as an achievement
|
|
self.data.local.user.put_achievement(
|
|
self.game,
|
|
self.version,
|
|
userid,
|
|
eventid,
|
|
'event',
|
|
oldevent,
|
|
)
|
|
|
|
# DigDig stuff
|
|
digdig = player.child('digdig')
|
|
digdigdict = newprofile.get_dict('digdig')
|
|
if digdig is not None:
|
|
digdigdict.replace_int('flag', digdig.child_value('flag'))
|
|
|
|
main = digdig.child('main')
|
|
if main is not None:
|
|
stage = main.child('stage')
|
|
stage_num = int(stage.attribute('number'))
|
|
digdigdict.replace_int('stage_number', stage_num)
|
|
digdigdict.replace_int('point', stage.child_value('point'))
|
|
digdigdict.replace_int_array('param', 12, stage.child_value('param'))
|
|
|
|
if stage.child_value('uc_available') is True:
|
|
# We should enable unlock challenge for this node because the game
|
|
# doesn't do it for us automatically.
|
|
stage_flags = self.data.local.user.get_achievement(self.game, self.version, userid, stage_num, 'stage')
|
|
if stage_flags is None:
|
|
stage_flags = ValidatedDict()
|
|
stage_flags.replace_int('state', stage_flags.get_int('state') | 0x2)
|
|
self.data.local.user.put_achievement(
|
|
self.game,
|
|
self.version,
|
|
userid,
|
|
stage_num,
|
|
'stage',
|
|
stage_flags,
|
|
)
|
|
|
|
eternal = digdig.child('eternal')
|
|
eternaldict = digdigdict.get_dict('eternal')
|
|
if eternal is not None:
|
|
eternaldict.replace_int('used_point', eternal.child_value('used_point'))
|
|
eternaldict.replace_int('point', eternal.child_value('point'))
|
|
eternaldict.replace_int('excavated_point', eternal.child_value('excavated_point'))
|
|
eternaldict.replace_int_array('state', 12, eternal.child_value('cube/state'))
|
|
eternaldict.replace_int_array('item_kind', 12, eternal.child_value('cube/item/kind'))
|
|
eternaldict.replace_int_array('item_value', 12, eternal.child_value('cube/item/value'))
|
|
eternaldict.replace_int_array('norma_kind', 12, eternal.child_value('cube/norma/kind'))
|
|
eternaldict.replace_int_array('norma_value', 12, eternal.child_value('cube/norma/value'))
|
|
eternaldict.replace_int_array('norma_param', 12, eternal.child_value('cube/norma/param'))
|
|
digdigdict.replace_dict('eternal', eternaldict)
|
|
|
|
if self.ENABLE_GARNET:
|
|
old = digdig.child('old')
|
|
olddict = digdigdict.get_dict('old')
|
|
if old is not None:
|
|
olddict.replace_int('need_point', old.child_value('need_point'))
|
|
olddict.replace_int('point', old.child_value('point'))
|
|
olddict.replace_int_array('excavated_point', 5, old.child_value('excavated_point'))
|
|
olddict.replace_int_array('excavated', 5, old.child_value('excavated'))
|
|
olddict.replace_int_array('param', 5, old.child_value('param'))
|
|
digdigdict.replace_dict('old', olddict)
|
|
|
|
# DigDig unlock event
|
|
unlock = player.child('unlock')
|
|
if unlock is not None:
|
|
stage = unlock.child('main/stage')
|
|
if stage is not None:
|
|
stage_num = int(stage.attribute('number'))
|
|
state = stage.child_value('state')
|
|
|
|
# Just overwrite the state with this value
|
|
self.data.local.user.put_achievement(
|
|
self.game,
|
|
self.version,
|
|
userid,
|
|
stage_num,
|
|
'stage',
|
|
{'state': state},
|
|
)
|
|
|
|
# If they cleared stage 13, we need to unlock eternal mode
|
|
if stage_num == 13 and (state & 0x18) > 0:
|
|
digdigdict.replace_int('flag', digdigdict.get_int('flag') | 0x2)
|
|
|
|
# Save this back now that we've parsed everything
|
|
newprofile.replace_dict('digdig', digdigdict)
|
|
|
|
# Still don't know what this is for lol
|
|
newprofile.replace_int('navi_flag', player.child_value('navi/flag'))
|
|
|
|
# Grab scores and save those
|
|
if result is not None:
|
|
for tune in result.children:
|
|
if tune.name != 'tune':
|
|
continue
|
|
result = tune.child('player')
|
|
|
|
songid = tune.child_value('music')
|
|
timestamp = tune.child_value('timestamp') / 1000
|
|
chart = int(result.child('score').attribute('seq'))
|
|
points = result.child_value('score')
|
|
flags = int(result.child('score').attribute('clear'))
|
|
combo = int(result.child('score').attribute('combo'))
|
|
ghost = result.child_value('mbar')
|
|
|
|
stats = {
|
|
'perfect': result.child_value('nr_perfect'),
|
|
'great': result.child_value('nr_great'),
|
|
'good': result.child_value('nr_good'),
|
|
'poor': result.child_value('nr_poor'),
|
|
'miss': result.child_value('nr_miss'),
|
|
}
|
|
|
|
# Miscelaneous last data for echoing to profile get
|
|
last.replace_int('music_id', songid)
|
|
last.replace_int('seq_id', chart)
|
|
|
|
mapping = {
|
|
self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED,
|
|
self.GAME_FLAG_BIT_FULL_COMBO: self.PLAY_MEDAL_FULL_COMBO,
|
|
self.GAME_FLAG_BIT_EXCELLENT: self.PLAY_MEDAL_EXCELLENT,
|
|
self.GAME_FLAG_BIT_NEARLY_FULL_COMBO: self.PLAY_MEDAL_NEARLY_FULL_COMBO,
|
|
self.GAME_FLAG_BIT_NEARLY_EXCELLENT: self.PLAY_MEDAL_NEARLY_EXCELLENT,
|
|
}
|
|
|
|
# Figure out the highest medal based on bits passed in
|
|
medal = self.PLAY_MEDAL_FAILED
|
|
for bit in mapping:
|
|
if flags & bit > 0:
|
|
medal = max(medal, mapping[bit])
|
|
|
|
self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost, stats)
|
|
|
|
# Born stuff
|
|
born = player.child('born')
|
|
if born is not None:
|
|
newprofile.replace_int('born_status', born.child_value('status'))
|
|
newprofile.replace_int('born_year', born.child_value('year'))
|
|
|
|
# Save back last information gleaned from results
|
|
newprofile.replace_dict('last', last)
|
|
|
|
# Keep track of play statistics
|
|
self.update_play_statistics(userid)
|
|
|
|
return newprofile
|