forked from Hay1tsme/artemis
add back games, conform them to new title dispatch
This commit is contained in:
18
titles/wacca/__init__.py
Normal file
18
titles/wacca/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
from titles.wacca.const import WaccaConstants
|
||||
from titles.wacca.index import WaccaServlet
|
||||
from titles.wacca.read import WaccaReader
|
||||
from titles.wacca.database import WaccaData
|
||||
|
||||
index = WaccaServlet
|
||||
database = WaccaData
|
||||
reader = WaccaReader
|
||||
|
||||
use_default_title = True
|
||||
include_protocol = True
|
||||
title_secure = False
|
||||
game_codes = [WaccaConstants.GAME_CODE]
|
||||
trailing_slash = False
|
||||
use_default_host = False
|
||||
host = ""
|
||||
|
||||
current_schema_version = 3
|
941
titles/wacca/base.py
Normal file
941
titles/wacca/base.py
Normal file
@ -0,0 +1,941 @@
|
||||
from typing import Any, List, Dict
|
||||
import logging
|
||||
from math import floor
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.const import WaccaConstants
|
||||
from titles.wacca.database import WaccaData
|
||||
|
||||
from titles.wacca.handlers import *
|
||||
|
||||
class WaccaBase():
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None:
|
||||
self.config = cfg # Config file
|
||||
self.game_config = game_cfg # Game Config file
|
||||
self.game = WaccaConstants.GAME_CODE # Game code
|
||||
self.version = WaccaConstants.VER_WACCA # Game version
|
||||
self.data = WaccaData(cfg) # Database
|
||||
self.logger = logging.getLogger("wacca")
|
||||
self.srvtime = datetime.now()
|
||||
self.season = 1
|
||||
|
||||
self.OPTIONS_DEFAULTS: Dict[str, Any] = {
|
||||
"note_speed": 5,
|
||||
"field_mask": 0,
|
||||
"note_sound": 105001,
|
||||
"note_color": 203001,
|
||||
"bgm_volume": 10,
|
||||
"bg_video": 0,
|
||||
|
||||
"mirror": 0,
|
||||
"judge_display_pos": 0,
|
||||
"judge_detail_display": 0,
|
||||
"measure_guidelines": 1,
|
||||
"guideline_mask": 1,
|
||||
"judge_line_timing_adjust": 10,
|
||||
"note_design": 3,
|
||||
"bonus_effect": 1,
|
||||
"chara_voice": 1,
|
||||
"score_display_method": 0,
|
||||
"give_up": 0,
|
||||
"guideline_spacing": 1,
|
||||
"center_display": 1,
|
||||
"ranking_display": 1,
|
||||
"stage_up_icon_display": 1,
|
||||
"rating_display": 1,
|
||||
"player_level_display": 1,
|
||||
"touch_effect": 1,
|
||||
"guide_sound_vol": 3,
|
||||
"touch_note_vol": 8,
|
||||
"hold_note_vol": 8,
|
||||
"slide_note_vol": 8,
|
||||
"snap_note_vol": 8,
|
||||
"chain_note_vol": 8,
|
||||
"bonus_note_vol": 8,
|
||||
"gate_skip": 0,
|
||||
"key_beam_display": 1,
|
||||
|
||||
"left_slide_note_color": 4,
|
||||
"right_slide_note_color": 3,
|
||||
"forward_slide_note_color": 1,
|
||||
"back_slide_note_color": 2,
|
||||
|
||||
"master_vol": 3,
|
||||
"set_title_id": 104001,
|
||||
"set_icon_id": 102001,
|
||||
"set_nav_id": 210001,
|
||||
"set_plate_id": 211001
|
||||
}
|
||||
self.allowed_stages = []
|
||||
|
||||
def handle_housing_get_request(self, data: Dict) -> Dict:
|
||||
req = BaseRequest(data)
|
||||
housing_id = 1337
|
||||
self.logger.info(f"{req.chipId} -> {housing_id}")
|
||||
resp = HousingGetResponse(housing_id)
|
||||
return resp.make()
|
||||
|
||||
def handle_housing_start_request(self, data: Dict) -> Dict:
|
||||
req = HousingStartRequest(data)
|
||||
|
||||
resp = HousingStartResponseV1(
|
||||
1,
|
||||
[ # Recomended songs
|
||||
1269,1007,1270,1002,1020,1003,1008,1211,1018,1092,1056,32,
|
||||
1260,1230,1258,1251,2212,1264,1125,1037,2001,1272,1126,1119,
|
||||
1104,1070,1047,1044,1027,1004,1001,24,2068,2062,2021,1275,
|
||||
1249,1207,1203,1107,1021,1009,9,4,3,23,22,2014,13,1276,1247,
|
||||
1240,1237,1128,1114,1110,1109,1102,1045,1043,1036,1035,1030,
|
||||
1023,1015
|
||||
]
|
||||
)
|
||||
return resp.make()
|
||||
|
||||
def handle_advertise_GetNews_request(self, data: Dict) -> Dict:
|
||||
resp = GetNewsResponseV1()
|
||||
return resp.make()
|
||||
|
||||
def handle_user_status_logout_request(self, data: Dict) -> Dict:
|
||||
req = UserStatusLogoutRequest(data)
|
||||
self.logger.info(f"Log out user {req.userId} from {req.chipId}")
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_status_login_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusLoginRequest(data)
|
||||
resp = UserStatusLoginResponseV1()
|
||||
is_new_day = False
|
||||
is_consec_day = False
|
||||
is_consec_day = True
|
||||
|
||||
if req.userId == 0:
|
||||
self.logger.info(f"Guest login on {req.chipId}")
|
||||
resp.lastLoginDate = 0
|
||||
|
||||
else:
|
||||
profile = self.data.profile.get_profile(req.userId)
|
||||
if profile is None:
|
||||
self.logger.warn(f"Unknown user id {req.userId} attempted login from {req.chipId}")
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"User {req.userId} login on {req.chipId}")
|
||||
last_login_time = int(profile["last_login_date"].timestamp())
|
||||
resp.lastLoginDate = last_login_time
|
||||
|
||||
# If somebodies login timestamp < midnight of current day, then they are logging in for the first time today
|
||||
if last_login_time < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()):
|
||||
is_new_day = True
|
||||
is_consec_day = True
|
||||
|
||||
# If somebodies login timestamp > midnight of current day + 1 day, then they broke their daily login streak
|
||||
elif last_login_time > int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) + timedelta(days=1)).timestamp()):
|
||||
is_consec_day = False
|
||||
# else, they are simply logging in again on the same day, and we don't need to do anything for that
|
||||
|
||||
self.data.profile.session_login(req.userId, is_new_day, is_consec_day)
|
||||
|
||||
resp.firstLoginDaily = int(is_new_day)
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_status_get_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusGetRequest(data)
|
||||
resp = UserStatusGetV1Response()
|
||||
ver_split = req.appVersion.split(".")
|
||||
|
||||
profile = self.data.profile.get_profile(aime_id=req.aimeId)
|
||||
if profile is None:
|
||||
self.logger.info(f"No user exists for aime id {req.aimeId}")
|
||||
return resp.make()
|
||||
|
||||
|
||||
self.logger.info(f"User preview for {req.aimeId} from {req.chipId}")
|
||||
if profile["last_game_ver"] is None:
|
||||
profile_ver_split = ver_split
|
||||
resp.lastGameVersion = req.appVersion
|
||||
else:
|
||||
profile_ver_split = profile["last_game_ver"].split(".")
|
||||
resp.lastGameVersion = profile["last_game_ver"]
|
||||
|
||||
resp.userStatus.userId = profile["id"]
|
||||
resp.userStatus.username = profile["username"]
|
||||
resp.userStatus.xp = profile["xp"]
|
||||
resp.userStatus.danLevel = profile["dan_level"]
|
||||
resp.userStatus.danType = profile["dan_type"]
|
||||
resp.userStatus.wp = profile["wp"]
|
||||
resp.userStatus.useCount = profile["login_count"]
|
||||
resp.userStatus.loginDays = profile["login_count_days"]
|
||||
resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"]
|
||||
|
||||
set_title_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"])
|
||||
if set_title_id is None:
|
||||
set_title_id = self.OPTIONS_DEFAULTS["set_title_id"]
|
||||
resp.setTitleId = set_title_id
|
||||
|
||||
set_icon_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"])
|
||||
if set_icon_id is None:
|
||||
set_icon_id = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
resp.setIconId = set_icon_id
|
||||
|
||||
if profile["last_login_date"].timestamp() < int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) - timedelta(days=1)).timestamp()):
|
||||
resp.userStatus.loginConsecutiveDays = 0
|
||||
|
||||
if int(ver_split[0]) > int(profile_ver_split[0]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionUpgrade
|
||||
|
||||
elif int(ver_split[0]) < int(profile_ver_split[0]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionTooNew
|
||||
|
||||
else:
|
||||
if int(ver_split[1]) > int(profile_ver_split[1]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionUpgrade
|
||||
|
||||
elif int(ver_split[1]) < int(profile_ver_split[1]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionTooNew
|
||||
|
||||
else:
|
||||
if int(ver_split[2]) > int(profile_ver_split[2]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionUpgrade
|
||||
|
||||
|
||||
elif int(ver_split[2]) < int(profile_ver_split[2]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionTooNew
|
||||
|
||||
if profile["always_vip"]:
|
||||
resp.userStatus.vipExpireTime = int((datetime.now() + timedelta(days=30)).timestamp())
|
||||
|
||||
elif profile["vip_expire_time"] is not None:
|
||||
resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp())
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_status_login_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusLoginRequest(data)
|
||||
resp = UserStatusLoginResponseV2()
|
||||
is_new_day = False
|
||||
is_consec_day = False
|
||||
is_consec_day = True
|
||||
|
||||
if req.userId == 0:
|
||||
self.logger.info(f"Guest login on {req.chipId}")
|
||||
resp.lastLoginDate = 0
|
||||
|
||||
else:
|
||||
profile = self.data.profile.get_profile(req.userId)
|
||||
if profile is None:
|
||||
self.logger.warn(f"Unknown user id {req.userId} attempted login from {req.chipId}")
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"User {req.userId} login on {req.chipId}")
|
||||
last_login_time = int(profile["last_login_date"].timestamp())
|
||||
resp.lastLoginDate = last_login_time
|
||||
|
||||
# If somebodies login timestamp < midnight of current day, then they are logging in for the first time today
|
||||
if last_login_time < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()):
|
||||
is_new_day = True
|
||||
is_consec_day = True
|
||||
|
||||
# If somebodies login timestamp > midnight of current day + 1 day, then they broke their daily login streak
|
||||
elif last_login_time > int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) + timedelta(days=1)).timestamp()):
|
||||
is_consec_day = False
|
||||
# else, they are simply logging in again on the same day, and we don't need to do anything for that
|
||||
|
||||
self.data.profile.session_login(req.userId, is_new_day, is_consec_day)
|
||||
resp.vipInfo.pageYear = datetime.now().year
|
||||
resp.vipInfo.pageMonth = datetime.now().month
|
||||
resp.vipInfo.pageDay = datetime.now().day
|
||||
resp.vipInfo.numItem = 1
|
||||
|
||||
resp.firstLoginDaily = int(is_new_day)
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_status_create_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusCreateRequest(data)
|
||||
|
||||
profileId = self.data.profile.create_profile(req.aimeId, req.username, self.version)
|
||||
|
||||
if profileId is None: return BaseResponse().make()
|
||||
|
||||
# Insert starting items
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104002)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104003)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104005)
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102002)
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 103001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 203001)
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 105001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 205005) # Added lily
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210001)
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["user_plate"], 211001) # Added lily
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312000) # Added reverse
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312001) # Added reverse
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312002) # Added reverse
|
||||
|
||||
return UserStatusCreateResponseV2(profileId, req.username).make()
|
||||
|
||||
def handle_user_status_getDetail_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusGetDetailRequest(data)
|
||||
resp = UserStatusGetDetailResponseV1()
|
||||
|
||||
profile = self.data.profile.get_profile(req.userId)
|
||||
if profile is None:
|
||||
self.logger.warn(f"Unknown profile {req.userId}")
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"Get detail for profile {req.userId}")
|
||||
user_id = profile["user"]
|
||||
|
||||
profile_scores = self.data.score.get_best_scores(user_id)
|
||||
profile_items = self.data.item.get_items(user_id)
|
||||
profile_song_unlocks = self.data.item.get_song_unlocks(user_id)
|
||||
profile_options = self.data.profile.get_options(user_id)
|
||||
profile_trophies = self.data.item.get_trophies(user_id)
|
||||
profile_tickets = self.data.item.get_tickets(user_id)
|
||||
|
||||
if profile["vip_expire_time"] is None:
|
||||
resp.userStatus.vipExpireTime = 0
|
||||
|
||||
else:
|
||||
resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp())
|
||||
|
||||
if profile["always_vip"] or self.game_config.mods.always_vip:
|
||||
resp.userStatus.vipExpireTime = int((self.srvtime + timedelta(days=31)).timestamp())
|
||||
|
||||
resp.songUpdateTime = int(profile["last_login_date"].timestamp())
|
||||
resp.songPlayStatus = [profile["last_song_id"], 1]
|
||||
|
||||
resp.userStatus.userId = profile["id"]
|
||||
resp.userStatus.username = profile["username"]
|
||||
resp.userStatus.xp = profile["xp"]
|
||||
resp.userStatus.danLevel = profile["dan_level"]
|
||||
resp.userStatus.danType = profile["dan_type"]
|
||||
resp.userStatus.wp = profile["wp"]
|
||||
resp.userStatus.useCount = profile["login_count"]
|
||||
resp.userStatus.loginDays = profile["login_count_days"]
|
||||
resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"]
|
||||
|
||||
if self.game_config.mods.infinite_wp:
|
||||
resp.userStatus.wp = 999999
|
||||
|
||||
if profile["friend_view_1"] is not None:
|
||||
pass
|
||||
if profile["friend_view_2"] is not None:
|
||||
pass
|
||||
if profile["friend_view_3"] is not None:
|
||||
pass
|
||||
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 1, profile["playcount_single"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 2, profile["playcount_multi_vs"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 3, profile["playcount_multi_coop"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 4, profile["playcount_stageup"]))
|
||||
|
||||
for opt in profile_options:
|
||||
resp.options.append(UserOption(opt["opt_id"], opt["value"]))
|
||||
|
||||
for unlock in profile_song_unlocks:
|
||||
for x in range(1, unlock["highest_difficulty"] + 1):
|
||||
resp.userItems.songUnlocks.append(SongUnlock(unlock["song_id"], x, 0, int(unlock["acquire_date"].timestamp())))
|
||||
if x > 2:
|
||||
resp.scores.append(BestScoreDetailV1(unlock["song_id"], x))
|
||||
|
||||
empty_scores = len(resp.scores)
|
||||
for song in profile_scores:
|
||||
resp.seasonInfo.cumulativeScore += song["score"]
|
||||
empty_score_idx = resp.find_score_idx(song["song_id"], song["chart_id"], 0, empty_scores)
|
||||
|
||||
clear_cts = SongDetailClearCounts(
|
||||
song["play_ct"],
|
||||
song["clear_ct"],
|
||||
song["missless_ct"],
|
||||
song["fullcombo_ct"],
|
||||
song["allmarv_ct"],
|
||||
)
|
||||
|
||||
grade_cts = SongDetailGradeCountsV1(
|
||||
song["grade_d_ct"], song["grade_c_ct"], song["grade_b_ct"], song["grade_a_ct"], song["grade_aa_ct"],
|
||||
song["grade_aaa_ct"], song["grade_s_ct"], song["grade_ss_ct"], song["grade_sss_ct"],
|
||||
song["grade_master_ct"]
|
||||
)
|
||||
|
||||
if empty_score_idx is not None:
|
||||
resp.scores[empty_score_idx].clearCounts = clear_cts
|
||||
resp.scores[empty_score_idx].clearCountsSeason = clear_cts
|
||||
resp.scores[empty_score_idx].gradeCounts = grade_cts
|
||||
resp.scores[empty_score_idx].score = song["score"]
|
||||
resp.scores[empty_score_idx].bestCombo = song["best_combo"]
|
||||
resp.scores[empty_score_idx].lowestMissCtMaybe = song["lowest_miss_ct"]
|
||||
resp.scores[empty_score_idx].rating = song["rating"]
|
||||
|
||||
else:
|
||||
deets = BestScoreDetailV1(song["song_id"], song["chart_id"])
|
||||
deets.clearCounts = clear_cts
|
||||
deets.clearCountsSeason = clear_cts
|
||||
deets.gradeCounts = grade_cts
|
||||
deets.score = song["score"]
|
||||
deets.bestCombo = song["best_combo"]
|
||||
deets.lowestMissCtMaybe = song["lowest_miss_ct"]
|
||||
deets.rating = song["rating"]
|
||||
|
||||
for trophy in profile_trophies:
|
||||
resp.userItems.trophies.append(TrophyItem(trophy["trophy_id"], trophy["season"], trophy["progress"], trophy["badge_type"]))
|
||||
|
||||
if self.game_config.mods.infinite_tickets:
|
||||
for x in range(5):
|
||||
resp.userItems.tickets.append(TicketItem(x, 106002, 0))
|
||||
else:
|
||||
for ticket in profile_tickets:
|
||||
if ticket["expire_date"] is None:
|
||||
expire = int((self.srvtime + timedelta(days=30)).timestamp())
|
||||
else:
|
||||
expire = int(ticket["expire_date"].timestamp())
|
||||
|
||||
resp.userItems.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], expire))
|
||||
|
||||
if profile_items:
|
||||
for item in profile_items:
|
||||
try:
|
||||
|
||||
if item["type"] == WaccaConstants.ITEM_TYPES["icon"]:
|
||||
resp.userItems.icons.append(IconItem(item["item_id"], 1, item["use_count"], int(item["acquire_date"].timestamp())))
|
||||
|
||||
else:
|
||||
itm_send = GenericItemSend(item["item_id"], 1, int(item["acquire_date"].timestamp()))
|
||||
|
||||
if item["type"] == WaccaConstants.ITEM_TYPES["title"]:
|
||||
resp.userItems.titles.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_color"]:
|
||||
resp.userItems.noteColors.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
||||
resp.userItems.noteSounds.append(itm_send)
|
||||
|
||||
except:
|
||||
self.logger.error(f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}")
|
||||
|
||||
resp.seasonInfo.level = profile["xp"]
|
||||
resp.seasonInfo.wpObtained = profile["wp_total"]
|
||||
resp.seasonInfo.wpSpent = profile["wp_spent"]
|
||||
resp.seasonInfo.titlesObtained = len(resp.userItems.titles)
|
||||
resp.seasonInfo.iconsObtained = len(resp.userItems.icons)
|
||||
resp.seasonInfo.noteColorsObtained = len(resp.userItems.noteColors)
|
||||
resp.seasonInfo.noteSoundsObtained = len(resp.userItems.noteSounds)
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_trial_get_request(self, data: Dict) -> List[Any]:
|
||||
req = UserTrialGetRequest(data)
|
||||
resp = UserTrialGetResponse()
|
||||
|
||||
user_id = self.data.profile.profile_to_aime_user(req.profileId)
|
||||
if user_id is None:
|
||||
self.logger.error(f"handle_user_trial_get_request: No profile with id {req.profileId}")
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"Get trial info for user {req.profileId}")
|
||||
|
||||
for d in self.allowed_stages:
|
||||
if d[1] > 0 and d[1] < 10:
|
||||
resp.stageList.append(StageInfo(d[0], d[1]))
|
||||
|
||||
stages = self.data.score.get_stageup(user_id, self.version)
|
||||
if stages is None:
|
||||
stages = []
|
||||
|
||||
add_next = True
|
||||
for d in self.allowed_stages:
|
||||
stage_info = StageInfo(d[0], d[1])
|
||||
|
||||
for score in stages:
|
||||
if score["stage_id"] == stage_info.danId:
|
||||
stage_info.clearStatus = score["clear_status"]
|
||||
stage_info.numSongCleared = score["clear_song_ct"]
|
||||
stage_info.song1BestScore = score["song1_score"]
|
||||
stage_info.song2BestScore = score["song2_score"]
|
||||
stage_info.song3BestScore = score["song3_score"]
|
||||
break
|
||||
|
||||
if add_next or stage_info.danLevel < 9:
|
||||
resp.stageList.append(stage_info)
|
||||
|
||||
if stage_info.danLevel >= 9 and stage_info.clearStatus < 1:
|
||||
add_next = False
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_trial_update_request(self, data: Dict) -> List[Any]:
|
||||
req = UserTrialUpdateRequest(data)
|
||||
|
||||
total_score = 0
|
||||
for score in req.songScores:
|
||||
total_score += score
|
||||
|
||||
while len(req.songScores) < 3:
|
||||
req.songScores.append(0)
|
||||
|
||||
profile = self.data.profile.get_profile(req.profileId)
|
||||
|
||||
user_id = profile["user"]
|
||||
old_stage = self.data.score.get_stageup_stage(user_id, self.version, req.stageId)
|
||||
|
||||
if old_stage is None:
|
||||
self.data.score.put_stageup(user_id, self.version, req.stageId, req.clearType.value, req.numSongsCleared, req.songScores[0], req.songScores[1], req.songScores[2])
|
||||
|
||||
else:
|
||||
# We only care about total score for best of, even if one score happens to be lower (I think)
|
||||
if total_score > (old_stage["song1_score"] + old_stage["song2_score"] + old_stage["song3_score"]):
|
||||
best_score1 = req.songScores[0]
|
||||
best_score2 = req.songScores[2]
|
||||
best_score3 = req.songScores[3]
|
||||
else:
|
||||
best_score1 = old_stage["song1_score"]
|
||||
best_score2 = old_stage["song2_score"]
|
||||
best_score3 = old_stage["song3_score"]
|
||||
|
||||
self.data.score.put_stageup(user_id, self.version, req.stageId, req.clearType.value, req.numSongsCleared,
|
||||
best_score1, best_score2, best_score3)
|
||||
|
||||
if req.stageLevel > 0 and req.stageLevel <= 14 and req.clearType.value > 0: # For some reason, special stages send dan level 1001...
|
||||
if req.stageLevel > profile["dan_level"] or (req.stageLevel == profile["dan_level"] and req.clearType.value > profile["dan_type"]):
|
||||
self.data.profile.update_profile_dan(req.profileId, req.stageLevel, req.clearType.value)
|
||||
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtained)
|
||||
|
||||
# user/status/update isn't called after stageup so we have to do some things now
|
||||
current_icon = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_icon_id"])
|
||||
current_nav = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_nav_id"])
|
||||
|
||||
if current_icon is None:
|
||||
current_icon = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
else:
|
||||
current_icon = current_icon["value"]
|
||||
if current_nav is None:
|
||||
current_nav = self.OPTIONS_DEFAULTS["set_nav_id"]
|
||||
else:
|
||||
current_nav = current_nav["value"]
|
||||
|
||||
self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon)
|
||||
self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav)
|
||||
self.data.profile.update_profile_playtype(req.profileId, 4, data["appVersion"][:7])
|
||||
return BaseResponse.make()
|
||||
|
||||
def handle_user_sugoroku_update_request(self, data: Dict) -> List[Any]:
|
||||
ver_split = data["appVersion"].split(".")
|
||||
resp = BaseResponse()
|
||||
|
||||
if int(ver_split[0]) <= 2 and int(ver_split[1]) < 53:
|
||||
req = UserSugarokuUpdateRequestV1(data)
|
||||
mission_flg = 0
|
||||
|
||||
else:
|
||||
req = UserSugarokuUpdateRequestV2(data)
|
||||
mission_flg = req.mission_flag
|
||||
|
||||
user_id = self.data.profile.profile_to_aime_user(req.profileId)
|
||||
if user_id is None:
|
||||
self.logger.info(f"handle_user_sugoroku_update_request unknwon profile ID {req.profileId}")
|
||||
return resp.make()
|
||||
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtainted)
|
||||
|
||||
self.data.profile.update_gate(user_id, req.gateId, req.page, req.progress, req.loops, mission_flg, req.totalPts)
|
||||
return resp.make()
|
||||
|
||||
def handle_user_info_getMyroom_request(self, data: Dict) -> List[Any]:
|
||||
return UserInfogetMyroomResponse().make()
|
||||
|
||||
def handle_user_music_unlock_request(self, data: Dict) -> List[Any]:
|
||||
req = UserMusicUnlockRequest(data)
|
||||
|
||||
profile = self.data.profile.get_profile(req.profileId)
|
||||
if profile is None: return BaseResponse().make()
|
||||
user_id = profile["user"]
|
||||
current_wp = profile["wp"]
|
||||
|
||||
tickets = self.data.item.get_tickets(user_id)
|
||||
new_tickets = []
|
||||
|
||||
for ticket in tickets:
|
||||
new_tickets.append([ticket["id"], ticket["ticket_id"], 9999999999])
|
||||
|
||||
for item in req.itemsUsed:
|
||||
if item.itemType == WaccaConstants.ITEM_TYPES["wp"]:
|
||||
if current_wp >= item.quantity:
|
||||
current_wp -= item.quantity
|
||||
self.data.profile.spend_wp(req.profileId, item.quantity)
|
||||
else: return BaseResponse().make()
|
||||
|
||||
elif item.itemType == WaccaConstants.ITEM_TYPES["ticket"]:
|
||||
for x in range(len(new_tickets)):
|
||||
if new_tickets[x][1] == item.itemId:
|
||||
self.data.item.spend_ticket(new_tickets[x][0])
|
||||
new_tickets.pop(x)
|
||||
break
|
||||
|
||||
# wp, ticket info
|
||||
if req.difficulty > WaccaConstants.Difficulty.HARD.value:
|
||||
old_score = self.data.score.get_best_score(user_id, req.songId, req.difficulty)
|
||||
if not old_score:
|
||||
self.data.score.put_best_score(user_id, req.songId, req.difficulty, 0, [0] * 5, [0] * 13, 0, 0)
|
||||
|
||||
self.data.item.unlock_song(user_id, req.songId, req.difficulty if req.difficulty > WaccaConstants.Difficulty.HARD.value else WaccaConstants.Difficulty.HARD.value)
|
||||
|
||||
if self.game_config.mods.infinite_tickets:
|
||||
new_tickets = [
|
||||
[0, 106002, 0],
|
||||
[1, 106002, 0],
|
||||
[2, 106002, 0],
|
||||
[3, 106002, 0],
|
||||
[4, 106002, 0],
|
||||
]
|
||||
|
||||
if self.game_config.mods.infinite_wp:
|
||||
current_wp = 999999
|
||||
|
||||
return UserMusicUnlockResponse(current_wp, new_tickets).make()
|
||||
|
||||
def handle_user_info_getRanking_request(self, data: Dict) -> List[Any]:
|
||||
# total score, high score by song, cumulative socre, stage up score, other score, WP ranking
|
||||
# This likely requies calculating standings at regular intervals and caching the results
|
||||
return UserInfogetRankingResponse().make()
|
||||
|
||||
def handle_user_music_update_request(self, data: Dict) -> List[Any]:
|
||||
req = UserMusicUpdateRequest(data)
|
||||
ver_split = req.appVersion.split(".")
|
||||
if int(ver_split[0]) >= 3:
|
||||
resp = UserMusicUpdateResponseV3()
|
||||
elif int(ver_split[0]) >= 2:
|
||||
resp = UserMusicUpdateResponseV2()
|
||||
else:
|
||||
resp = UserMusicUpdateResponseV1()
|
||||
|
||||
resp.songDetail.songId = req.songDetail.songId
|
||||
resp.songDetail.difficulty = req.songDetail.difficulty
|
||||
|
||||
profile = self.data.profile.get_profile(req.profileId)
|
||||
|
||||
if profile is None:
|
||||
self.logger.warn(f"handle_user_music_update_request: No profile for game_id {req.profileId}")
|
||||
return BaseResponse().make()
|
||||
|
||||
user_id = profile["user"]
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtained)
|
||||
|
||||
playlog_clear_status = req.songDetail.flagCleared + req.songDetail.flagMissless + req.songDetail.flagFullcombo + \
|
||||
req.songDetail.flagAllMarvelous
|
||||
|
||||
self.data.score.put_playlog(user_id, req.songDetail.songId, req.songDetail.difficulty, req.songDetail.score,
|
||||
playlog_clear_status, req.songDetail.grade.value, req.songDetail.maxCombo, req.songDetail.judgements.marvCt,
|
||||
req.songDetail.judgements.greatCt, req.songDetail.judgements.goodCt, req.songDetail.judgements.missCt,
|
||||
req.songDetail.fastCt, req.songDetail.slowCt, self.season)
|
||||
|
||||
old_score = self.data.score.get_best_score(user_id, req.songDetail.songId, req.songDetail.difficulty)
|
||||
|
||||
if not old_score:
|
||||
grades = [0] * 13
|
||||
clears = [0] * 5
|
||||
|
||||
clears[0] = 1
|
||||
clears[1] = 1 if req.songDetail.flagCleared else 0
|
||||
clears[2] = 1 if req.songDetail.flagMissless else 0
|
||||
clears[3] = 1 if req.songDetail.flagFullcombo else 0
|
||||
clears[4] = 1 if req.songDetail.flagAllMarvelous else 0
|
||||
|
||||
grades[req.songDetail.grade.value - 1] = 1
|
||||
|
||||
self.data.score.put_best_score(user_id, req.songDetail.songId, req.songDetail.difficulty, req.songDetail.score,
|
||||
clears, grades, req.songDetail.maxCombo, req.songDetail.judgements.missCt)
|
||||
|
||||
resp.songDetail.score = req.songDetail.score
|
||||
resp.songDetail.lowestMissCount = req.songDetail.judgements.missCt
|
||||
|
||||
else:
|
||||
grades = [
|
||||
old_score["grade_d_ct"],
|
||||
old_score["grade_c_ct"],
|
||||
old_score["grade_b_ct"],
|
||||
old_score["grade_a_ct"],
|
||||
old_score["grade_aa_ct"],
|
||||
old_score["grade_aaa_ct"],
|
||||
old_score["grade_s_ct"],
|
||||
old_score["grade_ss_ct"],
|
||||
old_score["grade_sss_ct"],
|
||||
old_score["grade_master_ct"],
|
||||
old_score["grade_sp_ct"],
|
||||
old_score["grade_ssp_ct"],
|
||||
old_score["grade_sssp_ct"],
|
||||
]
|
||||
clears = [
|
||||
old_score["play_ct"],
|
||||
old_score["clear_ct"],
|
||||
old_score["missless_ct"],
|
||||
old_score["fullcombo_ct"],
|
||||
old_score["allmarv_ct"],
|
||||
]
|
||||
|
||||
clears[0] += 1
|
||||
clears[1] += 1 if req.songDetail.flagCleared else 0
|
||||
clears[2] += 1 if req.songDetail.flagMissless else 0
|
||||
clears[3] += 1 if req.songDetail.flagFullcombo else 0
|
||||
clears[4] += 1 if req.songDetail.flagAllMarvelous else 0
|
||||
|
||||
grades[req.songDetail.grade.value - 1] += 1
|
||||
|
||||
best_score = max(req.songDetail.score, old_score["score"])
|
||||
best_max_combo = max(req.songDetail.maxCombo, old_score["best_combo"])
|
||||
lowest_miss_ct = min(req.songDetail.judgements.missCt, old_score["lowest_miss_ct"])
|
||||
best_rating = max(self.util_calc_song_rating(req.songDetail.score, req.songDetail.level), old_score["rating"])
|
||||
|
||||
self.data.score.put_best_score(user_id, req.songDetail.songId, req.songDetail.difficulty, best_score, clears,
|
||||
grades, best_max_combo, lowest_miss_ct)
|
||||
|
||||
resp.songDetail.score = best_score
|
||||
resp.songDetail.lowestMissCount = lowest_miss_ct
|
||||
resp.songDetail.rating = best_rating
|
||||
|
||||
resp.songDetail.clearCounts = SongDetailClearCounts(counts=clears)
|
||||
resp.songDetail.clearCountsSeason = SongDetailClearCounts(counts=clears)
|
||||
|
||||
if int(ver_split[0]) >= 3:
|
||||
resp.songDetail.grades = SongDetailGradeCountsV2(counts=grades)
|
||||
else:
|
||||
resp.songDetail.grades = SongDetailGradeCountsV1(counts=grades)
|
||||
|
||||
return resp.make()
|
||||
|
||||
#TODO: Coop and vs data
|
||||
def handle_user_music_updateCoop_request(self, data: Dict) -> List[Any]:
|
||||
coop_info = data["params"][4]
|
||||
return self.handle_user_music_update_request(data)
|
||||
|
||||
def handle_user_music_updateVersus_request(self, data: Dict) -> List[Any]:
|
||||
vs_info = data["params"][4]
|
||||
return self.handle_user_music_update_request(data)
|
||||
|
||||
def handle_user_music_updateTrial_request(self, data: Dict) -> List[Any]:
|
||||
return self.handle_user_music_update_request(data)
|
||||
|
||||
def handle_user_mission_update_request(self, data: Dict) -> List[Any]:
|
||||
req = UserMissionUpdateRequest(data)
|
||||
page_status = req.params[1][1]
|
||||
|
||||
profile = self.data.profile.get_profile(req.profileId)
|
||||
if profile is None:
|
||||
return BaseResponse().make()
|
||||
|
||||
if len(req.itemsObtained) > 0:
|
||||
self.util_put_items(req.profileId, profile["user"], req.itemsObtained)
|
||||
|
||||
self.data.profile.update_bingo(profile["user"], req.bingoDetail.pageNumber, page_status)
|
||||
self.data.profile.update_tutorial_flags(req.profileId, req.params[3])
|
||||
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_goods_purchase_request(self, data: Dict) -> List[Any]:
|
||||
req = UserGoodsPurchaseRequest(data)
|
||||
resp = UserGoodsPurchaseResponse()
|
||||
|
||||
profile = self.data.profile.get_profile(req.profileId)
|
||||
if profile is None: return BaseResponse().make()
|
||||
|
||||
user_id = profile["user"]
|
||||
resp.currentWp = profile["wp"]
|
||||
|
||||
if req.purchaseType == PurchaseType.PurchaseTypeWP:
|
||||
resp.currentWp -= req.cost
|
||||
self.data.profile.spend_wp(req.profileId, req.cost)
|
||||
|
||||
elif req.purchaseType == PurchaseType.PurchaseTypeCredit:
|
||||
self.logger.info(f"User {req.profileId} Purchased item {req.itemObtained.itemType} id {req.itemObtained.itemId} for {req.cost} credits on machine {req.chipId}")
|
||||
|
||||
self.util_put_items(req.profileId ,user_id, [req.itemObtained])
|
||||
|
||||
if self.game_config.mods.infinite_tickets:
|
||||
for x in range(5):
|
||||
resp.tickets.append(TicketItem(x, 106002, 0))
|
||||
else:
|
||||
tickets = self.data.item.get_tickets(user_id)
|
||||
|
||||
for ticket in tickets:
|
||||
resp.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], int((self.srvtime + timedelta(days=30)).timestamp())))
|
||||
|
||||
if self.game_config.mods.infinite_wp:
|
||||
resp.currentWp = 999999
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_competition_status_login_request(self, data: Dict) -> List[Any]:
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_competition_status_update_request(self, data: Dict) -> List[Any]:
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_rating_update_request(self, data: Dict) -> List[Any]:
|
||||
req = UserRatingUpdateRequest(data)
|
||||
|
||||
user_id = self.data.profile.profile_to_aime_user(req.profileId)
|
||||
|
||||
if user_id is None:
|
||||
self.logger.error(f"handle_user_rating_update_request: No profild with ID {req.profileId}")
|
||||
return BaseResponse().make()
|
||||
|
||||
for song in req.songs:
|
||||
self.data.score.update_song_rating(user_id, song.songId, song.difficulty, song.rating)
|
||||
|
||||
self.data.profile.update_user_rating(req.profileId, req.totalRating)
|
||||
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_status_update_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusUpdateRequestV2(data)
|
||||
|
||||
user_id = self.data.profile.profile_to_aime_user(req.profileId)
|
||||
if user_id is None:
|
||||
self.logger.info(f"handle_user_status_update_request: No profile with ID {req.profileId}")
|
||||
return BaseResponse().make()
|
||||
|
||||
self.util_put_items(req.profileId, user_id, req.itemsRecieved)
|
||||
self.data.profile.update_profile_playtype(req.profileId, req.playType.value, data["appVersion"][:7])
|
||||
self.data.profile.update_profile_lastplayed(req.profileId, req.lastSongInfo.lastSongId, req.lastSongInfo.lastSongDiff,
|
||||
req.lastSongInfo.lastFolderOrd, req.lastSongInfo.lastFolderId, req.lastSongInfo.lastSongOrd)
|
||||
|
||||
current_icon = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_icon_id"])
|
||||
current_nav = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_nav_id"])
|
||||
|
||||
if current_icon is None:
|
||||
current_icon = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
else:
|
||||
current_icon = current_icon["value"]
|
||||
if current_nav is None:
|
||||
current_nav = self.OPTIONS_DEFAULTS["set_nav_id"]
|
||||
else:
|
||||
current_nav = current_nav["value"]
|
||||
|
||||
self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon)
|
||||
self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav)
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_info_update_request(self, data: Dict) -> List[Any]:
|
||||
req = UserInfoUpdateRequest(data)
|
||||
|
||||
user_id = self.data.profile.profile_to_aime_user(req.profileId)
|
||||
|
||||
for opt in req.optsUpdated:
|
||||
self.data.profile.update_option(user_id, opt.id, opt.val)
|
||||
|
||||
for update in req.datesUpdated:
|
||||
pass
|
||||
|
||||
for fav in req.favoritesAdded:
|
||||
self.data.profile.add_favorite_song(user_id, fav)
|
||||
|
||||
for unfav in req.favoritesRemoved:
|
||||
self.data.profile.remove_favorite_song(user_id, unfav)
|
||||
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_vip_get_request(self, data: Dict) -> List[Any]:
|
||||
req = UserVipGetRequest(data)
|
||||
resp = UserVipGetResponse()
|
||||
|
||||
profile = self.data.profile.get_profile(req.profileId)
|
||||
if profile is None:
|
||||
self.logger.warn(f"handle_user_vip_get_request no profile with ID {req.profileId}")
|
||||
return BaseResponse().make()
|
||||
|
||||
if "vip_expire_time" in profile and profile["vip_expire_time"] is not None and profile["vip_expire_time"].timestamp() > int(self.srvtime.timestamp()):
|
||||
resp.vipDays = int((profile["vip_expire_time"].timestamp() - int(self.srvtime.timestamp())) / 86400)
|
||||
|
||||
resp.vipDays += 30
|
||||
|
||||
resp.presents.append(VipLoginBonus(1,0,16,211025,1))
|
||||
resp.presents.append(VipLoginBonus(2,0,6,202086,1))
|
||||
resp.presents.append(VipLoginBonus(3,0,11,205008,1))
|
||||
resp.presents.append(VipLoginBonus(4,0,10,203009,1))
|
||||
resp.presents.append(VipLoginBonus(5,0,16,211026,1))
|
||||
resp.presents.append(VipLoginBonus(6,0,9,206001,1))
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_vip_start_request(self, data: Dict) -> List[Any]:
|
||||
req = UserVipStartRequest(data)
|
||||
|
||||
profile = self.data.profile.get_profile(req.profileId)
|
||||
if profile is None: return BaseResponse().make()
|
||||
|
||||
# This should never happen because wacca stops you from buying VIP
|
||||
# if you have more then 10 days remaining, but this IS wacca we're dealing with...
|
||||
if "always_vip" in profile and profile["always_vip"] or self.game_config.mods.always_vip:
|
||||
return UserVipStartResponse(int((self.srvtime + timedelta(days=req.days)).timestamp())).make()
|
||||
|
||||
profile["vip_expire_time"] = int((self.srvtime + timedelta(days=req.days)).timestamp())
|
||||
self.data.profile.update_vip_time(req.profileId, self.srvtime + timedelta(days=req.days))
|
||||
return UserVipStartResponse(profile["vip_expire_time"]).make()
|
||||
|
||||
def util_put_items(self, profile_id: int, user_id: int, items_obtained: List[GenericItemRecv]) -> None:
|
||||
if user_id is None or profile_id <= 0:
|
||||
return None
|
||||
|
||||
if items_obtained:
|
||||
for item in items_obtained:
|
||||
|
||||
if item.itemType == WaccaConstants.ITEM_TYPES["xp"]:
|
||||
self.data.profile.add_xp(profile_id, item.quantity)
|
||||
|
||||
elif item.itemType == WaccaConstants.ITEM_TYPES["wp"]:
|
||||
self.data.profile.add_wp(profile_id, item.quantity)
|
||||
|
||||
elif item.itemType == WaccaConstants.ITEM_TYPES["music_difficulty_unlock"] or item.itemType == WaccaConstants.ITEM_TYPES["music_unlock"]:
|
||||
if item.quantity > WaccaConstants.Difficulty.HARD.value:
|
||||
old_score = self.data.score.get_best_score(user_id, item.itemId, item.quantity)
|
||||
if not old_score:
|
||||
self.data.score.put_best_score(user_id, item.itemId, item.quantity, 0, [0] * 5, [0] * 13, 0, 0)
|
||||
|
||||
if item.quantity == 0:
|
||||
item.quantity = WaccaConstants.Difficulty.HARD.value
|
||||
self.data.item.unlock_song(user_id, item.itemId, item.quantity)
|
||||
|
||||
elif item.itemType == WaccaConstants.ITEM_TYPES["ticket"]:
|
||||
self.data.item.add_ticket(user_id, item.itemId)
|
||||
|
||||
elif item.itemType == WaccaConstants.ITEM_TYPES["trophy"]:
|
||||
self.data.item.update_trophy(user_id, item.itemId, self.season, item.quantity, 0)
|
||||
|
||||
else:
|
||||
self.data.item.put_item(user_id, item.itemType, item.itemId)
|
||||
|
||||
def util_calc_song_rating(self, score: int, difficulty: float) -> int:
|
||||
if score >= 990000:
|
||||
const = 4.00
|
||||
elif score >= 980000 and score < 990000:
|
||||
const = 3.75
|
||||
elif score >= 970000 and score < 980000:
|
||||
const = 3.50
|
||||
elif score >= 960000 and score < 970000:
|
||||
const = 3.25
|
||||
elif score >= 950000 and score < 960000:
|
||||
const = 3.00
|
||||
elif score >= 940000 and score < 950000:
|
||||
const = 2.75
|
||||
elif score >= 930000 and score < 940000:
|
||||
const = 2.50
|
||||
elif score >= 920000 and score < 930000:
|
||||
const = 2.25
|
||||
elif score >= 910000 and score < 920000:
|
||||
const = 2.00
|
||||
elif score >= 900000 and score < 910000:
|
||||
const = 1.00
|
||||
else: const = 0.00
|
||||
|
||||
return floor((difficulty * const) * 10)
|
44
titles/wacca/config.py
Normal file
44
titles/wacca/config.py
Normal file
@ -0,0 +1,44 @@
|
||||
from typing import Dict, List
|
||||
from core.config import CoreConfig
|
||||
|
||||
class WaccaServerConfig():
|
||||
def __init__(self, parent_config: "WaccaConfig") -> None:
|
||||
self.__config = parent_config
|
||||
|
||||
@property
|
||||
def enable(self) -> bool:
|
||||
return CoreConfig.get_config_field(self.__config, 'wacca', 'server', 'enable', default=True)
|
||||
|
||||
@property
|
||||
def loglevel(self) -> int:
|
||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'wacca', 'server', 'loglevel', default="info"))
|
||||
|
||||
class WaccaModsConfig():
|
||||
def __init__(self, parent_config: "WaccaConfig") -> None:
|
||||
self.__config = parent_config
|
||||
|
||||
@property
|
||||
def always_vip(self) -> bool:
|
||||
return CoreConfig.get_config_field(self.__config, 'wacca', 'mods', 'always_vip', default=True)
|
||||
|
||||
@property
|
||||
def infinite_tickets(self) -> bool:
|
||||
return CoreConfig.get_config_field(self.__config, 'wacca', 'mods', 'infinite_tickets', default=True)
|
||||
|
||||
@property
|
||||
def infinite_wp(self) -> bool:
|
||||
return CoreConfig.get_config_field(self.__config, 'wacca', 'mods', 'infinite_wp', default=True)
|
||||
|
||||
class WaccaGateConfig():
|
||||
def __init__(self, parent_config: "WaccaConfig") -> None:
|
||||
self.__config = parent_config
|
||||
|
||||
@property
|
||||
def enabled_gates(self) -> List[int]:
|
||||
return CoreConfig.get_config_field(self.__config, 'wacca', 'gates', 'enabled_gates', default=[])
|
||||
|
||||
class WaccaConfig(dict):
|
||||
def __init__(self) -> None:
|
||||
self.server = WaccaServerConfig(self)
|
||||
self.mods = WaccaModsConfig(self)
|
||||
self.gates = WaccaGateConfig(self)
|
113
titles/wacca/const.py
Normal file
113
titles/wacca/const.py
Normal file
@ -0,0 +1,113 @@
|
||||
from enum import Enum
|
||||
|
||||
class WaccaConstants():
|
||||
CONFIG_NAME = "wacca.yaml"
|
||||
GAME_CODE = "SDFE"
|
||||
|
||||
VER_WACCA = 0
|
||||
VER_WACCA_S = 1
|
||||
VER_WACCA_LILY = 2
|
||||
VER_WACCA_LILY_R = 3
|
||||
VER_WACCA_REVERSE = 4
|
||||
|
||||
VERSION_NAMES = ("WACCA", "WACCA S", "WACCA Lily", "WACCA Lily R", "WACCA Reverse")
|
||||
|
||||
class GRADES(Enum):
|
||||
D = 1
|
||||
C = 2
|
||||
B = 3
|
||||
A = 4
|
||||
AA = 5
|
||||
AAA = 6
|
||||
S = 7
|
||||
SS = 8
|
||||
SSS = 9
|
||||
MASTER = 10
|
||||
S_PLUS = 11
|
||||
SS_PLUS = 12
|
||||
SSS_PLUS = 13
|
||||
|
||||
ITEM_TYPES = {
|
||||
"xp": 1,
|
||||
"wp": 2,
|
||||
"music_unlock": 3,
|
||||
"music_difficulty_unlock": 4,
|
||||
"title": 5,
|
||||
"icon": 6,
|
||||
"trophy": 7,
|
||||
"skill": 8,
|
||||
"ticket": 9,
|
||||
"note_color": 10,
|
||||
"note_sound": 11,
|
||||
"baht_do_not_send": 12,
|
||||
"boost_badge": 13,
|
||||
"gate_point": 14,
|
||||
"navigator": 15,
|
||||
"user_plate": 16,
|
||||
"touch_effect": 17,
|
||||
}
|
||||
|
||||
OPTIONS = {
|
||||
"note_speed": 1, # 1.0 - 6.0
|
||||
"field_mask": 2, # 0-4
|
||||
"note_sound": 3, # ID
|
||||
"note_color": 4, # ID
|
||||
"bgm_volume": 5, # 0-100 incremements of 10
|
||||
"bg_video": 7, # ask, on, or off
|
||||
|
||||
"mirror": 101, # none or left+right swap
|
||||
"judge_display_pos": 102, # center, under, over, top or off
|
||||
"judge_detail_display": 103, # on or off
|
||||
"measure_guidelines": 105, # on or off
|
||||
"guideline_mask": 106, # 0 - 5
|
||||
"judge_line_timing_adjust": 108, # -10 - 10
|
||||
"note_design": 110, # 1 - 5
|
||||
"bonus_effect": 114, # on or off
|
||||
"chara_voice": 115, # "usually" or none
|
||||
"score_display_method": 116, # add or subtract
|
||||
"give_up": 117, # off, no touch, can't achieve s, ss, sss, pb
|
||||
"guideline_spacing": 118, # none, or a-g type
|
||||
"center_display": 119, # none, combo, score add, score sub, s ss sss pb boarder
|
||||
"ranking_display": 120, # on or off
|
||||
"stage_up_icon_display": 121, # on or off
|
||||
"rating_display": 122, # on or off
|
||||
"player_level_display": 123, # on or off
|
||||
"touch_effect": 124, # on or off
|
||||
"guide_sound_vol": 125, # 0-100 incremements of 10
|
||||
"touch_note_vol": 126, # 0-100 incremements of 10
|
||||
"hold_note_vol": 127, # 0-100 incremements of 10
|
||||
"slide_note_vol": 128, # 0-100 incremements of 10
|
||||
"snap_note_vol": 129, # 0-100 incremements of 10
|
||||
"chain_note_vol": 130, # 0-100 incremements of 10
|
||||
"bonus_note_vol": 131, # 0-100 incremements of 10
|
||||
"gate_skip": 132, # on or off
|
||||
"key_beam_display": 133, # on or off
|
||||
|
||||
"left_slide_note_color": 201, # red blue green or orange
|
||||
"right_slide_note_color": 202, # red blue green or orange
|
||||
"forward_slide_note_color": 203, # red blue green or orange
|
||||
"back_slide_note_color": 204, # red blue green or orange
|
||||
|
||||
"master_vol": 1001, # 0-100 incremements of 10
|
||||
"set_title_id": 1002, # ID
|
||||
"set_icon_id": 1003, # ID
|
||||
"set_nav_id": 1004, # ID
|
||||
"set_plate_id": 1005, # ID
|
||||
}
|
||||
|
||||
DIFFICULTIES = {
|
||||
"Normal": 1,
|
||||
"Hard": 2,
|
||||
"Expert": 3,
|
||||
"Inferno": 4,
|
||||
}
|
||||
|
||||
class Difficulty(Enum):
|
||||
NORMAL = 1
|
||||
HARD = 2
|
||||
EXPERT = 3
|
||||
INFERNO = 4
|
||||
|
||||
@classmethod
|
||||
def game_ver_to_string(cls, ver: int):
|
||||
return cls.VERSION_NAMES[ver]
|
12
titles/wacca/database.py
Normal file
12
titles/wacca/database.py
Normal file
@ -0,0 +1,12 @@
|
||||
from core.data import Data
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.schema import *
|
||||
|
||||
class WaccaData(Data):
|
||||
def __init__(self, cfg: CoreConfig) -> None:
|
||||
super().__init__(cfg)
|
||||
|
||||
self.profile = WaccaProfileData(self.config, self.session)
|
||||
self.score = WaccaScoreData(self.config, self.session)
|
||||
self.item = WaccaItemData(self.config, self.session)
|
||||
self.static = WaccaStaticData(self.config, self.session)
|
9
titles/wacca/handlers/__init__.py
Normal file
9
titles/wacca/handlers/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from titles.wacca.handlers.base import *
|
||||
from titles.wacca.handlers.advertise import *
|
||||
from titles.wacca.handlers.housing import *
|
||||
from titles.wacca.handlers.user_info import *
|
||||
from titles.wacca.handlers.user_misc import *
|
||||
from titles.wacca.handlers.user_music import *
|
||||
from titles.wacca.handlers.user_status import *
|
||||
from titles.wacca.handlers.user_trial import *
|
||||
from titles.wacca.handlers.user_vip import *
|
45
titles/wacca/handlers/advertise.py
Normal file
45
titles/wacca/handlers/advertise.py
Normal file
@ -0,0 +1,45 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from titles.wacca.handlers.base import BaseResponse
|
||||
from titles.wacca.handlers.helpers import Notice
|
||||
|
||||
# ---advertise/GetNews---
|
||||
class GetNewsResponseV1(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.notices: list[Notice] = []
|
||||
self.copywrightListings: list[str] = []
|
||||
self.stoppedSongs: list[int] = []
|
||||
self.stoppedJackets: list[int] = []
|
||||
self.stoppedMovies: list[int] = []
|
||||
self.stoppedIcons: list[int] = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
note = []
|
||||
|
||||
for notice in self.notices:
|
||||
note.append(notice.make())
|
||||
|
||||
self.params = [
|
||||
note,
|
||||
self.copywrightListings,
|
||||
self.stoppedSongs,
|
||||
self.stoppedJackets,
|
||||
self.stoppedMovies,
|
||||
self.stoppedIcons
|
||||
]
|
||||
|
||||
return super().make()
|
||||
|
||||
class GetNewsResponseV2(GetNewsResponseV1):
|
||||
stoppedProducts: list[int] = []
|
||||
stoppedNavs: list[int] = []
|
||||
stoppedNavVoices: list[int] = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
super().make()
|
||||
self.params.append(self.stoppedProducts)
|
||||
self.params.append(self.stoppedNavs)
|
||||
self.params.append(self.stoppedNavVoices)
|
||||
|
||||
return super(GetNewsResponseV1, self).make()
|
31
titles/wacca/handlers/base.py
Normal file
31
titles/wacca/handlers/base.py
Normal file
@ -0,0 +1,31 @@
|
||||
from typing import Dict, List
|
||||
from datetime import datetime
|
||||
|
||||
class BaseRequest():
|
||||
def __init__(self, data: Dict) -> None:
|
||||
self.requestNo: int = data["requestNo"]
|
||||
self.appVersion: str = data["appVersion"]
|
||||
self.boardId: str = data["boardId"]
|
||||
self.chipId: str = data["chipId"]
|
||||
self.params: List = data["params"]
|
||||
|
||||
class BaseResponse():
|
||||
def __init__(self) -> None:
|
||||
self.status: int = 0
|
||||
self.message: str = ""
|
||||
self.serverTime: int = int(datetime.now().timestamp())
|
||||
self.maintNoticeTime: int = 0
|
||||
self.maintNotPlayableTime: int = 0
|
||||
self.maintStartTime: int = 0
|
||||
self.params: List = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
return {
|
||||
"status": self.status,
|
||||
"message": self.message,
|
||||
"serverTime": self.serverTime,
|
||||
"maintNoticeTime": self.maintNoticeTime,
|
||||
"maintNotPlayableTime": self.maintNotPlayableTime,
|
||||
"maintStartTime": self.maintStartTime,
|
||||
"params": self.params
|
||||
}
|
786
titles/wacca/handlers/helpers.py
Normal file
786
titles/wacca/handlers/helpers.py
Normal file
@ -0,0 +1,786 @@
|
||||
from typing import List, Dict, Any
|
||||
from enum import Enum
|
||||
|
||||
from titles.wacca.const import WaccaConstants
|
||||
|
||||
class HousingInfo():
|
||||
"""
|
||||
1 is lan install role, 2 is country
|
||||
"""
|
||||
id: int = 0
|
||||
val: str = ""
|
||||
|
||||
def __init__(self, id: int = 0, val: str = "") -> None:
|
||||
self.id = id
|
||||
self.val = val
|
||||
|
||||
def make(self) -> List:
|
||||
return [ self.id, self.val ]
|
||||
|
||||
class Notice():
|
||||
name: str = ""
|
||||
title: str = ""
|
||||
message: str = ""
|
||||
unknown3: str = ""
|
||||
unknown4: str = ""
|
||||
showTitleScreen: bool = True
|
||||
showWelcomeScreen: bool = True
|
||||
startTime: int = 0
|
||||
endTime: int = 0
|
||||
voiceline: int = 0
|
||||
|
||||
def __init__(self, name: str = "", title: str = "", message: str = "", start: int = 0, end: int = 0) -> None:
|
||||
self.name = name
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.startTime = start
|
||||
self.endTime = end
|
||||
|
||||
def make(self) -> List:
|
||||
return [ self.name, self.title, self.message, self.unknown3, self.unknown4, int(self.showTitleScreen),
|
||||
int(self.showWelcomeScreen), self.startTime, self.endTime, self.voiceline]
|
||||
|
||||
class UserOption():
|
||||
opt_id: int
|
||||
opt_val: Any
|
||||
|
||||
def __init__(self, opt_id: int = 0, opt_val: Any = 0) -> None:
|
||||
self.opt_id = opt_id
|
||||
self.opt_val = opt_val
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.opt_id, self.opt_val]
|
||||
|
||||
class UserStatusV1():
|
||||
def __init__(self) -> None:
|
||||
self.userId: int = -1
|
||||
self.username: str = ""
|
||||
self.userType: int = 1
|
||||
self.xp: int = 0
|
||||
self.danLevel: int = 0
|
||||
self.danType: int = 0
|
||||
self.wp: int = 0
|
||||
self.titlePartIds: List[int] = [0, 0, 0]
|
||||
self.useCount: int = 0
|
||||
self.loginDays: int = 0
|
||||
self.loginConsecutive: int = 0
|
||||
self.loginConsecutiveDays: int = 0
|
||||
self.vipExpireTime: int = 0
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.userId,
|
||||
self.username,
|
||||
self.userType,
|
||||
self.xp,
|
||||
self.danLevel,
|
||||
self.danType,
|
||||
self.wp,
|
||||
self.titlePartIds,
|
||||
self.useCount,
|
||||
self.loginDays,
|
||||
self.loginConsecutive,
|
||||
self.loginConsecutiveDays,
|
||||
self.vipExpireTime
|
||||
]
|
||||
|
||||
class UserStatusV2(UserStatusV1):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.loginsToday: int = 0
|
||||
self.rating: int = 0
|
||||
|
||||
def make(self) -> List:
|
||||
ret = super().make()
|
||||
|
||||
ret.append(self.loginsToday)
|
||||
ret.append(self.rating)
|
||||
|
||||
return ret
|
||||
|
||||
class ProfileStatus(Enum):
|
||||
ProfileGood = 0
|
||||
ProfileRegister = 1
|
||||
ProfileInUse = 2
|
||||
ProfileWrongRegion = 3
|
||||
|
||||
class PlayVersionStatus(Enum):
|
||||
VersionGood = 0
|
||||
VersionTooNew = 1
|
||||
VersionUpgrade = 2
|
||||
|
||||
class PlayModeCounts():
|
||||
seasonId: int = 0
|
||||
modeId: int = 0
|
||||
playNum: int = 0
|
||||
|
||||
def __init__(self, seasonId: int, modeId: int, playNum: int) -> None:
|
||||
self.seasonId = seasonId
|
||||
self.modeId = modeId
|
||||
self.playNum = playNum
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.seasonId,
|
||||
self.modeId,
|
||||
self.playNum
|
||||
]
|
||||
|
||||
class SongUnlock():
|
||||
songId: int = 0
|
||||
difficulty: int = 0
|
||||
whenAppeared: int = 0
|
||||
whenUnlocked: int = 0
|
||||
|
||||
def __init__(self, song_id: int = 0, difficulty: int = 1, whenAppered: int = 0, whenUnlocked: int = 0) -> None:
|
||||
self.songId = song_id
|
||||
self.difficulty = difficulty
|
||||
self.whenAppeared = whenAppered
|
||||
self.whenUnlocked = whenUnlocked
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.songId,
|
||||
self.difficulty,
|
||||
self.whenAppeared,
|
||||
self.whenUnlocked
|
||||
]
|
||||
|
||||
class GenericItemRecv():
|
||||
def __init__(self, item_type: int = 1, item_id: int = 1, quantity: int = 1) -> None:
|
||||
self.itemId = item_id
|
||||
self.itemType = item_type
|
||||
self.quantity = quantity
|
||||
|
||||
def make(self) -> List:
|
||||
return [ self.itemType, self.itemId, self.quantity ]
|
||||
|
||||
class GenericItemSend():
|
||||
def __init__(self, itemId: int, itemType: int, whenAcquired: int) -> None:
|
||||
self.itemId = itemId
|
||||
self.itemType = itemType
|
||||
self.whenAcquired = whenAcquired
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.itemId,
|
||||
self.itemType,
|
||||
self.whenAcquired
|
||||
]
|
||||
|
||||
class IconItem(GenericItemSend):
|
||||
uses: int = 0
|
||||
|
||||
def __init__(self, itemId: int, itemType: int, uses: int, whenAcquired: int) -> None:
|
||||
super().__init__(itemId, itemType, whenAcquired)
|
||||
self.uses = uses
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.itemId,
|
||||
self.itemType,
|
||||
self.uses,
|
||||
self.whenAcquired
|
||||
]
|
||||
|
||||
class TrophyItem():
|
||||
trophyId: int = 0
|
||||
season: int = 1
|
||||
progress: int = 0
|
||||
badgeType: int = 0
|
||||
|
||||
def __init__(self, trophyId: int, season: int, progress: int, badgeType: int) -> None:
|
||||
self.trophyId = trophyId
|
||||
self.season = season
|
||||
self.progress = progress
|
||||
self.badgeType = badgeType
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.trophyId,
|
||||
self.season,
|
||||
self.progress,
|
||||
self.badgeType
|
||||
]
|
||||
|
||||
class TicketItem():
|
||||
userTicketId: int = 0
|
||||
ticketId: int = 0
|
||||
whenExpires: int = 0
|
||||
|
||||
def __init__(self, userTicketId: int, ticketId: int, whenExpires: int) -> None:
|
||||
self.userTicketId = userTicketId
|
||||
self.ticketId = ticketId
|
||||
self.whenExpires = whenExpires
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.userTicketId,
|
||||
self.ticketId,
|
||||
self.whenExpires
|
||||
]
|
||||
|
||||
class NavigatorItem(IconItem):
|
||||
usesToday: int = 0
|
||||
|
||||
def __init__(self, itemId: int, itemType: int, whenAcquired: int, uses: int, usesToday: int) -> None:
|
||||
super().__init__(itemId, itemType, uses, whenAcquired)
|
||||
self.usesToday = usesToday
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.itemId,
|
||||
self.itemType,
|
||||
self.whenAcquired,
|
||||
self.uses,
|
||||
self.usesToday
|
||||
]
|
||||
|
||||
class SkillItem():
|
||||
skill_type: int
|
||||
level: int
|
||||
flag: int
|
||||
badge: int
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.skill_type,
|
||||
self.level,
|
||||
self.flag,
|
||||
self.badge
|
||||
]
|
||||
|
||||
class UserItemInfoV1():
|
||||
def __init__(self) -> None:
|
||||
self.songUnlocks: List[SongUnlock] = []
|
||||
self.titles: List[GenericItemSend] = []
|
||||
self.icons: List[IconItem] = []
|
||||
self.trophies: List[TrophyItem] = []
|
||||
self.skills: List[SkillItem] = []
|
||||
self.tickets: List[TicketItem] = []
|
||||
self.noteColors: List[GenericItemSend] = []
|
||||
self.noteSounds: List[GenericItemSend] = []
|
||||
|
||||
def make(self) -> List:
|
||||
unlocks = []
|
||||
titles = []
|
||||
icons = []
|
||||
trophies = []
|
||||
skills = []
|
||||
tickets = []
|
||||
colors = []
|
||||
sounds = []
|
||||
|
||||
for x in self.songUnlocks:
|
||||
unlocks.append(x.make())
|
||||
for x in self.titles:
|
||||
titles.append(x.make())
|
||||
for x in self.icons:
|
||||
icons.append(x.make())
|
||||
for x in self.trophies:
|
||||
trophies.append(x.make())
|
||||
for x in self.skills:
|
||||
skills.append(x.make())
|
||||
for x in self.tickets:
|
||||
tickets.append(x.make())
|
||||
for x in self.noteColors:
|
||||
colors.append(x.make())
|
||||
for x in self.noteSounds:
|
||||
sounds.append(x.make())
|
||||
|
||||
return [
|
||||
unlocks,
|
||||
titles,
|
||||
icons,
|
||||
trophies,
|
||||
skills,
|
||||
tickets,
|
||||
colors,
|
||||
sounds,
|
||||
]
|
||||
|
||||
class UserItemInfoV2(UserItemInfoV1):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.navigators: List[NavigatorItem] = []
|
||||
self.plates: List[GenericItemSend] = []
|
||||
|
||||
def make(self) -> List:
|
||||
ret = super().make()
|
||||
plates = []
|
||||
navs = []
|
||||
|
||||
for x in self.navigators:
|
||||
navs.append(x.make())
|
||||
for x in self.plates:
|
||||
plates.append(x.make())
|
||||
|
||||
ret.append(navs)
|
||||
ret.append(plates)
|
||||
return ret
|
||||
|
||||
class UserItemInfoV3(UserItemInfoV2):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.touchEffect: List[GenericItemSend] = []
|
||||
|
||||
def make(self) -> List:
|
||||
ret = super().make()
|
||||
effect = []
|
||||
|
||||
for x in self.touchEffect:
|
||||
effect.append(x.make())
|
||||
|
||||
ret.append(effect)
|
||||
return ret
|
||||
|
||||
class SongDetailClearCounts():
|
||||
def __init__(self, play_ct: int = 0, clear_ct: int = 0, ml_ct: int = 0, fc_ct: int = 0,
|
||||
am_ct: int = 0, counts: List[int] = None) -> None:
|
||||
if counts is None:
|
||||
self.playCt = play_ct
|
||||
self.clearCt = clear_ct
|
||||
self.misslessCt = ml_ct
|
||||
self.fullComboCt = fc_ct
|
||||
self.allMarvelousCt = am_ct
|
||||
|
||||
else:
|
||||
self.playCt = counts[0]
|
||||
self.clearCt = counts[1]
|
||||
self.misslessCt = counts[2]
|
||||
self.fullComboCt = counts[3]
|
||||
self.allMarvelousCt = counts[4]
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.playCt, self.clearCt, self.misslessCt, self.fullComboCt, self.allMarvelousCt]
|
||||
|
||||
class SongDetailGradeCountsV1():
|
||||
dCt: int
|
||||
cCt: int
|
||||
bCt: int
|
||||
aCt: int
|
||||
aaCt: int
|
||||
aaaCt: int
|
||||
sCt: int
|
||||
ssCt: int
|
||||
sssCt: int
|
||||
masterCt: int
|
||||
|
||||
def __init__(self, d: int = 0, c: int = 0, b: int = 0, a: int = 0, aa: int = 0, aaa: int = 0, s: int = 0,
|
||||
ss: int = 0, sss: int = 0, master: int = 0, counts: List[int] = None) -> None:
|
||||
if counts is None:
|
||||
self.dCt = d
|
||||
self.cCt = c
|
||||
self.bCt = b
|
||||
self.aCt = a
|
||||
self.aaCt = aa
|
||||
self.aaaCt = aaa
|
||||
self.sCt = s
|
||||
self.ssCt = ss
|
||||
self.sssCt = sss
|
||||
self.masterCt = master
|
||||
|
||||
else:
|
||||
self.dCt = counts[0]
|
||||
self.cCt = counts[1]
|
||||
self.bCt = counts[2]
|
||||
self.aCt = counts[3]
|
||||
self.aaCt = counts[4]
|
||||
self.aaaCt = counts[5]
|
||||
self.sCt = counts[6]
|
||||
self.ssCt = counts[7]
|
||||
self.sssCt = counts[8]
|
||||
self.masterCt =counts[9]
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.dCt, self.cCt, self.bCt, self.aCt, self.aaCt, self.aaaCt, self.sCt, self.ssCt, self.sssCt, self.masterCt]
|
||||
|
||||
class SongDetailGradeCountsV2(SongDetailGradeCountsV1):
|
||||
spCt: int
|
||||
sspCt: int
|
||||
ssspCt: int
|
||||
|
||||
def __init__(self, d: int = 0, c: int = 0, b: int = 0, a: int = 0, aa: int = 0, aaa: int = 0, s: int = 0,
|
||||
ss: int = 0, sss: int = 0, master: int = 0, sp: int = 0, ssp: int = 0, sssp: int = 0, counts: List[int] = None, ) -> None:
|
||||
super().__init__(d, c, b, a, aa, aaa, s, ss, sss, master, counts)
|
||||
if counts is None:
|
||||
self.spCt = sp
|
||||
self.sspCt = ssp
|
||||
self.ssspCt = sssp
|
||||
|
||||
else:
|
||||
self.spCt = counts[10]
|
||||
self.sspCt = counts[11]
|
||||
self.ssspCt = counts[12]
|
||||
|
||||
def make(self) -> List:
|
||||
return super().make() + [self.spCt, self.sspCt, self.ssspCt]
|
||||
|
||||
class BestScoreDetailV1():
|
||||
songId: int = 0
|
||||
difficulty: int = 1
|
||||
clearCounts: SongDetailClearCounts = SongDetailClearCounts()
|
||||
clearCountsSeason: SongDetailClearCounts = SongDetailClearCounts()
|
||||
gradeCounts: SongDetailGradeCountsV1 = SongDetailGradeCountsV1()
|
||||
score: int = 0
|
||||
bestCombo: int = 0
|
||||
lowestMissCtMaybe: int = 0
|
||||
isUnlock: int = 1
|
||||
rating: int = 0
|
||||
|
||||
def __init__(self, song_id: int, difficulty: int = 1) -> None:
|
||||
self.songId = song_id
|
||||
self.difficulty = difficulty
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.songId,
|
||||
self.difficulty,
|
||||
self.clearCounts.make(),
|
||||
self.clearCountsSeason.make(),
|
||||
self.gradeCounts.make(),
|
||||
self.score,
|
||||
self.bestCombo,
|
||||
self.lowestMissCtMaybe,
|
||||
self.isUnlock,
|
||||
self.rating
|
||||
]
|
||||
|
||||
class BestScoreDetailV2(BestScoreDetailV1):
|
||||
gradeCounts: SongDetailGradeCountsV2 = SongDetailGradeCountsV2()
|
||||
|
||||
class SongUpdateJudgementCounts():
|
||||
marvCt: int
|
||||
greatCt: int
|
||||
goodCt: int
|
||||
missCt: int
|
||||
|
||||
def __init__(self, marvs: int = 0, greats: int = 0, goods: int = 0, misses: int = 0) -> None:
|
||||
self.marvCt = marvs
|
||||
self.greatCt = greats
|
||||
self.goodCt = goods
|
||||
self.missCt = misses
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.marvCt, self.greatCt, self.goodCt, self.missCt]
|
||||
|
||||
class SongUpdateDetail():
|
||||
songId: int
|
||||
difficulty: int
|
||||
level: float
|
||||
score: int
|
||||
judgements: SongUpdateJudgementCounts
|
||||
maxCombo: int
|
||||
grade: WaccaConstants.GRADES
|
||||
flagCleared: bool
|
||||
flagMissless: bool
|
||||
flagFullcombo: bool
|
||||
flagAllMarvelous: bool
|
||||
flagGiveUp: bool
|
||||
skillPt: int
|
||||
fastCt: int
|
||||
slowCt: int
|
||||
flagNewRecord: bool
|
||||
|
||||
def __init__(self, data: List = None) -> None:
|
||||
if data is not None:
|
||||
self.songId = data[0]
|
||||
self.difficulty = data[1]
|
||||
self.level = data[2]
|
||||
self.score = data[3]
|
||||
|
||||
self.judgements = SongUpdateJudgementCounts(data[4][0], data[4][1], data[4][2], data[4][3])
|
||||
self.maxCombo = data[5]
|
||||
self.grade = WaccaConstants.GRADES(data[6]) # .value to get number, .name to get letter
|
||||
|
||||
self.flagCleared = False if data[7] == 0 else True
|
||||
self.flagMissless = False if data[8] == 0 else True
|
||||
self.flagFullcombo = False if data[9] == 0 else True
|
||||
self.flagAllMarvelous = False if data[10] == 0 else True
|
||||
self.flagGiveUp = False if data[11] == 0 else True
|
||||
|
||||
self.skillPt = data[12]
|
||||
self.fastCt = data[13]
|
||||
self.slowCt = data[14]
|
||||
self.flagNewRecord = False if data[15] == 0 else True
|
||||
|
||||
class SeasonalInfoV1():
|
||||
def __init__(self) -> None:
|
||||
self.level: int = 0
|
||||
self.wpObtained: int = 0
|
||||
self.wpSpent: int = 0
|
||||
self.cumulativeScore: int = 0
|
||||
self.titlesObtained: int = 0
|
||||
self.iconsObtained: int = 0
|
||||
self.skillPts: int = 0
|
||||
self.noteColorsObtained: int = 0
|
||||
self.noteSoundsObtained: int = 0
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.level,
|
||||
self.wpObtained,
|
||||
self.wpSpent,
|
||||
self.cumulativeScore,
|
||||
self.titlesObtained,
|
||||
self.iconsObtained,
|
||||
self.skillPts,
|
||||
self.noteColorsObtained,
|
||||
self.noteSoundsObtained
|
||||
]
|
||||
|
||||
class SeasonalInfoV2(SeasonalInfoV1):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.platesObtained: int = 0
|
||||
self.cumulativeGatePts: int = 0
|
||||
|
||||
def make(self) -> List:
|
||||
return super().make() + [self.platesObtained, self.cumulativeGatePts]
|
||||
|
||||
class BingoPageStatus():
|
||||
id = 0
|
||||
location = 1
|
||||
progress = 0
|
||||
|
||||
def __init__(self, id: int = 0, location: int = 1, progress: int = 0) -> None:
|
||||
self.id = id
|
||||
self.location = location
|
||||
self.progress = progress
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.id, self.location, self.progress]
|
||||
|
||||
class BingoDetail():
|
||||
def __init__(self, pageNumber: int) -> None:
|
||||
self.pageNumber = pageNumber
|
||||
self.pageStatus: List[BingoPageStatus] = []
|
||||
|
||||
def make(self) -> List:
|
||||
status = []
|
||||
for x in self.pageStatus:
|
||||
status.append(x.make())
|
||||
|
||||
return [
|
||||
self.pageNumber,
|
||||
status
|
||||
]
|
||||
|
||||
class GateDetailV1():
|
||||
def __init__(self, gate_id: int = 1, page: int = 1, progress: int = 0, loops: int = 0, last_used: int = 0, mission_flg = 0) -> None:
|
||||
self.id = gate_id
|
||||
self.page = page
|
||||
self.progress = progress
|
||||
self.loops = loops
|
||||
self.lastUsed = last_used
|
||||
self.missionFlg = mission_flg
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.id, 1, self.page, self.progress, self.loops, self.lastUsed]
|
||||
|
||||
class GateDetailV2(GateDetailV1):
|
||||
def make(self) -> List:
|
||||
return super().make() + [self.missionFlg]
|
||||
|
||||
class GachaInfo():
|
||||
def make() -> List:
|
||||
return []
|
||||
|
||||
class LastSongDetail():
|
||||
lastSongId = 90
|
||||
lastSongDiff = 1
|
||||
lastFolderOrd = 1
|
||||
lastFolderId = 1
|
||||
lastSongOrd = 1
|
||||
|
||||
def __init__(self, last_song: int = 90, last_diff: int = 1, last_folder_ord: int = 1,
|
||||
last_folder_id: int = 1, last_song_ord: int = 1) -> None:
|
||||
self.lastSongId = last_song
|
||||
self.lastSongDiff = last_diff
|
||||
self.lastFolderOrd = last_folder_ord
|
||||
self.lastFolderId = last_folder_id
|
||||
self.lastSongOrd = last_song_ord
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.lastSongId, self.lastSongDiff, self.lastFolderOrd, self.lastFolderId,
|
||||
self.lastSongOrd]
|
||||
|
||||
class FriendDetail():
|
||||
def make(self) -> List:
|
||||
return []
|
||||
|
||||
class UserOption():
|
||||
id = 1
|
||||
val = 1
|
||||
|
||||
def __init__(self, id: int = 1, val: int = val) -> None:
|
||||
self.id = id
|
||||
self.val = val
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.id, self.val]
|
||||
|
||||
class LoginBonusInfo():
|
||||
def __init__(self) -> None:
|
||||
self.tickets: List[TicketItem] = []
|
||||
self.items: List[GenericItemRecv] = []
|
||||
self.message: str = ""
|
||||
|
||||
def make(self) -> List:
|
||||
tks = []
|
||||
itms = []
|
||||
|
||||
for ticket in self.tickets:
|
||||
tks.append(ticket.make())
|
||||
|
||||
for item in self.items:
|
||||
itms.append(item.make())
|
||||
|
||||
return [ tks, itms, self.message ]
|
||||
|
||||
class VipLoginBonus():
|
||||
id = 1
|
||||
unknown = 0
|
||||
item: GenericItemRecv
|
||||
|
||||
def __init__(self, id: int = 1, unk: int = 0, item_type: int = 1, item_id: int = 1, item_qt: int = 1) -> None:
|
||||
self.id = id
|
||||
self.unknown = unk
|
||||
self.item = GenericItemRecv(item_type, item_id, item_qt)
|
||||
|
||||
def make(self) -> List:
|
||||
return [ self.id, self.unknown, self.item.make() ]
|
||||
|
||||
class VipInfo():
|
||||
def __init__(self, year: int = 2019, month: int = 1, day: int = 1, num_item: int = 1) -> None:
|
||||
self.pageYear = year
|
||||
self.pageMonth = month
|
||||
self.pageDay = day
|
||||
self.numItem = num_item
|
||||
self.presentInfo: List[LoginBonusInfo] = []
|
||||
self.vipLoginBonus: List[VipLoginBonus] = []
|
||||
|
||||
def make(self) -> List:
|
||||
pres = []
|
||||
vipBonus = []
|
||||
|
||||
for present in self.presentInfo:
|
||||
pres.append(present.make())
|
||||
|
||||
for b in self.vipLoginBonus:
|
||||
vipBonus.append(b.make())
|
||||
|
||||
return [ self.pageYear, self.pageMonth, self.pageDay, self.numItem, pres, vipBonus ]
|
||||
|
||||
class PurchaseType(Enum):
|
||||
PurchaseTypeCredit = 1
|
||||
PurchaseTypeWP = 2
|
||||
|
||||
class PlayType(Enum):
|
||||
PlayTypeSingle = 1
|
||||
PlayTypeVs = 2
|
||||
PlayTypeCoop = 3
|
||||
PlayTypeStageup = 4
|
||||
|
||||
class SongRatingUpdate():
|
||||
song_id = 0
|
||||
difficulty = 0
|
||||
rating = 0
|
||||
|
||||
def __init__(self, song: int = 0, difficulty: int = 0, rating: int = 0) -> None:
|
||||
self.song_id = song
|
||||
self.difficulty = difficulty
|
||||
self.rating = rating
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.song_id, self.difficulty, self.rating]
|
||||
|
||||
class StageInfo():
|
||||
danId: int = 0
|
||||
danLevel: int = 0
|
||||
clearStatus: int = 0
|
||||
numSongCleared: int = 0
|
||||
song1BestScore: int = 0
|
||||
song2BestScore: int = 0
|
||||
song3BestScore: int = 0
|
||||
unk5: int = 1
|
||||
|
||||
def __init__(self, dan_id: int = 0, dan_level: int = 0) -> None:
|
||||
self.danId = dan_id
|
||||
self.danLevel = dan_level
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.danId,
|
||||
self.danLevel,
|
||||
self.clearStatus,
|
||||
self.numSongCleared,
|
||||
[
|
||||
self.song1BestScore,
|
||||
self.song2BestScore,
|
||||
self.song3BestScore,
|
||||
],
|
||||
self.unk5
|
||||
]
|
||||
|
||||
class StageupClearType(Enum):
|
||||
FAIL = 0
|
||||
CLEAR_BLUE = 1
|
||||
CLEAR_SILVER = 2
|
||||
CLEAR_GOLD = 3
|
||||
|
||||
class MusicUpdateDetailV1():
|
||||
def __init__(self) -> None:
|
||||
self.songId = 0
|
||||
self.difficulty = 1
|
||||
self.clearCounts: SongDetailClearCounts = SongDetailClearCounts()
|
||||
self.clearCountsSeason: SongDetailClearCounts = SongDetailClearCounts()
|
||||
self.grades: SongDetailGradeCountsV1 = SongDetailGradeCountsV1()
|
||||
self.score = 0
|
||||
self.lowestMissCount = 0
|
||||
self.maxSkillPts = 0
|
||||
self.locked = 0
|
||||
self.rating = 0
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.songId,
|
||||
self.difficulty,
|
||||
self.clearCounts.make(),
|
||||
self.clearCountsSeason.make(),
|
||||
self.grades.make(),
|
||||
self.score,
|
||||
self.lowestMissCount,
|
||||
self.maxSkillPts,
|
||||
self.locked,
|
||||
self.rating
|
||||
]
|
||||
|
||||
class MusicUpdateDetailV2(MusicUpdateDetailV1):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.grades = SongDetailGradeCountsV2()
|
||||
|
||||
class SongRatingUpdate():
|
||||
def __init__(self, song_id: int = 0, difficulty: int = 1, new_rating: int = 0) -> None:
|
||||
self.songId = song_id
|
||||
self.difficulty = difficulty
|
||||
self.rating = new_rating
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.songId,
|
||||
self.difficulty,
|
||||
self.rating,
|
||||
]
|
||||
|
||||
class GateTutorialFlag():
|
||||
def __init__(self, tutorial_id: int = 1, flg_watched: bool = False) -> None:
|
||||
self.tutorialId = tutorial_id
|
||||
self.flagWatched = flg_watched
|
||||
|
||||
def make(self) -> List:
|
||||
return [
|
||||
self.tutorialId,
|
||||
int(self.flagWatched)
|
||||
]
|
38
titles/wacca/handlers/housing.py
Normal file
38
titles/wacca/handlers/housing.py
Normal file
@ -0,0 +1,38 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import HousingInfo
|
||||
|
||||
# ---housing/get----
|
||||
class HousingGetResponse(BaseResponse):
|
||||
def __init__(self, housingId: int) -> None:
|
||||
super().__init__()
|
||||
self.housingId: int = housingId
|
||||
self.regionId: int = 0
|
||||
|
||||
def make(self) -> Dict:
|
||||
self.params = [self.housingId, self.regionId]
|
||||
return super().make()
|
||||
|
||||
# ---housing/start----
|
||||
class HousingStartRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.unknown0: str = self.params[0]
|
||||
self.errorLog: str = self.params[1]
|
||||
self.unknown2: str = self.params[2]
|
||||
self.info: List[HousingInfo] = []
|
||||
|
||||
for info in self.params[3]:
|
||||
self.info.append(HousingInfo(info[0], info[1]))
|
||||
|
||||
class HousingStartResponseV1(BaseResponse):
|
||||
def __init__(self, regionId: int, songList: List[int]) -> None:
|
||||
super().__init__()
|
||||
self.regionId = regionId
|
||||
self.songList = songList
|
||||
|
||||
def make(self) -> Dict:
|
||||
self.params = [self.regionId, self.songList]
|
||||
|
||||
return super().make()
|
61
titles/wacca/handlers/user_info.py
Normal file
61
titles/wacca/handlers/user_info.py
Normal file
@ -0,0 +1,61 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import UserOption
|
||||
|
||||
# ---user/info/update---
|
||||
class UserInfoUpdateRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = int(self.params[0])
|
||||
self.optsUpdated: List[UserOption] = []
|
||||
self.datesUpdated: List = self.params[3]
|
||||
self.favoritesAdded: List[int] = self.params[4]
|
||||
self.favoritesRemoved: List[int] = self.params[5]
|
||||
|
||||
for x in self.params[2]:
|
||||
self.optsUpdated.append(UserOption(x[0], x[1]))
|
||||
|
||||
# ---user/info/getMyroom--- TODO: Understand this better
|
||||
class UserInfogetMyroomRequest(BaseRequest):
|
||||
game_id = 0
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.game_id = int(self.params[0])
|
||||
|
||||
class UserInfogetMyroomResponse(BaseResponse):
|
||||
def make(self) -> Dict:
|
||||
self.params = [
|
||||
0,0,0,0,0,[],0,0,0
|
||||
]
|
||||
|
||||
return super().make()
|
||||
|
||||
# ---user/info/getRanking---
|
||||
class UserInfogetRankingRequest(BaseRequest):
|
||||
game_id = 0
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.game_id = int(self.params[0])
|
||||
|
||||
class UserInfogetRankingResponse(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.total_score_rank = 0
|
||||
self.high_score_by_song_rank = 0
|
||||
self.cumulative_score_rank = 0
|
||||
self.state_up_score_rank = 0
|
||||
self.other_score_ranking = 0
|
||||
self.wacca_points_ranking = 0
|
||||
|
||||
def make(self) -> Dict:
|
||||
self.params = [
|
||||
self.total_score_rank,
|
||||
self.high_score_by_song_rank,
|
||||
self.cumulative_score_rank,
|
||||
self.state_up_score_rank,
|
||||
self.other_score_ranking,
|
||||
self.wacca_points_ranking,
|
||||
]
|
||||
|
||||
return super().make()
|
85
titles/wacca/handlers/user_misc.py
Normal file
85
titles/wacca/handlers/user_misc.py
Normal file
@ -0,0 +1,85 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import PurchaseType, GenericItemRecv
|
||||
from titles.wacca.handlers.helpers import TicketItem, SongRatingUpdate, BingoDetail
|
||||
from titles.wacca.handlers.helpers import BingoPageStatus, GateTutorialFlag
|
||||
|
||||
# ---user/goods/purchase---
|
||||
class UserGoodsPurchaseRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = int(self.params[0])
|
||||
self.purchaseId = int(self.params[1])
|
||||
self.purchaseCount = int(self.params[2])
|
||||
self.purchaseType = PurchaseType(self.params[3])
|
||||
self.cost = int(self.params[4])
|
||||
self.itemObtained: GenericItemRecv = GenericItemRecv(self.params[5][0], self.params[5][1], self.params[5][2])
|
||||
|
||||
class UserGoodsPurchaseResponse(BaseResponse):
|
||||
def __init__(self, wp: int = 0, tickets: List = []) -> None:
|
||||
super().__init__()
|
||||
self.currentWp = wp
|
||||
self.tickets: List[TicketItem] = []
|
||||
|
||||
for ticket in tickets:
|
||||
self.tickets.append(TicketItem(ticket[0], ticket[1], ticket[2]))
|
||||
|
||||
def make(self) -> List:
|
||||
tix = []
|
||||
for ticket in self.tickets:
|
||||
tix.append(ticket.make())
|
||||
|
||||
self.params = [self.currentWp, tix]
|
||||
|
||||
return super().make()
|
||||
|
||||
# ---user/sugaroku/update---
|
||||
class UserSugarokuUpdateRequestV1(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = int(self.params[0])
|
||||
self.gateId = int(self.params[1])
|
||||
self.page = int(self.params[2])
|
||||
self.progress = int(self.params[3])
|
||||
self.loops = int(self.params[4])
|
||||
self.boostsUsed = self.params[5]
|
||||
self.totalPts = int(self.params[7])
|
||||
self.itemsObtainted: List[GenericItemRecv] = []
|
||||
|
||||
for item in self.params[6]:
|
||||
self.itemsObtainted.append(GenericItemRecv(item[0], item[1], item[2]))
|
||||
|
||||
class UserSugarokuUpdateRequestV2(UserSugarokuUpdateRequestV1):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.mission_flag = int(self.params[8])
|
||||
|
||||
# ---user/rating/update---
|
||||
class UserRatingUpdateRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = self.params[0]
|
||||
self.totalRating = self.params[1]
|
||||
self.songs: List[SongRatingUpdate] = []
|
||||
|
||||
for x in self.params[2]:
|
||||
self.songs.append(SongRatingUpdate(x[0], x[1], x[2]))
|
||||
|
||||
# ---user/mission/update---
|
||||
class UserMissionUpdateRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = self.params[0]
|
||||
self.bingoDetail = BingoDetail(self.params[1][0])
|
||||
self.itemsObtained: List[GenericItemRecv] = []
|
||||
self.gateTutorialFlags: List[GateTutorialFlag] = []
|
||||
|
||||
for x in self.params[1][1]:
|
||||
self.bingoDetail.pageStatus.append(BingoPageStatus(x[0], x[1], x[2]))
|
||||
|
||||
for x in self.params[2]:
|
||||
self.itemsObtained.append(GenericItemRecv(x[0], x[1], x[2]))
|
||||
|
||||
for x in self.params[3]:
|
||||
self.gateTutorialFlags.append(GateTutorialFlag(x[0], x[1]))
|
92
titles/wacca/handlers/user_music.py
Normal file
92
titles/wacca/handlers/user_music.py
Normal file
@ -0,0 +1,92 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import GenericItemRecv, SongUpdateDetail, TicketItem
|
||||
from titles.wacca.handlers.helpers import MusicUpdateDetailV1, MusicUpdateDetailV2
|
||||
from titles.wacca.handlers.helpers import SeasonalInfoV2, SeasonalInfoV1
|
||||
|
||||
# ---user/music/update---
|
||||
class UserMusicUpdateRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId: int = self.params[0]
|
||||
self.songNumber: int = self.params[1]
|
||||
self.songDetail = SongUpdateDetail(self.params[2])
|
||||
self.itemsObtained: List[GenericItemRecv] = []
|
||||
|
||||
for itm in data["params"][3]:
|
||||
self.itemsObtained.append(GenericItemRecv(itm[0], itm[1], itm[2]))
|
||||
|
||||
class UserMusicUpdateResponseV1(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.songDetail = MusicUpdateDetailV1()
|
||||
self.seasonInfo = SeasonalInfoV1()
|
||||
self.rankingInfo: List[List[int]] = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
self.params = [
|
||||
self.songDetail.make(),
|
||||
[self.songDetail.songId, self.songDetail.clearCounts.playCt],
|
||||
self.seasonInfo.make(),
|
||||
self.rankingInfo
|
||||
]
|
||||
|
||||
return super().make()
|
||||
|
||||
class UserMusicUpdateResponseV2(UserMusicUpdateResponseV1):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.seasonInfo = SeasonalInfoV2()
|
||||
|
||||
class UserMusicUpdateResponseV3(UserMusicUpdateResponseV2):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.songDetail = MusicUpdateDetailV2()
|
||||
|
||||
# ---user/music/updateCoop---
|
||||
class UserMusicUpdateCoopRequest(UserMusicUpdateRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.coopData = self.params[4]
|
||||
|
||||
# ---user/music/updateVs---
|
||||
class UserMusicUpdateVsRequest(UserMusicUpdateRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.vsData = self.params[4]
|
||||
|
||||
# ---user/music/unlock---
|
||||
class UserMusicUnlockRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = self.params[0]
|
||||
self.songId = self.params[1]
|
||||
self.difficulty = self.params[2]
|
||||
self.itemsUsed: List[GenericItemRecv] = []
|
||||
|
||||
for itm in self.params[3]:
|
||||
self.itemsUsed.append(GenericItemRecv(itm[0], itm[1], itm[2]))
|
||||
|
||||
class UserMusicUnlockResponse(BaseResponse):
|
||||
def __init__(self, current_wp: int = 0, tickets_remaining: List = []) -> None:
|
||||
super().__init__()
|
||||
self.wp = current_wp
|
||||
self.tickets: List[TicketItem] = []
|
||||
|
||||
for ticket in tickets_remaining:
|
||||
self.tickets.append(TicketItem(ticket[0], ticket[1], ticket[2]))
|
||||
|
||||
def make(self) -> List:
|
||||
tickets = []
|
||||
|
||||
for ticket in self.tickets:
|
||||
tickets.append(ticket.make())
|
||||
|
||||
self.params = [
|
||||
self.wp,
|
||||
tickets
|
||||
]
|
||||
|
||||
return super().make()
|
||||
|
289
titles/wacca/handlers/user_status.py
Normal file
289
titles/wacca/handlers/user_status.py
Normal file
@ -0,0 +1,289 @@
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import *
|
||||
|
||||
# ---user/status/get----
|
||||
class UserStatusGetRequest(BaseRequest):
|
||||
aimeId: int = 0
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.aimeId = int(data["params"][0])
|
||||
|
||||
class UserStatusGetV1Response(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.userStatus: UserStatusV1 = UserStatusV1()
|
||||
self.setTitleId: int = 0
|
||||
self.setIconId: int = 0
|
||||
self.profileStatus: ProfileStatus = ProfileStatus.ProfileGood
|
||||
self.versionStatus: PlayVersionStatus = PlayVersionStatus.VersionGood
|
||||
self.lastGameVersion: str = ""
|
||||
|
||||
def make(self) -> Dict:
|
||||
self.params = [
|
||||
self.userStatus.make(),
|
||||
self.setTitleId,
|
||||
self.setIconId,
|
||||
self.profileStatus.value,
|
||||
[
|
||||
self.versionStatus.value,
|
||||
self.lastGameVersion
|
||||
]
|
||||
]
|
||||
|
||||
return super().make()
|
||||
|
||||
class UserStatusGetV2Response(UserStatusGetV1Response):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.userStatus: UserStatusV2 = UserStatusV2()
|
||||
self.unknownArr: List = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
super().make()
|
||||
|
||||
self.params.append(self.unknownArr)
|
||||
|
||||
return super(UserStatusGetV1Response, self).make()
|
||||
|
||||
# ---user/status/getDetail----
|
||||
class UserStatusGetDetailRequest(BaseRequest):
|
||||
userId: int = 0
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.userId = data["params"][0]
|
||||
|
||||
class UserStatusGetDetailResponseV1(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.userStatus: UserStatusV1 = UserStatusV1()
|
||||
self.options: List[UserOption] = []
|
||||
self.seasonalPlayModeCounts: List[PlayModeCounts] = []
|
||||
self.userItems: UserItemInfoV1 = UserItemInfoV1()
|
||||
self.scores: List[BestScoreDetailV1] = []
|
||||
self.songPlayStatus: List[int] = [0,0]
|
||||
self.seasonInfo: SeasonalInfoV1 = []
|
||||
self.playAreaList: List = [ [0],[0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0],[0,0,0,0],[0,0,0,0,0,0,0],[0] ]
|
||||
self.songUpdateTime: int = 0
|
||||
|
||||
def make(self) -> List:
|
||||
opts = []
|
||||
play_modes = []
|
||||
scores = []
|
||||
|
||||
for x in self.seasonalPlayModeCounts:
|
||||
play_modes.append(x.make())
|
||||
|
||||
for x in self.scores:
|
||||
scores.append(x.make())
|
||||
|
||||
for x in self.options:
|
||||
opts.append(x.make())
|
||||
|
||||
self.params = [
|
||||
self.userStatus.make(),
|
||||
opts,
|
||||
play_modes,
|
||||
self.userItems.make(),
|
||||
scores,
|
||||
self.songPlayStatus,
|
||||
self.seasonInfo.make(),
|
||||
self.playAreaList,
|
||||
self.songUpdateTime
|
||||
]
|
||||
|
||||
return super().make()
|
||||
|
||||
def find_score_idx(self, song_id: int, difficulty: int = 1, start_idx: int = 0, stop_idx: int = None) -> Optional[int]:
|
||||
if stop_idx is None or stop_idx > len(self.scores):
|
||||
stop_idx = len(self.scores)
|
||||
|
||||
for x in range(start_idx, stop_idx):
|
||||
if self.scores[x].songId == song_id and self.scores[x].difficulty == difficulty:
|
||||
return x
|
||||
|
||||
return None
|
||||
|
||||
class UserStatusGetDetailResponseV2(UserStatusGetDetailResponseV1):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.userStatus: UserStatusV2 = UserStatusV2()
|
||||
self.seasonInfo: SeasonalInfoV2 = SeasonalInfoV2()
|
||||
self.userItems: UserItemInfoV2 = UserItemInfoV2()
|
||||
self.favorites: List[int] = []
|
||||
self.stoppedSongIds: List[int] = []
|
||||
self.eventInfo: List[int] = []
|
||||
self.gateInfo: List[GateDetailV1] = []
|
||||
self.lastSongInfo: LastSongDetail = LastSongDetail()
|
||||
self.gateTutorialFlags: List[GateTutorialFlag] = []
|
||||
self.gatchaInfo: List[GachaInfo] = []
|
||||
self.friendList: List[FriendDetail] = []
|
||||
|
||||
def make(self) -> List:
|
||||
super().make()
|
||||
gates = []
|
||||
friends = []
|
||||
tut_flg = []
|
||||
|
||||
for x in self.gateInfo:
|
||||
gates.append(x.make())
|
||||
|
||||
for x in self.friendList:
|
||||
friends.append(x.make())
|
||||
|
||||
for x in self.gateTutorialFlags:
|
||||
tut_flg.append(x.make())
|
||||
|
||||
while len(tut_flg) < 5:
|
||||
flag_id = len(tut_flg) + 1
|
||||
tut_flg.append([flag_id, 0])
|
||||
|
||||
self.params.append(self.favorites)
|
||||
self.params.append(self.stoppedSongIds)
|
||||
self.params.append(self.eventInfo)
|
||||
self.params.append(gates)
|
||||
self.params.append(self.lastSongInfo.make())
|
||||
self.params.append(tut_flg)
|
||||
self.params.append(self.gatchaInfo)
|
||||
self.params.append(friends)
|
||||
|
||||
return super(UserStatusGetDetailResponseV1, self).make()
|
||||
|
||||
class UserStatusGetDetailResponseV3(UserStatusGetDetailResponseV2):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.gateInfo: List[GateDetailV2] = []
|
||||
|
||||
class UserStatusGetDetailResponseV4(UserStatusGetDetailResponseV3):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.userItems: UserItemInfoV3 = UserItemInfoV3()
|
||||
self.bingoStatus: BingoDetail = BingoDetail(0)
|
||||
self.scores: List[BestScoreDetailV2] = []
|
||||
|
||||
def make(self) -> List:
|
||||
super().make()
|
||||
self.params.append(self.bingoStatus.make())
|
||||
|
||||
return super(UserStatusGetDetailResponseV1, self).make()
|
||||
|
||||
# ---user/status/login----
|
||||
class UserStatusLoginRequest(BaseRequest):
|
||||
userId: int = 0
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.userId = data["params"][0]
|
||||
|
||||
class UserStatusLoginResponseV1(BaseResponse):
|
||||
def __init__(self, is_first_login_daily: bool = False, last_login_date: int = 0) -> None:
|
||||
super().__init__()
|
||||
self.dailyBonus: List[LoginBonusInfo] = []
|
||||
self.consecBonus: List[LoginBonusInfo] = []
|
||||
self.otherBonus: List[LoginBonusInfo] = []
|
||||
self.firstLoginDaily = is_first_login_daily
|
||||
self.lastLoginDate = last_login_date
|
||||
|
||||
def make(self) -> List:
|
||||
daily = []
|
||||
consec = []
|
||||
other = []
|
||||
|
||||
for bonus in self.dailyBonus:
|
||||
daily.append(bonus.make())
|
||||
|
||||
for bonus in self.consecBonus:
|
||||
consec.append(bonus.make())
|
||||
|
||||
for bonus in self.otherBonus:
|
||||
other.append(bonus.make())
|
||||
|
||||
self.params = [ daily, consec, other, int(self.firstLoginDaily)]
|
||||
return super().make()
|
||||
|
||||
class UserStatusLoginResponseV2(UserStatusLoginResponseV1):
|
||||
vipInfo: VipInfo
|
||||
lastLoginDate: int = 0
|
||||
|
||||
def __init__(self, is_first_login_daily: bool = False, last_login_date: int = 0) -> None:
|
||||
super().__init__(is_first_login_daily)
|
||||
self.lastLoginDate = last_login_date
|
||||
|
||||
self.vipInfo = VipInfo()
|
||||
|
||||
def make(self) -> List:
|
||||
super().make()
|
||||
self.params.append(self.vipInfo.make())
|
||||
self.params.append(self.lastLoginDate)
|
||||
return super(UserStatusLoginResponseV1, self).make()
|
||||
|
||||
class UserStatusLoginResponseV3(UserStatusLoginResponseV2):
|
||||
unk: List = []
|
||||
|
||||
def make(self) -> List:
|
||||
super().make()
|
||||
self.params.append(self.unk)
|
||||
return super(UserStatusLoginResponseV1, self).make()
|
||||
|
||||
# ---user/status/create---
|
||||
class UserStatusCreateRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.aimeId = data["params"][0]
|
||||
self.username = data["params"][1]
|
||||
|
||||
class UserStatusCreateResponseV1(BaseResponse):
|
||||
def __init__(self, userId: int, username: str) -> None:
|
||||
super().__init__()
|
||||
self.userStatus = UserStatusV1()
|
||||
self.userStatus.userId = userId
|
||||
self.userStatus.username = username
|
||||
|
||||
def make(self) -> List:
|
||||
self.params = [
|
||||
self.userStatus.make()
|
||||
]
|
||||
return super().make()
|
||||
|
||||
class UserStatusCreateResponseV2(UserStatusCreateResponseV1):
|
||||
def __init__(self, userId: int, username: str) -> None:
|
||||
super().__init__(userId, username)
|
||||
self.userStatus: UserStatusV2 = UserStatusV2()
|
||||
self.userStatus.userId = userId
|
||||
self.userStatus.username = username
|
||||
|
||||
# ---user/status/logout---
|
||||
class UserStatusLogoutRequest(BaseRequest):
|
||||
userId: int
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.userId = data["params"][0]
|
||||
|
||||
# ---user/status/update---
|
||||
class UserStatusUpdateRequestV1(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId: int = data["params"][0]
|
||||
self.playType: PlayType = PlayType(data["params"][1])
|
||||
self.itemsRecieved: List[GenericItemRecv] = []
|
||||
|
||||
for itm in data["params"][2]:
|
||||
self.itemsRecieved.append(GenericItemRecv(itm[0], itm[1], itm[2]))
|
||||
|
||||
class UserStatusUpdateRequestV2(UserStatusUpdateRequestV1):
|
||||
isContinue = False
|
||||
isFirstPlayFree = False
|
||||
itemsUsed = []
|
||||
lastSongInfo: LastSongDetail
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.isContinue = bool(data["params"][3])
|
||||
self.isFirstPlayFree = bool(data["params"][4])
|
||||
self.itemsUsed = data["params"][5]
|
||||
self.lastSongInfo = LastSongDetail(data["params"][6][0], data["params"][6][1],
|
||||
data["params"][6][2], data["params"][6][3], data["params"][6][4])
|
48
titles/wacca/handlers/user_trial.py
Normal file
48
titles/wacca/handlers/user_trial.py
Normal file
@ -0,0 +1,48 @@
|
||||
from typing import Dict, List
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import StageInfo, StageupClearType
|
||||
|
||||
# --user/trial/get--
|
||||
class UserTrialGetRequest(BaseRequest):
|
||||
profileId: int = 0
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = self.params[0]
|
||||
|
||||
class UserTrialGetResponse(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.stageList: List[StageInfo] = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
dans = []
|
||||
for x in self.stageList:
|
||||
dans.append(x.make())
|
||||
|
||||
self.params = [dans]
|
||||
return super().make()
|
||||
|
||||
# --user/trial/update--
|
||||
class UserTrialUpdateRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = self.params[0]
|
||||
self.stageId = self.params[1]
|
||||
self.stageLevel = self.params[2]
|
||||
self.clearType = StageupClearType(self.params[3])
|
||||
self.songScores = self.params[4]
|
||||
self.numSongsCleared = self.params[5]
|
||||
self.itemsObtained = self.params[6]
|
||||
self.unk7: List = []
|
||||
|
||||
if len(self.params) == 8:
|
||||
self.unk7 = self.params[7]
|
||||
|
||||
class UserTrialUpdateResponse(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
def make(self) -> Dict:
|
||||
return super().make()
|
54
titles/wacca/handlers/user_vip.py
Normal file
54
titles/wacca/handlers/user_vip.py
Normal file
@ -0,0 +1,54 @@
|
||||
from typing import Dict, List
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import VipLoginBonus
|
||||
|
||||
# --user/vip/get--
|
||||
class UserVipGetRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = self.params[0]
|
||||
|
||||
class UserVipGetResponse(BaseResponse):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.vipDays: int = 0
|
||||
self.unknown1: int = 1
|
||||
self.unknown2: int = 1
|
||||
self.presents: List[VipLoginBonus] = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
pres = []
|
||||
for x in self.presents:
|
||||
pres.append(x.make())
|
||||
|
||||
self.params = [
|
||||
self.vipDays,
|
||||
[
|
||||
self.unknown1,
|
||||
self.unknown2,
|
||||
pres
|
||||
]
|
||||
]
|
||||
return super().make()
|
||||
|
||||
# --user/vip/start--
|
||||
class UserVipStartRequest(BaseRequest):
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.profileId = self.params[0]
|
||||
self.cost = self.params[1]
|
||||
self.days = self.params[2]
|
||||
|
||||
class UserVipStartResponse(BaseResponse):
|
||||
def __init__(self, expires: int = 0) -> None:
|
||||
super().__init__()
|
||||
self.whenExpires: int = expires
|
||||
self.presents = []
|
||||
|
||||
def make(self) -> Dict:
|
||||
self.params = [
|
||||
self.whenExpires,
|
||||
self.presents
|
||||
]
|
||||
|
||||
return super().make()
|
126
titles/wacca/index.py
Normal file
126
titles/wacca/index.py
Normal file
@ -0,0 +1,126 @@
|
||||
import yaml
|
||||
import logging, coloredlogs
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
import logging
|
||||
import json
|
||||
from datetime import datetime
|
||||
from hashlib import md5
|
||||
from twisted.web.http import Request
|
||||
from typing import Dict
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.const import WaccaConstants
|
||||
from titles.wacca.reverse import WaccaReverse
|
||||
from titles.wacca.lilyr import WaccaLilyR
|
||||
from titles.wacca.lily import WaccaLily
|
||||
from titles.wacca.s import WaccaS
|
||||
from titles.wacca.base import WaccaBase
|
||||
from titles.wacca.handlers.base import BaseResponse
|
||||
|
||||
class WaccaServlet():
|
||||
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
||||
self.core_cfg = core_cfg
|
||||
self.game_cfg = WaccaConfig()
|
||||
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/wacca.yaml")))
|
||||
|
||||
self.versions = [
|
||||
WaccaBase(core_cfg, self.game_cfg),
|
||||
WaccaS(core_cfg, self.game_cfg),
|
||||
WaccaLily(core_cfg, self.game_cfg),
|
||||
WaccaLilyR(core_cfg, self.game_cfg),
|
||||
WaccaReverse(core_cfg, self.game_cfg),
|
||||
]
|
||||
|
||||
self.logger = logging.getLogger("wacca")
|
||||
log_fmt_str = "[%(asctime)s] Wacca | %(levelname)s | %(message)s"
|
||||
log_fmt = logging.Formatter(log_fmt_str)
|
||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "wacca"), encoding='utf8',
|
||||
when="d", backupCount=10)
|
||||
|
||||
fileHandler.setFormatter(log_fmt)
|
||||
|
||||
consoleHandler = logging.StreamHandler()
|
||||
consoleHandler.setFormatter(log_fmt)
|
||||
|
||||
self.logger.addHandler(fileHandler)
|
||||
self.logger.addHandler(consoleHandler)
|
||||
|
||||
self.logger.setLevel(self.game_cfg.server.loglevel)
|
||||
coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str)
|
||||
|
||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||
def end(resp: Dict) -> bytes:
|
||||
hash = md5(json.dumps(resp, ensure_ascii=False).encode()).digest()
|
||||
request.responseHeaders.addRawHeader(b"X-Wacca-Hash", hash.hex().encode())
|
||||
return json.dumps(resp).encode()
|
||||
|
||||
version_full = []
|
||||
|
||||
try:
|
||||
req_json = json.loads(request.content.getvalue())
|
||||
version_full = req_json["appVersion"].split(".")
|
||||
except:
|
||||
self.logger.error(f"Failed to parse request toi {request.uri} -> {request.content.getvalue()}")
|
||||
resp = BaseResponse()
|
||||
resp.status = 1
|
||||
resp.message = "不正なリクエスト エラーです"
|
||||
return end(resp.make())
|
||||
|
||||
url_split = url_path.split("/")
|
||||
start_req_idx = url_split.index("api") + 1
|
||||
|
||||
func_to_find = "handle_"
|
||||
for x in range(len(url_split) - start_req_idx):
|
||||
func_to_find += f"{url_split[x + start_req_idx]}_"
|
||||
func_to_find += "request"
|
||||
|
||||
ver_search = (int(version_full[0]) * 10000) + (int(version_full[1]) * 100) + int(version_full[2])
|
||||
|
||||
if ver_search < 15000:
|
||||
internal_ver = WaccaConstants.VER_WACCA
|
||||
|
||||
elif ver_search >= 15000 and ver_search < 20000:
|
||||
internal_ver = WaccaConstants.VER_WACCA_S
|
||||
|
||||
elif ver_search >= 20000 and ver_search < 25000:
|
||||
internal_ver = WaccaConstants.VER_WACCA_LILY
|
||||
|
||||
elif ver_search >= 25000 and ver_search < 30000:
|
||||
internal_ver = WaccaConstants.VER_WACCA_LILY_R
|
||||
|
||||
elif ver_search >= 30000:
|
||||
internal_ver = WaccaConstants.VER_WACCA_REVERSE
|
||||
|
||||
else:
|
||||
self.logger.warning(f"Unsupported version ({req_json['appVersion']}) request {url_path} - {req_json}")
|
||||
resp = BaseResponse()
|
||||
resp.status = 1
|
||||
resp.message = "不正なアプリバージョンエラーです"
|
||||
return end(resp.make())
|
||||
|
||||
self.logger.info(f"v{req_json['appVersion']} {url_path} request from {request.getClientAddress().host} with chipId {req_json['chipId']}")
|
||||
self.logger.debug(req_json)
|
||||
|
||||
try:
|
||||
handler = getattr(self.versions[internal_ver], func_to_find)
|
||||
if handler is not None:
|
||||
resp = handler(req_json)
|
||||
|
||||
else:
|
||||
self.logger.warn(f"{req_json['appVersion']} has no handler for {func_to_find}")
|
||||
resp = None
|
||||
|
||||
if resp is None:
|
||||
resp = BaseResponse().make()
|
||||
|
||||
self.logger.debug(f"{req_json['appVersion']} response {resp}")
|
||||
return end(resp)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"{req_json['appVersion']} Error handling method {url_path} -> {e}")
|
||||
if self.game_cfg.server.loglevel <= logging.DEBUG:
|
||||
raise
|
||||
resp = BaseResponse().make()
|
||||
return end(resp)
|
351
titles/wacca/lily.py
Normal file
351
titles/wacca/lily.py
Normal file
@ -0,0 +1,351 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.s import WaccaS
|
||||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.const import WaccaConstants
|
||||
|
||||
from titles.wacca.handlers import *
|
||||
|
||||
class WaccaLily(WaccaS):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = WaccaConstants.VER_WACCA_LILY
|
||||
self.season = 2
|
||||
|
||||
self.OPTIONS_DEFAULTS["set_nav_id"] = 210002
|
||||
self.allowed_stages = [
|
||||
(2001, 1),
|
||||
(2002, 2),
|
||||
(2003, 3),
|
||||
(2004, 4),
|
||||
(2005, 5),
|
||||
(2006, 6),
|
||||
(2007, 7),
|
||||
(2008, 8),
|
||||
(2009, 9),
|
||||
(2010, 10),
|
||||
(2011, 11),
|
||||
(2012, 12),
|
||||
(2013, 13),
|
||||
(2014, 14),
|
||||
(210001, 0),
|
||||
(210002, 0),
|
||||
(210003, 0),
|
||||
]
|
||||
|
||||
def handle_user_status_get_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusGetRequest(data)
|
||||
resp = UserStatusGetV2Response()
|
||||
ver_split = req.appVersion.split(".")
|
||||
|
||||
profile = self.data.profile.get_profile(aime_id=req.aimeId)
|
||||
if profile is None:
|
||||
self.logger.info(f"No user exists for aime id {req.aimeId}")
|
||||
resp.profileStatus = ProfileStatus.ProfileRegister
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"User preview for {req.aimeId} from {req.chipId}")
|
||||
if profile["last_game_ver"] is None:
|
||||
profile_ver_split = ver_split
|
||||
resp.lastGameVersion = req.appVersion
|
||||
else:
|
||||
profile_ver_split = profile["last_game_ver"].split(".")
|
||||
resp.lastGameVersion = profile["last_game_ver"]
|
||||
|
||||
resp.userStatus.userId = profile["id"]
|
||||
resp.userStatus.username = profile["username"]
|
||||
resp.userStatus.xp = profile["xp"]
|
||||
resp.userStatus.danLevel = profile["dan_level"]
|
||||
resp.userStatus.danType = profile["dan_type"]
|
||||
resp.userStatus.wp = profile["wp"]
|
||||
resp.userStatus.useCount = profile["login_count"]
|
||||
resp.userStatus.loginDays = profile["login_count_days"]
|
||||
resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"]
|
||||
resp.userStatus.loginsToday = profile["login_count_today"]
|
||||
resp.userStatus.rating = profile["rating"]
|
||||
|
||||
set_title_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"])
|
||||
if set_title_id is None:
|
||||
set_title_id = self.OPTIONS_DEFAULTS["set_title_id"]
|
||||
resp.setTitleId = set_title_id
|
||||
|
||||
set_icon_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"])
|
||||
if set_icon_id is None:
|
||||
set_icon_id = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
resp.setIconId = set_icon_id
|
||||
|
||||
if profile["last_login_date"].timestamp() < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()):
|
||||
resp.userStatus.loginsToday = 0
|
||||
|
||||
if profile["last_login_date"].timestamp() < int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) - timedelta(days=1)).timestamp()):
|
||||
resp.userStatus.loginConsecutiveDays = 0
|
||||
|
||||
if int(ver_split[0]) > int(profile_ver_split[0]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionUpgrade
|
||||
|
||||
elif int(ver_split[0]) < int(profile_ver_split[0]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionTooNew
|
||||
|
||||
else:
|
||||
if int(ver_split[1]) > int(profile_ver_split[1]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionUpgrade
|
||||
|
||||
elif int(ver_split[1]) < int(profile_ver_split[1]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionTooNew
|
||||
|
||||
else:
|
||||
if int(ver_split[2]) > int(profile_ver_split[2]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionUpgrade
|
||||
|
||||
|
||||
elif int(ver_split[2]) < int(profile_ver_split[2]):
|
||||
resp.versionStatus = PlayVersionStatus.VersionTooNew
|
||||
|
||||
if profile["vip_expire_time"] is not None:
|
||||
resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp())
|
||||
|
||||
if profile["always_vip"] or self.game_config.mods.always_vip:
|
||||
resp.userStatus.vipExpireTime = int((datetime.now() + timedelta(days=30)).timestamp())
|
||||
|
||||
if self.game_config.mods.infinite_wp:
|
||||
resp.userStatus.wp = 999999
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_status_login_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusLoginRequest(data)
|
||||
resp = UserStatusLoginResponseV2()
|
||||
is_new_day = False
|
||||
is_consec_day = False
|
||||
is_consec_day = True
|
||||
|
||||
if req.userId == 0:
|
||||
self.logger.info(f"Guest login on {req.chipId}")
|
||||
resp.lastLoginDate = 0
|
||||
|
||||
else:
|
||||
profile = self.data.profile.get_profile(req.userId)
|
||||
if profile is None:
|
||||
self.logger.warn(f"Unknown user id {req.userId} attempted login from {req.chipId}")
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"User {req.userId} login on {req.chipId}")
|
||||
last_login_time = int(profile["last_login_date"].timestamp())
|
||||
resp.lastLoginDate = last_login_time
|
||||
|
||||
# If somebodies login timestamp < midnight of current day, then they are logging in for the first time today
|
||||
if last_login_time < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()):
|
||||
is_new_day = True
|
||||
is_consec_day = True
|
||||
|
||||
# If somebodies login timestamp > midnight of current day + 1 day, then they broke their daily login streak
|
||||
elif last_login_time > int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) + timedelta(days=1)).timestamp()):
|
||||
is_consec_day = False
|
||||
# else, they are simply logging in again on the same day, and we don't need to do anything for that
|
||||
|
||||
self.data.profile.session_login(req.userId, is_new_day, is_consec_day)
|
||||
resp.vipInfo.pageYear = datetime.now().year
|
||||
resp.vipInfo.pageMonth = datetime.now().month
|
||||
resp.vipInfo.pageDay = datetime.now().day
|
||||
resp.vipInfo.numItem = 1
|
||||
|
||||
resp.firstLoginDaily = int(is_new_day)
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_status_getDetail_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusGetDetailRequest(data)
|
||||
ver_split = req.appVersion.split(".")
|
||||
if int(ver_split[1]) >= 53:
|
||||
resp = UserStatusGetDetailResponseV3()
|
||||
else:
|
||||
resp = UserStatusGetDetailResponseV2()
|
||||
|
||||
profile = self.data.profile.get_profile(req.userId)
|
||||
if profile is None:
|
||||
self.logger.warn(f"Unknown profile {req.userId}")
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"Get detail for profile {req.userId}")
|
||||
user_id = profile["user"]
|
||||
|
||||
profile_scores = self.data.score.get_best_scores(user_id)
|
||||
profile_items = self.data.item.get_items(user_id)
|
||||
profile_song_unlocks = self.data.item.get_song_unlocks(user_id)
|
||||
profile_options = self.data.profile.get_options(user_id)
|
||||
profile_favorites = self.data.profile.get_favorite_songs(user_id)
|
||||
profile_gates = self.data.profile.get_gates(user_id)
|
||||
profile_trophies = self.data.item.get_trophies(user_id)
|
||||
profile_tickets = self.data.item.get_tickets(user_id)
|
||||
|
||||
if profile["vip_expire_time"] is None:
|
||||
resp.userStatus.vipExpireTime = 0
|
||||
|
||||
else:
|
||||
resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp())
|
||||
|
||||
if profile["always_vip"] or self.game_config.mods.always_vip:
|
||||
resp.userStatus.vipExpireTime = int((self.srvtime + timedelta(days=31)).timestamp())
|
||||
|
||||
resp.songUpdateTime = int(profile["last_login_date"].timestamp())
|
||||
resp.lastSongInfo = LastSongDetail(profile["last_song_id"],profile["last_song_difficulty"],profile["last_folder_order"],profile["last_folder_id"],profile["last_song_order"])
|
||||
resp.songPlayStatus = [resp.lastSongInfo.lastSongId, 1]
|
||||
|
||||
resp.userStatus.userId = profile["id"]
|
||||
resp.userStatus.username = profile["username"]
|
||||
resp.userStatus.xp = profile["xp"]
|
||||
resp.userStatus.danLevel = profile["dan_level"]
|
||||
resp.userStatus.danType = profile["dan_type"]
|
||||
resp.userStatus.wp = profile["wp"]
|
||||
resp.userStatus.useCount = profile["login_count"]
|
||||
resp.userStatus.loginDays = profile["login_count_days"]
|
||||
resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"]
|
||||
resp.userStatus.loginsToday = profile["login_count_today"]
|
||||
resp.userStatus.rating = profile['rating']
|
||||
|
||||
if self.game_config.mods.infinite_wp:
|
||||
resp.userStatus.wp = 999999
|
||||
|
||||
for fav in profile_favorites:
|
||||
resp.favorites.append(fav["song_id"])
|
||||
|
||||
if profile["friend_view_1"] is not None:
|
||||
pass
|
||||
if profile["friend_view_2"] is not None:
|
||||
pass
|
||||
if profile["friend_view_3"] is not None:
|
||||
pass
|
||||
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 1, profile["playcount_single"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 2, profile["playcount_multi_vs"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 3, profile["playcount_multi_coop"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 4, profile["playcount_stageup"]))
|
||||
|
||||
for opt in profile_options:
|
||||
resp.options.append(UserOption(opt["opt_id"], opt["value"]))
|
||||
|
||||
for gate in self.game_config.gates.enabled_gates:
|
||||
added_gate = False
|
||||
|
||||
for user_gate in profile_gates:
|
||||
if user_gate["gate_id"] == gate:
|
||||
if int(ver_split[1]) >= 53:
|
||||
resp.gateInfo.append(GateDetailV2(user_gate["gate_id"],user_gate["page"],user_gate["progress"],
|
||||
user_gate["loops"],int(user_gate["last_used"].timestamp()),user_gate["mission_flag"]))
|
||||
|
||||
else:
|
||||
resp.gateInfo.append(GateDetailV1(user_gate["gate_id"],user_gate["page"],user_gate["progress"],
|
||||
user_gate["loops"],int(user_gate["last_used"].timestamp()),user_gate["mission_flag"]))
|
||||
|
||||
resp.seasonInfo.cumulativeGatePts += user_gate["total_points"]
|
||||
|
||||
added_gate = True
|
||||
break
|
||||
|
||||
if not added_gate:
|
||||
if int(ver_split[1]) >= 53:
|
||||
resp.gateInfo.append(GateDetailV2(gate))
|
||||
|
||||
else:
|
||||
resp.gateInfo.append(GateDetailV1(gate))
|
||||
|
||||
for unlock in profile_song_unlocks:
|
||||
for x in range(1, unlock["highest_difficulty"] + 1):
|
||||
resp.userItems.songUnlocks.append(SongUnlock(unlock["song_id"], x, 0, int(unlock["acquire_date"].timestamp())))
|
||||
if x > 2:
|
||||
resp.scores.append(BestScoreDetailV1(unlock["song_id"], x))
|
||||
|
||||
empty_scores = len(resp.scores)
|
||||
for song in profile_scores:
|
||||
resp.seasonInfo.cumulativeScore += song["score"]
|
||||
empty_score_idx = resp.find_score_idx(song["song_id"], song["chart_id"], 0, empty_scores)
|
||||
|
||||
clear_cts = SongDetailClearCounts(
|
||||
song["play_ct"],
|
||||
song["clear_ct"],
|
||||
song["missless_ct"],
|
||||
song["fullcombo_ct"],
|
||||
song["allmarv_ct"],
|
||||
)
|
||||
|
||||
grade_cts = SongDetailGradeCountsV1(
|
||||
song["grade_d_ct"], song["grade_c_ct"], song["grade_b_ct"], song["grade_a_ct"], song["grade_aa_ct"],
|
||||
song["grade_aaa_ct"], song["grade_s_ct"], song["grade_ss_ct"], song["grade_sss_ct"],
|
||||
song["grade_master_ct"]
|
||||
)
|
||||
|
||||
if empty_score_idx is not None:
|
||||
resp.scores[empty_score_idx].clearCounts = clear_cts
|
||||
resp.scores[empty_score_idx].clearCountsSeason = clear_cts
|
||||
resp.scores[empty_score_idx].gradeCounts = grade_cts
|
||||
resp.scores[empty_score_idx].score = song["score"]
|
||||
resp.scores[empty_score_idx].bestCombo = song["best_combo"]
|
||||
resp.scores[empty_score_idx].lowestMissCtMaybe = song["lowest_miss_ct"]
|
||||
resp.scores[empty_score_idx].rating = song["rating"]
|
||||
|
||||
else:
|
||||
deets = BestScoreDetailV1(song["song_id"], song["chart_id"])
|
||||
deets.clearCounts = clear_cts
|
||||
deets.clearCountsSeason = clear_cts
|
||||
deets.gradeCounts = grade_cts
|
||||
deets.score = song["score"]
|
||||
deets.bestCombo = song["best_combo"]
|
||||
deets.lowestMissCtMaybe = song["lowest_miss_ct"]
|
||||
deets.rating = song["rating"]
|
||||
|
||||
for trophy in profile_trophies:
|
||||
resp.userItems.trophies.append(TrophyItem(trophy["trophy_id"], trophy["season"], trophy["progress"], trophy["badge_type"]))
|
||||
|
||||
if self.game_config.mods.infinite_tickets:
|
||||
for x in range(5):
|
||||
resp.userItems.tickets.append(TicketItem(x, 106002, 0))
|
||||
else:
|
||||
for ticket in profile_tickets:
|
||||
if ticket["expire_date"] is None:
|
||||
expire = int((self.srvtime + timedelta(days=30)).timestamp())
|
||||
else:
|
||||
expire = int(ticket["expire_date"].timestamp())
|
||||
|
||||
resp.userItems.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], expire))
|
||||
|
||||
if profile_items:
|
||||
for item in profile_items:
|
||||
try:
|
||||
|
||||
if item["type"] == WaccaConstants.ITEM_TYPES["icon"]:
|
||||
resp.userItems.icons.append(IconItem(item["item_id"], 1, item["use_count"], int(item["acquire_date"].timestamp())))
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["navigator"]:
|
||||
resp.userItems.navigators.append(NavigatorItem(item["item_id"], 1, int(item["acquire_date"].timestamp()), item["use_count"], item["use_count"]))
|
||||
|
||||
else:
|
||||
itm_send = GenericItemSend(item["item_id"], 1, int(item["acquire_date"].timestamp()))
|
||||
|
||||
if item["type"] == WaccaConstants.ITEM_TYPES["title"]:
|
||||
resp.userItems.titles.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["user_plate"]:
|
||||
resp.userItems.plates.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_color"]:
|
||||
resp.userItems.noteColors.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
||||
resp.userItems.noteSounds.append(itm_send)
|
||||
|
||||
except:
|
||||
self.logger.error(f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}")
|
||||
|
||||
resp.seasonInfo.level = profile["xp"]
|
||||
resp.seasonInfo.wpObtained = profile["wp_total"]
|
||||
resp.seasonInfo.wpSpent = profile["wp_spent"]
|
||||
resp.seasonInfo.titlesObtained = len(resp.userItems.titles)
|
||||
resp.seasonInfo.iconsObtained = len(resp.userItems.icons)
|
||||
resp.seasonInfo.noteColorsObtained = len(resp.userItems.noteColors)
|
||||
resp.seasonInfo.noteSoundsObtained = len(resp.userItems.noteSounds)
|
||||
resp.seasonInfo.platesObtained = len(resp.userItems.plates)
|
||||
|
||||
return resp.make()
|
54
titles/wacca/lilyr.py
Normal file
54
titles/wacca/lilyr.py
Normal file
@ -0,0 +1,54 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.lily import WaccaLily
|
||||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.const import WaccaConstants
|
||||
from titles.wacca.handlers import *
|
||||
|
||||
class WaccaLilyR(WaccaLily):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = WaccaConstants.VER_WACCA_LILY_R
|
||||
self.season = 2
|
||||
|
||||
self.OPTIONS_DEFAULTS["set_nav_id"] = 210002
|
||||
self.allowed_stages = [
|
||||
(2501, 1),
|
||||
(2502, 2),
|
||||
(2503, 3),
|
||||
(2504, 4),
|
||||
(2505, 5),
|
||||
(2506, 6),
|
||||
(2507, 7),
|
||||
(2508, 8),
|
||||
(2509, 9),
|
||||
(2510, 10),
|
||||
(2511, 11),
|
||||
(2512, 12),
|
||||
(2513, 13),
|
||||
(2514, 14),
|
||||
(210001, 0),
|
||||
(210002, 0),
|
||||
(210003, 0),
|
||||
]
|
||||
|
||||
def handle_user_status_create_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusCreateRequest(data)
|
||||
resp = super().handle_user_status_create_request(data)
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210054) # Added lily r
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210055) # Added lily r
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210056) # Added lily r
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210057) # Added lily r
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210058) # Added lily r
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210059) # Added lily r
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210060) # Added lily r
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210061) # Added lily r
|
||||
|
||||
return resp
|
||||
|
||||
def handle_user_status_logout_request(self, data: Dict) -> List[Any]:
|
||||
return BaseResponse().make()
|
80
titles/wacca/read.py
Normal file
80
titles/wacca/read.py
Normal file
@ -0,0 +1,80 @@
|
||||
from typing import Optional
|
||||
import wacky
|
||||
import json
|
||||
from os import walk, path
|
||||
|
||||
from read import BaseReader
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.database import WaccaData
|
||||
from titles.wacca.const import WaccaConstants
|
||||
|
||||
class WaccaReader(BaseReader):
|
||||
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None:
|
||||
super().__init__(config, version, bin_dir, opt_dir, extra)
|
||||
self.data = WaccaData(config)
|
||||
|
||||
try:
|
||||
self.logger.info(f"Start importer for {WaccaConstants.game_ver_to_string(version)}")
|
||||
except IndexError:
|
||||
self.logger.error(f"Invalid wacca version {version}")
|
||||
exit(1)
|
||||
|
||||
def read(self) -> None:
|
||||
if not (path.exists(f"{self.bin_dir}/Table") and path.exists(f"{self.bin_dir}/Message")):
|
||||
self.logger.error("Could not find Table or Message folder, nothing to read")
|
||||
return
|
||||
|
||||
self.read_music(f"{self.bin_dir}/Table", "MusicParameterTable")
|
||||
|
||||
def read_music(self, base_dir: str, table: str) -> None:
|
||||
if not self.check_valid_pair(base_dir, table):
|
||||
self.logger.warn(f"Cannot find {table} uasset/uexp pair at {base_dir}, music will not be read")
|
||||
return
|
||||
|
||||
uasset=open(f"{base_dir}/{table}.uasset", "rb")
|
||||
uexp=open(f"{base_dir}/{table}.uexp", "rb")
|
||||
|
||||
package = wacky.jsonify(uasset,uexp)
|
||||
package_json = json.dumps(package, indent=4, sort_keys=True)
|
||||
data=json.loads(package_json)
|
||||
|
||||
first_elem = data[0]
|
||||
wacca_data = first_elem['rows']
|
||||
|
||||
for i, key in enumerate(wacca_data):
|
||||
song_id = int(key)
|
||||
title = wacca_data[str(key)]["MusicMessage"]
|
||||
artist = wacca_data[str(key)]["ArtistMessage"]
|
||||
bpm = wacca_data[str(key)]["Bpm"]
|
||||
jacket_asset_name = wacca_data[str(key)]["JacketAssetName"]
|
||||
|
||||
diff = float(wacca_data[str(key)]["DifficultyNormalLv"])
|
||||
designer = wacca_data[str(key)]["NotesDesignerNormal"]
|
||||
|
||||
if diff > 0:
|
||||
self.data.static.put_music(self.version, song_id, 1, title, artist, bpm, diff, designer, jacket_asset_name)
|
||||
self.logger.info(f"Read song {song_id} chart 1")
|
||||
|
||||
diff = float(wacca_data[str(key)]["DifficultyHardLv"])
|
||||
designer = wacca_data[str(key)]["NotesDesignerHard"]
|
||||
|
||||
if diff > 0:
|
||||
self.data.static.put_music(self.version, song_id, 2, title, artist, bpm, diff, designer, jacket_asset_name)
|
||||
self.logger.info(f"Read song {song_id} chart 2")
|
||||
|
||||
diff = float(wacca_data[str(key)]["DifficultyExtremeLv"])
|
||||
designer = wacca_data[str(key)]["NotesDesignerExpert"]
|
||||
|
||||
if diff > 0:
|
||||
self.data.static.put_music(self.version, song_id, 3, title, artist, bpm, diff, designer, jacket_asset_name)
|
||||
self.logger.info(f"Read song {song_id} chart 3")
|
||||
|
||||
diff = float(wacca_data[str(key)]["DifficultyInfernoLv"])
|
||||
designer = wacca_data[str(key)]["NotesDesignerInferno"]
|
||||
|
||||
if diff > 0:
|
||||
self.data.static.put_music(self.version, song_id, 4, title, artist, bpm, diff, designer, jacket_asset_name)
|
||||
self.logger.info(f"Read song {song_id} chart 4")
|
||||
|
||||
def check_valid_pair(self, dir: str, file: str) -> bool:
|
||||
return path.exists(f"{dir}/{file}.uasset") and path.exists(f"{dir}/{file}.uexp")
|
258
titles/wacca/reverse.py
Normal file
258
titles/wacca/reverse.py
Normal file
@ -0,0 +1,258 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.lilyr import WaccaLilyR
|
||||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.const import WaccaConstants
|
||||
|
||||
from titles.wacca.handlers import *
|
||||
|
||||
class WaccaReverse(WaccaLilyR):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = WaccaConstants.VER_WACCA_REVERSE
|
||||
|
||||
self.season = 3
|
||||
|
||||
self.OPTIONS_DEFAULTS["set_nav_id"] = 310001
|
||||
self.allowed_stages = [
|
||||
(3001, 1),
|
||||
(3002, 2),
|
||||
(3003, 3),
|
||||
(3004, 4),
|
||||
(3005, 5),
|
||||
(3006, 6),
|
||||
(3007, 7),
|
||||
(3008, 8),
|
||||
(3009, 9),
|
||||
(3010, 10),
|
||||
(3011, 11),
|
||||
(3012, 12),
|
||||
(3013, 13),
|
||||
(3014, 14),
|
||||
# Touhou
|
||||
(210001, 0),
|
||||
(210002, 0),
|
||||
(210003, 0),
|
||||
# Final spurt
|
||||
(310001, 0),
|
||||
(310002, 0),
|
||||
(310003, 0),
|
||||
# boss remix
|
||||
(310004, 0),
|
||||
(310005, 0),
|
||||
(310006, 0),
|
||||
]
|
||||
|
||||
def handle_user_status_login_request(self, data: Dict) -> List[Any]:
|
||||
resp = super().handle_user_status_login_request(data)
|
||||
resp["params"].append([])
|
||||
return resp
|
||||
|
||||
def handle_user_status_getDetail_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusGetDetailRequest(data)
|
||||
resp = UserStatusGetDetailResponseV4()
|
||||
|
||||
profile = self.data.profile.get_profile(req.userId)
|
||||
if profile is None:
|
||||
self.logger.warn(f"Unknown profile {req.userId}")
|
||||
return resp.make()
|
||||
|
||||
self.logger.info(f"Get detail for profile {req.userId}")
|
||||
user_id = profile["user"]
|
||||
|
||||
profile_scores = self.data.score.get_best_scores(user_id)
|
||||
profile_items = self.data.item.get_items(user_id)
|
||||
profile_song_unlocks = self.data.item.get_song_unlocks(user_id)
|
||||
profile_options = self.data.profile.get_options(user_id)
|
||||
profile_favorites = self.data.profile.get_favorite_songs(user_id)
|
||||
profile_gates = self.data.profile.get_gates(user_id)
|
||||
profile_bingo = self.data.profile.get_bingo(user_id)
|
||||
profile_trophies = self.data.item.get_trophies(user_id)
|
||||
profile_tickets = self.data.item.get_tickets(user_id)
|
||||
|
||||
if profile["gate_tutorial_flags"] is not None:
|
||||
for x in profile["gate_tutorial_flags"]:
|
||||
resp.gateTutorialFlags.append(GateTutorialFlag(x[0], x[1]))
|
||||
|
||||
if profile["vip_expire_time"] is None:
|
||||
resp.userStatus.vipExpireTime = 0
|
||||
|
||||
else:
|
||||
resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp())
|
||||
|
||||
if profile["always_vip"] or self.game_config.mods.always_vip:
|
||||
resp.userStatus.vipExpireTime = int((self.srvtime + timedelta(days=31)).timestamp())
|
||||
|
||||
resp.songUpdateTime = int(profile["last_login_date"].timestamp())
|
||||
resp.lastSongInfo = LastSongDetail(profile["last_song_id"],profile["last_song_difficulty"],profile["last_folder_order"],profile["last_folder_id"],profile["last_song_order"])
|
||||
resp.songPlayStatus = [resp.lastSongInfo.lastSongId, 1]
|
||||
|
||||
resp.userStatus.userId = profile["id"]
|
||||
resp.userStatus.username = profile["username"]
|
||||
resp.userStatus.xp = profile["xp"]
|
||||
resp.userStatus.danLevel = profile["dan_level"]
|
||||
resp.userStatus.danType = profile["dan_type"]
|
||||
resp.userStatus.wp = profile["wp"]
|
||||
resp.userStatus.useCount = profile["login_count"]
|
||||
resp.userStatus.loginDays = profile["login_count_days"]
|
||||
resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"]
|
||||
resp.userStatus.loginsToday = profile["login_count_today"]
|
||||
resp.userStatus.rating = profile['rating']
|
||||
|
||||
if self.game_config.mods.infinite_wp:
|
||||
resp.userStatus.wp = 999999
|
||||
|
||||
for fav in profile_favorites:
|
||||
resp.favorites.append(fav["song_id"])
|
||||
|
||||
if profile["friend_view_1"] is not None:
|
||||
pass
|
||||
if profile["friend_view_2"] is not None:
|
||||
pass
|
||||
if profile["friend_view_3"] is not None:
|
||||
pass
|
||||
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 1, profile["playcount_single"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 2, profile["playcount_multi_vs"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 3, profile["playcount_multi_coop"]))
|
||||
resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 4, profile["playcount_stageup"]))
|
||||
|
||||
for opt in profile_options:
|
||||
resp.options.append(UserOption(opt["opt_id"], opt["value"]))
|
||||
|
||||
if profile_bingo is not None:
|
||||
resp.bingoStatus = BingoDetail(profile_bingo["page_number"])
|
||||
for x in profile_bingo["page_progress"]:
|
||||
resp.bingoStatus.pageStatus.append(BingoPageStatus(x[0], x[1], x[2]))
|
||||
|
||||
for gate in self.game_config.gates.enabled_gates:
|
||||
added_gate = False
|
||||
|
||||
for user_gate in profile_gates:
|
||||
if user_gate["gate_id"] == gate:
|
||||
|
||||
resp.gateInfo.append(GateDetailV2(user_gate["gate_id"],user_gate["page"],user_gate["progress"],
|
||||
user_gate["loops"],int(user_gate["last_used"].timestamp()),user_gate["mission_flag"]))
|
||||
|
||||
resp.seasonInfo.cumulativeGatePts += user_gate["total_points"]
|
||||
|
||||
added_gate = True
|
||||
break
|
||||
|
||||
if not added_gate:
|
||||
resp.gateInfo.append(GateDetailV2(gate))
|
||||
|
||||
for unlock in profile_song_unlocks:
|
||||
for x in range(1, unlock["highest_difficulty"] + 1):
|
||||
resp.userItems.songUnlocks.append(SongUnlock(unlock["song_id"], x, 0, int(unlock["acquire_date"].timestamp())))
|
||||
if x > 2:
|
||||
resp.scores.append(BestScoreDetailV2(unlock["song_id"], x))
|
||||
|
||||
empty_scores = len(resp.scores)
|
||||
for song in profile_scores:
|
||||
resp.seasonInfo.cumulativeScore += song["score"]
|
||||
empty_score_idx = resp.find_score_idx(song["song_id"], song["chart_id"], 0, empty_scores)
|
||||
|
||||
clear_cts = SongDetailClearCounts(
|
||||
song["play_ct"],
|
||||
song["clear_ct"],
|
||||
song["missless_ct"],
|
||||
song["fullcombo_ct"],
|
||||
song["allmarv_ct"],
|
||||
)
|
||||
|
||||
grade_cts = SongDetailGradeCountsV2(
|
||||
song["grade_d_ct"], song["grade_c_ct"], song["grade_b_ct"], song["grade_a_ct"], song["grade_aa_ct"],
|
||||
song["grade_aaa_ct"], song["grade_s_ct"], song["grade_ss_ct"], song["grade_sss_ct"],
|
||||
song["grade_master_ct"], song["grade_sp_ct"], song["grade_ssp_ct"], song["grade_sssp_ct"]
|
||||
)
|
||||
|
||||
if empty_score_idx is not None:
|
||||
resp.scores[empty_score_idx].clearCounts = clear_cts
|
||||
resp.scores[empty_score_idx].clearCountsSeason = clear_cts
|
||||
resp.scores[empty_score_idx].gradeCounts = grade_cts
|
||||
resp.scores[empty_score_idx].score = song["score"]
|
||||
resp.scores[empty_score_idx].bestCombo = song["best_combo"]
|
||||
resp.scores[empty_score_idx].lowestMissCtMaybe = song["lowest_miss_ct"]
|
||||
resp.scores[empty_score_idx].rating = song["rating"]
|
||||
|
||||
else:
|
||||
deets = BestScoreDetailV2(song["song_id"], song["chart_id"])
|
||||
deets.clearCounts = clear_cts
|
||||
deets.clearCountsSeason = clear_cts
|
||||
deets.gradeCounts = grade_cts
|
||||
deets.score = song["score"]
|
||||
deets.bestCombo = song["best_combo"]
|
||||
deets.lowestMissCtMaybe = song["lowest_miss_ct"]
|
||||
deets.rating = song["rating"]
|
||||
resp.scores.append(deets)
|
||||
|
||||
for trophy in profile_trophies:
|
||||
resp.userItems.trophies.append(TrophyItem(trophy["trophy_id"], trophy["season"], trophy["progress"], trophy["badge_type"]))
|
||||
|
||||
if self.game_config.mods.infinite_tickets:
|
||||
for x in range(5):
|
||||
resp.userItems.tickets.append(TicketItem(x, 106002, 0))
|
||||
else:
|
||||
for ticket in profile_tickets:
|
||||
if ticket["expire_date"] is None:
|
||||
expire = int((self.srvtime + timedelta(days=30)).timestamp())
|
||||
else:
|
||||
expire = int(ticket["expire_date"].timestamp())
|
||||
|
||||
resp.userItems.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], expire))
|
||||
|
||||
if profile_items:
|
||||
for item in profile_items:
|
||||
try:
|
||||
|
||||
if item["type"] == WaccaConstants.ITEM_TYPES["icon"]:
|
||||
resp.userItems.icons.append(IconItem(item["item_id"], 1, item["use_count"], int(item["acquire_date"].timestamp())))
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["navigator"]:
|
||||
resp.userItems.navigators.append(NavigatorItem(item["item_id"], 1, int(item["acquire_date"].timestamp()), item["use_count"], item["use_count"]))
|
||||
|
||||
else:
|
||||
itm_send = GenericItemSend(item["item_id"], 1, int(item["acquire_date"].timestamp()))
|
||||
|
||||
if item["type"] == WaccaConstants.ITEM_TYPES["title"]:
|
||||
resp.userItems.titles.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["user_plate"]:
|
||||
resp.userItems.plates.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["touch_effect"]:
|
||||
resp.userItems.touchEffect.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_color"]:
|
||||
resp.userItems.noteColors.append(itm_send)
|
||||
|
||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
||||
resp.userItems.noteSounds.append(itm_send)
|
||||
|
||||
except:
|
||||
self.logger.error(f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}")
|
||||
|
||||
resp.seasonInfo.level = profile["xp"]
|
||||
resp.seasonInfo.wpObtained = profile["wp_total"]
|
||||
resp.seasonInfo.wpSpent = profile["wp_spent"]
|
||||
resp.seasonInfo.titlesObtained = len(resp.userItems.titles)
|
||||
resp.seasonInfo.iconsObtained = len(resp.userItems.icons)
|
||||
resp.seasonInfo.noteColorsObtained = len(resp.userItems.noteColors)
|
||||
resp.seasonInfo.noteSoundsObtained = len(resp.userItems.noteSounds)
|
||||
resp.seasonInfo.platesObtained = len(resp.userItems.plates)
|
||||
|
||||
return resp.make()
|
||||
|
||||
def handle_user_status_create_request(self, data: Dict) -> List[Any]:
|
||||
req = UserStatusCreateRequest(data)
|
||||
resp = super().handle_user_status_create_request(data)
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310001) # Added reverse
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310002) # Added reverse
|
||||
|
||||
return resp
|
||||
|
35
titles/wacca/s.py
Normal file
35
titles/wacca/s.py
Normal file
@ -0,0 +1,35 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.base import WaccaBase
|
||||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.const import WaccaConstants
|
||||
|
||||
from titles.wacca.handlers import *
|
||||
|
||||
class WaccaS(WaccaBase):
|
||||
allowed_stages = [
|
||||
(1501, 1),
|
||||
(1502, 2),
|
||||
(1503, 3),
|
||||
(1504, 4),
|
||||
(1505, 5),
|
||||
(1506, 6),
|
||||
(1507, 7),
|
||||
(1508, 8),
|
||||
(1509, 9),
|
||||
(1510, 10),
|
||||
(1511, 11),
|
||||
(1512, 12),
|
||||
(1513, 13),
|
||||
]
|
||||
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = WaccaConstants.VER_WACCA_S
|
||||
|
||||
def handle_advertise_GetNews_request(self, data: Dict) -> List[Any]:
|
||||
resp = GetNewsResponseV2()
|
||||
return resp.make()
|
6
titles/wacca/schema/__init__.py
Normal file
6
titles/wacca/schema/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from titles.wacca.schema.profile import WaccaProfileData
|
||||
from titles.wacca.schema.score import WaccaScoreData
|
||||
from titles.wacca.schema.item import WaccaItemData
|
||||
from titles.wacca.schema.static import WaccaStaticData
|
||||
|
||||
__all__ = ["WaccaProfileData", "WaccaScoreData", "WaccaItemData", "WaccaStaticData"]
|
177
titles/wacca/schema/item.py
Normal file
177
titles/wacca/schema/item.py
Normal file
@ -0,0 +1,177 @@
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
item = Table(
|
||||
"wacca_item",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("item_id", Integer, nullable=False),
|
||||
Column("type", Integer, nullable=False),
|
||||
Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||
Column("use_count", Integer, server_default="0"),
|
||||
UniqueConstraint("user", "item_id", "type", name="wacca_item_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
ticket = Table(
|
||||
"wacca_ticket",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("ticket_id", Integer, nullable=False),
|
||||
Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||
Column("expire_date", TIMESTAMP),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
song_unlock = Table(
|
||||
"wacca_song_unlock",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("song_id", Integer, nullable=False),
|
||||
Column("highest_difficulty", Integer, nullable=False),
|
||||
Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||
UniqueConstraint("user", "song_id", name="wacca_song_unlock_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
trophy = Table(
|
||||
"wacca_trophy",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("trophy_id", Integer, nullable=False),
|
||||
Column("season", Integer, nullable=False),
|
||||
Column("progress", Integer, nullable=False, server_default="0"),
|
||||
Column("badge_type", Integer, nullable=False, server_default="0"),
|
||||
UniqueConstraint("user", "trophy_id", "season", name="wacca_trophy_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
class WaccaItemData(BaseData):
|
||||
def get_song_unlocks(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = song_unlock.select(song_unlock.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
|
||||
return result.fetchall()
|
||||
|
||||
def unlock_song(self, user_id: int, song_id: int, difficulty: int) -> Optional[int]:
|
||||
sql = insert(song_unlock).values(
|
||||
user=user_id,
|
||||
song_id=song_id,
|
||||
highest_difficulty=difficulty
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
highest_difficulty=case(
|
||||
(song_unlock.c.highest_difficulty >= difficulty, song_unlock.c.highest_difficulty),
|
||||
(song_unlock.c.highest_difficulty < difficulty, difficulty),
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to unlock song! user: {user_id}, song_id: {song_id}, difficulty: {difficulty}")
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_item(self, user_id: int, item_type: int, item_id: int) -> Optional[int]:
|
||||
sql = insert(item).values(
|
||||
user = user_id,
|
||||
item_id = item_id,
|
||||
type = item_type,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
use_count = item.c.use_count + 1
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to insert item! user: {user_id}, item_id: {item_id}, item_type: {item_type}")
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_items(self, user_id: int, item_type: int = None, item_id: int = None) -> Optional[List[Row]]:
|
||||
"""
|
||||
A catch-all item lookup given a profile and option item type and ID specifiers
|
||||
"""
|
||||
sql = item.select(
|
||||
and_(item.c.user == user_id,
|
||||
item.c.type == item_type if item_type is not None else True,
|
||||
item.c.item_id == item_id if item_id is not None else True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_tickets(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = select(ticket).where(ticket.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def add_ticket(self, user_id: int, ticket_id: int) -> None:
|
||||
sql = insert(ticket).values(
|
||||
user = user_id,
|
||||
ticket_id = ticket_id
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"add_ticket: Failed to insert wacca ticket! user_id: {user_id} ticket_id {ticket_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def spend_ticket(self, id: int) -> None:
|
||||
sql = delete(ticket).where(ticket.c.id == id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to delete ticket id {id}")
|
||||
return None
|
||||
|
||||
def get_trophies(self, user_id: int, season: int = None) -> Optional[List[Row]]:
|
||||
if season is None:
|
||||
sql = select(trophy).where(trophy.c.user == user_id)
|
||||
else:
|
||||
sql = select(trophy).where(and_(trophy.c.user == user_id, trophy.c.season == season))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def update_trophy(self, user_id: int, trophy_id: int, season: int, progress: int, badge_type: int) -> Optional[int]:
|
||||
sql = insert(trophy).values(
|
||||
user = user_id,
|
||||
trophy_id = trophy_id,
|
||||
season = season,
|
||||
progress = progress,
|
||||
badge_type = badge_type
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
progress = progress
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"update_trophy: Failed to insert wacca trophy! user_id: {user_id} trophy_id: {trophy_id} progress {progress}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
428
titles/wacca/schema/profile.py
Normal file
428
titles/wacca/schema/profile.py
Normal file
@ -0,0 +1,428 @@
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
profile = Table(
|
||||
"wacca_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("username", String(8), nullable=False),
|
||||
Column("xp", Integer, server_default="0"),
|
||||
Column("wp", Integer, server_default="0"),
|
||||
Column("wp_total", Integer, server_default="0"),
|
||||
Column("wp_spent", Integer, server_default="0"),
|
||||
Column("dan_type", Integer, server_default="0"),
|
||||
Column("dan_level", Integer, server_default="0"),
|
||||
Column("title_0", Integer, server_default="0"),
|
||||
Column("title_1", Integer, server_default="0"),
|
||||
Column("title_2", Integer, server_default="0"),
|
||||
Column("rating", Integer, server_default="0"),
|
||||
Column("vip_expire_time", TIMESTAMP),
|
||||
Column("always_vip", Boolean, server_default="0"),
|
||||
Column("login_count", Integer, server_default="0"),
|
||||
Column("login_count_consec", Integer, server_default="0"),
|
||||
Column("login_count_days", Integer, server_default="0"),
|
||||
Column("login_count_days_consec", Integer, server_default="0"),
|
||||
Column("login_count_today", Integer, server_default="0"),
|
||||
Column("playcount_single", Integer, server_default="0"),
|
||||
Column("playcount_multi_vs", Integer, server_default="0"),
|
||||
Column("playcount_multi_coop", Integer, server_default="0"),
|
||||
Column("playcount_stageup", Integer, server_default="0"),
|
||||
Column("friend_view_1", Integer),
|
||||
Column("friend_view_2", Integer),
|
||||
Column("friend_view_3", Integer),
|
||||
Column("last_game_ver", String(50)),
|
||||
Column("last_song_id", Integer, server_default="0"),
|
||||
Column("last_song_difficulty", Integer, server_default="0"),
|
||||
Column("last_folder_order", Integer, server_default="0"),
|
||||
Column("last_folder_id", Integer, server_default="0"),
|
||||
Column("last_song_order", Integer, server_default="0"),
|
||||
Column("last_login_date", TIMESTAMP, server_default=func.now()),
|
||||
Column("gate_tutorial_flags", JSON),
|
||||
UniqueConstraint("user", "version", name="wacca_profile_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
option = Table(
|
||||
"wacca_option",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("opt_id", Integer, nullable=False),
|
||||
Column("value", Integer, nullable=False),
|
||||
UniqueConstraint("user", "opt_id", name="wacca_option_uk"),
|
||||
)
|
||||
|
||||
bingo = Table(
|
||||
"wacca_bingo",
|
||||
metadata,
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), primary_key=True, nullable=False),
|
||||
Column("page_number", Integer, nullable=False),
|
||||
Column("page_progress", JSON, nullable=False),
|
||||
UniqueConstraint("user", "page_number", name="wacca_bingo_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
friend = Table(
|
||||
"wacca_friend",
|
||||
metadata,
|
||||
Column("profile_sender", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("profile_reciever", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("is_accepted", Boolean, server_default="0"),
|
||||
PrimaryKeyConstraint('profile_sender', 'profile_reciever', name='arcade_owner_pk'),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
favorite = Table(
|
||||
"wacca_favorite_song",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("song_id", Integer, nullable=False),
|
||||
UniqueConstraint("user", "song_id", name="wacca_favorite_song_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
gate = Table(
|
||||
"wacca_gate",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("gate_id", Integer, nullable=False),
|
||||
Column("page", Integer, nullable=False, server_default="0"),
|
||||
Column("progress", Integer, nullable=False, server_default="0"),
|
||||
Column("loops", Integer, nullable=False, server_default="0"),
|
||||
Column("last_used", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||
Column("mission_flag", Integer, nullable=False, server_default="0"),
|
||||
Column("total_points", Integer, nullable=False, server_default="0"),
|
||||
UniqueConstraint("user", "gate_id", name="wacca_gate_uk"),
|
||||
)
|
||||
|
||||
class WaccaProfileData(BaseData):
|
||||
def create_profile(self, aime_id: int, username: str, version: int) -> Optional[int]:
|
||||
"""
|
||||
Given a game version, aime id, and username, create a profile and return it's ID
|
||||
"""
|
||||
sql = insert(profile).values(
|
||||
user=aime_id,
|
||||
username=username,
|
||||
version=version
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
username = sql.inserted.username
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} Failed to insert wacca profile! aime id: {aime_id} username: {username}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def update_profile_playtype(self, profile_id: int, play_type: int, game_version: str) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
playcount_single = profile.c.playcount_single + 1 if play_type == 1 else profile.c.playcount_single,
|
||||
|
||||
playcount_multi_vs = profile.c.playcount_multi_vs + 1 if play_type == 2 else profile.c.playcount_multi_vs,
|
||||
|
||||
playcount_multi_coop = profile.c.playcount_multi_coop + 1 if play_type == 3 else profile.c.playcount_multi_coop,
|
||||
|
||||
playcount_stageup = profile.c.playcount_stageup + 1 if play_type == 4 else profile.c.playcount_stageup,
|
||||
|
||||
last_game_ver = game_version,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"update_profile: failed to update profile! profile: {profile_id}")
|
||||
return None
|
||||
|
||||
def update_profile_lastplayed(self, profile_id: int, last_song_id: int, last_song_difficulty: int, last_folder_order: int,
|
||||
last_folder_id: int, last_song_order: int) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
last_song_id = last_song_id,
|
||||
last_song_difficulty = last_song_difficulty,
|
||||
last_folder_order = last_folder_order,
|
||||
last_folder_id = last_folder_id,
|
||||
last_song_order = last_song_order
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"update_profile_lastplayed: failed to update profile! profile: {profile_id}")
|
||||
return None
|
||||
|
||||
def update_profile_dan(self, profile_id: int, dan_level: int, dan_type: int) -> Optional[int]:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
dan_level = dan_level,
|
||||
dan_type = dan_type
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"update_profile_dan: Failed to update! profile {profile_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile(self, profile_id: int = 0, aime_id: int = None) -> Optional[Row]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
if aime_id is not None:
|
||||
sql = profile.select(profile.c.user == aime_id)
|
||||
elif profile_id > 0:
|
||||
sql = profile.select(profile.c.id == profile_id)
|
||||
else:
|
||||
self.logger.error(f"get_profile: Bad arguments!! profile_id {profile_id} aime_id {aime_id}")
|
||||
return None
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_options(self, user_id: int, option_id: int = None) -> Optional[List[Row]]:
|
||||
"""
|
||||
Get a specific user option for a profile, or all of them if none specified
|
||||
"""
|
||||
sql = option.select(
|
||||
and_(option.c.user == user_id,
|
||||
option.c.opt_id == option_id if option_id is not None else True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
if option_id is not None:
|
||||
return result.fetchone()
|
||||
else:
|
||||
return result.fetchall()
|
||||
|
||||
def update_option(self, user_id: int, option_id: int, value: int) -> Optional[int]:
|
||||
sql = insert(option).values(
|
||||
user = user_id,
|
||||
opt_id = option_id,
|
||||
value = value
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
value = sql.inserted.value
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to insert option! profile: {user_id}, option: {option_id}, value: {value}")
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def add_favorite_song(self, user_id: int, song_id: int) -> Optional[int]:
|
||||
sql = favorite.insert().values(
|
||||
user=user_id,
|
||||
song_id=song_id
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to insert favorite! profile: {user_id}, song_id: {song_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def remove_favorite_song(self, user_id: int, song_id: int) -> None:
|
||||
sql = favorite.delete(and_(favorite.c.user == user_id, favorite.c.song_id == song_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to remove favorite! profile: {user_id}, song_id: {song_id}")
|
||||
return None
|
||||
|
||||
def get_favorite_songs(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = favorite.select(favorite.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_gates(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = select(gate).where(gate.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def update_gate(self, user_id: int, gate_id: int, page: int, progress: int, loop: int, mission_flag: int,
|
||||
total_points: int) -> Optional[int]:
|
||||
sql = insert(gate).values(
|
||||
user=user_id,
|
||||
gate_id=gate_id,
|
||||
page=page,
|
||||
progress=progress,
|
||||
loops=loop,
|
||||
mission_flag=mission_flag,
|
||||
total_points=total_points
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
page=sql.inserted.page,
|
||||
progress=sql.inserted.progress,
|
||||
loops=sql.inserted.loops,
|
||||
mission_flag=sql.inserted.mission_flag,
|
||||
total_points=sql.inserted.total_points,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to update gate! user: {user_id}, gate_id: {gate_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_friends(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = friend.select(friend.c.profile_sender == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def profile_to_aime_user(self, profile_id: int) -> Optional[int]:
|
||||
sql = select(profile.c.user).where(profile.c.id == profile_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.info(f"profile_to_aime_user: No user found for profile {profile_id}")
|
||||
return None
|
||||
|
||||
this_profile = result.fetchone()
|
||||
if this_profile is None:
|
||||
self.logger.info(f"profile_to_aime_user: No user found for profile {profile_id}")
|
||||
return None
|
||||
|
||||
return this_profile['user']
|
||||
|
||||
def session_login(self, profile_id: int, is_new_day: bool, is_consec_day: bool) -> None:
|
||||
# TODO: Reset consec days counter
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
login_count = profile.c.login_count + 1,
|
||||
login_count_consec = profile.c.login_count_consec + 1,
|
||||
login_count_days = profile.c.login_count_days + 1 if is_new_day else profile.c.login_count_days,
|
||||
login_count_days_consec = profile.c.login_count_days_consec + 1 if is_new_day and is_consec_day else profile.c.login_count_days_consec,
|
||||
login_count_today = 1 if is_new_day else profile.c.login_count_today + 1,
|
||||
last_login_date = func.now()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"session_login: failed to update profile! profile: {profile_id}")
|
||||
return None
|
||||
|
||||
def session_logout(self, profile_id: int) -> None:
|
||||
sql = profile.update(profile.c.id == id).values(
|
||||
login_count_consec = 0
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to update profile! profile: {profile_id}")
|
||||
return None
|
||||
|
||||
def add_xp(self, profile_id: int, xp: int) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
xp = profile.c.xp + xp
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"add_xp: Failed to update profile! profile_id {profile_id} xp {xp}")
|
||||
return None
|
||||
|
||||
def add_wp(self, profile_id: int, wp: int) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
wp = profile.c.wp + wp,
|
||||
wp_total = profile.c.wp_total + wp,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"add_wp: Failed to update profile! profile_id {profile_id} wp {wp}")
|
||||
return None
|
||||
|
||||
def spend_wp(self, profile_id: int, wp: int) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
wp = profile.c.wp - wp,
|
||||
wp_spent = profile.c.wp_spent + wp,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"spend_wp: Failed to update profile! profile_id {profile_id} wp {wp}")
|
||||
return None
|
||||
|
||||
def activate_vip(self, profile_id: int, expire_time) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
vip_expire_time = expire_time
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"activate_vip: Failed to update profile! profile_id {profile_id} expire_time {expire_time}")
|
||||
return None
|
||||
|
||||
def update_user_rating(self, profile_id: int, new_rating: int) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(
|
||||
rating = new_rating
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"update_user_rating: Failed to update profile! profile_id {profile_id} new_rating {new_rating}")
|
||||
return None
|
||||
|
||||
def update_bingo(self, aime_id: int, page: int, progress: int) -> Optional[int]:
|
||||
sql = insert(bingo).values(
|
||||
user=aime_id,
|
||||
page_number=page,
|
||||
page_progress=progress
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
page_number=page,
|
||||
page_progress=progress
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_bingo: failed to update! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_bingo(self, aime_id: int) -> Optional[List[Row]]:
|
||||
sql = select(bingo).where(bingo.c.user==aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_bingo_page(self, aime_id: int, page: Dict) -> Optional[List[Row]]:
|
||||
sql = select(bingo).where(and_(bingo.c.user==aime_id, bingo.c.page_number==page))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def update_vip_time(self, profile_id: int, time_left) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(vip_expire_time = time_left)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to update VIP time for profile {profile_id}")
|
||||
|
||||
def update_tutorial_flags(self, profile_id: int, flags: Dict) -> None:
|
||||
sql = profile.update(profile.c.id == profile_id).values(gate_tutorial_flags = flags)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to update tutorial flags for profile {profile_id}")
|
260
titles/wacca/schema/score.py
Normal file
260
titles/wacca/schema/score.py
Normal file
@ -0,0 +1,260 @@
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
best_score = Table(
|
||||
"wacca_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("song_id", Integer),
|
||||
Column("chart_id", Integer),
|
||||
Column("score", Integer),
|
||||
Column("play_ct", Integer),
|
||||
Column("clear_ct", Integer),
|
||||
Column("missless_ct", Integer),
|
||||
Column("fullcombo_ct", Integer),
|
||||
Column("allmarv_ct", Integer),
|
||||
Column("grade_d_ct", Integer),
|
||||
Column("grade_c_ct", Integer),
|
||||
Column("grade_b_ct", Integer),
|
||||
Column("grade_a_ct", Integer),
|
||||
Column("grade_aa_ct", Integer),
|
||||
Column("grade_aaa_ct", Integer),
|
||||
Column("grade_s_ct", Integer),
|
||||
Column("grade_ss_ct", Integer),
|
||||
Column("grade_sss_ct", Integer),
|
||||
Column("grade_master_ct", Integer),
|
||||
Column("grade_sp_ct", Integer),
|
||||
Column("grade_ssp_ct", Integer),
|
||||
Column("grade_sssp_ct", Integer),
|
||||
Column("best_combo", Integer),
|
||||
Column("lowest_miss_ct", Integer),
|
||||
Column("rating", Integer),
|
||||
UniqueConstraint("user", "song_id", "chart_id", name="wacca_score_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"wacca_score_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("song_id", Integer),
|
||||
Column("chart_id", Integer),
|
||||
Column("score", Integer),
|
||||
Column("clear", Integer),
|
||||
Column("grade", Integer),
|
||||
Column("max_combo", Integer),
|
||||
Column("marv_ct", Integer),
|
||||
Column("great_ct", Integer),
|
||||
Column("good_ct", Integer),
|
||||
Column("miss_ct", Integer),
|
||||
Column("fast_ct", Integer),
|
||||
Column("late_ct", Integer),
|
||||
Column("season", Integer),
|
||||
Column("date_scored", TIMESTAMP, server_default=func.now()),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
stageup = Table(
|
||||
"wacca_score_stageup",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("stage_id", Integer),
|
||||
Column("clear_status", Integer),
|
||||
Column("clear_song_ct", Integer),
|
||||
Column("song1_score", Integer),
|
||||
Column("song2_score", Integer),
|
||||
Column("song3_score", Integer),
|
||||
Column("play_ct", Integer, server_default="1"),
|
||||
UniqueConstraint("user", "stage_id", name="wacca_score_stageup_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
class WaccaScoreData(BaseData):
|
||||
def put_best_score(self, user_id: int, song_id: int, chart_id: int, score: int, clear: List[int],
|
||||
grade: List[int], best_combo: int, lowest_miss_ct: int) -> Optional[int]:
|
||||
"""
|
||||
Update the user's best score for a chart
|
||||
"""
|
||||
while len(grade) < 13:
|
||||
grade.append(0)
|
||||
|
||||
sql = insert(best_score).values(
|
||||
user=user_id,
|
||||
song_id=song_id,
|
||||
chart_id=chart_id,
|
||||
score=score,
|
||||
play_ct=clear[0],
|
||||
clear_ct=clear[1],
|
||||
missless_ct=clear[2],
|
||||
fullcombo_ct=clear[3],
|
||||
allmarv_ct=clear[4],
|
||||
grade_d_ct=grade[0],
|
||||
grade_c_ct=grade[1],
|
||||
grade_b_ct=grade[2],
|
||||
grade_a_ct=grade[3],
|
||||
grade_aa_ct=grade[4],
|
||||
grade_aaa_ct=grade[5],
|
||||
grade_s_ct=grade[6],
|
||||
grade_ss_ct=grade[7],
|
||||
grade_sss_ct=grade[8],
|
||||
grade_master_ct=grade[9],
|
||||
grade_sp_ct=grade[10],
|
||||
grade_ssp_ct=grade[11],
|
||||
grade_sssp_ct=grade[12],
|
||||
best_combo=best_combo,
|
||||
lowest_miss_ct=lowest_miss_ct,
|
||||
rating=0
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
score=score,
|
||||
play_ct=clear[0],
|
||||
clear_ct=clear[1],
|
||||
missless_ct=clear[2],
|
||||
fullcombo_ct=clear[3],
|
||||
allmarv_ct=clear[4],
|
||||
grade_d_ct=grade[0],
|
||||
grade_c_ct=grade[1],
|
||||
grade_b_ct=grade[2],
|
||||
grade_a_ct=grade[3],
|
||||
grade_aa_ct=grade[4],
|
||||
grade_aaa_ct=grade[5],
|
||||
grade_s_ct=grade[6],
|
||||
grade_ss_ct=grade[7],
|
||||
grade_sss_ct=grade[8],
|
||||
grade_master_ct=grade[9],
|
||||
grade_sp_ct=grade[10],
|
||||
grade_ssp_ct=grade[11],
|
||||
grade_sssp_ct=grade[12],
|
||||
best_combo=best_combo,
|
||||
lowest_miss_ct=lowest_miss_ct,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__}: failed to insert best score! profile: {user_id}, song: {song_id}, chart: {chart_id}")
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_playlog(self, user_id: int, song_id: int, chart_id: int, this_score: int, clear: int, grade: int, max_combo: int,
|
||||
marv_ct: int, great_ct: int, good_ct: int, miss_ct: int, fast_ct: int, late_ct: int, season: int) -> Optional[int]:
|
||||
"""
|
||||
Add an entry to the user's play log
|
||||
"""
|
||||
sql = playlog.insert().values(
|
||||
user=user_id,
|
||||
song_id=song_id,
|
||||
chart_id=chart_id,
|
||||
score=this_score,
|
||||
clear=clear,
|
||||
grade=grade,
|
||||
max_combo=max_combo,
|
||||
marv_ct=marv_ct,
|
||||
great_ct=great_ct,
|
||||
good_ct=good_ct,
|
||||
miss_ct=miss_ct,
|
||||
fast_ct=fast_ct,
|
||||
late_ct=late_ct,
|
||||
season=season
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {chart_id}")
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_score(self, user_id: int, song_id: int, chart_id: int) -> Optional[Row]:
|
||||
sql = best_score.select(
|
||||
and_(best_score.c.user == user_id, best_score.c.song_id == song_id, best_score.c.chart_id == chart_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_best_scores(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = best_score.select(
|
||||
best_score.c.user == user_id
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def update_song_rating(self, user_id: int, song_id: int, chart_id: int, new_rating: int) -> None:
|
||||
sql = best_score.update(
|
||||
and_(
|
||||
best_score.c.user == user_id,
|
||||
best_score.c.song_id == song_id,
|
||||
best_score.c.chart_id == chart_id
|
||||
)).values(
|
||||
rating = new_rating
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"update_song_rating: failed to update rating! user_id: {user_id} song_id: {song_id} chart_id {chart_id} new_rating {new_rating}")
|
||||
return None
|
||||
|
||||
def put_stageup(self, user_id: int, version: int, stage_id: int, clear_status: int, clear_song_ct: int, score1: int,
|
||||
score2: int, score3: int) -> Optional[int]:
|
||||
sql = insert(stageup).values(
|
||||
user = user_id,
|
||||
version = version,
|
||||
stage_id = stage_id,
|
||||
clear_status = clear_status,
|
||||
clear_song_ct = clear_song_ct,
|
||||
song1_score = score1,
|
||||
song2_score = score2,
|
||||
song3_score = score3,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
clear_status = clear_status,
|
||||
clear_song_ct = clear_song_ct,
|
||||
song1_score = score1,
|
||||
song2_score = score2,
|
||||
song3_score = score3,
|
||||
play_ct = stageup.c.play_ct + 1
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_stageup: failed to update! user_id: {user_id} version: {version} stage_id: {stage_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_stageup(self, user_id: int, version: int) -> Optional[List[Row]]:
|
||||
sql = select(stageup).where(and_(stageup.c.user==user_id, stageup.c.version==version))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_stageup_stage(self, user_id: int, version: int, stage_id: int) -> Optional[Row]:
|
||||
sql = select(stageup).where(
|
||||
and_(
|
||||
stageup.c.user == user_id,
|
||||
stageup.c.version == version,
|
||||
stageup.c.stage_id == stage_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
68
titles/wacca/schema/static.py
Normal file
68
titles/wacca/schema/static.py
Normal file
@ -0,0 +1,68 @@
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
music = Table(
|
||||
"wacca_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("bpm", String(255)),
|
||||
Column("difficulty", Float),
|
||||
Column("chartDesigner", String(255)),
|
||||
Column("jacketFile", String(255)),
|
||||
UniqueConstraint("version", "songId", "chartId", name="wacca_static_music_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
class WaccaStaticData(BaseData):
|
||||
def put_music(self, version: int, song_id: int, chart_id: int, title: str, artist: str, bpm: str,
|
||||
difficulty: float, chart_designer: str, jacket: str) -> Optional[int]:
|
||||
sql = insert(music).values(
|
||||
version = version,
|
||||
songId = song_id,
|
||||
chartId = chart_id,
|
||||
title = title,
|
||||
artist = artist,
|
||||
bpm = bpm,
|
||||
difficulty = difficulty,
|
||||
chartDesigner = chart_designer,
|
||||
jacketFile = jacket
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
title = title,
|
||||
artist = artist,
|
||||
bpm = bpm,
|
||||
difficulty = difficulty,
|
||||
chartDesigner = chart_designer,
|
||||
jacketFile = jacket
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]:
|
||||
sql = select(music).where(and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id
|
||||
))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
Reference in New Issue
Block a user