1
0
mirror of synced 2024-12-11 22:15:59 +01:00
bemaniutils/bemani/backend/ddr/common.py

510 lines
18 KiB
Python

from typing import Dict, Optional, Tuple
from bemani.backend.ddr.base import DDRBase
from bemani.common import Profile, intish
from bemani.data import Score, UserID
from bemani.protocol import Node
class DDRGameShopHandler(DDRBase):
def handle_game_shop_request(self, request: Node) -> Node:
self.update_machine_name(request.attribute("name"))
game = Node.void("game")
game.set_attribute("stop", "0")
return game
class DDRGameLogHandler(DDRBase):
def handle_game_log_request(self, request: Node) -> Node:
return Node.void("game")
class DDRGameMessageHandler(DDRBase):
def handle_game_message_request(self, request: Node) -> Node:
return Node.void("game")
class DDRGameRankingHandler(DDRBase):
def handle_game_ranking_request(self, request: Node) -> Node:
# Ranking request, unknown what its for
return Node.void("game")
class DDRGameLockHandler(DDRBase):
def handle_game_lock_request(self, request: Node) -> Node:
game = Node.void("game")
game.set_attribute("now_login", "0")
return game
class DDRGameTaxInfoHandler(DDRBase):
def handle_game_tax_info_request(self, request: Node) -> Node:
game = Node.void("game")
tax_info = Node.void("tax_info")
game.add_child(tax_info)
tax_info.set_attribute("tax_phase", "0")
return game
class DDRGameRecorderHandler(DDRBase):
def handle_game_recorder_request(self, request: Node) -> Node:
return Node.void("game")
class DDRGameHiscoreHandler(DDRBase):
def handle_game_hiscore_request(self, request: Node) -> Node:
records = self.data.remote.music.get_all_records(self.game, self.music_version)
sortedrecords: Dict[int, Dict[int, Tuple[UserID, Score]]] = {}
missing_profiles = []
for (userid, score) in records:
if score.id not in sortedrecords:
sortedrecords[score.id] = {}
sortedrecords[score.id][score.chart] = (userid, score)
missing_profiles.append(userid)
users = {
userid: profile
for (userid, profile) in self.get_any_profiles(missing_profiles)
}
game = Node.void("game")
for song in sortedrecords:
music = Node.void("music")
game.add_child(music)
music.set_attribute("reclink_num", str(song))
for chart in sortedrecords[song]:
userid, score = sortedrecords[song][chart]
try:
gamechart = self.db_to_game_chart(chart)
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.string("name", users[userid].get_str("name")))
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(
Node.u16(
"area", users[userid].get_int("area", self.get_machine_region())
)
)
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
typenode.add_child(Node.u32("code", users[userid].extid))
return game
class DDRGameAreaHiscoreHandler(DDRBase):
def handle_game_area_hiscore_request(self, request: Node) -> Node:
shop_area = int(request.attribute("shop_area"))
# First, get all users that are in the current shop's area
area_users = {
uid: prof
for (uid, prof) in self.data.local.user.get_all_profiles(
self.game, self.version
)
if prof.get_int("area", self.get_machine_region()) == shop_area
}
# Second, look up records belonging only to those users
records = self.data.local.music.get_all_records(
self.game, self.music_version, userlist=list(area_users.keys())
)
# Now, do the same lazy thing as 'hiscore' because I don't want
# to think about how to change this knowing that we only pulled
# up area records.
area_records: Dict[int, Dict[int, Tuple[UserID, Score]]] = {}
for (userid, score) in records:
if score.id not in area_records:
area_records[score.id] = {}
area_records[score.id][score.chart] = (userid, score)
game = Node.void("game")
for song in area_records:
music = Node.void("music")
game.add_child(music)
music.set_attribute("reclink_num", str(song))
for chart in area_records[song]:
userid, score = area_records[song][chart]
if (
area_users[userid].get_int("area", self.get_machine_region())
!= shop_area
):
# Don't return this, this user isn't in this area
continue
try:
gamechart = self.db_to_game_chart(chart)
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(
Node.string("name", area_users[userid].get_str("name"))
)
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(
Node.u16(
"area",
area_users[userid].get_int("area", self.get_machine_region()),
)
)
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
typenode.add_child(Node.u32("code", area_users[userid].extid))
return game
class DDRGameScoreHandler(DDRBase):
def handle_game_score_request(self, request: Node) -> Node:
refid = request.attribute("refid")
songid = int(request.attribute("mid"))
chart = self.game_to_db_chart(int(request.attribute("type")))
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
attempts = self.data.local.music.get_all_attempts(
self.game,
self.music_version,
userid,
songid=songid,
songchart=chart,
limit=5,
)
recentscores = [attempt.points for (_, attempt) in attempts]
else:
recentscores = []
# Always pad to five, so we ensure that we return all the scores
while len(recentscores) < 5:
recentscores.append(0)
# Return the most recent five scores
game = Node.void("game")
for i in range(len(recentscores)):
game.set_attribute(f"sc{i + 1}", str(recentscores[i]))
return game
class DDRGameTraceHandler(DDRBase):
def handle_game_trace_request(self, request: Node) -> Node:
extid = int(request.attribute("code"))
chart = int(request.attribute("type"))
cid = intish(request.attribute("cid"))
mid = intish(request.attribute("mid"))
# Base packet is just game, if we find something we add to it
game = Node.void("game")
# Rival trace loading
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is None:
# Nothing to load
return game
if mid is not None:
# Load trace from song score
songscore = self.data.remote.music.get_score(
self.game,
self.music_version,
userid,
mid,
self.game_to_db_chart(chart),
)
if songscore is not None and "trace" in songscore.data:
game.add_child(Node.u32("size", len(songscore.data["trace"])))
game.add_child(Node.u8_array("trace", songscore.data["trace"]))
elif cid is not None:
# Load trace from achievement
coursescore = self.data.local.user.get_achievement(
self.game,
self.version,
userid,
(cid * 4) + chart,
"course",
)
if coursescore is not None and "trace" in coursescore:
game.add_child(Node.u32("size", len(coursescore["trace"])))
game.add_child(Node.u8_array("trace", coursescore["trace"]))
# Nothing found, return empty
return game
class DDRGameLoadHandler(DDRBase):
def handle_game_load_request(self, request: Node) -> Node:
refid = request.attribute("refid")
profile = self.get_profile_by_refid(refid)
if profile is not None:
return profile
game = Node.void("game")
game.set_attribute("none", "0")
return game
class DDRGameLoadDailyHandler(DDRBase):
def handle_game_load_daily_request(self, request: Node) -> Node:
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
game = Node.void("game")
profiledict = None
if extid is not None:
# Rival daily loading
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
else:
# Self daily loading
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
profiledict = self.get_profile(userid)
if profiledict is not None:
play_stats = self.get_play_statistics(userid)
# Day play counts
daycount = Node.void("daycount")
game.add_child(daycount)
daycount.set_attribute("playcount", str(play_stats.today_plays))
# Daily combo stuff, unclear how this works
dailycombo = Node.void("dailycombo")
game.add_child(dailycombo)
dailycombo.set_attribute("daily_combo", str(0))
dailycombo.set_attribute("daily_combo_lv", str(0))
return game
class DDRGameOldHandler(DDRBase):
def handle_game_old_request(self, request: Node) -> Node:
refid = request.attribute("refid")
game = Node.void("game")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
previous_version: Optional[DDRBase] = None
oldprofile: Optional[Profile] = None
if userid is not None:
previous_version = self.previous_version()
if previous_version is not None:
oldprofile = previous_version.get_profile(userid)
if oldprofile is not None:
game.set_attribute("name", oldprofile.get_str("name"))
game.set_attribute(
"area", str(oldprofile.get_int("area", self.get_machine_region()))
)
return game
class DDRGameNewHandler(DDRBase):
def handle_game_new_request(self, request: Node) -> Node:
refid = request.attribute("refid")
area = int(request.attribute("area"))
name = request.attribute("name").strip()
# Create a new profile for this user!
self.new_profile_by_refid(refid, name, area)
# No response needed
game = Node.void("game")
return game
class DDRGameSaveHandler(DDRBase):
def handle_game_save_request(self, request: Node) -> Node:
refid = request.attribute("refid")
self.put_profile_by_refid(refid, request)
# No response needed
game = Node.void("game")
return game
class DDRGameFriendHandler(DDRBase):
def handle_game_friend_request(self, request: Node) -> Node:
extid = intish(request.attribute("code"))
userid = None
friend = None
if extid is not None:
# Rival score loading
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
friend = self.get_profile(userid)
play_stats = self.get_play_statistics(userid)
if friend is None:
# Return an empty node to tell the game we don't have a player here
game = Node.void("game")
return game
game = Node.void("game")
game.set_attribute("data", "1")
game.add_child(Node.u32("code", friend.extid))
game.add_child(Node.string("name", friend.get_str("name")))
game.add_child(
Node.u8("area", friend.get_int("area", self.get_machine_region()))
)
game.add_child(Node.u32("exp", play_stats.get_int("exp")))
game.add_child(Node.u32("star", friend.get_int("star")))
# Drill rankings
if "title" in friend:
title = Node.void("title")
game.add_child(title)
titledict = friend.get_dict("title")
if "t" in titledict:
title.set_attribute("t", str(titledict.get_int("t")))
if "s" in titledict:
title.set_attribute("s", str(titledict.get_int("s")))
if "d" in titledict:
title.set_attribute("d", str(titledict.get_int("d")))
if "title_gr" in friend:
title_gr = Node.void("title_gr")
game.add_child(title_gr)
title_grdict = friend.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Groove gauge level-ups
gr_s = Node.void("gr_s")
game.add_child(gr_s)
index = 1
for entry in friend.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void("gr_d")
game.add_child(gr_d)
index = 1
for entry in friend.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
return game
class DDRGameLoadCourseHandler(DDRBase):
def handle_game_load_c_request(self, request: Node) -> Node:
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
if extid is not None:
# Rival score loading
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
else:
# Self score loading
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
coursedata = [0] * 3200
if userid is not None:
for course in self.data.local.user.get_achievements(
self.game, self.version, userid
):
if course.type != "course":
continue
# Grab course ID and chart (kinda pointless because we add it right back up
# below, but it is more documented/readable this way.
courseid = int(course.id / 4)
coursechart = course.id % 4
# Populate course data
index = ((courseid * 4) + coursechart) * 8
if index >= 0 and index <= (len(coursedata) - 8):
coursedata[index + 0] = int(course.data.get_int("score") / 10000)
coursedata[index + 1] = course.data.get_int("score") % 10000
coursedata[index + 2] = course.data.get_int("combo")
coursedata[index + 3] = self.db_to_game_rank(
course.data.get_int("rank")
)
coursedata[index + 5] = course.data.get_int("stage")
coursedata[index + 6] = course.data.get_int("combo_type")
game = Node.void("game")
game.add_child(Node.u16_array("course", coursedata))
return game
class DDRGameSaveCourseHandler(DDRBase):
def handle_game_save_c_request(self, request: Node) -> Node:
refid = request.attribute("refid")
courseid = int(request.attribute("cid"))
chart = int(request.attribute("ctype"))
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
# Calculate statistics
data = request.child("data")
points = int(data.attribute("score"))
combo = int(data.attribute("combo"))
combo_type = int(data.attribute("combo_type"))
stage = int(data.attribute("stage"))
rank = self.game_to_db_rank(int(data.attribute("rank")))
trace = request.child_value("trace")
# Grab the old course score
oldcourse = self.data.local.user.get_achievement(
self.game,
self.version,
userid,
(courseid * 4) + chart,
"course",
)
if oldcourse is not None:
highscore = points > oldcourse.get_int("score")
points = max(points, oldcourse.get_int("score"))
combo = max(combo, oldcourse.get_int("combo"))
stage = max(stage, oldcourse.get_int("stage"))
rank = max(rank, oldcourse.get_int("rank"))
combo_type = max(combo_type, oldcourse.get_int("combo_type"))
if not highscore:
# Don't overwrite the ghost for a non-highscore
trace = oldcourse.get_int_array("trace", len(trace))
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
(courseid * 4) + chart,
"course",
{
"score": points,
"combo": combo,
"stage": stage,
"rank": rank,
"combo_type": combo_type,
"trace": trace,
},
)
# No response needed
game = Node.void("game")
return game