1
0
mirror of synced 2024-11-28 07:50:51 +01:00
bemaniutils/bemani/backend/iidx/cannonballers.py

2181 lines
96 KiB
Python

# vim: set fileencoding=utf-8
import random
import struct
from typing import Optional, Dict, Any, List, Tuple
from typing_extensions import Final
from bemani.backend.iidx.base import IIDXBase
from bemani.backend.iidx.course import IIDXCourse
from bemani.backend.iidx.sinobuz import IIDXSinobuz
from bemani.common import Profile, ValidatedDict, VersionConstants, BroadcastConstants, Time, ID, intish
from bemani.data import Data, UserID
from bemani.protocol import Node
class IIDXCannonBallers(IIDXCourse, IIDXBase):
name: str = 'Beatmania IIDX CANNON BALLERS'
version: int = VersionConstants.IIDX_CANNON_BALLERS
GAME_CLTYPE_SINGLE: Final[int] = 0
GAME_CLTYPE_DOUBLE: Final[int] = 1
DAN_STAGES: Final[int] = 4
GAME_CLEAR_STATUS_NO_PLAY: Final[int] = 0
GAME_CLEAR_STATUS_FAILED: Final[int] = 1
GAME_CLEAR_STATUS_ASSIST_CLEAR: Final[int] = 2
GAME_CLEAR_STATUS_EASY_CLEAR: Final[int] = 3
GAME_CLEAR_STATUS_CLEAR: Final[int] = 4
GAME_CLEAR_STATUS_HARD_CLEAR: Final[int] = 5
GAME_CLEAR_STATUS_EX_HARD_CLEAR: Final[int] = 6
GAME_CLEAR_STATUS_FULL_COMBO: Final[int] = 7
GAME_GHOST_TYPE_RIVAL: Final[int] = 1
GAME_GHOST_TYPE_GLOBAL_TOP: Final[int] = 2
GAME_GHOST_TYPE_GLOBAL_AVERAGE: Final[int] = 3
GAME_GHOST_TYPE_LOCAL_TOP: Final[int] = 4
GAME_GHOST_TYPE_LOCAL_AVERAGE: Final[int] = 5
GAME_GHOST_TYPE_DAN_TOP: Final[int] = 6
GAME_GHOST_TYPE_DAN_AVERAGE: Final[int] = 7
GAME_GHOST_TYPE_RIVAL_TOP: Final[int] = 8
GAME_GHOST_TYPE_RIVAL_AVERAGE: Final[int] = 9
GAME_GHOST_LENGTH: Final[int] = 64
GAME_SP_DAN_RANK_7_KYU: Final[int] = 0
GAME_SP_DAN_RANK_6_KYU: Final[int] = 1
GAME_SP_DAN_RANK_5_KYU: Final[int] = 2
GAME_SP_DAN_RANK_4_KYU: Final[int] = 3
GAME_SP_DAN_RANK_3_KYU: Final[int] = 4
GAME_SP_DAN_RANK_2_KYU: Final[int] = 5
GAME_SP_DAN_RANK_1_KYU: Final[int] = 6
GAME_SP_DAN_RANK_1_DAN: Final[int] = 7
GAME_SP_DAN_RANK_2_DAN: Final[int] = 8
GAME_SP_DAN_RANK_3_DAN: Final[int] = 9
GAME_SP_DAN_RANK_4_DAN: Final[int] = 10
GAME_SP_DAN_RANK_5_DAN: Final[int] = 11
GAME_SP_DAN_RANK_6_DAN: Final[int] = 12
GAME_SP_DAN_RANK_7_DAN: Final[int] = 13
GAME_SP_DAN_RANK_8_DAN: Final[int] = 14
GAME_SP_DAN_RANK_9_DAN: Final[int] = 15
GAME_SP_DAN_RANK_10_DAN: Final[int] = 16
GAME_SP_DAN_RANK_CHUDEN: Final[int] = 17
GAME_SP_DAN_RANK_KAIDEN: Final[int] = 18
GAME_DP_DAN_RANK_7_KYU: Final[int] = 0
GAME_DP_DAN_RANK_6_KYU: Final[int] = 1
GAME_DP_DAN_RANK_5_KYU: Final[int] = 2
GAME_DP_DAN_RANK_4_KYU: Final[int] = 3
GAME_DP_DAN_RANK_3_KYU: Final[int] = 4
GAME_DP_DAN_RANK_2_KYU: Final[int] = 5
GAME_DP_DAN_RANK_1_KYU: Final[int] = 6
GAME_DP_DAN_RANK_1_DAN: Final[int] = 7
GAME_DP_DAN_RANK_2_DAN: Final[int] = 8
GAME_DP_DAN_RANK_3_DAN: Final[int] = 9
GAME_DP_DAN_RANK_4_DAN: Final[int] = 10
GAME_DP_DAN_RANK_5_DAN: Final[int] = 11
GAME_DP_DAN_RANK_6_DAN: Final[int] = 12
GAME_DP_DAN_RANK_7_DAN: Final[int] = 13
GAME_DP_DAN_RANK_8_DAN: Final[int] = 14
GAME_DP_DAN_RANK_9_DAN: Final[int] = 15
GAME_DP_DAN_RANK_10_DAN: Final[int] = 16
GAME_DP_DAN_RANK_CHUDEN: Final[int] = 17
GAME_DP_DAN_RANK_KAIDEN: Final[int] = 18
FAVORITE_LIST_LENGTH: Final[int] = 20
requires_extended_regions: bool = True
def previous_version(self) -> Optional[IIDXBase]:
return IIDXSinobuz(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)]))
if len(all_songs) >= 3:
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',
},
],
'ints': [
{
'name': 'Event Phase',
'tip': 'Event phase for all players.',
'category': 'game_config',
'setting': 'event_phase',
'values': {
0: 'No Event',
1: 'Gekisou! Cannon racer Continent 1',
2: 'Gekisou! Cannon racer Continent 2',
3: 'Gekisou! Cannon racer Continent 3',
4: 'Gekisou! Cannon racer Continent 4',
}
},
],
}
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_CHUDEN: self.GAME_SP_DAN_RANK_CHUDEN,
self.DAN_RANK_KAIDEN: self.GAME_SP_DAN_RANK_KAIDEN,
}[db_dan]
elif cltype == self.GAME_CLTYPE_DOUBLE:
return {
self.DAN_RANK_7_KYU: self.GAME_DP_DAN_RANK_7_KYU,
self.DAN_RANK_6_KYU: self.GAME_DP_DAN_RANK_6_KYU,
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_CHUDEN: self.GAME_DP_DAN_RANK_CHUDEN,
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_CHUDEN: self.DAN_RANK_CHUDEN,
self.GAME_SP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN,
}[game_dan]
elif cltype == self.GAME_CLTYPE_DOUBLE:
return {
self.GAME_DP_DAN_RANK_7_KYU: self.DAN_RANK_7_KYU,
self.GAME_DP_DAN_RANK_6_KYU: self.DAN_RANK_6_KYU,
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_CHUDEN: self.DAN_RANK_CHUDEN,
self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN,
}[game_dan]
else:
raise Exception('Invalid cltype!')
def handle_IIDX25shop_getname_request(self, request: Node) -> Node:
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
if machine is not None:
machine_name = machine.name
close = machine.data.get_bool('close')
hour = machine.data.get_int('hour')
minute = machine.data.get_int('minute')
else:
machine_name = ''
close = False
hour = 0
minute = 0
root = Node.void('IIDX25shop')
root.set_attribute('opname', machine_name)
root.set_attribute('pid', str(self.get_machine_region()))
root.set_attribute('cls_opt', '1' if close else '0')
root.set_attribute('hr', str(hour))
root.set_attribute('mi', str(minute))
return root
def handle_IIDX25shop_savename_request(self, request: Node) -> Node:
self.update_machine_name(request.attribute('opname'))
shop_close = intish(request.attribute('cls_opt')) or 0
minutes = intish(request.attribute('mnt')) or 0
hours = intish(request.attribute('hr')) or 0
self.update_machine_data({
'close': shop_close != 0,
'minutes': minutes,
'hours': hours,
})
return Node.void('IIDX25shop')
def handle_IIDX25shop_sentinfo_request(self, request: Node) -> Node:
return Node.void('IIDX25shop')
def handle_IIDX25shop_sendescapepackageinfo_request(self, request: Node) -> Node:
root = Node.void('IIDX25shop')
root.set_attribute('expire', str((Time.now() + 86400 * 365) * 1000))
return root
def handle_IIDX25shop_getconvention_request(self, request: Node) -> Node:
root = Node.void('IIDX25shop')
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
def handle_IIDX25shop_setconvention_request(self, request: Node) -> Node:
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 Node.void('IIDX25shop')
def handle_IIDX25ranking_getranker_request(self, request: Node) -> Node:
root = Node.void('IIDX25ranking')
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 all scores not achieved
# on this machine 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:
# Exclude scores not achieved here
if score[1].location != machine.id:
continue
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
],
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
def handle_IIDX25ranking_entry_request(self, request: Node) -> Node:
extid = int(request.attribute('iidxid'))
courseid = int(request.attribute('coid'))
chart = int(request.attribute('clid'))
course_type = int(request.attribute('regist_type'))
clear_status = self.game_to_db_status(int(request.attribute('clr')))
pgreats = int(request.attribute('pgnum'))
greats = int(request.attribute('gnum'))
if course_type == 0:
index = self.COURSE_TYPE_INTERNET_RANKING
elif course_type == 1:
index = self.COURSE_TYPE_SECRET
else:
raise Exception('Unknown registration type for course entry!')
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
# Update achievement to track course statistics
self.update_course(
userid,
index,
courseid,
chart,
clear_status,
pgreats,
greats,
)
# We should return the user's position, but its not displayed anywhere
# so fuck it.
root = Node.void('IIDX25ranking')
root.set_attribute('anum', '1')
root.set_attribute('jun', '1')
return root
def handle_IIDX25ranking_classicentry_request(self, request: Node) -> Node:
extid = int(request.attribute('iidx_id'))
courseid = int(request.attribute('course_id'))
coursestyle = int(request.attribute('play_style'))
clear_status = self.game_to_db_status(int(request.attribute('clear_flg')))
pgreats = int(request.attribute('pgnum'))
greats = int(request.attribute('gnum'))
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
# Update achievement to track course statistics
self.update_course(
userid,
self.COURSE_TYPE_CLASSIC,
courseid,
coursestyle,
clear_status,
pgreats,
greats,
)
return Node.void('IIDX25ranking')
def handle_IIDX25music_crate_request(self, request: Node) -> Node:
root = Node.void('IIDX25music')
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((1000 * values['clears']) / values['total']))
fcs.append(int((1000 * values['fcs']) / values['total']))
placed = True
if not placed:
clears.append(1001)
fcs.append(1001)
clearnode = Node.s32_array('c', clears + fcs)
clearnode.set_attribute('mid', str(song))
root.add_child(clearnode)
return root
def handle_IIDX25music_getrank_request(self, request: Node) -> Node:
cltype = int(request.attribute('cltype'))
root = Node.void('IIDX25music')
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
def handle_IIDX25music_appoint_request(self, request: Node) -> Node:
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('IIDX25music')
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
def handle_IIDX25music_breg_request(self, request: Node) -> Node:
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.
return Node.void('IIDX25music')
def handle_IIDX25music_reg_request(self, request: Node) -> Node:
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])
}
shop_id = ID.parse_machine_id(request.attribute('location_id'))
if not global_scores:
all_scores = [
score for score in all_scores
if (
score[0] == userid or
score[1].location == shop_id
)
]
# 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('convid'))
self.update_score(
userid,
musicid,
chart,
clear_status,
pgreats,
greats,
miss_count,
ghost,
shopid,
)
# Calculate and return statistics about this song
root = Node.void('IIDX25music')
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((1000 * clear) / count)))
root.set_attribute('frate', str(int((1000 * full_combo) / count)))
else:
root.set_attribute('crate', '0')
root.set_attribute('frate', '0')
root.set_attribute('rankside', '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
score[1].location == shop_id
)
]
# 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 = ''
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('update', '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
def handle_IIDX25music_play_request(self, request: Node) -> Node:
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('IIDX25music')
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((1000 * clear) / count)))
root.set_attribute('frate', str(int((1000 * full_combo) / count)))
else:
root.set_attribute('crate', '0')
root.set_attribute('frate', '0')
return root
# Bare minimum response to say we handled the request
def handle_IIDX25music_beginnerplay_request(self, request: Node) -> Node:
return Node.void('IIDX25music')
def handle_IIDX25music_arenaCPU_request(self, request: Node) -> Node:
root = Node.void('IIDX25music')
music_list = [0, 0, 0, 0]
cpu_list = [0, 0, 0, 0]
grade_to_percentage_map = {
self.GAME_SP_DAN_RANK_1_KYU: 50,
self.GAME_SP_DAN_RANK_1_DAN: 55,
self.GAME_SP_DAN_RANK_2_DAN: 60,
self.GAME_SP_DAN_RANK_3_DAN: 60,
self.GAME_SP_DAN_RANK_4_DAN: 65,
self.GAME_SP_DAN_RANK_5_DAN: 65,
self.GAME_SP_DAN_RANK_6_DAN: 70,
self.GAME_SP_DAN_RANK_7_DAN: 70,
self.GAME_SP_DAN_RANK_8_DAN: 75,
self.GAME_SP_DAN_RANK_9_DAN: 75,
self.GAME_SP_DAN_RANK_10_DAN: 80,
self.GAME_SP_DAN_RANK_CHUDEN: 80,
self.GAME_SP_DAN_RANK_KAIDEN: 80,
}
for item in request.children:
if item.name == 'music_list':
music_list[int(item.child_value('index'))] = int(item.child_value('total_notes'))
if item.name == 'cpu_list':
cpu_list[int(item.child_value('index'))] = int(item.child_value('grade_id'))
for index in range(4):
cpu_score_list = Node.void('cpu_score_list')
cpu_score_list.add_child(Node.s32('index', index))
root.add_child(cpu_score_list)
for i in range(4):
score_list = Node.void('score_list')
cpu_score_list.add_child(score_list)
percentage = grade_to_percentage_map.get(cpu_list[index], 0)
notes = music_list[i]
score = (notes * 2 * percentage) // 100
ghost_section = score // 64
ghost = [ghost_section] * 64
ghost_total = ghost_section * 64
ghost_lost = score - ghost_total
ghost[0] = ghost[0] + ghost_lost
score_list.add_child(Node.s32('score', score))
score_list.add_child(Node.bool('enable_score', True))
score_list.add_child(Node.u8_array('ghost', ghost))
score_list.add_child(Node.bool('enable_ghost', True))
return root
def handle_IIDX25grade_raised_request(self, request: Node) -> Node:
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('cstage'))
cleared = stages_cleared == self.DAN_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, achievementid=rank, achievementtype=index)
root = Node.void('IIDX25grade')
root.set_attribute('pnum', str(len(all_achievements)))
return root
def handle_IIDX25pc_common_request(self, request: Node) -> Node:
root = Node.void('IIDX25pc')
root.set_attribute('expire', '600')
ir = Node.void('ir')
root.add_child(ir)
ir.set_attribute('beat', '2')
vip_black_pass = Node.void('vip_pass_black')
root.add_child(vip_black_pass)
newsong_another = Node.void('newsong_another')
root.add_child(newsong_another)
newsong_another.set_attribute('open', '1')
deller_bonus = Node.void('deller_bonus')
root.add_child(deller_bonus)
deller_bonus.set_attribute('open', '1')
common_evnet = Node.void('common_evnet') # Yes, this is misspelled in the game
root.add_child(common_evnet)
common_evnet.set_attribute('flg', '0')
# See if we configured event overrides
if self.machine_joined_arcade():
game_config = self.get_game_config()
event_phase = game_config.get_int('event_phase')
omni_events = game_config.get_bool('omnimix_events_enabled')
else:
# If we aren't in an arcade, we turn off events
event_phase = 0
omni_events = False
if event_phase == 0 or (self.omnimix and (not omni_events)):
event1 = 0
boss_phase = 0
elif event_phase in [1, 2, 3, 4]:
boss_phase = 1
event1 = event_phase - 1
boss = Node.void('boss')
root.add_child(boss)
boss.set_attribute('phase', str(boss_phase))
event1_phase = Node.void('event1_phase')
root.add_child(event1_phase)
event1_phase.set_attribute('phase', str(event1))
extra_boss_event = Node.void('extra_boss_event') # Always enable IIDX AIR RACE 5
root.add_child(extra_boss_event)
extra_boss_event.set_attribute('phase', '4')
# Course definitions
courses: List[Dict[str, Any]] = [
{
'name': 'EUROBEAT',
'id': 1,
'songs': [
17040,
8006,
10033,
25001,
],
},
{
'name': 'WEATHER',
'id': 2,
'open_music_id': 15104,
'songs': [
17023,
9030,
15004,
10024,
],
},
{
'name': 'FEAT. KANAE ASABA',
'id': 3,
'open_music_id': 24101,
'songs': [
25056,
23033,
24011,
21034,
],
},
{
'name': 'BLOOM',
'id': 4,
'songs': [
15054,
20053,
20004,
23083,
],
},
{
'name': 'VACUUM',
'id': 5,
'songs': [
19070,
23021,
17002,
18071,
],
},
{
'name': 'PRINCESS',
'id': 6,
'open_music_id': 12100,
'songs': [
25024,
20021,
19046,
12002,
],
},
{
'name': 'TECHNO',
'id': 7,
'open_music_id': 19100,
'songs': [
25026,
19063,
17061,
23085,
],
},
{
'name': 'POP',
'id': 8,
'open_music_id': 15103,
'songs': [
15061,
20099,
20101,
22093,
],
},
{
'name': 'CARDINAL',
'id': 9,
'open_music_id': 13101,
'songs': [
13038,
13026,
13000,
13010,
],
},
{
'name': 'O2K',
'id': 10,
'songs': [
21059,
19002,
22054,
16020,
],
},
]
# Secret course definitions
secret_courses: List[Dict[str, Any]] = [
{
'name': 'EMOTIONAL',
'id': 1,
'songs': [
21009,
25006,
17065,
25066,
],
},
{
'name': 'RYU',
'id': 2,
'songs': [
23082,
21007,
23037,
25067,
],
},
]
# For some reason, omnimix crashes on course mode, so don't enable it
if not self.omnimix:
internet_ranking = Node.void('internet_ranking')
root.add_child(internet_ranking)
used_ids: List[int] = []
for c in courses:
if c['id'] in used_ids:
raise Exception('Cannot have multiple courses with the same ID!')
elif c['id'] < 0 or c['id'] >= 20:
raise Exception('Course ID is out of bounds!')
else:
used_ids.append(c['id'])
course = Node.void('course')
internet_ranking.add_child(course)
course.set_attribute('course_id', str(c['id']))
course.set_attribute('name', c['name'])
course.set_attribute('mid0', str(c['songs'][0]))
course.set_attribute('mid1', str(c['songs'][1]))
course.set_attribute('mid2', str(c['songs'][2]))
course.set_attribute('mid3', str(c['songs'][3]))
course.set_attribute('open_music_id', str(c.get('open_music_id', 0)))
course.set_attribute('opflg', '1')
secret_ex_course = Node.void('secret_ex_course')
root.add_child(secret_ex_course)
used_secret_ids: List[int] = []
for c in secret_courses:
if c['id'] in used_secret_ids:
raise Exception('Cannot have multiple secret courses with the same ID!')
elif c['id'] < 0 or c['id'] >= 20:
raise Exception('Secret course ID is out of bounds!')
else:
used_secret_ids.append(c['id'])
course = Node.void('course')
secret_ex_course.add_child(course)
course.set_attribute('course_id', str(c['id']))
course.set_attribute('name', c['name'])
course.set_attribute('mid0', str(c['songs'][0]))
course.set_attribute('mid1', str(c['songs'][1]))
course.set_attribute('mid2', str(c['songs'][2]))
course.set_attribute('mid3', str(c['songs'][3]))
expert = Node.void('expert')
root.add_child(expert)
expert.set_attribute('phase', '1')
expert_random_select = Node.void('expert_random_select')
root.add_child(expert_random_select)
expert_random_select.set_attribute('phase', '1')
expert_full = Node.void('expert_secret_full_open')
root.add_child(expert_full)
return root
def handle_IIDX25pc_delete_request(self, request: Node) -> Node:
return Node.void('IIDX25pc')
def handle_IIDX25pc_playstart_request(self, request: Node) -> Node:
return Node.void('IIDX25pc')
def handle_IIDX25pc_playend_request(self, request: Node) -> Node:
return Node.void('IIDX25pc')
def handle_IIDX25pc_visit_request(self, request: Node) -> Node:
root = Node.void('IIDX25pc')
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
def handle_IIDX25pc_shopregister_request(self, request: Node) -> Node:
root = Node.void('IIDX25pc')
return root
def handle_IIDX25pc_oldget_request(self, request: Node) -> Node:
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('IIDX25pc')
root.set_attribute('status', '1' if profile is None else '0')
return root
def handle_IIDX25pc_getname_request(self, request: Node) -> Node:
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('IIDX25pc')
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
def handle_IIDX25pc_takeover_request(self, request: Node) -> Node:
refid = request.attribute('rid')
name = request.attribute('name')
pid = int(request.attribute('pid'))
newprofile = self.new_profile_by_refid(refid, name, pid)
root = Node.void('IIDX25pc')
if newprofile is not None:
root.set_attribute('id', str(newprofile.extid))
return root
def handle_IIDX25pc_reg_request(self, request: Node) -> Node:
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('IIDX25pc')
if profile is not None:
root.set_attribute('id', str(profile.extid))
root.set_attribute('id_str', ID.format_extid(profile.extid))
return root
def handle_IIDX25pc_get_request(self, request: Node) -> Node:
refid = request.attribute('rid')
root = self.get_profile_by_refid(refid)
if root is None:
root = Node.void('IIDX25pc')
return root
def handle_IIDX25pc_save_request(self, request: Node) -> Node:
extid = int(request.attribute('iidxid'))
self.put_profile_by_extid(extid, request)
return Node.void('IIDX25pc')
def handle_IIDX25pc_logout_request(self, request: Node) -> Node:
return Node.void('IIDX25pc')
def handle_IIDX25gameSystem_systemInfo_request(self, request: Node) -> Node:
root = Node.void('IIDX25gameSystem')
arena_schedule = Node.void('arena_schedule')
root.add_child(arena_schedule)
arena_schedule.add_child(Node.u8('phase', 1))
arena_schedule.add_child(Node.u32('start', 0))
arena_schedule.add_child(Node.u32('end', 0))
cpu_grades = [6, 7, 8, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 18]
cpu_low_diff = [4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12]
cpu_high_diff = [5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 12]
for playstyle in range(2):
for arena_class in range(20):
arena_music_difficult = Node.void('arena_music_difficult')
root.add_child(arena_music_difficult)
arena_music_difficult.add_child(Node.s32('play_style', playstyle))
arena_music_difficult.add_child(Node.s32('arena_class', arena_class))
arena_music_difficult.add_child(Node.s32('low_difficult', 1))
arena_music_difficult.add_child(Node.s32('high_difficult', 12))
arena_music_difficult.add_child(Node.bool('is_leggendaria', True))
arena_music_difficult.add_child(Node.s32('force_music_list_id', 0))
arena_cpu_define = Node.void('arena_cpu_define')
root.add_child(arena_cpu_define)
arena_cpu_define.add_child(Node.s32('play_style', playstyle))
arena_cpu_define.add_child(Node.s32('arena_class', arena_class))
arena_cpu_define.add_child(Node.s32('grade_id', cpu_grades[arena_class]))
arena_cpu_define.add_child(Node.s32('low_music_difficult', cpu_low_diff[arena_class]))
arena_cpu_define.add_child(Node.s32('high_music_difficult', cpu_high_diff[arena_class]))
arena_cpu_define.add_child(Node.bool('is_leggendaria', arena_class >= 13))
for matching_class in range(21):
matching_class_range = Node.void('matching_class_range')
root.add_child(matching_class_range)
matching_class_range.add_child(Node.s32('play_style', playstyle))
matching_class_range.add_child(Node.s32('matching_class', matching_class))
matching_class_range.add_child(Node.s32('low_arena_class', 1))
matching_class_range.add_child(Node.s32('high_arena_class', 20))
return root
def handle_IIDX25pc_eaappliresult_request(self, request: Node) -> Node:
clear_map = {
self.GAME_CLEAR_STATUS_NO_PLAY: 'NO PLAY',
self.GAME_CLEAR_STATUS_FAILED: 'FAILED',
self.GAME_CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR',
self.GAME_CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR',
self.GAME_CLEAR_STATUS_CLEAR: 'CLEAR',
self.GAME_CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR',
self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR',
self.GAME_CLEAR_STATUS_FULL_COMBO: 'FULL COMBO',
}
# first we'll grab the data from the packet
# did = request.child_value('did')
# rid = request.child_value('rid')
name = request.child_value('name')
# qpro_hair = request.child_value('qpro_hair')
# qpro_head = request.child_value('qpro_head')
# qpro_body = request.child_value('qpro_body')
# qpro_hand = request.child_value('qpro_hand')
music_id = request.child_value('music_id')
class_id = request.child_value('class_id')
# no_save = request.child_value('no_save')
# is_couple = request.child_value('is_couple')
# target_graph = request.child_value('target_graph')
target_exscore = request.child_value('target_exscore')
# pacemaker = request.child_value('pacemaker')
best_clear = request.child_value('best_clear')
# best_djlevel = request.child_value('best_djlevel')
# best_exscore = request.child_value('best_exscore')
# best_misscount = request.child_value('best_misscount')
now_clear = request.child_value('now_clear')
# now_djlevel = request.child_value('now_djlevel')
now_exscore = request.child_value('now_exscore')
# now_misscount = request.child_value('now_misscount')
now_pgreat = request.child_value('now_pgreat')
now_great = request.child_value('now_great')
now_good = request.child_value('now_good')
now_bad = request.child_value('now_bad')
now_poor = request.child_value('now_poor')
now_combo = request.child_value('now_combo')
now_fast = request.child_value('now_fast')
now_slow = request.child_value('now_slow')
best_clear_string = clear_map.get(best_clear, 'NO PLAY')
now_clear_string = clear_map.get(now_clear, 'NO PLAY')
# let's get the song info first
song = self.data.local.music.get_song(self.game, self.music_version, music_id, class_id)
notecount = song.data.get('notecount', 0)
# Construct the dictionary for the broadcast
card_data = {
BroadcastConstants.DJ_NAME: name,
BroadcastConstants.SONG_NAME: song.name,
BroadcastConstants.ARTIST_NAME: song.artist,
BroadcastConstants.DIFFICULTY: song.data.get('difficulty', 0),
BroadcastConstants.TARGET_EXSCORE: target_exscore,
BroadcastConstants.EXSCORE: now_exscore,
BroadcastConstants.BEST_CLEAR_STATUS: best_clear_string,
BroadcastConstants.CLEAR_STATUS: now_clear_string,
BroadcastConstants.PLAY_STATS_HEADER: 'How did you do?',
BroadcastConstants.PERFECT_GREATS: now_pgreat,
BroadcastConstants.GREATS: now_great,
BroadcastConstants.GOODS: now_good,
BroadcastConstants.BADS: now_bad,
BroadcastConstants.POORS: now_poor,
BroadcastConstants.COMBO_BREAKS: now_combo,
BroadcastConstants.SLOWS: now_slow,
BroadcastConstants.FASTS: now_fast,
}
if notecount != 0:
max_score = notecount * 2
percent = now_exscore / max_score
grade = int(9 * percent)
grades = ['F', 'F', 'E', 'D', 'C', 'B', 'A', 'AA', 'AAA', 'MAX']
card_data[BroadcastConstants.GRADE] = grades[grade]
card_data[BroadcastConstants.RATE] = str(round(percent, 2))
# Try to broadcast out the score to our webhook(s)
self.data.triggers.broadcast_score(card_data, self.game, song)
return Node.void('IIDX25pc')
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('IIDX25pc')
# 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('mode', str(profile.get_int('mode')))
pcdata.set_attribute('pmode', str(profile.get_int('pmode')))
pcdata.set_attribute('rtype', str(profile.get_int('rtype')))
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('gpos', str(profile.get_int('gpos')))
pcdata.set_attribute('s_sorttype', str(profile.get_int('s_sorttype')))
pcdata.set_attribute('d_sorttype', str(profile.get_int('d_sorttype')))
pcdata.set_attribute('s_pace', str(profile.get_int('s_pace')))
pcdata.set_attribute('d_pace', str(profile.get_int('d_pace')))
pcdata.set_attribute('s_gno', str(profile.get_int('s_gno')))
pcdata.set_attribute('d_gno', str(profile.get_int('d_gno')))
pcdata.set_attribute('s_gtype', str(profile.get_int('s_gtype')))
pcdata.set_attribute('d_gtype', str(profile.get_int('d_gtype')))
pcdata.set_attribute('s_sdlen', str(profile.get_int('s_sdlen')))
pcdata.set_attribute('d_sdlen', str(profile.get_int('d_sdlen')))
pcdata.set_attribute('s_sdtype', str(profile.get_int('s_sdtype')))
pcdata.set_attribute('d_sdtype', str(profile.get_int('d_sdtype')))
pcdata.set_attribute('s_timing', str(profile.get_int('s_timing')))
pcdata.set_attribute('d_timing', str(profile.get_int('d_timing')))
pcdata.set_attribute('s_notes', str(profile.get_float('s_notes')))
pcdata.set_attribute('d_notes', str(profile.get_float('d_notes')))
pcdata.set_attribute('s_judge', str(profile.get_int('s_judge')))
pcdata.set_attribute('d_judge', str(profile.get_int('d_judge')))
pcdata.set_attribute('s_judgeAdj', str(machine_judge.get_int('single')))
pcdata.set_attribute('d_judgeAdj', str(machine_judge.get_int('double')))
pcdata.set_attribute('s_hispeed', str(profile.get_float('s_hispeed')))
pcdata.set_attribute('d_hispeed', str(profile.get_float('d_hispeed')))
pcdata.set_attribute('s_liflen', str(profile.get_int('s_lift')))
pcdata.set_attribute('d_liflen', str(profile.get_int('d_lift')))
pcdata.set_attribute('s_disp_judge', str(profile.get_int('s_disp_judge')))
pcdata.set_attribute('d_disp_judge', str(profile.get_int('d_disp_judge')))
pcdata.set_attribute('s_opstyle', str(profile.get_int('s_opstyle')))
pcdata.set_attribute('d_opstyle', str(profile.get_int('d_opstyle')))
pcdata.set_attribute('s_exscore', str(profile.get_int('s_exscore')))
pcdata.set_attribute('d_exscore', str(profile.get_int('d_exscore')))
pcdata.set_attribute('s_graph_score', str(profile.get_int('s_graph_score')))
pcdata.set_attribute('d_graph_score', str(profile.get_int('d_graph_score')))
pcdata.set_attribute('s_auto_scrach', str(profile.get_int('s_auto_scrach')))
pcdata.set_attribute('d_auto_scrach', str(profile.get_int('d_auto_scrach')))
pcdata.set_attribute('s_gauge_disp', str(profile.get_int('s_gauge_disp')))
pcdata.set_attribute('d_gauge_disp', str(profile.get_int('d_gauge_disp')))
pcdata.set_attribute('s_lane_brignt', str(profile.get_int('s_lane_brignt')))
pcdata.set_attribute('d_lane_brignt', str(profile.get_int('d_lane_brignt')))
pcdata.set_attribute('s_camera_layout', str(profile.get_int('s_camera_layout')))
pcdata.set_attribute('d_camera_layout', str(profile.get_int('d_camera_layout')))
spdp_rival = Node.void('spdp_rival')
root.add_child(spdp_rival)
spdp_rival.set_attribute('flg', str(profile.get_int('spdp_rival_flag')))
premium_unlocks = Node.void('ea_premium_course')
root.add_child(premium_unlocks)
legendarias = Node.void('leggendaria_open')
root.add_child(legendarias)
# Song unlock flags
secret_dict = profile.get_dict('secret')
secret = Node.void('secret')
root.add_child(secret)
secret.add_child(Node.s64_array('flg1', secret_dict.get_int_array('flg1', 3)))
secret.add_child(Node.s64_array('flg2', secret_dict.get_int_array('flg2', 3)))
secret.add_child(Node.s64_array('flg3', secret_dict.get_int_array('flg3', 3)))
# Favorites
for folder in ['favorite1', 'favorite2', 'favorite3']:
favorite_dict = profile.get_dict(folder)
sp_mlist = b''
sp_clist = b''
singles_list = favorite_dict['single'] if 'single' in favorite_dict else []
for single in singles_list:
sp_mlist = sp_mlist + struct.pack('<L', single['id'])
sp_clist = sp_clist + struct.pack('B', single['chart'])
while len(sp_mlist) < (4 * self.FAVORITE_LIST_LENGTH):
sp_mlist = sp_mlist + b'\x00\x00\x00\x00'
while len(sp_clist) < self.FAVORITE_LIST_LENGTH:
sp_clist = sp_clist + b'\x00'
dp_mlist = b''
dp_clist = b''
doubles_list = favorite_dict['double'] if 'double' in favorite_dict else []
for double in doubles_list:
dp_mlist = dp_mlist + struct.pack('<L', double['id'])
dp_clist = dp_clist + struct.pack('B', double['chart'])
while len(dp_mlist) < (4 * self.FAVORITE_LIST_LENGTH):
dp_mlist = dp_mlist + b'\x00\x00\x00\x00'
while len(dp_clist) < self.FAVORITE_LIST_LENGTH:
dp_clist = dp_clist + b'\x00'
if folder == 'favorite1':
favorite = Node.void('favorite')
elif folder == 'favorite2':
favorite = Node.void('extra_favorite')
favorite.set_attribute('folder_id', '0')
elif folder == 'favorite3':
favorite = Node.void('extra_favorite')
favorite.set_attribute('folder_id', '1')
root.add_child(favorite)
favorite.add_child(Node.binary('sp_mlist', sp_mlist))
favorite.add_child(Node.binary('sp_clist', sp_clist))
favorite.add_child(Node.binary('dp_mlist', dp_mlist))
favorite.add_child(Node.binary('dp_clist', dp_clist))
# 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)))
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
for rank in achievements:
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'),
settings_dict.get_int('pacemaker'),
settings_dict.get_int('effector_lock'),
settings_dict.get_int('effector_preset'),
],
)
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'),
],
))
# Qpro secret data from step-up mode
qpro_secrete_dict = profile.get_dict('qpro_secret')
qpro_secret = Node.void('qpro_secret')
root.add_child(qpro_secret)
qpro_secret.add_child(Node.s64_array('head', qpro_secrete_dict.get_int_array('head', 5)))
qpro_secret.add_child(Node.s64_array('hair', qpro_secrete_dict.get_int_array('hair', 5)))
qpro_secret.add_child(Node.s64_array('face', qpro_secrete_dict.get_int_array('face', 5)))
qpro_secret.add_child(Node.s64_array('body', qpro_secrete_dict.get_int_array('body', 5)))
qpro_secret.add_child(Node.s64_array('hand', qpro_secrete_dict.get_int_array('hand', 5)))
# 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')))
rival.add_child(Node.bool('is_robo', False))
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')))
# Expert courses
ir_data = Node.void('ir_data')
root.add_child(ir_data)
for course in achievements:
if course.type == self.COURSE_TYPE_INTERNET_RANKING:
courseid, coursechart = self.id_and_chart_from_courseid(course.id)
ir_data.add_child(Node.s32_array('e', [
courseid, # course ID
coursechart, # course chart
self.db_to_game_status(course.data.get_int('clear_status')), # course clear status
course.data.get_int('pgnum'), # flashing great count
course.data.get_int('gnum'), # great count
]))
secret_course_data = Node.void('secret_course_data')
root.add_child(secret_course_data)
for course in achievements:
if course.type == self.COURSE_TYPE_SECRET:
courseid, coursechart = self.id_and_chart_from_courseid(course.id)
secret_course_data.add_child(Node.s32_array('e', [
courseid, # course ID
coursechart, # course chart
self.db_to_game_status(course.data.get_int('clear_status')), # course clear status
course.data.get_int('pgnum'), # flashing great count
course.data.get_int('gnum'), # great count
]))
classic_course_data = Node.void('classic_course_data')
root.add_child(classic_course_data)
for course in achievements:
if course.type == self.COURSE_TYPE_CLASSIC:
courseid, playstyle = self.id_and_chart_from_courseid(course.id)
score_data = Node.void('score_data')
classic_course_data.add_child(score_data)
score_data.set_attribute('play_style', str(playstyle))
score_data.set_attribute('course_id', str(courseid))
score_data.set_attribute('score', str(course.data.get_int('pgnum') * 2 + course.data.get_int('gnum')))
score_data.set_attribute('pgnum', str(course.data.get_int('pgnum')))
score_data.set_attribute('gnum', str(course.data.get_int('gnum')))
score_data.set_attribute('cflg', str(self.db_to_game_status(course.data.get_int('clear_status'))))
# DJ RANK
for dj_rank in achievements:
if dj_rank.type != 'dj_rank':
continue
dj_rank_node = Node.void('dj_rank')
root.add_child(dj_rank_node)
dj_rank_node.set_attribute('style', str(dj_rank.id))
dj_rank_node.add_child(Node.s32_array('rank', dj_rank.data.get_int_array('rank', 15)))
dj_rank_node.add_child(Node.s32_array('point', dj_rank.data.get_int_array('point', 15)))
for i in range(2):
for j in range(15):
dj_rank_ranking_node = Node.void('dj_rank_ranking')
root.add_child(dj_rank_ranking_node)
dj_rank_ranking_node.set_attribute('style', str(i))
detail = Node.void('detail')
dj_rank_ranking_node.add_child(detail)
detail.set_attribute('category', str(j))
detail.set_attribute('total_user', '0')
detail.set_attribute('rank', '0')
detail.set_attribute('platinum_point', '0')
detail.set_attribute('platinum_rank', '0')
detail.set_attribute('gold_point', '0')
detail.set_attribute('gold_rank', '0')
detail.set_attribute('silver_point', '0')
detail.set_attribute('silver_rank', '0')
detail.set_attribute('bronze_point', '0')
detail.set_attribute('bronze_rank', '0')
detail.set_attribute('white_point', '0')
detail.set_attribute('white_rank', '0')
event1_dict = profile.get_dict('event1')
event1 = Node.void('event1')
root.add_child(event1)
event1.set_attribute('tuneup_point', str(event1_dict.get_int('tuneup_point')))
event1.set_attribute('body_parts_list', str(event1_dict.get_int('body_parts_list')))
event1.set_attribute('engine_parts_list', str(event1_dict.get_int('engine_parts_list')))
event1.set_attribute('tire_parts_list', str(event1_dict.get_int('tire_parts_list')))
event1.set_attribute('body_equip_parts', str(event1_dict.get_int('body_equip_parts')))
event1.set_attribute('engine_equip_parts', str(event1_dict.get_int('engine_equip_parts')))
event1.set_attribute('tire_equip_parts', str(event1_dict.get_int('tire_equip_parts')))
event1.set_attribute('gift_point', str(event1_dict.get_int('play_gift')))
for map_data in achievements:
if map_data.type != 'map_data':
continue
map_data_node = Node.void('map_data')
event1.add_child(map_data_node)
map_data_node.set_attribute('map_id', str(map_data.id))
map_data_node.set_attribute('play_num', str(map_data.data.get_int('play_num')))
map_data_node.set_attribute('progress', str(map_data.data.get_int('progress')))
map_data_node.set_attribute('boost_fuel', str(map_data.data.get_int('boost_fuel')))
map_data_node.set_attribute('is_clear', str(map_data.data.get_int('is_clear')))
map_data_node.set_attribute('rare_defeat_list', str(map_data.data.get_int('rare_defeat_list')))
map_data_node.set_attribute('rare1_appearance', str(map_data.data.get_int('rare1_appearance')))
map_data_node.set_attribute('rare2_appearance', str(map_data.data.get_int('rare2_appearance')))
map_data_node.set_attribute('rare3_appearance', str(map_data.data.get_int('rare3_appearance')))
map_data_node.set_attribute('rare4_appearance', str(map_data.data.get_int('rare4_appearance')))
map_data_node.set_attribute('rare5_appearance', str(map_data.data.get_int('rare5_appearance')))
map_data_node.set_attribute('rare6_appearance', str(map_data.data.get_int('rare6_appearance')))
# OMES Data
omes_dict = profile.get_dict('omes')
onemore_data = Node.void('onemore_event')
root.add_child(onemore_data)
for i in range(6):
onemore_data.set_attribute(f'defeat_{i}', str(omes_dict.get_int(f'defeat_{i}')))
for i in range(3):
i = i + 1
onemore_data.set_attribute(f'challenge_num_{i}_n', str(omes_dict.get_int(f'challenge_num_{i}_n')))
onemore_data.set_attribute(f'challenge_num_{i}_h', str(omes_dict.get_int(f'challenge_num_{i}_h')))
onemore_data.set_attribute(f'challenge_num_{i}_a', str(omes_dict.get_int(f'challenge_num_{i}_a')))
# Step up mode
step_dict = profile.get_dict('step')
step = Node.void('step')
root.add_child(step)
step.set_attribute('enemy_damage', str(step_dict.get_int('enemy_damage')))
step.set_attribute('progress', str(step_dict.get_int('progress')))
step.set_attribute('point', str(step_dict.get_int('point')))
step.set_attribute('enemy_defeat_flg', str(step_dict.get_int('enemy_defeat_flg')))
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_mplay', str(step_dict.get_int('sp_mplay')))
step.set_attribute('dp_mplay', str(step_dict.get_int('dp_mplay')))
# 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
achievement_node = Node.void('achievements')
root.add_child(achievement_node)
# Dailies
if pack_id is None:
achievement_node.set_attribute('pack', '0')
achievement_node.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()
achievement_node.set_attribute('pack', str(daily_played.get_int('pack_flg')))
achievement_node.set_attribute('pack_comp', str(daily_played.get_int('pack_comp')))
# Weeklies
achievement_node.set_attribute('last_weekly', str(profile.get_int('last_weekly')))
achievement_node.set_attribute('weekly_num', str(profile.get_int('weekly_num')))
# Prefecture visit flag
achievement_node.set_attribute('visit_flg', str(profile.get_int('visit_flg')))
# Number of rivals beaten
achievement_node.set_attribute('rival_crush', str(profile.get_int('rival_crush')))
# Tran medals
achievement_node.add_child(Node.s64_array('trophy', profile.get_int_array('trophy', 20)))
# Track deller
deller = Node.void('deller')
root.add_child(deller)
deller.set_attribute('deller', str(profile.get_int('deller')))
deller.set_attribute('rate', '0')
# Orb data
orb_data = Node.void('orb_data')
root.add_child(orb_data)
orb_data.set_attribute('rest_orb', str(profile.get_int('orbs')))
# Expert points
expert_point = Node.void('expert_point')
root.add_child(expert_point)
for rank in achievements:
if rank.type == 'expert_point':
detail = Node.void('detail')
expert_point.add_child(detail)
detail.set_attribute('course_id', str(rank.id))
detail.set_attribute('n_point', str(rank.data.get_int('normal_points')))
detail.set_attribute('h_point', str(rank.data.get_int('hyper_points')))
detail.set_attribute('a_point', str(rank.data.get_int('another_points')))
nostalgia = Node.void('nostalgia_open')
root.add_child(nostalgia)
# Ea app features
if self.data.triggers.has_broadcast_destination(self.game):
root.add_child(Node.void('bind_eaappli'))
pay_per_use = Node.void('pay_per_use')
root.add_child(pay_per_use)
pay_per_use.set_attribute('item_num', '99')
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
newprofile = oldprofile.clone()
play_stats = self.get_play_statistics(userid)
# Track play counts
cltype = int(request.attribute('cltype'))
if cltype == self.GAME_CLTYPE_SINGLE:
play_stats.increment_int('single_plays')
if cltype == self.GAME_CLTYPE_DOUBLE:
play_stats.increment_int('double_plays')
# Track DJ points
play_stats.replace_int('single_dj_points', int(request.attribute('s_achi')))
play_stats.replace_int('double_dj_points', int(request.attribute('d_achi')))
# Profile settings
newprofile.replace_int('sp_opt', int(request.attribute('sp_opt')))
newprofile.replace_int('dp_opt', int(request.attribute('dp_opt')))
newprofile.replace_int('dp_opt2', int(request.attribute('dp_opt2')))
newprofile.replace_int('gpos', int(request.attribute('gpos')))
newprofile.replace_int('s_sorttype', int(request.attribute('s_sorttype')))
newprofile.replace_int('d_sorttype', int(request.attribute('d_sorttype')))
newprofile.replace_int('s_disp_judge', int(request.attribute('s_disp_judge')))
newprofile.replace_int('d_disp_judge', int(request.attribute('d_disp_judge')))
newprofile.replace_int('s_pace', int(request.attribute('s_pace')))
newprofile.replace_int('d_pace', int(request.attribute('d_pace')))
newprofile.replace_int('s_gno', int(request.attribute('s_gno')))
newprofile.replace_int('d_gno', int(request.attribute('d_gno')))
newprofile.replace_int('s_gtype', int(request.attribute('s_gtype')))
newprofile.replace_int('d_gtype', int(request.attribute('d_gtype')))
newprofile.replace_int('s_sdlen', int(request.attribute('s_sdlen')))
newprofile.replace_int('d_sdlen', int(request.attribute('d_sdlen')))
newprofile.replace_int('s_sdtype', int(request.attribute('s_sdtype')))
newprofile.replace_int('d_sdtype', int(request.attribute('d_sdtype')))
newprofile.replace_int('s_timing', int(request.attribute('s_timing')))
newprofile.replace_int('d_timing', int(request.attribute('d_timing')))
newprofile.replace_float('s_notes', float(request.attribute('s_notes')))
newprofile.replace_float('d_notes', float(request.attribute('d_notes')))
newprofile.replace_int('s_judge', int(request.attribute('s_judge')))
newprofile.replace_int('d_judge', int(request.attribute('d_judge')))
newprofile.replace_float('s_hispeed', float(request.attribute('s_hispeed')))
newprofile.replace_float('d_hispeed', float(request.attribute('d_hispeed')))
newprofile.replace_int('s_opstyle', int(request.attribute('s_opstyle')))
newprofile.replace_int('d_opstyle', int(request.attribute('d_opstyle')))
newprofile.replace_int('s_graph_score', int(request.attribute('s_graph_score')))
newprofile.replace_int('d_graph_score', int(request.attribute('d_graph_score')))
newprofile.replace_int('s_auto_scrach', int(request.attribute('s_auto_scrach')))
newprofile.replace_int('d_auto_scrach', int(request.attribute('d_auto_scrach')))
newprofile.replace_int('s_gauge_disp', int(request.attribute('s_gauge_disp')))
newprofile.replace_int('d_gauge_disp', int(request.attribute('d_gauge_disp')))
newprofile.replace_int('s_lane_brignt', int(request.attribute('s_lane_brignt')))
newprofile.replace_int('d_lane_brignt', int(request.attribute('d_lane_brignt')))
newprofile.replace_int('s_camera_layout', int(request.attribute('s_camera_layout')))
newprofile.replace_int('d_camera_layout', int(request.attribute('d_camera_layout')))
newprofile.replace_int('s_lift', int(request.attribute('s_lift')))
newprofile.replace_int('d_lift', int(request.attribute('d_lift')))
newprofile.replace_int('mode', int(request.attribute('mode')))
newprofile.replace_int('pmode', int(request.attribute('pmode')))
newprofile.replace_int('rtype', int(request.attribute('rtype')))
# 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('single', int(request.attribute('s_judgeAdj')))
machine_judge.replace_int('double', int(request.attribute('d_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_array('flg1', 3, secret.child_value('flg1'))
secret_dict.replace_int_array('flg2', 3, secret.child_value('flg2'))
secret_dict.replace_int_array('flg3', 3, 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 20 in profile load.
newprofile.replace_int_array('trophy', 20, trophies.value[:20])
# Deller saving
deller = request.child('deller')
if deller is not None:
newprofile.replace_int('deller', newprofile.get_int('deller') + int(deller.attribute('deller')))
# Secret course expert point saving
expert_point = request.child('expert_point')
if expert_point is not None:
courseid = int(expert_point.attribute('course_id'))
# Update achievement to track expert points
expert_point_achievement = self.data.local.user.get_achievement(
self.game,
self.version,
userid,
courseid,
'expert_point',
)
if expert_point_achievement is None:
expert_point_achievement = ValidatedDict()
expert_point_achievement.replace_int(
'normal_points',
int(expert_point.attribute('n_point')),
)
expert_point_achievement.replace_int(
'hyper_points',
int(expert_point.attribute('h_point')),
)
expert_point_achievement.replace_int(
'another_points',
int(expert_point.attribute('a_point')),
)
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
courseid,
'expert_point',
expert_point_achievement,
)
# Favorites saving
for favorite in request.children:
singles = []
doubles = []
name = None
if favorite.name in ['favorite', 'extra_favorite']:
if favorite.name == 'favorite':
name = 'favorite1'
elif favorite.name == 'extra_favorite':
folder = favorite.attribute('folder_id')
if folder == '0':
name = 'favorite2'
if folder == '1':
name = 'favorite3'
if name is None:
continue
single_music_bin = favorite.child_value('sp_mlist')
single_chart_bin = favorite.child_value('sp_clist')
double_music_bin = favorite.child_value('dp_mlist')
double_chart_bin = favorite.child_value('dp_clist')
for i in range(self.FAVORITE_LIST_LENGTH):
singles.append({
'id': struct.unpack('<L', single_music_bin[(i * 4):((i + 1) * 4)])[0],
'chart': struct.unpack('B', single_chart_bin[i:(i + 1)])[0],
})
doubles.append({
'id': struct.unpack('<L', double_music_bin[(i * 4):((i + 1) * 4)])[0],
'chart': struct.unpack('B', double_chart_bin[i:(i + 1)])[0],
})
# Filter out empty charts
if name is not None:
singles = [single for single in singles if single['id'] != 0]
doubles = [double for double in doubles if double['id'] != 0]
newprofile.replace_dict(
name,
{
'single': singles,
'double': doubles,
},
)
# DJ rank saving
for dj_rank in request.children:
if dj_rank.name != 'dj_rank':
continue
rankid = int(dj_rank.attribute('style'))
rank = dj_rank.child_value('rank')
point = dj_rank.child_value('point')
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
rankid,
'dj_rank',
{
'rank': rank,
'point': point,
}
)
# Gekisou! Cannon racer Event Saving
event1 = request.child('event1')
if event1 is not None:
event1_dict = newprofile.get_dict('event1')
event1_dict.replace_int('tuneup_point', int(event1.attribute('tuneup_point')))
event1_dict.replace_int('body_parts_list', int(event1.attribute('body_parts_list')))
event1_dict.replace_int('engine_parts_list', int(event1.attribute('engine_parts_list')))
event1_dict.replace_int('tire_parts_list', int(event1.attribute('tire_parts_list')))
event1_dict.replace_int('body_equip_parts', int(event1.attribute('body_equip_parts')))
event1_dict.replace_int('engine_equip_parts', int(event1.attribute('engine_equip_parts')))
event1_dict.replace_int('tire_equip_parts', int(event1.attribute('tire_equip_parts')))
event1_dict.replace_int('play_gift', int(event1.attribute('play_gift')))
event1_dict.replace_bool('is_use_gift', event1.child_value('is_use_gift'))
for map_data in event1.children:
if map_data.name != 'map_data':
continue
# Update the gym's progress in the DB
map_id = int(map_data.attribute('map_id'))
play_num = int(map_data.attribute('play_num'))
is_clear = map_data.child_value('is_clear') or False
progress = int(map_data.attribute('progress'))
boost_fuel = int(map_data.attribute('boost_fuel'))
rare_defeat_list = int(map_data.attribute('rare_defeat_list'))
rare1_appearance = int(map_data.attribute('rare1_appearance'))
rare2_appearance = int(map_data.attribute('rare2_appearance'))
rare3_appearance = int(map_data.attribute('rare3_appearance'))
rare4_appearance = int(map_data.attribute('rare4_appearance'))
rare5_appearance = int(map_data.attribute('rare5_appearance'))
rare6_appearance = int(map_data.attribute('rare6_appearance'))
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
map_id,
'map_data',
{
'play_num': play_num,
'progress': progress,
'boost_fuel': boost_fuel,
'rare_defeat_list': rare_defeat_list,
'rare1_appearance': rare1_appearance,
'rare2_appearance': rare2_appearance,
'rare3_appearance': rare3_appearance,
'rare4_appearance': rare4_appearance,
'rare5_appearance': rare5_appearance,
'rare6_appearance': rare6_appearance,
'is_clear': is_clear,
}
)
# Step-up mode
step = request.child('step')
if step is not None:
step_dict = newprofile.get_dict('step')
step_dict.replace_int('enemy_damage', int(step.attribute('enemy_damage')))
step_dict.replace_int('progress', int(step.attribute('progress')))
step_dict.replace_int('point', int(step.attribute('point')))
step_dict.replace_int('enemy_defeat_flg', int(step.attribute('enemy_defeat_flg')))
step_dict.replace_int('sp_level', int(step.attribute('sp_level')))
step_dict.replace_int('dp_level', int(step.attribute('dp_level')))
step_dict.replace_int('sp_mplay', int(step.attribute('sp_mplay')))
step_dict.replace_int('dp_mplay', int(step.attribute('dp_mplay')))
newprofile.replace_dict('step', step_dict)
# QPro equip in step-up mode
qpro_equip = request.child('qpro_equip')
if qpro_equip is not None:
qpro_dict = newprofile.get_dict('qpro')
qpro_dict.replace_int('head', int(qpro_equip.attribute('head')))
qpro_dict.replace_int('hair', int(qpro_equip.attribute('hair')))
qpro_dict.replace_int('face', int(qpro_equip.attribute('face')))
qpro_dict.replace_int('hand', int(qpro_equip.attribute('hand')))
qpro_dict.replace_int('body', int(qpro_equip.attribute('body')))
newprofile.replace_dict('qpro', qpro_dict)
# Qpro secret unlocks in step-up mode
qpro_secret = request.child('qpro_secret')
if qpro_secret is not None:
qpro_secret_dict = newprofile.get_dict('qpro_secret')
qpro_secret_dict.replace_int_array('head', 5, qpro_secret.child_value('head'))
qpro_secret_dict.replace_int_array('hair', 5, qpro_secret.child_value('hair'))
qpro_secret_dict.replace_int_array('face', 5, qpro_secret.child_value('face'))
qpro_secret_dict.replace_int_array('body', 5, qpro_secret.child_value('body'))
qpro_secret_dict.replace_int_array('hand', 5, qpro_secret.child_value('hand'))
newprofile.replace_dict('qpro_secret', qpro_secret_dict)
# Orb data saving
orb_data = request.child('orb_data')
if orb_data is not None:
orbs = newprofile.get_int('orbs')
orbs = orbs + int(orb_data.attribute('add_orb'))
if orb_data.child_value('use_vip_pass'):
orbs = 0
newprofile.replace_int('orbs', orbs)
# OMES Tracking
onemore_data = request.child('onemore_event')
if onemore_data is not None:
omes_dict = newprofile.get_dict('omes')
for i in range(6):
omes_dict.replace_int(f'defeat_{i}', int(onemore_data.attribute(f'defeat_{i}')))
for i in range(3):
i = i + 1
omes_dict.replace_int(f'challenge_num_{i}_n', int(onemore_data.attribute(f'challenge_num_{i}_n')))
omes_dict.replace_int(f'challenge_num_{i}_h', int(onemore_data.attribute(f'challenge_num_{i}_h')))
omes_dict.replace_int(f'challenge_num_{i}_a', int(onemore_data.attribute(f'challenge_num_{i}_a')))
newprofile.replace_dict('omes', omes_dict)
# Keep track of play statistics across all mixes
self.update_play_statistics(userid, play_stats)
return newprofile