1
0
mirror of synced 2024-12-15 15:51:15 +01:00
bemaniutils/bemani/backend/reflec/limelight.py

1122 lines
45 KiB
Python
Raw Normal View History

from typing import Optional, Dict, Any, Tuple
from typing_extensions import Final
from bemani.backend.reflec.base import ReflecBeatBase
from bemani.backend.reflec.reflecbeat import ReflecBeat
from bemani.common import Profile, VersionConstants, ID, Time
from bemani.data import UserID
from bemani.protocol import Node
class ReflecBeatLimelight(ReflecBeatBase):
name: str = "REFLEC BEAT limelight"
version: int = VersionConstants.REFLEC_BEAT_LIMELIGHT
# Clear types according to the game
GAME_CLEAR_TYPE_NO_PLAY: Final[int] = 0
GAME_CLEAR_TYPE_FAILED: Final[int] = 2
GAME_CLEAR_TYPE_CLEARED: Final[int] = 3
GAME_CLEAR_TYPE_FULL_COMBO: Final[int] = 4
# Reflec Beat Limelight requires non-expired profiles to do conversions properly
supports_expired_profiles: bool = False
def previous_version(self) -> Optional[ReflecBeatBase]:
return ReflecBeat(self.data, self.config, self.model)
@classmethod
def get_settings(cls) -> Dict[str, Any]:
"""
Return all of our front-end modifiably settings.
"""
return {
"bools": [
{
"name": "Force Song Unlock",
"tip": "Force unlock all songs.",
"category": "game_config",
"setting": "force_unlock_songs",
},
],
"ints": [],
}
def __db_to_game_clear_type(self, db_clear_type: int, db_combo_type: int) -> int:
if db_clear_type == self.CLEAR_TYPE_NO_PLAY:
return self.GAME_CLEAR_TYPE_NO_PLAY
if db_clear_type == self.CLEAR_TYPE_FAILED:
return self.GAME_CLEAR_TYPE_FAILED
if db_clear_type in [
self.CLEAR_TYPE_CLEARED,
self.CLEAR_TYPE_HARD_CLEARED,
self.CLEAR_TYPE_S_HARD_CLEARED,
]:
if db_combo_type in [
self.COMBO_TYPE_NONE,
self.COMBO_TYPE_ALMOST_COMBO,
]:
return self.GAME_CLEAR_TYPE_CLEARED
if db_combo_type in [
self.COMBO_TYPE_FULL_COMBO,
self.COMBO_TYPE_FULL_COMBO_ALL_JUST,
]:
return self.GAME_CLEAR_TYPE_FULL_COMBO
raise Exception(f"Invalid db_combo_type {db_combo_type}")
raise Exception(f"Invalid db_clear_type {db_clear_type}")
def __game_to_db_clear_type(self, game_clear_type: int) -> Tuple[int, int]:
if game_clear_type == self.GAME_CLEAR_TYPE_NO_PLAY:
return (self.CLEAR_TYPE_NO_PLAY, self.COMBO_TYPE_NONE)
if game_clear_type == self.GAME_CLEAR_TYPE_FAILED:
return (self.CLEAR_TYPE_FAILED, self.COMBO_TYPE_NONE)
if game_clear_type == self.GAME_CLEAR_TYPE_CLEARED:
return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_NONE)
if game_clear_type == self.GAME_CLEAR_TYPE_FULL_COMBO:
return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_FULL_COMBO)
raise Exception(f"Invalid game_clear_type {game_clear_type}")
def handle_log_exception_request(self, request: Node) -> Node:
return Node.void("log")
def handle_log_pcb_status_request(self, request: Node) -> Node:
return Node.void("log")
def handle_log_opsetting_request(self, request: Node) -> Node:
return Node.void("log")
def handle_log_play_request(self, request: Node) -> Node:
return Node.void("log")
def handle_pcbinfo_get_request(self, request: Node) -> Node:
shop_id = ID.parse_machine_id(request.child_value("lid"))
machine = self.get_machine_by_id(shop_id)
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")
pref = machine.data.get_int("pref", self.get_machine_region())
else:
machine_name = ""
close = False
hour = 0
minute = 0
pref = self.get_machine_region()
root = Node.void("pcbinfo")
info = Node.void("info")
root.add_child(info)
info.add_child(Node.string("name", machine_name))
info.add_child(Node.s16("pref", pref))
info.add_child(Node.bool("close", close))
info.add_child(Node.u8("hour", hour))
info.add_child(Node.u8("min", minute))
return root
def handle_pcbinfo_set_request(self, request: Node) -> Node:
self.update_machine_name(request.child_value("info/name"))
self.update_machine_data(
{
"close": request.child_value("info/close"),
"hour": request.child_value("info/hour"),
"minute": request.child_value("info/min"),
"pref": request.child_value("info/pref"),
}
)
return Node.void("pcbinfo")
def __add_event_info(self, request: Node) -> None:
events: Dict[int, int] = {}
for _eventid, _phase in events.items():
data = Node.void("data")
request.add_child(data)
data.add_child(Node.s32("type", -1))
data.add_child(Node.s32("value", -1))
def handle_sysinfo_get_request(self, request: Node) -> Node:
root = Node.void("sysinfo")
trd = Node.void("trd")
root.add_child(trd)
# Add event info
self.__add_event_info(trd)
return root
def handle_ranking_read_request(self, request: Node) -> Node:
root = Node.void("ranking")
licenses = Node.void("lic_10")
root.add_child(licenses)
originals = Node.void("org_10")
root.add_child(originals)
licenses.add_child(Node.time("time", Time.now()))
originals.add_child(Node.time("time", Time.now()))
hitchart = self.data.local.music.get_hit_chart(self.game, self.version, 10)
rank = 1
for mid, _plays in hitchart:
record = Node.void("record")
originals.add_child(record)
record.add_child(Node.s16("id", mid))
record.add_child(Node.s16("rank", rank))
rank = rank + 1
return root
def handle_event_r_get_all_request(self, request: Node) -> Node:
limit = request.child_value("limit")
comments = [
achievement
for achievement in self.data.local.user.get_all_time_based_achievements(
self.game, self.version
)
if achievement[1].type == "puzzle_comment"
]
comments.sort(key=lambda x: x[1].timestamp, reverse=True)
statuses = self.data.local.lobby.get_all_play_session_infos(
self.game, self.version
)
statuses.sort(key=lambda x: x[1]["time"], reverse=True)
# Cap all comment blocks to the limit
if limit >= 0:
comments = comments[:limit]
statuses = statuses[:limit]
# Mapping of profiles to userIDs
uid_mapping = {
uid: prof
for (uid, prof) in self.get_any_profiles(
[c[0] for c in comments] + [s[0] for s in statuses]
)
}
# Mapping of location ID to machine name
lid_mapping: Dict[int, str] = {}
root = Node.void("event_r")
root.add_child(Node.s32("time", Time.now()))
statusnode = Node.void("status")
root.add_child(statusnode)
commentnode = Node.void("comment")
root.add_child(commentnode)
for uid, comment in comments:
lid = ID.parse_machine_id(comment.data.get_str("lid"))
# Look up external data for the request
if lid not in lid_mapping:
machine = self.get_machine_by_id(lid)
if machine is not None:
lid_mapping[lid] = machine.name
else:
lid_mapping[lid] = ""
c = Node.void("c")
commentnode.add_child(c)
c.add_child(Node.s32("uid", uid_mapping[uid].extid))
c.add_child(Node.string("p_name", uid_mapping[uid].get_str("name")))
c.add_child(Node.s32("exp", uid_mapping[uid].get_int("exp")))
c.add_child(Node.s32("customize", comment.data.get_int("customize")))
c.add_child(Node.s32("tid", comment.data.get_int("teamid")))
c.add_child(Node.string("t_name", comment.data.get_str("teamname")))
c.add_child(Node.string("lid", comment.data.get_str("lid")))
c.add_child(Node.string("s_name", lid_mapping[lid]))
c.add_child(Node.s8("pref", comment.data.get_int("prefecture")))
c.add_child(Node.s32("time", comment.timestamp))
c.add_child(Node.string("comment", comment.data.get_str("comment")))
c.add_child(Node.bool("is_tweet", comment.data.get_bool("tweet")))
for uid, status in statuses:
lid = ID.parse_machine_id(status.get_str("lid"))
# Look up external data for the request
if lid not in lid_mapping:
machine = self.get_machine_by_id(lid)
if machine is not None:
lid_mapping[lid] = machine.name
else:
lid_mapping[lid] = ""
s = Node.void("s")
statusnode.add_child(s)
s.add_child(Node.s32("uid", uid_mapping[uid].extid))
s.add_child(Node.string("p_name", uid_mapping[uid].get_str("name")))
s.add_child(Node.s32("exp", uid_mapping[uid].get_int("exp")))
s.add_child(Node.s32("customize", status.get_int("customize")))
s.add_child(Node.s32("tid", uid_mapping[uid].get_int("team_id", -1)))
s.add_child(
Node.string("t_name", uid_mapping[uid].get_str("team_name", ""))
)
s.add_child(Node.string("lid", status.get_str("lid")))
s.add_child(Node.string("s_name", lid_mapping[lid]))
s.add_child(Node.s8("pref", status.get_int("prefecture")))
s.add_child(Node.s32("time", status.get_int("time")))
s.add_child(Node.s8("status", status.get_int("status")))
s.add_child(Node.s8("stage", status.get_int("stage")))
s.add_child(Node.s32("mid", status.get_int("mid")))
s.add_child(Node.s8("ng", status.get_int("ng")))
return root
def handle_event_w_add_comment_request(self, request: Node) -> Node:
extid = request.child_value("uid")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is None:
# Anonymous comment
userid = UserID(0)
customize = request.child_value("customize")
lid = request.child_value("lid")
teamid = request.child_value("tid")
teamname = request.child_value("t_name")
prefecture = request.child_value("pref")
comment = request.child_value("comment")
is_tweet = request.child_value("is_tweet")
# Link comment to user's profile
self.data.local.user.put_time_based_achievement(
self.game,
self.version,
userid,
0, # We never have an ID for this, since comments are add-only
"puzzle_comment",
{
"customize": customize,
"lid": lid,
"teamid": teamid,
"teamname": teamname,
"prefecture": prefecture,
"comment": comment,
"tweet": is_tweet,
},
)
return Node.void("event_w")
def handle_event_w_update_status_request(self, request: Node) -> Node:
# Update user status so puzzle comments can show it
extid = request.child_value("uid")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
customize = request.child_value("customize")
status = request.child_value("status")
stage = request.child_value("stage")
mid = request.child_value("mid")
ng = request.child_value("ng")
lid = request.child_value("lid")
prefecture = request.child_value("pref")
self.data.local.lobby.put_play_session_info(
self.game,
self.version,
userid,
{
"customize": customize,
"status": status,
"stage": stage,
"mid": mid,
"ng": ng,
"lid": lid,
"prefecture": prefecture,
},
)
return Node.void("event_w")
def handle_lobby_entry_request(self, request: Node) -> Node:
root = Node.void("lobby")
root.add_child(Node.s32("interval", 120))
root.add_child(Node.s32("interval_p", 120))
# Create a lobby entry for this user
extid = request.child_value("e/uid")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
profile = self.get_profile(userid)
self.data.local.lobby.put_lobby(
self.game,
self.version,
userid,
{
"mid": request.child_value("e/mid"),
"ng": request.child_value("e/ng"),
"mopt": request.child_value("e/mopt"),
"tid": request.child_value("e/tid"),
"tn": request.child_value("e/tn"),
"topt": request.child_value("e/topt"),
"lid": request.child_value("e/lid"),
"sn": request.child_value("e/sn"),
"pref": request.child_value("e/pref"),
"stg": request.child_value("e/stg"),
"pside": request.child_value("e/pside"),
"eatime": request.child_value("e/eatime"),
"ga": request.child_value("e/ga"),
"gp": request.child_value("e/gp"),
"la": request.child_value("e/la"),
},
)
lobby = self.data.local.lobby.get_lobby(
self.game,
self.version,
userid,
)
root.add_child(Node.s32("eid", lobby.get_int("id")))
e = Node.void("e")
root.add_child(e)
e.add_child(Node.s32("eid", lobby.get_int("id")))
e.add_child(Node.u16("mid", lobby.get_int("mid")))
e.add_child(Node.u8("ng", lobby.get_int("ng")))
e.add_child(Node.s32("uid", profile.extid))
e.add_child(Node.string("pn", profile.get_str("name")))
e.add_child(Node.s32("uattr", profile.get_int("uattr")))
e.add_child(Node.s32("mopt", lobby.get_int("mopt")))
e.add_child(Node.s16("mg", profile.get_int("mg")))
e.add_child(Node.s32("tid", lobby.get_int("tid")))
e.add_child(Node.string("tn", lobby.get_str("tn")))
e.add_child(Node.s32("topt", lobby.get_int("topt")))
e.add_child(Node.string("lid", lobby.get_str("lid")))
e.add_child(Node.string("sn", lobby.get_str("sn")))
e.add_child(Node.u8("pref", lobby.get_int("pref")))
e.add_child(Node.s8("stg", lobby.get_int("stg")))
e.add_child(Node.s8("pside", lobby.get_int("pside")))
e.add_child(Node.s16("eatime", lobby.get_int("eatime")))
e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4)))
e.add_child(Node.u16("gp", lobby.get_int("gp")))
e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4)))
return root
def handle_lobby_read_request(self, request: Node) -> Node:
root = Node.void("lobby")
root.add_child(Node.s32("interval", 120))
root.add_child(Node.s32("interval_p", 120))
# Look up all lobbies matching the criteria specified
mg = request.child_value("m_grade") # noqa: F841
extid = request.child_value("uid")
limit = request.child_value("max")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version)
for user, lobby in lobbies:
if limit <= 0:
break
if user == userid:
# If we have our own lobby, don't return it
continue
profile = self.get_profile(user)
if profile is None:
# No profile info, don't return this lobby
continue
e = Node.void("e")
root.add_child(e)
e.add_child(Node.s32("eid", lobby.get_int("id")))
e.add_child(Node.u16("mid", lobby.get_int("mid")))
e.add_child(Node.u8("ng", lobby.get_int("ng")))
e.add_child(Node.s32("uid", profile.extid))
e.add_child(Node.string("pn", profile.get_str("name")))
e.add_child(Node.s32("uattr", profile.get_int("uattr")))
e.add_child(Node.s32("mopt", lobby.get_int("mopt")))
e.add_child(Node.s16("mg", profile.get_int("mg")))
e.add_child(Node.s32("tid", lobby.get_int("tid")))
e.add_child(Node.string("tn", lobby.get_str("tn")))
e.add_child(Node.s32("topt", lobby.get_int("topt")))
e.add_child(Node.string("lid", lobby.get_str("lid")))
e.add_child(Node.string("sn", lobby.get_str("sn")))
e.add_child(Node.u8("pref", lobby.get_int("pref")))
e.add_child(Node.s8("stg", lobby.get_int("stg")))
e.add_child(Node.s8("pside", lobby.get_int("pside")))
e.add_child(Node.s16("eatime", lobby.get_int("eatime")))
e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4)))
e.add_child(Node.u16("gp", lobby.get_int("gp")))
e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4)))
limit = limit - 1
return root
def handle_lobby_delete_request(self, request: Node) -> Node:
eid = request.child_value("eid")
self.data.local.lobby.destroy_lobby(eid)
return Node.void("lobby")
def handle_player_start_request(self, request: Node) -> Node:
# Add a dummy entry into the lobby setup so we can clean up on end play
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
self.data.local.lobby.put_play_session_info(
self.game, self.version, userid, {}
)
root = Node.void("player")
root.add_child(Node.bool("is_suc", True))
unlock_music = Node.void("unlock_music")
root.add_child(unlock_music)
unlock_item = Node.void("unlock_item")
root.add_child(unlock_item)
item_lock_ctrl = Node.void("item_lock_ctrl")
root.add_child(item_lock_ctrl)
lincle_link_4 = Node.void("lincle_link_4")
root.add_child(lincle_link_4)
lincle_link_4.add_child(Node.u32("qpro", 0))
lincle_link_4.add_child(Node.u32("glass", 0))
lincle_link_4.add_child(Node.u32("treasure", 0))
lincle_link_4.add_child(Node.bool("for_iidx_0_0", False))
lincle_link_4.add_child(Node.bool("for_iidx_0_1", False))
lincle_link_4.add_child(Node.bool("for_iidx_0_2", False))
lincle_link_4.add_child(Node.bool("for_iidx_0_3", False))
lincle_link_4.add_child(Node.bool("for_iidx_0_4", False))
lincle_link_4.add_child(Node.bool("for_iidx_0_5", False))
lincle_link_4.add_child(Node.bool("for_iidx_0_6", False))
lincle_link_4.add_child(Node.bool("for_iidx_0", False))
lincle_link_4.add_child(Node.bool("for_iidx_1", False))
lincle_link_4.add_child(Node.bool("for_iidx_2", False))
lincle_link_4.add_child(Node.bool("for_iidx_3", False))
lincle_link_4.add_child(Node.bool("for_iidx_4", False))
lincle_link_4.add_child(Node.bool("for_rb_0_0", False))
lincle_link_4.add_child(Node.bool("for_rb_0_1", False))
lincle_link_4.add_child(Node.bool("for_rb_0_2", False))
lincle_link_4.add_child(Node.bool("for_rb_0_3", False))
lincle_link_4.add_child(Node.bool("for_rb_0_4", False))
lincle_link_4.add_child(Node.bool("for_rb_0_5", False))
lincle_link_4.add_child(Node.bool("for_rb_0_6", False))
lincle_link_4.add_child(Node.bool("for_rb_0", False))
lincle_link_4.add_child(Node.bool("for_rb_1", False))
lincle_link_4.add_child(Node.bool("for_rb_2", False))
lincle_link_4.add_child(Node.bool("for_rb_3", False))
lincle_link_4.add_child(Node.bool("for_rb_4", False))
lincle_link_4.add_child(Node.bool("qproflg", False))
lincle_link_4.add_child(Node.bool("glassflg", False))
lincle_link_4.add_child(Node.bool("complete", False))
# Add event info
self.__add_event_info(root)
return root
def handle_player_delete_request(self, request: Node) -> Node:
return Node.void("player")
def handle_player_end_request(self, request: Node) -> Node:
# Destroy play session based on info from the request
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
# Kill any lingering lobbies by this user
lobby = self.data.local.lobby.get_lobby(
self.game,
self.version,
userid,
)
if lobby is not None:
self.data.local.lobby.destroy_lobby(lobby.get_int("id"))
self.data.local.lobby.destroy_play_session_info(
self.game, self.version, userid
)
return Node.void("player")
def handle_player_read_request(self, request: Node) -> Node:
refid = request.child_value("rid")
profile = self.get_profile_by_refid(refid)
if profile:
return profile
return Node.void("player")
def handle_player_write_request(self, request: Node) -> Node:
refid = request.child_value("rid")
profile = self.put_profile_by_refid(refid, request)
root = Node.void("player")
if profile is None:
root.add_child(Node.s32("uid", 0))
else:
root.add_child(Node.s32("uid", profile.extid))
root.add_child(Node.s32("time", Time.now()))
return root
def handle_player_succeed_request(self, request: Node) -> Node:
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
previous_version = self.previous_version()
profile = previous_version.get_profile(userid)
achievements = self.data.local.user.get_achievements(
previous_version.game, previous_version.version, userid
)
scores = self.data.remote.music.get_scores(
previous_version.game, previous_version.version, userid
)
else:
profile = None
root = Node.void("player")
if profile is None:
root.add_child(Node.string("name", ""))
root.add_child(Node.s32("lv", -1))
root.add_child(Node.s32("exp", -1))
root.add_child(Node.s32("grade", -1))
root.add_child(Node.s32("ap", -1))
root.add_child(Node.void("released"))
root.add_child(Node.void("mrecord"))
else:
root.add_child(Node.string("name", profile.get_str("name")))
root.add_child(Node.s32("lv", profile.get_int("lvl")))
root.add_child(Node.s32("exp", profile.get_int("exp")))
root.add_child(Node.s32("grade", profile.get_int("mg"))) # This is a guess
root.add_child(Node.s32("ap", profile.get_int("ap")))
released = Node.void("released")
root.add_child(released)
for item in achievements:
if item.type != "item_0":
continue
released.add_child(Node.s16("i", item.id))
mrecord = Node.void("mrecord")
root.add_child(mrecord)
for score in scores:
mrec = Node.void("mrec")
mrecord.add_child(mrec)
mrec.add_child(Node.s32("mid", score.id))
mrec.add_child(Node.s32("ctype", score.chart))
mrec.add_child(
Node.s32("win", score.data.get_dict("stats").get_int("win"))
)
mrec.add_child(
Node.s32("lose", score.data.get_dict("stats").get_int("win"))
)
mrec.add_child(
Node.s32("draw", score.data.get_dict("stats").get_int("win"))
)
mrec.add_child(Node.s32("score", score.points))
mrec.add_child(Node.s32("combo", score.data.get_int("combo")))
mrec.add_child(Node.s32("miss", score.data.get_int("miss_count")))
mrec.add_child(
Node.s32(
"grade",
self.__db_to_game_clear_type(
score.data.get_int("clear_type"),
score.data.get_int("combo_type"),
),
)
)
mrec.add_child(Node.s32("ap", score.data.get_int("achievement_rate")))
return root
def format_profile(self, userid: UserID, profile: Profile) -> Node:
statistics = self.get_play_statistics(userid)
game_config = self.get_game_config()
achievements = self.data.local.user.get_achievements(
self.game, self.version, userid
)
scores = self.data.remote.music.get_scores(self.game, self.version, userid)
links = self.data.local.user.get_links(self.game, self.version, userid)
root = Node.void("player")
pdata = Node.void("pdata")
root.add_child(pdata)
base = Node.void("base")
pdata.add_child(base)
base.add_child(Node.s32("uid", profile.extid))
base.add_child(Node.string("name", profile.get_str("name")))
base.add_child(Node.s16("icon_id", profile.get_int("icon")))
base.add_child(Node.s16("lv", profile.get_int("lvl")))
base.add_child(Node.s32("exp", profile.get_int("exp")))
base.add_child(Node.s16("mg", profile.get_int("mg")))
base.add_child(Node.s16("ap", profile.get_int("ap")))
base.add_child(Node.s32("pc", profile.get_int("pc")))
base.add_child(Node.s32("uattr", profile.get_int("uattr")))
con = Node.void("con")
pdata.add_child(con)
con.add_child(Node.s32("day", statistics.today_plays))
con.add_child(Node.s32("cnt", statistics.total_plays))
con.add_child(Node.s32("total_cnt", statistics.total_plays))
con.add_child(Node.s32("last", statistics.last_play_timestamp))
con.add_child(Node.s32("now", Time.now()))
team = Node.void("team")
pdata.add_child(team)
team.add_child(Node.s32("id", profile.get_int("team_id", -1)))
team.add_child(Node.string("name", profile.get_str("team_name", "")))
custom = Node.void("custom")
customdict = profile.get_dict("custom")
pdata.add_child(custom)
custom.add_child(Node.u8("s_gls", customdict.get_int("s_gls")))
custom.add_child(Node.u8("bgm_m", customdict.get_int("bgm_m")))
custom.add_child(Node.u8("st_f", customdict.get_int("st_f")))
custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg")))
custom.add_child(Node.u8("st_bg_b", customdict.get_int("st_bg_b")))
custom.add_child(Node.u8("eff_e", customdict.get_int("eff_e")))
custom.add_child(Node.u8("se_s", customdict.get_int("se_s")))
custom.add_child(Node.u8("se_s_v", customdict.get_int("se_s_v")))
custom.add_child(Node.s16("last_music_id", customdict.get_int("last_music_id")))
custom.add_child(
Node.u8("last_note_grade", customdict.get_int("last_note_grade"))
)
custom.add_child(Node.u8("sort_type", customdict.get_int("sort_type")))
custom.add_child(
Node.u8("narrowdown_type", customdict.get_int("narrowdown_type"))
)
custom.add_child(
Node.bool("is_begginer", customdict.get_bool("is_begginer"))
) # Yes, this is spelled right
custom.add_child(Node.bool("is_tut", customdict.get_bool("is_tut")))
custom.add_child(
Node.s16_array(
"symbol_chat_0", customdict.get_int_array("symbol_chat_0", 6)
)
)
custom.add_child(
Node.s16_array(
"symbol_chat_1", customdict.get_int_array("symbol_chat_1", 6)
)
)
custom.add_child(Node.u8("gauge_style", customdict.get_int("gauge_style")))
custom.add_child(Node.u8("obj_shade", customdict.get_int("obj_shade")))
custom.add_child(Node.u8("obj_size", customdict.get_int("obj_size")))
custom.add_child(
Node.s16_array("byword", customdict.get_int_array("byword", 2))
)
custom.add_child(
Node.bool_array(
"is_auto_byword", customdict.get_bool_array("is_auto_byword", 2)
)
)
custom.add_child(Node.bool("is_tweet", customdict.get_bool("is_tweet")))
custom.add_child(
Node.bool("is_link_twitter", customdict.get_bool("is_link_twitter"))
)
custom.add_child(Node.s16("mrec_type", customdict.get_int("mrec_type")))
custom.add_child(
Node.s16("card_disp_type", customdict.get_int("card_disp_type"))
)
custom.add_child(Node.s16("tab_sel", customdict.get_int("tab_sel")))
custom.add_child(
Node.s32_array("hidden_param", customdict.get_int_array("hidden_param", 20))
)
released = Node.void("released")
pdata.add_child(released)
for item in achievements:
if item.type[:5] != "item_":
continue
itemtype = int(item.type[5:])
if game_config.get_bool("force_unlock_songs") and itemtype == 0:
# Don't echo unlocks when we're force unlocking, we'll do it later
continue
info = Node.void("info")
released.add_child(info)
info.add_child(Node.u8("type", itemtype))
info.add_child(Node.u16("id", item.id))
info.add_child(Node.u16("param", item.data.get_int("param")))
if game_config.get_bool("force_unlock_songs"):
ids: Dict[int, int] = {}
songs = self.data.local.music.get_all_songs(self.game, self.version)
for song in songs:
if song.id not in ids:
ids[song.id] = 0
if song.data.get_int("difficulty") > 0:
ids[song.id] = ids[song.id] | (1 << song.chart)
for songid in ids:
if ids[songid] == 0:
continue
info = Node.void("info")
released.add_child(info)
info.add_child(Node.u8("type", 0))
info.add_child(Node.u16("id", songid))
info.add_child(Node.u16("param", ids[songid]))
# Scores
record = Node.void("record")
pdata.add_child(record)
for score in scores:
rec = Node.void("rec")
record.add_child(rec)
rec.add_child(Node.u16("mid", score.id))
rec.add_child(Node.u8("ng", score.chart))
rec.add_child(
Node.s32("point", score.data.get_dict("stats").get_int("earned_points"))
)
rec.add_child(Node.s32("played_time", score.timestamp))
mrec_0 = Node.void("mrec_0")
rec.add_child(mrec_0)
mrec_0.add_child(
Node.s32("win", score.data.get_dict("stats").get_int("win"))
)
mrec_0.add_child(
Node.s32("lose", score.data.get_dict("stats").get_int("lose"))
)
mrec_0.add_child(
Node.s32("draw", score.data.get_dict("stats").get_int("draw"))
)
mrec_0.add_child(
Node.u8(
"ct",
self.__db_to_game_clear_type(
score.data.get_int("clear_type"),
score.data.get_int("combo_type"),
),
)
)
mrec_0.add_child(
Node.s16("ar", int(score.data.get_int("achievement_rate") / 10))
)
mrec_0.add_child(Node.s32("bs", score.points))
mrec_0.add_child(Node.s16("mc", score.data.get_int("combo")))
mrec_0.add_child(Node.s16("bmc", score.data.get_int("miss_count")))
mrec_1 = Node.void("mrec_1")
rec.add_child(mrec_1)
mrec_1.add_child(Node.s32("win", 0))
mrec_1.add_child(Node.s32("lose", 0))
mrec_1.add_child(Node.s32("draw", 0))
mrec_1.add_child(Node.u8("ct", 0))
mrec_1.add_child(Node.s16("ar", 0))
mrec_1.add_child(Node.s32("bs", 0))
mrec_1.add_child(Node.s16("mc", 0))
mrec_1.add_child(Node.s16("bmc", -1))
# Comment (seems unused?)
pdata.add_child(Node.string("cmnt", ""))
# Rivals
rival = Node.void("rival")
pdata.add_child(rival)
slotid = 0
for link in links:
if link.type != "rival":
continue
rprofile = self.get_profile(link.other_userid)
if rprofile is None:
continue
r = Node.void("r")
rival.add_child(r)
r.add_child(Node.u8("slot_id", slotid))
r.add_child(Node.string("name", rprofile.get_str("name")))
r.add_child(Node.s32("id", rprofile.extid))
r.add_child(Node.bool("friend", True))
r.add_child(Node.bool("locked", False))
r.add_child(Node.s32("rc", 0))
slotid = slotid + 1
# Glass points
glass = Node.void("glass")
pdata.add_child(glass)
for item in achievements:
if item.type != "glass":
continue
g = Node.void("g")
glass.add_child(g)
g.add_child(Node.s32("id", item.id))
g.add_child(Node.s32("exp", item.data.get_int("exp")))
# Favorite music
fav_music_slot = Node.void("fav_music_slot")
pdata.add_child(fav_music_slot)
for item in achievements:
if item.type != "music":
continue
slot = Node.void("slot")
fav_music_slot.add_child(slot)
slot.add_child(Node.u8("slot_id", item.id))
slot.add_child(Node.s16("music_id", item.data.get_int("music_id")))
narrow_down = Node.void("narrow_down")
pdata.add_child(narrow_down)
narrow_down.add_child(
Node.s32_array(
"adv_param",
[
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
],
)
)
return root
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
game_config = self.get_game_config()
newprofile = oldprofile.clone()
newprofile.replace_int("lid", ID.parse_machine_id(request.child_value("lid")))
newprofile.replace_str("name", request.child_value("pdata/base/name"))
newprofile.replace_int("icon", request.child_value("pdata/base/icon_id"))
newprofile.replace_int("lvl", request.child_value("pdata/base/lv"))
newprofile.replace_int("exp", request.child_value("pdata/base/exp"))
newprofile.replace_int("mg", request.child_value("pdata/base/mg"))
newprofile.replace_int("ap", request.child_value("pdata/base/ap"))
newprofile.replace_int("pc", request.child_value("pdata/base/pc"))
newprofile.replace_int("uattr", request.child_value("pdata/base/uattr"))
customdict = newprofile.get_dict("custom")
custom = request.child("pdata/custom")
if custom:
customdict.replace_int("s_gls", custom.child_value("s_gls"))
customdict.replace_int("bgm_m", custom.child_value("bgm_m"))
customdict.replace_int("st_f", custom.child_value("st_f"))
customdict.replace_int("st_bg", custom.child_value("st_bg"))
customdict.replace_int("st_bg_b", custom.child_value("st_bg_b"))
customdict.replace_int("eff_e", custom.child_value("eff_e"))
customdict.replace_int("se_s", custom.child_value("se_s"))
customdict.replace_int("se_s_v", custom.child_value("se_s_v"))
customdict.replace_int("last_music_id", custom.child_value("last_music_id"))
customdict.replace_int(
"last_note_grade", custom.child_value("last_note_grade")
)
customdict.replace_int("sort_type", custom.child_value("sort_type"))
customdict.replace_int(
"narrowdown_type", custom.child_value("narrowdown_type")
)
customdict.replace_bool(
"is_begginer", custom.child_value("is_begginer")
) # Yes, this is spelled right
customdict.replace_bool("is_tut", custom.child_value("is_tut"))
customdict.replace_int_array(
"symbol_chat_0", 6, custom.child_value("symbol_chat_0")
)
customdict.replace_int_array(
"symbol_chat_1", 6, custom.child_value("symbol_chat_1")
)
customdict.replace_int("gauge_style", custom.child_value("gauge_style"))
customdict.replace_int("obj_shade", custom.child_value("obj_shade"))
customdict.replace_int("obj_size", custom.child_value("obj_size"))
customdict.replace_int_array("byword", 2, custom.child_value("byword"))
customdict.replace_bool_array(
"is_auto_byword", 2, custom.child_value("is_auto_byword")
)
customdict.replace_bool("is_tweet", custom.child_value("is_tweet"))
customdict.replace_bool(
"is_link_twitter", custom.child_value("is_link_twitter")
)
customdict.replace_int("mrec_type", custom.child_value("mrec_type"))
customdict.replace_int(
"card_disp_type", custom.child_value("card_disp_type")
)
customdict.replace_int("tab_sel", custom.child_value("tab_sel"))
customdict.replace_int_array(
"hidden_param", 20, custom.child_value("hidden_param")
)
newprofile.replace_dict("custom", customdict)
# Music unlocks and other stuff
released = request.child("pdata/released")
if released:
for child in released.children:
if child.name != "info":
continue
item_id = child.child_value("id")
item_type = child.child_value("type")
param = child.child_value("param")
if game_config.get_bool("force_unlock_songs") and item_type == 0:
# Don't save unlocks when we're force unlocking
continue
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
item_id,
f"item_{item_type}",
{
"param": param,
},
)
# Grab any new records set during this play session. Reflec Beat Limelight only sends
# the top record back for songs that were played at least once during the session.
# Note that it sends the top record, so if you play the song twice, it will return
# only one record. Also, if you get a lower score than a previous try, it will return
# the previous try. So, we must also look at the battle log for the actual play scores,
# and combine the data if we can.
savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {}
songplays = request.child("pdata/record")
if songplays:
for child in songplays.children:
if child.name != "rec":
continue
songid = child.child_value("mid")
chart = child.child_value("ng")
# These don't get sent with the battle logs, so we try to construct
# the values here.
if songid not in savedrecords:
savedrecords[songid] = {}
savedrecords[songid][chart] = {
"achievement_rate": child.child_value("mrec_0/ar") * 10,
"points": child.child_value("mrec_0/bs"),
"combo": child.child_value("mrec_0/mc"),
"miss_count": child.child_value("mrec_0/bmc"),
"win": child.child_value("mrec_0/win"),
"lose": child.child_value("mrec_0/lose"),
"draw": child.child_value("mrec_0/draw"),
"earned_points": child.child_value("point"),
}
# Now, see the actual battles that were played. If we can, unify the data with a record.
# We only do that when the record achievement rate and score matches the battle achievement
# rate and score, so we know for a fact that that record was generated by this battle.
battlelogs = request.child("pdata/blog")
if battlelogs:
for child in battlelogs.children:
if child.name != "log":
continue
songid = child.child_value("mid")
chart = child.child_value("ng")
clear_type = child.child_value("myself/ct")
achievement_rate = child.child_value("myself/ar") * 10
points = child.child_value("myself/s")
clear_type, combo_type = self.__game_to_db_clear_type(clear_type)
combo = None
miss_count = -1
stats = None
if songid in savedrecords:
if chart in savedrecords[songid]:
data = savedrecords[songid][chart]
if (
data["achievement_rate"] == achievement_rate
and data["points"] == points
):
# This is the same record! Use the stats from it to update our
# internal representation.
combo = data["combo"]
miss_count = data["miss_count"]
stats = {
"win": data["win"],
"lose": data["lose"],
"draw": data["draw"],
"earned_points": data["earned_points"],
}
self.update_score(
userid,
songid,
chart,
points,
achievement_rate,
clear_type,
combo_type,
miss_count,
combo=combo,
stats=stats,
)
# Keep track of glass points so unlocks work
glass = request.child("pdata/glass")
if glass:
for child in glass.children:
if child.name != "g":
continue
gid = child.child_value("id")
exp = child.child_value("exp")
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
gid,
"glass",
{
"exp": exp,
},
)
# Keep track of favorite music selections
fav_music_slot = request.child("pdata/fav_music_slot")
if fav_music_slot:
for child in fav_music_slot.children:
if child.name != "slot":
continue
slot_id = child.child_value("slot_id")
music_id = child.child_value("music_id")
if music_id == -1:
# Delete this favorite
self.data.local.user.destroy_achievement(
self.game,
self.version,
userid,
slot_id,
"music",
)
else:
# Add/update this favorite
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
slot_id,
"music",
{
"music_id": music_id,
},
)
# Keep track of play statistics
self.update_play_statistics(userid)
return newprofile