1
0
mirror of synced 2024-12-01 09:07:18 +01:00
bemaniutils/bemani/backend/iidx/tricoro.py

1402 lines
58 KiB
Python
Raw Normal View History

# vim: set fileencoding=utf-8
import copy
import random
from typing import Optional, Dict, List, Tuple, Any
from bemani.backend.iidx.base import IIDXBase
from bemani.backend.iidx.stubs import IIDXLincle
from bemani.common import Profile, ValidatedDict, VersionConstants, Time, ID
from bemani.data import Data, UserID
from bemani.protocol import Node
class IIDXTricoro(IIDXBase):
name = 'Beatmania IIDX Tricoro'
version = VersionConstants.IIDX_TRICORO
GAME_CLTYPE_SINGLE = 0
GAME_CLTYPE_DOUBLE = 1
DAN_STAGES_SINGLE = 4
DAN_STAGES_DOUBLE = 3
GAME_CLEAR_STATUS_NO_PLAY = 0
GAME_CLEAR_STATUS_FAILED = 1
GAME_CLEAR_STATUS_ASSIST_CLEAR = 2
GAME_CLEAR_STATUS_EASY_CLEAR = 3
GAME_CLEAR_STATUS_CLEAR = 4
GAME_CLEAR_STATUS_HARD_CLEAR = 5
GAME_CLEAR_STATUS_EX_HARD_CLEAR = 6
GAME_CLEAR_STATUS_FULL_COMBO = 7
GAME_GHOST_TYPE_RIVAL = 1
GAME_GHOST_TYPE_GLOBAL_TOP = 2
GAME_GHOST_TYPE_GLOBAL_AVERAGE = 3
GAME_GHOST_TYPE_LOCAL_TOP = 4
GAME_GHOST_TYPE_LOCAL_AVERAGE = 5
GAME_GHOST_TYPE_DAN_TOP = 6
GAME_GHOST_TYPE_DAN_AVERAGE = 7
GAME_GHOST_TYPE_RIVAL_TOP = 8
GAME_GHOST_TYPE_RIVAL_AVERAGE = 9
GAME_GHOST_LENGTH = 64
GAME_SP_DAN_RANK_7_KYU = 0
GAME_SP_DAN_RANK_6_KYU = 1
GAME_SP_DAN_RANK_5_KYU = 2
GAME_SP_DAN_RANK_4_KYU = 3
GAME_SP_DAN_RANK_3_KYU = 4
GAME_SP_DAN_RANK_2_KYU = 5
GAME_SP_DAN_RANK_1_KYU = 6
GAME_SP_DAN_RANK_1_DAN = 7
GAME_SP_DAN_RANK_2_DAN = 8
GAME_SP_DAN_RANK_3_DAN = 9
GAME_SP_DAN_RANK_4_DAN = 10
GAME_SP_DAN_RANK_5_DAN = 11
GAME_SP_DAN_RANK_6_DAN = 12
GAME_SP_DAN_RANK_7_DAN = 13
GAME_SP_DAN_RANK_8_DAN = 14
GAME_SP_DAN_RANK_9_DAN = 15
GAME_SP_DAN_RANK_10_DAN = 16
GAME_SP_DAN_RANK_KAIDEN = 17
GAME_DP_DAN_RANK_5_KYU = 0
GAME_DP_DAN_RANK_4_KYU = 1
GAME_DP_DAN_RANK_3_KYU = 2
GAME_DP_DAN_RANK_2_KYU = 3
GAME_DP_DAN_RANK_1_KYU = 4
GAME_DP_DAN_RANK_1_DAN = 5
GAME_DP_DAN_RANK_2_DAN = 6
GAME_DP_DAN_RANK_3_DAN = 7
GAME_DP_DAN_RANK_4_DAN = 8
GAME_DP_DAN_RANK_5_DAN = 9
GAME_DP_DAN_RANK_6_DAN = 10
GAME_DP_DAN_RANK_7_DAN = 11
GAME_DP_DAN_RANK_8_DAN = 12
GAME_DP_DAN_RANK_9_DAN = 13
GAME_DP_DAN_RANK_10_DAN = 14
GAME_DP_DAN_RANK_KAIDEN = 15
FAVORITE_LIST_LENGTH = 20
def previous_version(self) -> Optional[IIDXBase]:
return IIDXLincle(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 dailies into the DB.
"""
events = []
if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'):
# Generate a new list of three dailies.
start_time, end_time = data.local.network.get_schedule_duration('daily')
all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)]))
daily_songs = random.sample(all_songs, 3)
data.local.game.put_time_sensitive_settings(
cls.game,
cls.version,
'dailies',
{
'start_time': start_time,
'end_time': end_time,
'music': daily_songs,
},
)
events.append((
'iidx_daily_charts',
{
'version': cls.version,
'music': daily_songs,
},
))
# Mark that we did some actual work here.
data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily')
return events
@classmethod
def get_settings(cls) -> Dict[str, Any]:
"""
Return all of our front-end modifiably settings.
"""
return {
'bools': [
{
'name': 'Global Shop Ranking',
'tip': 'Return network-wide ranking instead of shop ranking on results screen.',
'category': 'game_config',
'setting': 'global_shop_ranking',
},
{
'name': 'Events In Omnimix',
'tip': 'Allow events to be enabled at all for Omnimix.',
'category': 'game_config',
'setting': 'omnimix_events_enabled',
},
],
}
def db_to_game_status(self, db_status: int) -> int:
return {
self.CLEAR_STATUS_NO_PLAY: self.GAME_CLEAR_STATUS_NO_PLAY,
self.CLEAR_STATUS_FAILED: self.GAME_CLEAR_STATUS_FAILED,
self.CLEAR_STATUS_ASSIST_CLEAR: self.GAME_CLEAR_STATUS_ASSIST_CLEAR,
self.CLEAR_STATUS_EASY_CLEAR: self.GAME_CLEAR_STATUS_EASY_CLEAR,
self.CLEAR_STATUS_CLEAR: self.GAME_CLEAR_STATUS_CLEAR,
self.CLEAR_STATUS_HARD_CLEAR: self.GAME_CLEAR_STATUS_HARD_CLEAR,
self.CLEAR_STATUS_EX_HARD_CLEAR: self.GAME_CLEAR_STATUS_EX_HARD_CLEAR,
self.CLEAR_STATUS_FULL_COMBO: self.GAME_CLEAR_STATUS_FULL_COMBO,
}[db_status]
def game_to_db_status(self, game_status: int) -> int:
return {
self.GAME_CLEAR_STATUS_NO_PLAY: self.CLEAR_STATUS_NO_PLAY,
self.GAME_CLEAR_STATUS_FAILED: self.CLEAR_STATUS_FAILED,
self.GAME_CLEAR_STATUS_ASSIST_CLEAR: self.CLEAR_STATUS_ASSIST_CLEAR,
self.GAME_CLEAR_STATUS_EASY_CLEAR: self.CLEAR_STATUS_EASY_CLEAR,
self.GAME_CLEAR_STATUS_CLEAR: self.CLEAR_STATUS_CLEAR,
self.GAME_CLEAR_STATUS_HARD_CLEAR: self.CLEAR_STATUS_HARD_CLEAR,
self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: self.CLEAR_STATUS_EX_HARD_CLEAR,
self.GAME_CLEAR_STATUS_FULL_COMBO: self.CLEAR_STATUS_FULL_COMBO,
}[game_status]
def db_to_game_rank(self, db_dan: int, cltype: int) -> int:
# Special case for no DAN rank
if db_dan == -1:
return -1
if cltype == self.GAME_CLTYPE_SINGLE:
return {
self.DAN_RANK_7_KYU: self.GAME_SP_DAN_RANK_7_KYU,
self.DAN_RANK_6_KYU: self.GAME_SP_DAN_RANK_6_KYU,
self.DAN_RANK_5_KYU: self.GAME_SP_DAN_RANK_5_KYU,
self.DAN_RANK_4_KYU: self.GAME_SP_DAN_RANK_4_KYU,
self.DAN_RANK_3_KYU: self.GAME_SP_DAN_RANK_3_KYU,
self.DAN_RANK_2_KYU: self.GAME_SP_DAN_RANK_2_KYU,
self.DAN_RANK_1_KYU: self.GAME_SP_DAN_RANK_1_KYU,
self.DAN_RANK_1_DAN: self.GAME_SP_DAN_RANK_1_DAN,
self.DAN_RANK_2_DAN: self.GAME_SP_DAN_RANK_2_DAN,
self.DAN_RANK_3_DAN: self.GAME_SP_DAN_RANK_3_DAN,
self.DAN_RANK_4_DAN: self.GAME_SP_DAN_RANK_4_DAN,
self.DAN_RANK_5_DAN: self.GAME_SP_DAN_RANK_5_DAN,
self.DAN_RANK_6_DAN: self.GAME_SP_DAN_RANK_6_DAN,
self.DAN_RANK_7_DAN: self.GAME_SP_DAN_RANK_7_DAN,
self.DAN_RANK_8_DAN: self.GAME_SP_DAN_RANK_8_DAN,
self.DAN_RANK_9_DAN: self.GAME_SP_DAN_RANK_9_DAN,
self.DAN_RANK_10_DAN: self.GAME_SP_DAN_RANK_10_DAN,
self.DAN_RANK_KAIDEN: self.GAME_SP_DAN_RANK_KAIDEN,
}[db_dan]
elif cltype == self.GAME_CLTYPE_DOUBLE:
return {
self.DAN_RANK_5_KYU: self.GAME_DP_DAN_RANK_5_KYU,
self.DAN_RANK_4_KYU: self.GAME_DP_DAN_RANK_4_KYU,
self.DAN_RANK_3_KYU: self.GAME_DP_DAN_RANK_3_KYU,
self.DAN_RANK_2_KYU: self.GAME_DP_DAN_RANK_2_KYU,
self.DAN_RANK_1_KYU: self.GAME_DP_DAN_RANK_1_KYU,
self.DAN_RANK_1_DAN: self.GAME_DP_DAN_RANK_1_DAN,
self.DAN_RANK_2_DAN: self.GAME_DP_DAN_RANK_2_DAN,
self.DAN_RANK_3_DAN: self.GAME_DP_DAN_RANK_3_DAN,
self.DAN_RANK_4_DAN: self.GAME_DP_DAN_RANK_4_DAN,
self.DAN_RANK_5_DAN: self.GAME_DP_DAN_RANK_5_DAN,
self.DAN_RANK_6_DAN: self.GAME_DP_DAN_RANK_6_DAN,
self.DAN_RANK_7_DAN: self.GAME_DP_DAN_RANK_7_DAN,
self.DAN_RANK_8_DAN: self.GAME_DP_DAN_RANK_8_DAN,
self.DAN_RANK_9_DAN: self.GAME_DP_DAN_RANK_9_DAN,
self.DAN_RANK_10_DAN: self.GAME_DP_DAN_RANK_10_DAN,
self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN,
}[db_dan]
else:
raise Exception('Invalid cltype!')
def game_to_db_rank(self, game_dan: int, cltype: int) -> int:
# Special case for no DAN rank
if game_dan == -1:
return -1
if cltype == self.GAME_CLTYPE_SINGLE:
return {
self.GAME_SP_DAN_RANK_7_KYU: self.DAN_RANK_7_KYU,
self.GAME_SP_DAN_RANK_6_KYU: self.DAN_RANK_6_KYU,
self.GAME_SP_DAN_RANK_5_KYU: self.DAN_RANK_5_KYU,
self.GAME_SP_DAN_RANK_4_KYU: self.DAN_RANK_4_KYU,
self.GAME_SP_DAN_RANK_3_KYU: self.DAN_RANK_3_KYU,
self.GAME_SP_DAN_RANK_2_KYU: self.DAN_RANK_2_KYU,
self.GAME_SP_DAN_RANK_1_KYU: self.DAN_RANK_1_KYU,
self.GAME_SP_DAN_RANK_1_DAN: self.DAN_RANK_1_DAN,
self.GAME_SP_DAN_RANK_2_DAN: self.DAN_RANK_2_DAN,
self.GAME_SP_DAN_RANK_3_DAN: self.DAN_RANK_3_DAN,
self.GAME_SP_DAN_RANK_4_DAN: self.DAN_RANK_4_DAN,
self.GAME_SP_DAN_RANK_5_DAN: self.DAN_RANK_5_DAN,
self.GAME_SP_DAN_RANK_6_DAN: self.DAN_RANK_6_DAN,
self.GAME_SP_DAN_RANK_7_DAN: self.DAN_RANK_7_DAN,
self.GAME_SP_DAN_RANK_8_DAN: self.DAN_RANK_8_DAN,
self.GAME_SP_DAN_RANK_9_DAN: self.DAN_RANK_9_DAN,
self.GAME_SP_DAN_RANK_10_DAN: self.DAN_RANK_10_DAN,
self.GAME_SP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN,
}[game_dan]
elif cltype == self.GAME_CLTYPE_DOUBLE:
return {
self.GAME_DP_DAN_RANK_5_KYU: self.DAN_RANK_5_KYU,
self.GAME_DP_DAN_RANK_4_KYU: self.DAN_RANK_4_KYU,
self.GAME_DP_DAN_RANK_3_KYU: self.DAN_RANK_3_KYU,
self.GAME_DP_DAN_RANK_2_KYU: self.DAN_RANK_2_KYU,
self.GAME_DP_DAN_RANK_1_KYU: self.DAN_RANK_1_KYU,
self.GAME_DP_DAN_RANK_1_DAN: self.DAN_RANK_1_DAN,
self.GAME_DP_DAN_RANK_2_DAN: self.DAN_RANK_2_DAN,
self.GAME_DP_DAN_RANK_3_DAN: self.DAN_RANK_3_DAN,
self.GAME_DP_DAN_RANK_4_DAN: self.DAN_RANK_4_DAN,
self.GAME_DP_DAN_RANK_5_DAN: self.DAN_RANK_5_DAN,
self.GAME_DP_DAN_RANK_6_DAN: self.DAN_RANK_6_DAN,
self.GAME_DP_DAN_RANK_7_DAN: self.DAN_RANK_7_DAN,
self.GAME_DP_DAN_RANK_8_DAN: self.DAN_RANK_8_DAN,
self.GAME_DP_DAN_RANK_9_DAN: self.DAN_RANK_9_DAN,
self.GAME_DP_DAN_RANK_10_DAN: self.DAN_RANK_10_DAN,
self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN,
}[game_dan]
else:
raise Exception('Invalid cltype!')
def handle_shop_request(self, request: Node) -> Optional[Node]:
method = request.attribute('method')
if method == 'getname':
root = Node.void('shop')
root.set_attribute('cls_opt', '0')
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
root.set_attribute('opname', machine.name)
root.set_attribute('pid', '51')
return root
if method == 'savename':
self.update_machine_name(request.attribute('opname'))
root = Node.void('shop')
return root
if method == 'sentinfo':
root = Node.void('shop')
return root
if method == 'getconvention':
root = Node.void('shop')
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
if machine.arcade is not None:
course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course')
else:
course = None
if course is None:
course = ValidatedDict()
root.set_attribute('music_0', str(course.get_int('music_0', 20032)))
root.set_attribute('music_1', str(course.get_int('music_1', 20009)))
root.set_attribute('music_2', str(course.get_int('music_2', 20015)))
root.set_attribute('music_3', str(course.get_int('music_3', 20064)))
root.add_child(Node.bool('valid', course.get_bool('valid')))
return root
if method == 'setconvention':
root = Node.void('shop')
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
if machine.arcade is not None:
course = ValidatedDict()
course.replace_int('music_0', request.child_value('music_0'))
course.replace_int('music_1', request.child_value('music_1'))
course.replace_int('music_2', request.child_value('music_2'))
course.replace_int('music_3', request.child_value('music_3'))
course.replace_bool('valid', request.child_value('valid'))
self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course)
return root
# Invalid method
return None
def handle_ranking_request(self, request: Node) -> Optional[Node]:
method = request.attribute('method')
if method == 'getranker':
root = Node.void('ranking')
chart = int(request.attribute('clid'))
if chart not in [
self.CHART_TYPE_N7,
self.CHART_TYPE_H7,
self.CHART_TYPE_A7,
self.CHART_TYPE_N14,
self.CHART_TYPE_H14,
self.CHART_TYPE_A14,
]:
# Chart type 6 is presumably beginner mode, but it crashes the game
return root
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
if machine.arcade is not None:
course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course')
else:
course = None
if course is None:
course = ValidatedDict()
if not course.get_bool('valid'):
# Shop course not enabled or not present
return root
convention = Node.void('convention')
root.add_child(convention)
convention.set_attribute('clid', str(chart))
convention.set_attribute('update_date', str(Time.now() * 1000))
# Grab all scores for each of the four songs, filter out people who haven't
# set us as their arcade and then return the top 20 scores (adding all 4 songs).
songids = [
course.get_int('music_0'),
course.get_int('music_1'),
course.get_int('music_2'),
course.get_int('music_3'),
]
totalscores: Dict[UserID, int] = {}
profiles: Dict[UserID, Profile] = {}
for songid in songids:
scores = self.data.local.music.get_all_scores(
self.game,
self.music_version,
songid=songid,
songchart=chart,
)
for score in scores:
if score[0] not in totalscores:
totalscores[score[0]] = 0
profile = self.get_any_profile(score[0])
if profile is None:
profile = Profile(self.game, self.version, "", 0)
profiles[score[0]] = profile
totalscores[score[0]] += score[1].points
topscores = sorted(
[
(totalscores[userid], profiles[userid])
for userid in totalscores
if self.user_joined_arcade(machine, profiles[userid])
],
key=lambda tup: tup[0],
reverse=True,
)[:20]
rank = 0
for topscore in topscores:
rank = rank + 1
detail = Node.void('detail')
convention.add_child(detail)
detail.set_attribute('name', topscore[1].get_str('name'))
detail.set_attribute('rank', str(rank))
detail.set_attribute('score', str(topscore[0]))
detail.set_attribute('pid', str(topscore[1].get_int('pid')))
qpro = topscore[1].get_dict('qpro')
detail.set_attribute('head', str(qpro.get_int('head')))
detail.set_attribute('hair', str(qpro.get_int('hair')))
detail.set_attribute('face', str(qpro.get_int('face')))
detail.set_attribute('body', str(qpro.get_int('body')))
detail.set_attribute('hand', str(qpro.get_int('hand')))
return root
# Invalid method
return None
def handle_music_request(self, request: Node) -> Optional[Node]:
method = request.attribute('method')
if method == 'crate':
root = Node.void('music')
attempts = self.get_clear_rates()
all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)]))
for song in all_songs:
clears = []
fcs = []
for chart in [0, 1, 2, 3, 4, 5]:
placed = False
if song in attempts and chart in attempts[song]:
values = attempts[song][chart]
if values['total'] > 0:
clears.append(int((100 * values['clears']) / values['total']))
fcs.append(int((100 * values['fcs']) / values['total']))
placed = True
if not placed:
clears.append(101)
fcs.append(101)
clearnode = Node.u8_array('c', clears + fcs)
clearnode.set_attribute('mid', str(song))
root.add_child(clearnode)
return root
if method == 'getrank':
cltype = int(request.attribute('cltype'))
root = Node.void('music')
style = Node.void('style')
root.add_child(style)
style.set_attribute('type', str(cltype))
for rivalid in [-1, 0, 1, 2, 3, 4]:
if rivalid == -1:
attr = 'iidxid'
else:
attr = f'iidxid{rivalid}'
try:
extid = int(request.attribute(attr))
except Exception:
# Invalid extid
continue
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
# Grab score data for user/rival
scoredata = self.make_score_struct(
scores,
self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE,
rivalid,
)
for s in scoredata:
root.add_child(Node.s16_array('m', s))
# Grab most played for user/rival
most_played = [
play[0] for play in
self.data.local.music.get_most_played(self.game, self.music_version, userid, 20)
]
if len(most_played) < 20:
most_played.extend([0] * (20 - len(most_played)))
best = Node.u16_array('best', most_played)
best.set_attribute('rno', str(rivalid))
root.add_child(best)
if rivalid == -1:
# Grab beginner statuses for user only
beginnerdata = self.make_beginner_struct(scores)
for b in beginnerdata:
root.add_child(Node.u16_array('b', b))
return root
if method == 'reg':
extid = int(request.attribute('iidxid'))
musicid = int(request.attribute('mid'))
chart = int(request.attribute('clid'))
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
# See if we need to report global or shop scores
if self.machine_joined_arcade():
game_config = self.get_game_config()
global_scores = game_config.get_bool('global_shop_ranking')
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
else:
# If we aren't in an arcade, we can only show global scores
global_scores = True
machine = None
# First, determine our current ranking before saving the new score
all_scores = sorted(
self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart),
key=lambda s: (s[1].points, s[1].timestamp),
reverse=True,
)
all_players = {
uid: prof for (uid, prof) in
self.get_any_profiles([s[0] for s in all_scores])
}
if not global_scores:
all_scores = [
score for score in all_scores
if (
score[0] == userid or
self.user_joined_arcade(machine, all_players[score[0]])
)
]
# Find our actual index
oldindex = None
for i in range(len(all_scores)):
if all_scores[i][0] == userid:
oldindex = i
break
if userid is not None:
clear_status = self.game_to_db_status(int(request.attribute('cflg')))
pgreats = int(request.attribute('pgnum'))
greats = int(request.attribute('gnum'))
miss_count = int(request.attribute('mnum'))
ghost = request.child_value('ghost')
shopid = ID.parse_machine_id(request.attribute('shopconvid'))
self.update_score(
userid,
musicid,
chart,
clear_status,
pgreats,
greats,
miss_count,
ghost,
shopid,
)
# Calculate and return statistics about this song
root = Node.void('music')
root.set_attribute('clid', request.attribute('clid'))
root.set_attribute('mid', request.attribute('mid'))
attempts = self.get_clear_rates(musicid, chart)
count = attempts[musicid][chart]['total']
clear = attempts[musicid][chart]['clears']
full_combo = attempts[musicid][chart]['fcs']
if count > 0:
root.set_attribute('crate', str(int((100 * clear) / count)))
root.set_attribute('frate', str(int((100 * full_combo) / count)))
else:
root.set_attribute('crate', '0')
root.set_attribute('frate', '0')
if userid is not None:
# Shop ranking
shopdata = Node.void('shopdata')
root.add_child(shopdata)
shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1))
# Grab the rank of some other players on this song
ranklist = Node.void('ranklist')
root.add_child(ranklist)
all_scores = sorted(
self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart),
key=lambda s: (s[1].points, s[1].timestamp),
reverse=True,
)
missing_players = [
uid for (uid, _) in all_scores
if uid not in all_players
]
for (uid, prof) in self.get_any_profiles(missing_players):
all_players[uid] = prof
if not global_scores:
all_scores = [
score for score in all_scores
if (
score[0] == userid or
self.user_joined_arcade(machine, all_players[score[0]])
)
]
# Find our actual index
ourindex = None
for i in range(len(all_scores)):
if all_scores[i][0] == userid:
ourindex = i
break
if ourindex is None:
raise Exception('Cannot find our own score after saving to DB!')
start = ourindex - 4
end = ourindex + 4
if start < 0:
start = 0
if end >= len(all_scores):
end = len(all_scores) - 1
relevant_scores = all_scores[start:(end + 1)]
record_num = start + 1
for score in relevant_scores:
profile = all_players[score[0]]
data = Node.void('data')
ranklist.add_child(data)
data.set_attribute('iidx_id', str(profile.extid))
data.set_attribute('name', profile.get_str('name'))
machine_name = ''
if 'shop_location' in profile:
shop_id = profile.get_int('shop_location')
machine = self.get_machine_by_id(shop_id)
if machine is not None:
machine_name = machine.name
data.set_attribute('opname', machine_name)
data.set_attribute('rnum', str(record_num))
data.set_attribute('score', str(score[1].points))
data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status'))))
data.set_attribute('pid', str(profile.get_int('pid')))
data.set_attribute('myFlg', '1' if score[0] == userid else '0')
data.set_attribute('sgrade', str(
self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE),
))
data.set_attribute('dgrade', str(
self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE),
))
qpro = profile.get_dict('qpro')
data.set_attribute('head', str(qpro.get_int('head')))
data.set_attribute('hair', str(qpro.get_int('hair')))
data.set_attribute('face', str(qpro.get_int('face')))
data.set_attribute('body', str(qpro.get_int('body')))
data.set_attribute('hand', str(qpro.get_int('hand')))
record_num = record_num + 1
return root
if method == 'breg':
extid = int(request.attribute('iidxid'))
musicid = int(request.attribute('mid'))
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
clear_status = self.game_to_db_status(int(request.attribute('cflg')))
pgreats = int(request.attribute('pgnum'))
greats = int(request.attribute('gnum'))
self.update_score(
userid,
musicid,
self.CHART_TYPE_B7,
clear_status,
pgreats,
greats,
-1,
b'',
None,
)
# Return nothing.
root = Node.void('music')
return root
if method == 'play':
musicid = int(request.attribute('mid'))
chart = int(request.attribute('clid'))
clear_status = self.game_to_db_status(int(request.attribute('cflg')))
self.update_score(
None, # No userid since its anonymous
musicid,
chart,
clear_status,
0, # No ex score
0, # No ex score
0, # No miss count
None, # No ghost
None, # No shop for this user
)
# Calculate and return statistics about this song
root = Node.void('music')
root.set_attribute('clid', request.attribute('clid'))
root.set_attribute('mid', request.attribute('mid'))
attempts = self.get_clear_rates(musicid, chart)
count = attempts[musicid][chart]['total']
clear = attempts[musicid][chart]['clears']
full_combo = attempts[musicid][chart]['fcs']
if count > 0:
root.set_attribute('crate', str(int((100 * clear) / count)))
root.set_attribute('frate', str(int((100 * full_combo) / count)))
else:
root.set_attribute('crate', '0')
root.set_attribute('frate', '0')
return root
if method == 'appoint':
musicid = int(request.attribute('mid'))
chart = int(request.attribute('clid'))
ghost_type = int(request.attribute('ctype'))
extid = int(request.attribute('iidxid'))
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
root = Node.void('music')
if userid is not None:
# Try to look up previous ghost for user
my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart)
if my_score is not None:
mydata = Node.binary('mydata', my_score.data.get_bytes('ghost'))
mydata.set_attribute('score', str(my_score.points))
root.add_child(mydata)
ghost_score = self.get_ghost(
{
self.GAME_GHOST_TYPE_RIVAL: self.GHOST_TYPE_RIVAL,
self.GAME_GHOST_TYPE_GLOBAL_TOP: self.GHOST_TYPE_GLOBAL_TOP,
self.GAME_GHOST_TYPE_GLOBAL_AVERAGE: self.GHOST_TYPE_GLOBAL_AVERAGE,
self.GAME_GHOST_TYPE_LOCAL_TOP: self.GHOST_TYPE_LOCAL_TOP,
self.GAME_GHOST_TYPE_LOCAL_AVERAGE: self.GHOST_TYPE_LOCAL_AVERAGE,
self.GAME_GHOST_TYPE_DAN_TOP: self.GHOST_TYPE_DAN_TOP,
self.GAME_GHOST_TYPE_DAN_AVERAGE: self.GHOST_TYPE_DAN_AVERAGE,
self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP,
self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE,
}.get(ghost_type, self.GHOST_TYPE_NONE),
request.attribute('subtype'),
self.GAME_GHOST_LENGTH,
musicid,
chart,
userid,
)
# Add ghost score if we support it
if ghost_score is not None:
sdata = Node.binary('sdata', ghost_score['ghost'])
sdata.set_attribute('score', str(ghost_score['score']))
if 'name' in ghost_score:
sdata.set_attribute('name', ghost_score['name'])
if 'pid' in ghost_score:
sdata.set_attribute('pid', str(ghost_score['pid']))
if 'extid' in ghost_score:
sdata.set_attribute('riidxid', str(ghost_score['extid']))
root.add_child(sdata)
return root
# Invalid method
return None
def handle_pc_request(self, request: Node) -> Optional[Node]:
method = request.attribute('method')
if method == 'common':
root = Node.void('pc')
root.set_attribute('expire', '600')
# TODO: Hook all of these up to config options I guess?
ir = Node.void('ir')
root.add_child(ir)
ir.set_attribute('beat', '2')
limit = Node.void('limit')
root.add_child(limit)
limit.set_attribute('phase', '24')
# See if we configured event overrides
if self.machine_joined_arcade():
game_config = self.get_game_config()
omni_events = game_config.get_bool('omnimix_events_enabled')
else:
# If we aren't in an arcade, we turn off events
omni_events = False
if self.omnimix and (not omni_events):
boss_phase = 0
else:
# TODO: Figure out what these map to
boss_phase = 0
boss = Node.void('boss')
root.add_child(boss)
boss.set_attribute('phase', str(boss_phase))
red = Node.void('red')
root.add_child(red)
red.set_attribute('phase', '0')
yellow = Node.void('yellow')
root.add_child(yellow)
yellow.set_attribute('phase', '0')
medal = Node.void('medal')
root.add_child(medal)
medal.set_attribute('phase', '1')
cafe = Node.void('cafe')
root.add_child(cafe)
cafe.set_attribute('open', '1')
tricolettepark = Node.void('tricolettepark')
root.add_child(tricolettepark)
tricolettepark.set_attribute('open', '0')
return root
if method == 'delete':
return Node.void('pc')
if method == 'playstart':
return Node.void('pc')
if method == 'playend':
return Node.void('pc')
if method == 'oldget':
refid = request.attribute('rid')
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
oldversion = self.previous_version()
profile = oldversion.get_profile(userid)
else:
profile = None
root = Node.void('pc')
root.set_attribute('status', '1' if profile is None else '0')
return root
if method == 'getname':
refid = request.attribute('rid')
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
oldversion = self.previous_version()
profile = oldversion.get_profile(userid)
else:
profile = None
if profile is None:
raise Exception(
'Should not get here if we have no profile, we should ' +
'have returned \'1\' in the \'oldget\' method above ' +
'which should tell the game not to present a migration.'
)
root = Node.void('pc')
root.set_attribute('name', profile.get_str('name'))
root.set_attribute('idstr', ID.format_extid(profile.extid))
root.set_attribute('pid', str(profile.get_int('pid')))
return root
if method == 'reg':
refid = request.attribute('rid')
name = request.attribute('name')
pid = int(request.attribute('pid'))
profile = self.new_profile_by_refid(refid, name, pid)
root = Node.void('pc')
if profile is not None:
root.set_attribute('id', str(profile.extid))
root.set_attribute('id_str', ID.format_extid(profile.extid))
return root
if method == 'get':
refid = request.attribute('rid')
root = self.get_profile_by_refid(refid)
if root is None:
root = Node.void('pc')
return root
if method == 'save':
extid = int(request.attribute('iidxid'))
self.put_profile_by_extid(extid, request)
root = Node.void('pc')
return root
if method == 'visit':
root = Node.void('pc')
root.set_attribute('anum', '0')
root.set_attribute('snum', '0')
root.set_attribute('pnum', '0')
root.set_attribute('aflg', '0')
root.set_attribute('sflg', '0')
root.set_attribute('pflg', '0')
return root
if method == 'shopregister':
extid = int(request.child_value('iidx_id'))
location = ID.parse_machine_id(request.child_value('location_id'))
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
profile = self.get_profile(userid)
if profile is None:
profile = Profile(self.game, self.version, "", extid)
profile.replace_int('shop_location', location)
self.put_profile(userid, profile)
root = Node.void('pc')
return root
# Invalid method
return None
def handle_grade_request(self, request: Node) -> Optional[Node]:
method = request.attribute('method')
if method == 'raised':
extid = int(request.attribute('iidxid'))
cltype = int(request.attribute('gtype'))
rank = self.game_to_db_rank(int(request.attribute('gid')), cltype)
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
percent = int(request.attribute('achi'))
stages_cleared = int(request.attribute('cflg'))
if cltype == self.GAME_CLTYPE_SINGLE:
max_stages = self.DAN_STAGES_SINGLE
else:
max_stages = self.DAN_STAGES_DOUBLE
cleared = stages_cleared == max_stages
if cltype == self.GAME_CLTYPE_SINGLE:
index = self.DAN_RANKING_SINGLE
else:
index = self.DAN_RANKING_DOUBLE
self.update_rank(
userid,
index,
rank,
percent,
cleared,
stages_cleared,
)
# Figure out number of players that played this ranking
all_achievements = self.data.local.user.get_all_achievements(self.game, self.version)
num_players = 0
for [_, ach] in all_achievements:
if ach.type != index:
continue
if ach.id != rank:
continue
num_players = num_players + 1
root = Node.void('grade')
root.set_attribute('pnum', str(num_players))
return root
# Invalid method
return None
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('pc')
# Look up play stats we bridge to every mix
play_stats = self.get_play_statistics(userid)
# Look up judge window adjustments
judge_dict = profile.get_dict('machine_judge_adjust')
machine_judge = judge_dict.get_dict(self.config.machine.pcbid)
# Profile data
pcdata = Node.void('pcdata')
root.add_child(pcdata)
pcdata.set_attribute('id', str(profile.extid))
pcdata.set_attribute('idstr', ID.format_extid(profile.extid))
pcdata.set_attribute('name', profile.get_str('name'))
pcdata.set_attribute('pid', str(profile.get_int('pid')))
pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays')))
pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays')))
pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points')))
pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points')))
pcdata.set_attribute('help', str(profile.get_int('help')))
pcdata.set_attribute('gno', str(profile.get_int('gno')))
pcdata.set_attribute('gpos', str(profile.get_int('gpos')))
pcdata.set_attribute('timing', str(profile.get_int('timing')))
pcdata.set_attribute('sdhd', str(profile.get_int('sdhd')))
pcdata.set_attribute('sdtype', str(profile.get_int('sdtype')))
pcdata.set_attribute('notes', str(profile.get_float('notes')))
pcdata.set_attribute('pase', str(profile.get_int('pase')))
pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt')))
pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt')))
pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2')))
pcdata.set_attribute('mode', str(profile.get_int('mode')))
pcdata.set_attribute('pmode', str(profile.get_int('pmode')))
pcdata.set_attribute('liflen', str(profile.get_int('lift')))
pcdata.set_attribute('judge', str(profile.get_int('judge')))
pcdata.set_attribute('opstyle', str(profile.get_int('opstyle')))
pcdata.set_attribute('hispeed', str(profile.get_float('hispeed')))
pcdata.set_attribute('judgeAdj', str(machine_judge.get_int('adj')))
# Secret flags (shh!)
secret_dict = profile.get_dict('secret')
secret = Node.void('secret')
root.add_child(secret)
secret.add_child(Node.s64('flg1', secret_dict.get_int('flg1')))
secret.add_child(Node.s64('flg2', secret_dict.get_int('flg2')))
secret.add_child(Node.s64('flg3', secret_dict.get_int('flg3')))
# DAN rankings
grade = Node.void('grade')
root.add_child(grade)
grade.set_attribute('sgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE)))
grade.set_attribute('dgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE)))
rankings = self.data.local.user.get_achievements(self.game, self.version, userid)
for rank in rankings:
if rank.type == self.DAN_RANKING_SINGLE:
grade.add_child(Node.u8_array('g', [
self.GAME_CLTYPE_SINGLE,
self.db_to_game_rank(rank.id, self.GAME_CLTYPE_SINGLE),
rank.data.get_int('stages_cleared'),
rank.data.get_int('percent'),
]))
if rank.type == self.DAN_RANKING_DOUBLE:
grade.add_child(Node.u8_array('g', [
self.GAME_CLTYPE_DOUBLE,
self.db_to_game_rank(rank.id, self.GAME_CLTYPE_DOUBLE),
rank.data.get_int('stages_cleared'),
rank.data.get_int('percent'),
]))
# User settings
settings_dict = profile.get_dict('settings')
skin = Node.s16_array(
'skin',
[
settings_dict.get_int('frame'),
settings_dict.get_int('turntable'),
settings_dict.get_int('burst'),
settings_dict.get_int('bgm'),
settings_dict.get_int('flags'),
settings_dict.get_int('towel'),
settings_dict.get_int('judge_pos'),
settings_dict.get_int('voice'),
settings_dict.get_int('noteskin'),
settings_dict.get_int('full_combo'),
settings_dict.get_int('beam'),
settings_dict.get_int('judge'),
0,
settings_dict.get_int('disable_song_preview'),
],
)
root.add_child(skin)
# Qpro data
qpro_dict = profile.get_dict('qpro')
root.add_child(Node.u32_array(
'qprodata',
[
qpro_dict.get_int('head'),
qpro_dict.get_int('hair'),
qpro_dict.get_int('face'),
qpro_dict.get_int('hand'),
qpro_dict.get_int('body'),
],
))
# Rivals
rlist = Node.void('rlist')
root.add_child(rlist)
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
rival_type = None
if link.type == 'sp_rival':
rival_type = '1'
elif link.type == 'dp_rival':
rival_type = '2'
else:
# No business with this link type
continue
other_profile = self.get_profile(link.other_userid)
if other_profile is None:
continue
other_play_stats = self.get_play_statistics(link.other_userid)
rival = Node.void('rival')
rlist.add_child(rival)
rival.set_attribute('spdp', rival_type)
rival.set_attribute('id', str(other_profile.extid))
rival.set_attribute('id_str', ID.format_extid(other_profile.extid))
rival.set_attribute('djname', other_profile.get_str('name'))
rival.set_attribute('pid', str(other_profile.get_int('pid')))
rival.set_attribute('sg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE)))
rival.set_attribute('dg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE)))
rival.set_attribute('sa', str(other_play_stats.get_int('single_dj_points')))
rival.set_attribute('da', str(other_play_stats.get_int('double_dj_points')))
# If the user joined a particular shop, let the game know.
if 'shop_location' in other_profile:
shop_id = other_profile.get_int('shop_location')
machine = self.get_machine_by_id(shop_id)
if machine is not None:
shop = Node.void('shop')
rival.add_child(shop)
shop.set_attribute('name', machine.name)
qprodata = Node.void('qprodata')
rival.add_child(qprodata)
qpro = other_profile.get_dict('qpro')
qprodata.set_attribute('head', str(qpro.get_int('head')))
qprodata.set_attribute('hair', str(qpro.get_int('hair')))
qprodata.set_attribute('face', str(qpro.get_int('face')))
qprodata.set_attribute('body', str(qpro.get_int('body')))
qprodata.set_attribute('hand', str(qpro.get_int('hand')))
# If the user joined a particular shop, let the game know.
if 'shop_location' in profile:
shop_id = profile.get_int('shop_location')
machine = self.get_machine_by_id(shop_id)
if machine is not None:
join_shop = Node.void('join_shop')
root.add_child(join_shop)
join_shop.set_attribute('joinflg', '1')
join_shop.set_attribute('join_cflg', '1')
join_shop.set_attribute('join_id', ID.format_machine_id(machine.id))
join_shop.set_attribute('join_name', machine.name)
# Step up mode
step_dict = profile.get_dict('step')
step = Node.void('step')
root.add_child(step)
step.set_attribute('sp_ach', str(step_dict.get_int('sp_ach')))
step.set_attribute('dp_ach', str(step_dict.get_int('dp_ach')))
step.set_attribute('sp_hdpt', str(step_dict.get_int('sp_hdpt')))
step.set_attribute('dp_hdpt', str(step_dict.get_int('dp_hdpt')))
step.set_attribute('sp_level', str(step_dict.get_int('sp_level')))
step.set_attribute('dp_level', str(step_dict.get_int('dp_level')))
step.set_attribute('sp_round', str(step_dict.get_int('sp_round')))
step.set_attribute('dp_round', str(step_dict.get_int('dp_round')))
step.set_attribute('sp_mplay', str(step_dict.get_int('sp_mplay')))
step.set_attribute('dp_mplay', str(step_dict.get_int('dp_mplay')))
step.set_attribute('review', str(step_dict.get_int('review')))
if 'stamp' in step_dict:
step.add_child(Node.binary('stamp', step_dict.get_bytes('stamp', bytes([0] * 36))))
if 'help' in step_dict:
step.add_child(Node.binary('help', step_dict.get_bytes('help', bytes([0] * 6))))
# Daily recommendations
entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'dailies')
if entry is not None:
packinfo = Node.void('packinfo')
root.add_child(packinfo)
pack_id = int(entry['start_time'] / 86400)
packinfo.set_attribute('pack_id', str(pack_id))
packinfo.set_attribute('music_0', str(entry['music'][0]))
packinfo.set_attribute('music_1', str(entry['music'][1]))
packinfo.set_attribute('music_2', str(entry['music'][2]))
else:
# No dailies :(
pack_id = None
# Tran medals and shit
achievements = Node.void('achievements')
root.add_child(achievements)
# Dailies
if pack_id is None:
achievements.set_attribute('pack', '0')
achievements.set_attribute('pack_comp', '0')
else:
daily_played = self.data.local.user.get_achievement(self.game, self.version, userid, pack_id, 'daily')
if daily_played is None:
daily_played = ValidatedDict()
achievements.set_attribute('pack', str(daily_played.get_int('pack_flg')))
achievements.set_attribute('pack_comp', str(daily_played.get_int('pack_comp')))
# Weeklies
achievements.set_attribute('last_weekly', str(profile.get_int('last_weekly')))
achievements.set_attribute('weekly_num', str(profile.get_int('weekly_num')))
# Prefecture visit flag
achievements.set_attribute('visit_flg', str(profile.get_int('visit_flg')))
# Number of rivals beaten
achievements.set_attribute('rival_crush', str(profile.get_int('rival_crush')))
# Tran medals
achievements.add_child(Node.s64_array('trophy', profile.get_int_array('trophy', 10)))
# Link5 data
if 'link5' in profile:
# Don't provide link5 if we haven't saved it, so the game can
# initialize it properly.
link5_dict = profile.get_dict('link5')
link5 = Node.void('link5')
root.add_child(link5)
for attr in [
'qpro',
'glass',
'treasure', # not saved
'beautiful',
'quaver',
'castle',
'flip',
'titans',
'exusia',
'waxing',
'sampling',
'beachside',
'cuvelia',
'reunion',
'bad',
'turii',
'anisakis',
'second',
'whydidyou',
'china',
'fallen',
'broken',
'summer',
'sakura',
'wuv',
'survival',
'thunder',
'qproflg', # not saved
'glassflg', # not saved
'reflec_data', # not saved
]:
link5.set_attribute(attr, str(link5_dict.get_int(attr)))
# Track deller, orbs and baron
commonboss = Node.void('commonboss')
root.add_child(commonboss)
commonboss.set_attribute('deller', str(profile.get_int('deller')))
commonboss.set_attribute('orb', str(profile.get_int('orbs')))
commonboss.set_attribute('baron', str(profile.get_int('baron')))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
newprofile = copy.deepcopy(oldprofile)
play_stats = self.get_play_statistics(userid)
# Track play counts, DJ points and options
cltype = int(request.attribute('cltype'))
if cltype == self.GAME_CLTYPE_SINGLE:
play_stats.increment_int('single_plays')
play_stats.replace_int('single_dj_points', int(request.attribute('achi')))
newprofile.replace_int('sp_opt', int(request.attribute('opt')))
if cltype == self.GAME_CLTYPE_DOUBLE:
play_stats.increment_int('double_plays')
play_stats.replace_int('double_dj_points', int(request.attribute('achi')))
newprofile.replace_int('dp_opt', int(request.attribute('opt')))
newprofile.replace_int('dp_opt2', int(request.attribute('opt2')))
# Profile settings
newprofile.replace_int('gno', int(request.attribute('gno')))
newprofile.replace_int('gpos', int(request.attribute('gpos')))
newprofile.replace_int('timing', int(request.attribute('timing')))
newprofile.replace_int('help', int(request.attribute('help')))
newprofile.replace_int('sdhd', int(request.attribute('sdhd')))
newprofile.replace_int('sdtype', int(request.attribute('sdtype')))
newprofile.replace_float('notes', float(request.attribute('notes')))
newprofile.replace_int('pase', int(request.attribute('pase')))
newprofile.replace_int('judge', int(request.attribute('judge')))
newprofile.replace_int('opstyle', int(request.attribute('opstyle')))
newprofile.replace_float('hispeed', float(request.attribute('hispeed')))
newprofile.replace_int('mode', int(request.attribute('mode')))
newprofile.replace_int('pmode', int(request.attribute('pmode')))
if 'lift' in request.attributes:
newprofile.replace_int('lift', int(request.attribute('lift')))
# Update judge window adjustments per-machine
judge_dict = newprofile.get_dict('machine_judge_adjust')
machine_judge = judge_dict.get_dict(self.config.machine.pcbid)
machine_judge.replace_int('adj', int(request.attribute('judgeAdj')))
judge_dict.replace_dict(self.config.machine.pcbid, machine_judge)
newprofile.replace_dict('machine_judge_adjust', judge_dict)
# Secret flags saving
secret = request.child('secret')
if secret is not None:
secret_dict = newprofile.get_dict('secret')
secret_dict.replace_int('flg1', secret.child_value('flg1'))
secret_dict.replace_int('flg2', secret.child_value('flg2'))
secret_dict.replace_int('flg3', secret.child_value('flg3'))
newprofile.replace_dict('secret', secret_dict)
# Basic achievements
achievements = request.child('achievements')
if achievements is not None:
newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg')))
newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly')))
newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num')))
pack_id = int(achievements.attribute('pack_id'))
if pack_id > 0:
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
pack_id,
'daily',
{
'pack_flg': int(achievements.attribute('pack_flg')),
'pack_comp': int(achievements.attribute('pack_comp')),
},
)
trophies = achievements.child('trophy')
if trophies is not None:
# We only load the first 10 in profile load.
newprofile.replace_int_array('trophy', 10, trophies.value[:10])
# Deller and orb saving
commonboss = request.child('commonboss')
if commonboss is not None:
newprofile.replace_int('deller', newprofile.get_int('deller') + int(commonboss.attribute('deller')))
orbs = newprofile.get_int('orbs')
orbs = orbs + int(commonboss.attribute('orb'))
newprofile.replace_int('orbs', orbs)
# Step-up mode
step = request.child('step')
if step is not None:
step_dict = newprofile.get_dict('step')
if cltype == self.GAME_CLTYPE_SINGLE:
step_dict.replace_int('sp_ach', int(step.attribute('sp_ach')))
step_dict.replace_int('sp_hdpt', int(step.attribute('sp_hdpt')))
step_dict.replace_int('sp_level', int(step.attribute('sp_level')))
step_dict.replace_int('sp_round', int(step.attribute('sp_round')))
step_dict.replace_int('sp_mplay', int(step.attribute('sp_mplay')))
else:
step_dict.replace_int('dp_ach', int(step.attribute('dp_ach')))
step_dict.replace_int('dp_hdpt', int(step.attribute('dp_hdpt')))
step_dict.replace_int('dp_level', int(step.attribute('dp_level')))
step_dict.replace_int('dp_round', int(step.attribute('dp_round')))
step_dict.replace_int('dp_mplay', int(step.attribute('dp_mplay')))
step_dict.replace_int('review', int(step.attribute('review')))
newprofile.replace_dict('step', step_dict)
# Link5 data
link5 = request.child('link5')
if link5 is not None:
link5_dict = newprofile.get_dict('link5')
for attr in [
'qpro',
'glass',
'beautiful',
'quaver',
'castle',
'flip',
'titans',
'exusia',
'waxing',
'sampling',
'beachside',
'cuvelia',
'reunion',
'bad',
'turii',
'anisakis',
'second',
'whydidyou',
'china',
'fallen',
'broken',
'summer',
'sakura',
'wuv',
'survival',
'thunder',
]:
link5_dict.replace_int(attr, int(link5.attribute(attr)))
newprofile.replace_dict('link5', link5_dict)
# Keep track of play statistics across all mixes
self.update_play_statistics(userid, play_stats)
return newprofile