artemis/titles/idac/season2.py

3365 lines
124 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from collections import defaultdict
from datetime import datetime, timedelta
import os
from random import choice, randint
from typing import Any, Dict, List
import json
import logging
import asyncio
from core.config import CoreConfig
from core.utils import Utils
from titles.idac.const import IDACConstants
from titles.idac.config import IDACConfig
from titles.idac.base import IDACBase
class IDACSeason2(IDACBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: IDACConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = IDACConstants.VER_IDAC_SEASON_2
# load timerelease numbers from config
self.timerelease_no = self.game_config.timerelease.timerelease_no
self.timerelease_avatar_gacha_no = (
self.game_config.timerelease.timerelease_avatar_gacha_no
)
# load the user configured play stamps (up to 3) for each game version
self.stamp_info = defaultdict(list)
if self.game_config.stamp.enable:
enabled_stamps = self.game_config.stamp.enabled_stamps
for game_ver in enabled_stamps.keys():
for stamp in enabled_stamps[game_ver][:3]:
if not os.path.exists(f"./titles/idac/data/stamp/{stamp}.json"):
self.logger.warning(
f"Stamp {stamp} is enabled but does not exist!"
)
continue
with open(
f"./titles/idac/data/stamp/{stamp}.json", encoding="UTF-8"
) as f:
self.logger.debug(f"Loading stamp {stamp}")
self.stamp_info[game_ver].append(self._fix_dates(json.load(f)))
# load the user configured time trial (only one) for each game version
self.timetrial_event = {}
self.timetrial_event_id = {}
if self.game_config.timetrial.enable:
enabled_timetrial = self.game_config.timetrial.enabled_timetrial
for game_ver in enabled_timetrial.keys():
timetrial = enabled_timetrial[game_ver]
if timetrial is not None:
if not os.path.exists(
f"./titles/idac/data/timetrial/{timetrial}.json"
):
self.logger.warning(
f"Timetrial {timetrial} is enabled but does not exist!"
)
else:
self.logger.debug(f"Loading timetrial {timetrial}")
with open(
f"./titles/idac/data/timetrial/{timetrial}.json",
encoding="UTF-8",
) as f:
self.timetrial_event[game_ver] = self._fix_dates(
json.load(f)
)
# required for saving
self.timetrial_event_id[game_ver] = self.timetrial_event.get(
game_ver
).get("timetrial_event_id")
# load the user configured round event (only one)
asyncio.create_task(self._load_round_event())
# load the user configured battle gifts (only one)
self.battle_gift_event = None
if self.game_config.battle_gift.enable:
battle_gift = self.game_config.battle_gift.enabled_battle_gift
if battle_gift is not None:
if not os.path.exists(
f"./titles/idac/data/battle_gift/{battle_gift}.json"
):
self.logger.warning(
f"Battle gift {battle_gift} is enabled but does not exist!"
)
else:
self.logger.debug(f"Loading battle gift {battle_gift}")
with open(
f"./titles/idac/data/battle_gift/{battle_gift}.json",
encoding="UTF-8",
) as f:
self.battle_gift_event = self._fix_dates(json.load(f))
async def _load_round_event(self):
self.round_event_id = 0
self.round_event = []
self.last_round_event_id = 0
self.last_round_event = []
self.last_round_event_ranking = []
if self.game_config.round_event.enable:
# Load current round event
round = self.game_config.round_event.enabled_round
if round is not None:
path = f"./titles/idac/data/rounds/season{self.version+1}/{round}.json"
if not os.path.exists(path):
self.logger.warning(
f"Round info {round} is enabled but json file does not exist!"
)
else:
with open(path, encoding="UTF-8") as f:
self.logger.debug(f"Loading round info {round}...")
tmp = json.load(f)
self.round_event_id = (
await self.data.rounds._try_load_round_event(
tmp["round_event_id"], self.version, tmp
)
)
self.round_event.append(self._fix_dates(tmp))
self.logger.debug(
f"Loaded round id for database: {self.round_event_id}..."
)
# Load last round event
round = self.game_config.round_event.last_round
if round is not None:
path = f"./titles/idac/data/rounds/season{self.version+1}/{round}.json"
if not os.path.exists(path):
self.logger.warning(
f"Round info {round} is enabled but json file does not exist!"
)
else:
with open(path, encoding="UTF-8") as f:
self.logger.debug(f"Loading round info {round}...")
tmp = json.load(f)
self.last_round_event_id = (
await self.data.rounds._try_load_round_event(
tmp["round_event_id"], self.version, tmp
)
)
self.last_round_event.append(tmp)
self.logger.debug(
f"Loaded round id for database: {self.last_round_event_id}..."
)
# Load top five of last round event
# class LastRoundEventRanking(BaseModel):
# round_rank: int
# round_point: int
# round_play_count: int
ranking = await self.data.rounds.get_round_top_five(
self.last_round_event_id
)
if ranking is not None:
rank_profile = {
"round_rank": 0,
"round_point": 0,
"round_play_count": 0,
"username": "DUMMY",
"country": 9,
"store": self.core_cfg.server.name,
"online_battle_rank": 0,
"mytitle_id": 0,
"mytitle_effect_id": 0,
"car_data": [],
"user_avatar": [],
}
for user in ranking:
# get the user's profile
user_id = user["user"]
p = await self.data.profile.get_profile(user_id, self.version)
user_data = p._asdict()
arcade = await self.data.arcade.get_arcade(user_data["store"])
user_data["store_name"] = (
self.core_cfg.server.name if arcade is None else arcade["name"]
)
rank_profile["username"] = user_data["username"]
rank_profile["country"] = user_data["country"]
rank_profile["store"] = user_data["store_name"]
rank_profile["mytitle_id"] = user_data["mytitle_id"]
rank_profile["mytitle_effect_id"] = user_data["mytitle_effect_id"]
# get the user's avatar
a = await self.data.profile.get_profile_avatar(user_id)
avatar_data = a._asdict()
del avatar_data["id"]
del avatar_data["user"]
rank_profile["user_avatar"] = avatar_data
# get the user's rank
r = await self.data.profile.get_profile_rank(user_id, self.version)
rank_data = r._asdict()
del rank_data["id"]
del rank_data["user"]
del rank_data["version"]
rank_profile["online_battle_rank"] = rank_data["online_battle_rank"]
# get the user's car
cars = await self.data.item.get_cars(
self.version, user_id, only_pickup=True
)
fulltune_count = 0
total_car_parts_count = 0
car_data = []
for car in cars:
tmp = car._asdict()
del tmp["id"]
del tmp["user"]
del tmp["version"]
car_data.append(tmp)
rank_profile["car_data"] = car_data
# get round info
ri = await self.data.rounds.get_round_info_by_id(
user_id, self.last_round_event_id
)
if ri is not None:
ri = ri._asdict()
rank_profile["round_rank"] = user["find_in_set_1"]
rank_profile["round_point"] = ri["point"]
rank_profile["round_play_count"] = ri["count"]
self.last_round_event_ranking.append(rank_profile)
async def handle_alive_get_request(self, data: Dict, headers: Dict):
return {
"status_code": "0",
# 1 = success, 0 = failed
"server_status": 1,
"force_reboot_time": int(datetime.now().timestamp()) - 86400,
}
def _fix_dates(self, input: dict):
"""
Fix "start_dt" and "end_dt" dates in a JSON file.
"""
output = {}
self.logger.debug(f"Fixing dates in {type(input)}")
for key, value in input.items():
if key in {"start_dt", "end_dt"}:
if isinstance(value, str):
value = int(datetime.strptime(value, "%Y-%m-%d").timestamp())
output[key] = value
return output
def _headers_to_version(self, headers: Dict) -> int:
version = headers.get("device_version", "1.00.00")
return int(version.replace(".", "")[:-2])
def _special_story_type(self, headers: Dict):
ver = self._headers_to_version(headers)
# 4 = touhou project, 5 = hatsune miku
return {150: 4, 170: 5}.get(ver, 0)
async def handle_boot_getconfigdata_request(self, data: Dict, headers: Dict):
"""
category:
1 = D Coin
3 = Car Dressup Token
5 = Avatar Dressup Token
6 = Tachometer
7 = Aura
8 = Aura Color
9 = Avatar Face
10 = Avatar Eye
11 = Avatar Mouth
12 = Avatar Hair
13 = Avatar Glasses
14 = Avatar Face accessories
15 = Avatar Body
18 = Avatar Background
21 = Chat Stamp
22 = Keychain
24 = Title
25 = FullTune Ticket
26 = Paper Cup
27 = BGM
28 = Drifting Text
31 = Start Menu BG
32 = Car Color/Paint
33 = Aura Level
34 = FullTune Ticket Fragment
35 = Underneon Lights
"""
ver_str = self._headers_to_version(headers)
if self.core_cfg.server.is_using_proxy:
domain_api_game = f"http://{self.core_cfg.server.hostname}/{ver_str}/"
else:
domain_api_game = f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/{ver_str}/"
# sets the special mode data if the version supports the special mode
special_mode_data = {}
special_story_type = self._special_story_type(headers)
if special_story_type != 0:
special_mode_data = {
"start_dt": int(
datetime.strptime("2023-01-01", "%Y-%m-%d").timestamp()
),
"end_dt": int(datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp()),
"story_type": special_story_type,
}
return {
"status_code": "0",
"free_continue_enable": 1,
"free_continue_new": 1,
"free_continue_play": 1,
"difference_time_to_jp": 0,
# has to match the game asset version to show theory of street
"asset_version": "1",
"optional_version": "1",
"disconnect_offset": 0,
"boost_balance_version": "0",
"time_release_number": "0",
"play_stamp_enable": 1,
"play_stamp_bonus_coin": 1,
"gacha_chara_needs": 1,
"both_win_system_control": 0,
"subcard_system_congrol": 0,
"server_maintenance_start_hour": 0,
"server_maintenance_start_minutes": 0,
"server_maintenance_end_hour": 0,
"server_maintenance_end_minutes": 0,
"domain_api_game": domain_api_game,
"domain_matching": f"{domain_api_game}initiald-matching/",
"domain_echo1": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}",
"domain_echo2": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}",
"domain_ping": f"{self.core_cfg.server.hostname}",
"battle_gift_event_master": (
[self.battle_gift_event] if self.battle_gift_event else []
),
# online battle round event
"round_event": self.round_event,
"last_round_event": self.last_round_event,
"last_round_event_ranking": self.last_round_event_ranking,
"round_event_exp": [],
"stamp_info": self.stamp_info[ver_str],
# 0 = use default data, 1+ = server version of timereleasedata response
"timerelease_no": self.timerelease_no,
# 0 = use default data, 1+ = server version of gachadata response
"timerelease_avatar_gacha_no": self.timerelease_avatar_gacha_no,
# takover reward from other games such as 1=IDZ and 3=SWDC
"takeover_reward": [
{
"takeover_reward_type": 0,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 1, "reward_type": 5000},
{"reward_no": 2, "reward_category": 25, "reward_type": 1},
],
},
{
"takeover_reward_type": 1,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 15, "reward_type": 82},
{"reward_no": 1, "reward_category": 15, "reward_type": 190},
{"reward_no": 2, "reward_category": 3, "reward_type": 2},
{"reward_no": 2, "reward_category": 5, "reward_type": 2},
{"reward_no": 3, "reward_category": 3, "reward_type": 3},
{"reward_no": 3, "reward_category": 5, "reward_type": 3},
{"reward_no": 4, "reward_category": 3, "reward_type": 5},
{"reward_no": 4, "reward_category": 5, "reward_type": 5},
{"reward_no": 5, "reward_category": 15, "reward_type": 104},
{"reward_no": 5, "reward_category": 15, "reward_type": 212},
],
},
{
"takeover_reward_type": 2,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 24, "reward_type": 4052},
{"reward_no": 2, "reward_category": 24, "reward_type": 4053},
{"reward_no": 3, "reward_category": 24, "reward_type": 4054},
{"reward_no": 4, "reward_category": 24, "reward_type": 4055},
{"reward_no": 5, "reward_category": 24, "reward_type": 4056},
{"reward_no": 6, "reward_category": 24, "reward_type": 4057},
{"reward_no": 7, "reward_category": 24, "reward_type": 4058},
],
},
{
"takeover_reward_type": 3,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 15, "reward_type": 81},
{"reward_no": 1, "reward_category": 15, "reward_type": 189},
{"reward_no": 2, "reward_category": 25, "reward_type": 1},
{"reward_no": 2, "reward_category": 15, "reward_type": 103},
{"reward_no": 2, "reward_category": 15, "reward_type": 211},
],
},
],
"subcard_judge": [
{
"condition_id": 1,
"lower_rank": 1,
"higher_rank": 7,
"condition_start": 17,
"condition_end": 20,
"calc": 3,
},
{
"condition_id": 2,
"lower_rank": 1,
"higher_rank": 15,
"condition_start": 10,
"condition_end": 10,
"calc": 20,
},
{
"condition_id": 3,
"lower_rank": 11,
"higher_rank": 15,
"condition_start": 80,
"condition_end": 89,
"calc": 7,
},
{
"condition_id": 3,
"lower_rank": 11,
"higher_rank": 15,
"condition_start": 90,
"condition_end": 100,
"calc": 10,
},
{
"condition_id": 3,
"lower_rank": 1,
"higher_rank": 15,
"condition_start": 0,
"condition_end": 40,
"calc": -100,
},
{
"condition_id": 1,
"lower_rank": 1,
"higher_rank": 15,
"condition_start": 21,
"condition_end": 24,
"calc": 7,
},
{
"condition_id": 1,
"lower_rank": 1,
"higher_rank": 15,
"condition_start": 25,
"condition_end": 25,
"calc": 10,
},
{
"condition_id": 3,
"lower_rank": 16,
"higher_rank": 20,
"condition_start": 70,
"condition_end": 89,
"calc": 6,
},
{
"condition_id": 3,
"lower_rank": 16,
"higher_rank": 20,
"condition_start": 90,
"condition_end": 100,
"calc": 12,
},
{
"condition_id": 3,
"lower_rank": 16,
"higher_rank": 20,
"condition_start": 0,
"condition_end": 50,
"calc": -100,
},
],
"special_promote": [
{"counter": 50, "online_rank_id": 8},
{"counter": 200, "online_rank_id": 16},
{"counter": 255, "online_rank_id": 21},
],
"matching_id": 0,
"matching_group": [
{"group_id": 1, "group_percent": 30},
{"group_id": 2, "group_percent": 50},
{"group_id": 3, "group_percent": 100},
],
"timetrial_disp_date": int(
datetime.strptime("2023-10-01", "%Y-%m-%d").timestamp()
),
# price for every car
"buy_car_need_cash": 5000,
# number of buyable shop/customization time limits
"time_extension_limit": 5,
"collabo_id": 0,
"driver_debut_end_date": int(
datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp()
),
"online_battle_param1": 0,
"online_battle_param2": 0,
"online_battle_param3": 0,
"online_battle_param4": 0,
"online_battle_param5": 0,
"online_battle_param6": 2,
"online_battle_param7": 0,
"online_battle_param8": 3900,
"theory_open_version": "1.30.00",
"theory_close_version": "9.99.99",
# unlocks the version specific special mode
"special_mode_data": special_mode_data,
"timetrial_event_data": self.timetrial_event[ver_str],
"number_lottery_data": [
{
"m_number_lottery_win_number_no": 10,
"m_number_lottery_schedule_no": 1,
"win_number": 0,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 11,
"m_number_lottery_schedule_no": 1,
"win_number": 1111,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 12,
"m_number_lottery_schedule_no": 1,
"win_number": 2222,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 13,
"m_number_lottery_schedule_no": 1,
"win_number": 3333,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 14,
"m_number_lottery_schedule_no": 1,
"win_number": 4444,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 15,
"m_number_lottery_schedule_no": 1,
"win_number": 5555,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 16,
"m_number_lottery_schedule_no": 1,
"win_number": 6666,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 17,
"m_number_lottery_schedule_no": 1,
"win_number": 7777,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 18,
"m_number_lottery_schedule_no": 1,
"win_number": 8888,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
{
"m_number_lottery_win_number_no": 19,
"m_number_lottery_schedule_no": 1,
"win_number": 9999,
"reward_category": 34,
"reward_type": 1,
"end_dt": 0,
},
],
}
async def handle_boot_bookkeep_request(self, data: Dict, headers: Dict):
pass
async def handle_boot_getgachadata_request(self, data: Dict, headers: Dict):
"""
Reward category types:
9: Face
10: Eye
11: Mouth
12: Hair
13: Glasses
14: Face accessories
15: Body
18: Background
"""
with open("./titles/idac/data/avatarGacha.json", encoding="UTF-8") as f:
avatar_gacha_data = json.load(f)
# avatar_gacha_data = {
# "status_code": "0",
# "avatar_gacha_data": [
# {
# "avatar_gacha_id": 0,
# "avatar_gacha_nm": "Standard",
# "gacha_type": 0,
# "save_filename": "0",
# "use_ticket_cnt": 1,
# "start_dt": int(
# datetime.strptime("2019-01-01", "%Y-%m-%d").timestamp()
# ),
# "end_dt": int(
# datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp()
# ),
# "gacha_reward": [
# {
# "reward_id": 117,
# "reward_type": 118,
# "reward_category": 18,
# "rate": 1000,
# "pickup_flag": 0,
# },
# ],
# }
# ],
# }
self.logger.debug(
f'Available avatar gacha items: {len(avatar_gacha_data["avatar_gacha_data"][0]["gacha_reward"])}'
)
return avatar_gacha_data
async def handle_boot_gettimereleasedata_request(self, data: Dict, headers: Dict):
"""
timerelease chapter:
1 = Story: 1, 2, 3, 4, 5, 6, 7, 8, 9, 19 (Chapter 10), 29 (Chapter 11)
2 = MF Ghost: 10, 11, 12, 13, 14
3 = Bunta: 15, 16, 17, 18, 20, 21, 21, 22
4 = Touhou Project Special Event: 23, 24, 25, 26, 27, 28
5 = Hatsune Miku Special Event: 36, 37, 38
"""
path = "./titles/idac/data/"
# 1.00.00 is default
device_version_data = headers.get("device_version", "1.00.00")
device_version = int(device_version_data.replace(".", "")[:-2])
timerelease_filename = f"timeRelease_v{device_version:04d}"
timerelease_path = f"{path}{timerelease_filename}.json"
# if the file doesn't exist, try to find the next lowest version
if not os.path.exists(timerelease_path):
while device_version > 100:
device_version -= 1
timerelease_filename = f"timeRelease_v{device_version:04d}"
timerelease_path = f"{path}{timerelease_filename}.json"
# if the file exists, break out of the loop
if os.path.exists(timerelease_path):
break
self.logger.debug(f"Using time release file: {timerelease_filename}")
# load the time release data
with open(f"{path}{timerelease_filename}.json") as f:
time_release_data = json.load(f)
return time_release_data
async def handle_advertise_getrankingdata_request(self, data: Dict, headers: Dict):
best_data = []
for last_update in data.get("last_update_date"):
course_id = last_update.get("course_id")
ranking = await self.data.item.get_time_trial_ranking_by_course(
self.version, course_id
)
ranking_data = []
for i, rank in enumerate(ranking):
user_id = rank["user"]
# get the username, country and store from the profile
profile = await self.data.profile.get_profile(user_id, self.version)
arcade = await self.data.arcade.get_arcade(profile["store"])
if arcade is None:
arcade = {}
arcade["name"] = self.core_cfg.server.name
# should never happen
if profile is None:
continue
ranking_data.append(
{
"course_id": course_id,
"rank": i + 1,
"username": profile["username"],
"value": rank["goal_time"],
# gat the store name from the profile
"store": arcade["name"],
# get the country id from the profile, 9 is JPN
"country": profile["country"],
"style_car_id": rank["style_car_id"],
# convert the datetime to a timestamp
"play_dt": int(rank["play_dt"].timestamp()),
"section_time_1": rank["section_time_1"],
"section_time_2": rank["section_time_2"],
"section_time_3": rank["section_time_3"],
"section_time_4": rank["section_time_4"],
"mission": rank["mission"],
}
)
best_data.append(
{
"course_id": course_id,
"ranking_data": ranking_data,
}
)
return {
"status_code": "0",
"national_best_data": best_data,
"shop_best_data": best_data,
"rank_management_flag": 0,
}
def _is_valid_version(self, db_version: str, cl_version: str) -> bool:
if len(db_version) < 7 or len(cl_version) < 7:
return False
# convert the trings to int and compare
db_version = int(db_version.replace(".", "")[:7])
cl_version = int(cl_version.replace(".", "")[:7])
return cl_version >= db_version
async def handle_login_checklock_request(self, data: Dict, headers: Dict):
user_id = data["id"]
access_code = data["accesscode"]
is_new_player = 0
# check that the user_id from access_code matches the user_id
if user_id == await self.data.card.get_user_id_from_card(access_code):
lock_result = 1
# check if an IDAC profile already exists
p = await self.data.profile.get_profile(user_id, self.version)
is_new_player = 1 if p is None else 0
db_version = p["device_version"] if p is not None else "1.00.00"
cl_version = headers["device_version"]
# check if the client version is valid
if not self._is_valid_version(db_version, cl_version):
lock_result = 2
else:
lock_result = 0
user_id = ""
# other: in use
return {
"status_code": "0",
# 0 = already in use, 1 = good, 2 = too new
"lock_result": lock_result,
"lock_date": int(datetime.now().timestamp()),
"daily_play": 1,
"session": f"{user_id}",
"shared_security_key": "a",
"session_procseq": "a",
"new_player": is_new_player,
"server_status": 1,
}
async def handle_login_unlock_request(self, data: Dict, headers: Dict):
return {
"status_code": "0",
"lock_result": 1,
}
async def handle_login_relock_request(self, data: Dict, headers: Dict):
return {
"status_code": "0",
"lock_result": 1,
"lock_date": int(datetime.now().timestamp()),
}
async def handle_login_guestplay_request(self, data: Dict, headers: Dict):
# TODO
pass
async def _generate_story_data(self, user_id: int) -> Dict:
stories = await self.data.item.get_stories(user_id)
story_data = []
for s in stories:
chapter_id = s["chapter"]
episodes = await self.data.item.get_story_episodes(user_id, chapter_id)
episode_data = []
for e in episodes:
episode_id = e["episode"]
difficulties = await self.data.item.get_story_episode_difficulties(
user_id, episode_id
)
difficulty_data = []
for d in difficulties:
difficulty_data.append(
{
"difficulty": d["difficulty"],
"play_count": d["play_count"],
"clear_count": d["clear_count"],
"play_status": d["play_status"],
"play_score": d["play_score"],
}
)
episode_data.append(
{
"episode": e["episode"],
"play_status": e["play_status"],
"difficulty_data": difficulty_data,
}
)
story_data.append(
{
"story_type": s["story_type"],
"chapter": s["chapter"],
"loop_count": s["loop_count"],
"episode_data": episode_data,
}
)
return story_data
async def _generate_special_data(self, user_id: int, headers: Dict) -> Dict:
# 4 = touhou project, 5 = hatsune miku
specials = await self.data.item.get_best_challenges_by_vs_type(
user_id, story_type=self._special_story_type(headers)
)
special_data = []
for s in specials:
special_data.append(
{
"story_type": s["story_type"],
"vs_type": s["vs_type"],
"max_clear_lv": s["max_clear_lv"],
"last_play_lv": s["last_play_lv"],
# change to last_play_course_id?
"last_play_course_id": s["course_id"],
}
)
return special_data
async def _generate_challenge_data(self, user_id: int) -> Dict:
# challenge mode (Bunta challenge only right now)
challenges = await self.data.item.get_best_challenges_by_vs_type(
user_id, story_type=3
)
challenge_data = []
for c in challenges:
challenge_data.append(
{
"story_type": c["story_type"],
"vs_type": c["vs_type"],
"max_clear_lv": c["max_clear_lv"],
"last_play_lv": c["last_play_lv"],
# change to last_play_course_id?
"last_play_course_id": c["course_id"],
"play_count": c["play_count"],
}
)
return challenge_data
async def _save_stock_data(self, user_id: int, stock_data: Dict):
updated_stock_data = {}
for k, v in stock_data.items():
if v != "":
updated_stock_data[k] = v
if updated_stock_data:
await self.data.profile.put_profile_stock(
user_id, self.version, updated_stock_data
)
async def _update_vs_info(self, user_id: int, battle_mode: int, data: Dict) -> Dict:
vs_info = await self.data.item.get_vs_info_by_mode(user_id, battle_mode)
if vs_info is not None:
vs_info = vs_info._asdict()
del vs_info["id"]
del vs_info["user"]
vs_info["invalid"] = vs_info["invalid"] + data.get("result")
vs_info["str_now"] = (
vs_info["str_now"] + data.get("win_flg")
if data.get("win_flg") == 1
else 0
)
vs_info["str"] = (
vs_info["str"]
if vs_info["str"] > vs_info["str_now"]
else vs_info["str_now"]
)
vs_info["lose_now"] += 1 if data.get("win_flg") == 0 else 0
vs_info["vs_history"] = data.get("vs_history")
vs_info["break_count"] += data.get("break_count")
vs_info["break_penalty_flag"] = data.get("break_penalty_flag")
await self.data.item.put_vs_info(user_id, battle_mode, vs_info)
vs_info["vs_cnt"] = 0
vs_info["vs_win"] = 0
vs_info["vsinfo_course_data"] = []
vs_courses_info = await self.data.item.get_vs_course_infos_by_mode(
user_id, battle_mode
)
course_not_exists = True
if vs_courses_info is not None:
for course in vs_courses_info:
course = course._asdict()
del course["id"]
del course["user"]
if course["course_id"] == data.get("course_id"):
course["vs_cnt"] += 1
course["vs_win"] += data.get("win_flg")
vs_info["vs_cnt"] += course["vs_cnt"]
vs_info["vs_win"] += course["vs_win"]
await self.data.item.put_vs_course_info(
user_id, battle_mode, course
)
course_not_exists = False
else:
vs_info["vs_cnt"] += course["vs_cnt"]
vs_info["vs_win"] += course["vs_win"]
vs_info["vsinfo_course_data"].append(course)
if course_not_exists:
course = {}
course["course_id"] = data.get("course_id")
course["vs_cnt"] = 1
course["vs_win"] = data.get("win_flg")
vs_info["vs_cnt"] += course["vs_cnt"]
vs_info["vs_win"] += course["vs_win"]
vs_info["vsinfo_course_data"].append(course)
await self.data.item.put_vs_course_info(user_id, battle_mode, course)
else:
vs_info = {
"battle_mode": battle_mode,
# "vs_cnt": 1,
# "vs_win": data.get("win_flg"),
"invalid": data.get("result"),
"str": data.get("win_flg"),
"str_now": data.get("win_flg"),
"lose_now": 1 if data.get("win_flg") == 0 else 0,
"vs_history": data.get("vs_history"),
"break_count": data.get("break_count"),
"break_penalty_flag": data.get("break_penalty_flag"),
# "vsinfo_course_data": [
# {
# "course_id": data.get("course_id"),
# "vs_cnt": 1,
# "vs_win": data.get("win_flg")
# }
# ],
}
await self.data.item.put_vs_info(user_id, battle_mode, vs_info)
course_info = {
"course_id": data.get("course_id"),
"vs_cnt": 1,
"vs_win": data.get("win_flg"),
}
await self.data.item.put_vs_course_info(user_id, battle_mode, course_info)
vs_info["vs_cnt"] = 1
vs_info["vs_win"] = data.get("win_flg")
vs_info["vsinfo_course_data"] = []
vs_info["vsinfo_course_data"].append(course_info)
vs_info["course_select_priority"] = data.get("course_select_priority")
return vs_info
async def _update_round_info(
self, user_id: int, round_event_id: int, point: int, win: int
) -> Dict:
# get the round info data from database
round_info = []
ri = await self.data.rounds.get_round_info_by_id(user_id, round_event_id)
if ri is not None:
tmp = ri._asdict()
del tmp["id"]
# calculate new round points and info
tmp["point"] = 0 if tmp["point"] + point < 1 else tmp["point"] + point
tmp["count"] += 1
tmp["win"] += win
tmp["play_dt"] = datetime.now()
# update players round info
await self.data.rounds.put_round_event(user_id, round_event_id, tmp)
del tmp["play_dt"]
# get players new round ranking
r = await self.data.rounds.get_round_rank_by_id(user_id, round_event_id)
round_ranking = r._asdict()
tmp["rank"] = round_ranking["find_in_set_1"] if tmp["point"] > 0 else 0
# TODO: get players historical earned points
tmp["total_round_point"] = 0
round_info.append(tmp)
else:
# new player of now-going round event
tmp = {}
tmp["point"] = 0 if point < 1 else point
tmp["count"] = 1
tmp["win"] = win
tmp["play_dt"] = datetime.now()
await self.data.rounds.put_round_event(user_id, round_event_id, tmp)
del tmp["play_dt"]
r = await self.data.rounds.get_round_rank_by_id(user_id, round_event_id)
round_ranking = r._asdict()
tmp["rank"] = round_ranking["find_in_set_1"] if tmp["point"] > 0 else 0
# TODO: get players historical earned points
tmp["total_round_point"] = 0
round_info.append(tmp)
return round_info
def _choose_gift_id(self) -> Dict:
gift_data = self.battle_gift_event["gift_data"]
# calculate the total_rate based on the first_distribution_rate
total_rate = sum(gift["first_distribution_rate"] for gift in gift_data)
# randomly choose a number between 1 and the total rate
rand_num = randint(1, total_rate)
cumulative_rate = 0
for gift in gift_data:
# if the random number is less than the cumulative rate, return the gift
cumulative_rate += gift["first_distribution_rate"]
if rand_num <= cumulative_rate:
return gift
async def handle_user_getdata_request(self, data: Dict, headers: Dict):
user_id = int(headers["session"])
# get the user's profile, can never be None
p = await self.data.profile.get_profile(user_id, self.version)
user_data = p._asdict()
arcade = await self.data.arcade.get_arcade(user_data["store"])
del user_data["id"]
del user_data["user"]
del user_data["version"]
user_data["id"] = user_id
user_data["store_name"] = (
self.core_cfg.server.name if arcade is None else arcade["name"]
)
user_data["last_play_date"] = int(user_data["last_play_date"].timestamp())
user_data["create_date"] = int(user_data["create_date"].timestamp())
# get the user's rank
r = await self.data.profile.get_profile_rank(user_id, self.version)
rank_data = r._asdict()
del rank_data["id"]
del rank_data["user"]
del rank_data["version"]
# add the mode_rank_data to the user_data
user_data["mode_rank_data"] = rank_data
# get the user's avatar
a = await self.data.profile.get_profile_avatar(user_id)
avatar_data = a._asdict()
del avatar_data["id"]
del avatar_data["user"]
# get the user's stock
s = await self.data.profile.get_profile_stock(user_id, self.version)
stock_data = s._asdict()
del stock_data["id"]
del stock_data["user"]
del stock_data["version"]
# get the user's config
c = await self.data.profile.get_profile_config(user_id)
config_data = c._asdict()
del config_data["id"]
del config_data["user"]
config_data["id"] = config_data.pop("config_id")
# get the user's ticket
tickets: list = await self.data.item.get_tickets(user_id)
"""
ticket_id:
3 = Car Dressup Points
5 = Avatar Dressup Points
25 = Full Tune Tickets
34 = Full Tune Fragments
"""
ticket_data = []
for ticket in tickets:
ticket_data.append(
{
"ticket_id": ticket["ticket_id"],
"ticket_cnt": ticket["ticket_cnt"],
}
)
# get the users'd round data
round_info = []
ri = await self.data.rounds.get_round_info_by_id(user_id, self.round_event_id)
if ri is not None:
r = await self.data.rounds.get_round_rank_by_id(
user_id, self.round_event_id
)
round_ranking = r._asdict()
tmp = ri._asdict()
del tmp["id"]
del tmp["user"]
del tmp["round_id"]
del tmp["play_dt"]
tmp["rank"] = round_ranking["find_in_set_1"]
# TODO: calculate this
tmp["total_round_point"] = 0
round_info.append(tmp)
# get the user's course, required for the "course proeficiency"
courses = await self.data.item.get_courses(user_id)
course_data = []
for course in courses:
course_data.append(
{
"id": 0, # no clue, always 0?
"course_id": course["course_id"],
"run_counts": course["run_counts"],
# "course proeficiency" in exp points
"skill_level_exp": course["skill_level_exp"],
}
)
# get the profile theory data
theory_data = {}
theory = await self.data.profile.get_profile_theory(user_id, self.version)
if theory is not None:
theory_data = theory._asdict()
del theory_data["id"]
del theory_data["user"]
del theory_data["version"]
# get the users theory course data
theory_course_data = []
theory_courses = await self.data.item.get_theory_courses(user_id)
for course in theory_courses:
tmp = course._asdict()
del tmp["id"]
del tmp["user"]
tmp["update_dt"] = int(tmp["update_dt"].timestamp())
theory_course_data.append(tmp)
# get the users theory partner data
theory_partner_data = []
theory_partners = await self.data.item.get_theory_partners(user_id)
for partner in theory_partners:
tmp = partner._asdict()
del tmp["id"]
del tmp["user"]
theory_partner_data.append(tmp)
# get the users theory running pram data
theory_running_pram_data = []
theory_running = await self.data.item.get_theory_running(user_id)
for running in theory_running:
tmp = running._asdict()
del tmp["id"]
del tmp["user"]
theory_running_pram_data.append(tmp)
# get the users vs info data
vs_info_data = []
vs_info = await self.data.item.get_vs_infos(user_id)
if vs_info is not None:
for vs in vs_info:
vs = vs._asdict()
vs_courses_infos = await self.data.item.get_vs_course_infos_by_mode(
user_id, vs["battle_mode"]
)
total_vs_win = 0
total_vs_cnt = 0
courses_info = []
if vs_courses_infos is not None:
for course in vs_courses_infos:
tmp = course._asdict()
del tmp["id"]
del tmp["user"]
del tmp["battle_mode"]
total_vs_win += tmp["vs_win"]
total_vs_cnt += tmp["vs_cnt"]
courses_info.append(tmp)
vs_info_data.append(
{
"battle_mode": vs["battle_mode"],
"vs_cnt": total_vs_cnt,
"vs_win": total_vs_win,
"invalid": vs["invalid"],
"str": vs["str"],
"str_now": vs["str_now"],
"lose_now": vs["lose_now"],
"vs_history": vs["vs_history"],
"course_select_priority": 0,
"break_count": vs["break_count"],
"break_penalty_flag": vs["break_penalty_flag"],
"vsinfo_course_data": courses_info,
}
)
# get the user's car
cars = await self.data.item.get_cars(self.version, user_id, only_pickup=True)
fulltune_count = 0
total_car_parts_count = 0
car_data = []
for car in cars:
tmp = car._asdict()
del tmp["id"]
del tmp["user"]
del tmp["version"]
car_data.append(tmp)
# tune_level of 16 means fully tuned, so add 1 to fulltune_count
if car["tune_level"] >= 16:
fulltune_count += 1
# add the number of car parts to total_car_parts_count?
# total_car_parts_count += tmp["total_car_parts_count"]
car_data.append(tmp)
# update user profile car count
user_data["have_car_cnt"] = len(car_data)
# get the user's play stamps
stamps = await self.data.item.get_stamps(user_id)
stamp_event_data = []
for stamp in stamps:
tmp = stamp._asdict()
del tmp["id"]
del tmp["user"]
now = datetime.now()
# create timestamp for today at 1am
this_day = now.replace(hour=1, minute=0, second=0, microsecond=0)
# check if this_day is greater than or equal to create_date_daily
if this_day >= tmp["create_date_daily"]:
# reset the daily stamp
tmp["create_date_daily"] = now
tmp["daily_bonus"] = 0
# create a timestamp for this monday at 1am
this_monday = now - timedelta(days=now.weekday())
this_monday = this_monday.replace(hour=1, minute=0, second=0, microsecond=0)
# check if this_monday is greater than or equal to create_date_weekly
if this_monday >= tmp["create_date_weekly"]:
# reset the weekly stamp
tmp["create_date_weekly"] = now
tmp["weekly_bonus"] = 0
# update the play stamp in the database
await self.data.item.put_stamp(user_id, tmp)
del tmp["create_date_daily"]
del tmp["create_date_weekly"]
stamp_event_data.append(tmp)
ver_str = self._headers_to_version(headers)
# get the user's timetrial event data
timetrial_event_data = {}
timetrial = await self.data.item.get_timetrial_event(
user_id, self.timetrial_event_id[ver_str]
)
if timetrial is not None:
timetrial_event_data = {
"timetrial_event_id": timetrial["timetrial_event_id"],
"point": timetrial["point"],
}
# check if the battle gift event is active
if self.battle_gift_event:
# get the users battle gifts, for the current active battle gift event
battle_gifts = await self.data.item.get_battle_gifts(
user_id, self.battle_gift_event.get("battle_gift_event_id")
)
if battle_gifts:
# just return all aquired gifts
battle_gift_data = [
{
"first_distribution_flag": 0,
"gift_data": [
{
"gift_id": gift["gift_id"],
# 1 = acquired
"gift_get_status": gift["gift_status"],
}
for gift in battle_gifts
],
}
]
else:
# get a random gift from the active battle gift event
gift_id = self._choose_gift_id()["gift_id"]
# save the battle_gift inside the database
await self.data.item.put_battle_gift(
user_id,
{
"battle_gift_event_id": self.battle_gift_event.get(
"battle_gift_event_id"
),
"gift_id": gift_id,
"gift_status": 1, # aquired
},
)
battle_gift_data = [
{
# trigger the first gift animation
"first_distribution_flag": 1,
"gift_data": [
{
# return a random selected gift_id
"gift_id": gift_id,
# set the status to 2 = new gift
"gift_get_status": 2,
}
],
}
]
# get the tips from the database
tips = await self.data.profile.get_profile_tips(user_id, self.version)
# if there are no tips, create a new one
if tips is None:
await self.data.profile.put_profile_tips(user_id, self.version, {})
# get the tips from the database
tips = await self.data.profile.get_profile_tips(user_id, self.version)
tips_info = tips._asdict()
del tips_info["id"]
del tips_info["user"]
del tips_info["version"]
return {
"status_code": "0",
"user_base_data": user_data,
"avatar_data": avatar_data,
"pick_up_car_data": car_data,
"story_data": await self._generate_story_data(user_id),
"vsinfo_data": vs_info_data,
"stock_data": stock_data,
"mission_data": {
"id": 0,
"achieve_flag": 0,
"received_flag": 0,
"update_dt": int(datetime.now().timestamp() - 86400),
},
"weekly_mission_data": [],
"course_data": course_data,
"toppatu_event_data": {
"id": 0,
"event_id": 0,
"count1": 0,
"count2": 0,
"count3": 0,
"accept_flag": 0,
},
"event_data": {
"id": 0,
"active_event_id": 0,
"dialog_show_date": int(datetime.now().timestamp() - 86400),
"show_start_dialog_flag": 1,
"show_progress_dialog_flag": 1,
"show_end_dialog_flag": 1,
"end_event_id": 0,
},
"rewards_data": {},
"login_bonus_data": {
"gacha_id": 0,
"gacha_item_id": 0,
"category": 0,
"type": 0,
},
"frozen_data": {"frozen_status": 2},
"penalty_data": {"penalty_flag": 0, "penalty_2_level": 0},
"config_data": config_data,
# gift_get_status 2 = new gift, 1 = acquired
# first_distribution related are useless since this is all handled by server
"battle_gift_data": battle_gift_data,
"ticket_data": ticket_data,
"round_event": round_info,
"last_round_event": [],
"past_round_event": [],
"total_round_point": 0,
"stamp_event_data": stamp_event_data,
"avatar_gacha_lottery_data": {"avatar_gacha_id": 0},
"fulltune_count": fulltune_count,
"total_car_parts_count": total_car_parts_count,
"car_layout_count": [],
"car_style_count": [],
"car_use_count": [],
"maker_use_count": [],
"story_course": [{"course_id": 0, "count": 1}],
"driver_debut": {
# "play_count": 5,
# "daily_play": 5,
# "last_play_dt": datetime.now().timestamp() - 86400,
# "use_start_date": datetime.now().timestamp() - 86400,
# "use_end_date": datetime.now().timestamp() + 86400,
# "use_dt": datetime.now().timestamp(),
# "ticket_cnt": 1,
# "ticket_get_bit": (2 ** 5) - 1,
},
"theory_data": theory_data,
"theory_course_data": theory_course_data,
"theory_partner_data": theory_partner_data,
"theory_running_pram_data": theory_running_pram_data,
"special_mode_data": await self._generate_special_data(user_id, headers),
"challenge_mode_data": await self._generate_challenge_data(user_id),
"season_rewards_data": [],
"timetrial_event_data": timetrial_event_data,
"special_mode_hint_data": {"story_type": 0, "hint_display_flag": 0},
"tips_info": tips_info,
}
async def handle_timetrial_getbestrecordpreta_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
for car_id in data["car_ids"]:
pass
course_mybest_data = []
courses = await self.data.item.get_time_trial_user_best_courses(
self.version, user_id
)
for course in courses:
course_mybest_data.append(
{
"course_id": course["course_id"],
# local rank, store rank, worldwide rank?
"rank": 1,
# no clue
"member": 10000,
# goal_time in ms
"value": course["goal_time"],
# total number of entries per course?
"total": 10,
"store": self.core_cfg.server.name,
# use car_id from request?
"car_id": 0,
"style_car_id": course["style_car_id"],
"play_dt": course["play_dt"].timestamp(),
"section_time_1": course["section_time_1"],
"section_time_2": course["section_time_2"],
"section_time_3": course["section_time_3"],
"section_time_4": course["section_time_4"],
# no clue
"mission": course["mission"],
}
)
course_pickup_car_best_data = []
courses = await self.data.item.get_time_trial_courses(self.version)
for course in courses:
car_list = []
best_cars = await self.data.item.get_time_trial_best_cars_by_course(
self.version, course["course_id"], user_id
)
for i, car in enumerate(best_cars):
car_list.append(
{
"rank": i + 1,
# no clue
"member": user_id,
"value": car["goal_time"],
"store": self.core_cfg.server.name,
# use car_id from request?
"car_id": 0,
"style_car_id": car["style_car_id"],
"play_dt": car["play_dt"].timestamp(),
"section_time_1": car["section_time_1"],
"section_time_2": car["section_time_2"],
"section_time_3": car["section_time_3"],
"section_time_4": car["section_time_4"],
"mission": car["mission"],
}
)
course_pickup_car_best_data.append(
{
"course_id": course["course_id"],
"car_list": car_list,
}
)
return {
"status_code": "0",
"course_mybest_data": course_mybest_data,
"course_pickup_car_best_data": course_pickup_car_best_data,
}
async def handle_timetrial_getbestrecordprerace_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
course_id = data["course_id"]
for car in data["car_ids"]:
# TODO: get the best record for this car
style_car_id = car["style_car_id"]
# Not sure if this is actually correct
ranking = await self.data.item.get_time_trial_ranking_by_course(
self.version, course_id
)
course_best_data = []
for i, rank in enumerate(ranking):
car_user_id = rank["user"]
# get the username, country and store from the profile
profile = await self.data.profile.get_profile(car_user_id, self.version)
arcade = await self.data.arcade.get_arcade(profile["store"])
if arcade is None:
arcade = {}
arcade["name"] = self.core_cfg.server.name
# should never happen
if profile is None:
continue
course_best_data.append(
{
"course_id": course_id,
"rank": i + 1,
"member": car_user_id,
"value": rank["goal_time"],
"store": arcade["name"],
# use car_id from request?
"car_id": 0,
"style_car_id": rank["style_car_id"],
"play_dt": rank["play_dt"].timestamp(),
"section_time_1": rank["section_time_1"],
"section_time_2": rank["section_time_2"],
"section_time_3": rank["section_time_3"],
"section_time_4": rank["section_time_4"],
"mission": rank["mission"],
}
)
best_cars = await self.data.item.get_time_trial_best_cars_by_course(
self.version, course_id
)
car_list = []
for i, rank in enumerate(best_cars):
car_user_id = rank["user"]
# get the username, country and store from the profile
profile = await self.data.profile.get_profile(car_user_id, self.version)
arcade = await self.data.arcade.get_arcade(profile["store"])
if arcade is None:
arcade = {}
arcade["name"] = self.core_cfg.server.name
# should never happen
if profile is None:
continue
car_list.append(
{
"rank": i + 1,
# no clue
"member": car_user_id,
"value": rank["goal_time"],
"store": arcade["name"],
# use car_id from request?
"car_id": 0,
"style_car_id": rank["style_car_id"],
"play_dt": rank["play_dt"].timestamp(),
"section_time_1": rank["section_time_1"],
"section_time_2": rank["section_time_2"],
"section_time_3": rank["section_time_3"],
"section_time_4": rank["section_time_4"],
"mission": rank["mission"],
}
)
return {
"status_code": "0",
"course_car_best_data": [{"course_id": course_id, "car_list": car_list}],
"course_best_data": course_best_data,
}
async def handle_user_createaccount_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
car_data: Dict = data.pop("car_obj")
parts_data: List = car_data.pop("parts_list")
avatar_data: Dict = data.pop("avatar_obj")
config_data: Dict = data.pop("config_obj")
rank_data: Dict = data.pop("mode_rank_data")
stock_data: Dict = data.pop("takeover_stock_obj")
takeover_ticket_list: List = data.pop("takeover_ticket")
# not required?
use_ticket = data.pop("use_ticket")
# save profile in database
data["store"] = headers.get("a_store", 0)
data["country"] = headers.get("a_country", 0)
data["device_version"] = headers.get("device_version", "1.50.00")
await self.data.profile.put_profile(user_id, self.version, data)
# save rank data in database
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save tickets in database
for ticket in takeover_ticket_list:
await self.data.item.put_ticket(user_id, ticket)
config_data["config_id"] = config_data.pop("id")
await self.data.profile.put_profile_config(user_id, config_data)
await self.data.profile.put_profile_avatar(user_id, avatar_data)
# save car data and car parts in database
car_data["parts_list"] = parts_data
await self.data.item.put_car(user_id, self.version, car_data)
# create the tips row, so that all default tips are populated
await self.data.profile.put_profile_tips(user_id, self.version, {})
return {"status_code": "0"}
async def handle_user_updatelogin_request(self, data: Dict, headers: Dict):
pass
async def handle_timetrial_getcarbest_request(self, data: Dict, headers: Dict):
pass
async def handle_factory_avatargacharesult_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
use_ticket_cnt = data["use_ticket_cnt"]
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# get the user's ticket
tickets: list = await self.data.item.get_tickets(user_id)
ticket_list = []
for ticket in tickets:
# avatar tickets
if ticket["ticket_id"] == 5:
ticket_data = {
"ticket_id": ticket["ticket_id"],
"ticket_cnt": ticket["ticket_cnt"] - use_ticket_cnt,
}
# update the ticket in the database
await self.data.item.put_ticket(user_id, ticket_data)
ticket_list.append(ticket_data)
continue
ticket_list.append(
{
"ticket_id": ticket["ticket_id"],
"ticket_cnt": ticket["ticket_cnt"],
}
)
return {"status_code": "0", "ticket_data": ticket_list}
async def handle_factory_savefavoritecar_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
# save favorite cars in database
for car in data["pickup_on_car_ids"]:
await self.data.item.put_car(user_id, self.version, car)
for car in data["pickup_off_car_ids"]:
await self.data.item.put_car(
user_id,
self.version,
{"style_car_id": car["style_car_id"], "pickup_seq": 0},
)
return {"status_code": "0"}
async def handle_factory_updatemultiplecustomizeresult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
car_list = data.pop("car_list")
ticket_data: List = data.pop("ticket_data")
# unused
total_car_parts_count = data.pop("total_car_parts_count")
# save tickets in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
for car in car_list:
# save car data and car parts in database
await self.data.item.put_car(user_id, self.version, car)
return {"status_code": "0"}
async def handle_factory_updatecustomizeresult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
parts_data: List = data.pop("parts_list")
ticket_data: List = data.pop("ticket_data")
# save tickets in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save car data in database
data["parts_list"] = parts_data
await self.data.item.put_car(user_id, self.version, data)
return {"status_code": "0"}
async def handle_factory_getcardata_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
cars = await self.data.item.get_cars(self.version, user_id)
car_data = []
for car in cars:
tmp = car._asdict()
del tmp["id"]
del tmp["user"]
del tmp["version"]
car_data.append(tmp)
return {
"status_code": "0",
"car_data": car_data,
}
async def handle_factory_renamebefore_request(self, data: Dict, headers: Dict):
pass
async def handle_factory_buycarresult_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
parts_data: List = data.pop("parts_list")
pickup_on_list: List = data.pop("pickup_on_car_ids")
pickup_off_list: List = data.pop("pickup_off_car_ids")
style_car_id = data.get("style_car_id")
# get the pickup_seq for the new car
pickup_seq = 0
# save favorite cars in database
for car in pickup_on_list:
# if the new car is a favorite get the new pickup_seqn for later
if car["style_car_id"] == style_car_id:
pickup_seq = car["pickup_seq"]
else:
await self.data.item.put_car(user_id, self.version, car)
data["pickup_seq"] = pickup_seq
cash = data.pop("cash")
total_cash = data.pop("total_cash")
# save the new cash in database
await self.data.profile.put_profile(
user_id, self.version, {"total_cash": total_cash, "cash": cash}
)
# full tune ticket
use_ticket = data.pop("use_ticket")
if use_ticket:
# get the user's tickets, full tune ticket id is 25
ticket = await self.data.item.get_ticket(user_id, ticket_id=25)
# update the ticket in the database
await self.data.item.put_ticket(
user_id,
{
"ticket_id": ticket["ticket_id"],
"ticket_cnt": ticket["ticket_cnt"] - 1,
},
)
# also set the tune_level to 16 (fully tuned)
data["tune_level"] = 16
# save car data and car parts in database
data["parts_list"] = parts_data
await self.data.item.put_car(user_id, self.version, data)
for car in pickup_off_list:
await self.data.item.put_car(
user_id,
self.version,
{"style_car_id": car["style_car_id"], "pickup_seq": 0},
)
# get the user's car
cars = await self.data.item.get_cars(self.version, user_id)
fulltune_count = 0
total_car_parts_count = 0
for car in cars:
# tune_level of 16 means fully tuned, so add 1 to fulltune_count
if car["tune_level"] >= 16:
fulltune_count += 1
# add the number of car parts to total_car_parts_count
# total_car_parts_count += car["total_car_parts_count"]
# get the user's ticket
tickets = await self.data.item.get_tickets(user_id)
ticket_data = []
for ticket in tickets:
ticket_data.append(
{
"ticket_id": ticket["ticket_id"],
"ticket_cnt": ticket["ticket_cnt"],
}
)
return {
"status_code": "0",
"ticket_data": ticket_data,
"fulltune_count": fulltune_count,
"total_car_parts_count": total_car_parts_count,
"car_layout_count": [],
"car_style_count": [],
}
async def handle_factory_renameresult_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
new_username = data.get("username")
# save new username in database
if new_username:
await self.data.profile.put_profile(user_id, self.version, data)
return {"status_code": "0"}
async def handle_factory_updatecustomizeavatar_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
avatar_data: Dict = data.pop("avatar_obj")
stock_data: Dict = data.pop("stock_obj")
# update the stock data in database
await self._save_stock_data(user_id, stock_data)
# save avatar data and avatar parts in database
await self.data.profile.put_profile_avatar(user_id, avatar_data)
return {"status_code": "0"}
async def handle_factory_updatecustomizeuser_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
# update the stock data in database
await self._save_stock_data(user_id, stock_data)
# update profile data and config in database
await self.data.profile.put_profile(user_id, self.version, data)
return {"status_code": "0"}
async def handle_user_updatestampinfo_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
stamp_event_data = data.pop("stamp_event_data")
for stamp in stamp_event_data:
await self.data.item.put_stamp(user_id, stamp)
return {"status_code": "0"}
async def handle_user_updatetimetrialresult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
ticket_data: List = data.pop("ticket_data")
reward_dist_data: Dict = data.pop("reward_dist_obj")
driver_debut_data = data.pop("driver_debut_obj")
rank_data: Dict = data.pop("mode_rank_obj")
# time trial event points
event_point = data.pop("event_point")
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save tickets in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save mode rank data in database
rank_data.update(reward_dist_data)
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
# get the profile data, update total_play and daily_play, and save it
profile = await self.data.profile.get_profile(user_id, self.version)
total_play = profile["total_play"] + 1
# update profile
await self.data.profile.put_profile(
user_id,
self.version,
{
"total_play": total_play,
"last_play_date": datetime.now(),
"aura_id": data.pop("aura_id"),
"aura_color_id": data.pop("aura_color_id"),
"aura_line_id": data.pop("aura_line_id"),
"cash": data.pop("cash"),
"total_cash": data.pop("total_cash"),
"dressup_point": data.pop("dressup_point"),
"avatar_point": data.pop("avatar_point"),
"mileage": data.pop("mileage"),
},
)
# get the use_count and story_use_count of the used car
style_car_id = data.get("style_car_id")
car_mileage = data.pop("car_mileage")
used_car = await self.data.item.get_car(user_id, self.version, style_car_id)
used_car = used_car._asdict()
# increase the use_count and story_use_count of the used car
used_car["use_count"] += 1
used_car["timetrial_use_count"] += 1
used_car["car_mileage"] = car_mileage
# save the used car in database
await self.data.item.put_car(user_id, self.version, used_car)
# skill_level_exp is the "course proeficiency" and is saved
# in the course table
course_id = data.get("course_id")
run_counts = 1
skill_level_exp = data.pop("skill_level_exp")
# get the course data
course = await self.data.item.get_course(user_id, course_id)
if course:
# update run_counts
run_counts = course["run_counts"] + 1
await self.data.item.put_course(
user_id,
{
"course_id": course_id,
"run_counts": run_counts,
"skill_level_exp": skill_level_exp,
},
)
goal_time = data.get("goal_time")
# grab the ranking data and count the numbers of rows with a faster time
# than the current goal_time
course_rank = await self.data.item.get_time_trial_ranking_by_course(
self.version, course_id, limit=None
)
course_rank = len([r for r in course_rank if r["goal_time"] < goal_time]) + 1
car_course_rank = await self.data.item.get_time_trial_ranking_by_course(
self.version, course_id, style_car_id, limit=None
)
car_course_rank = (
len([r for r in car_course_rank if r["goal_time"] < goal_time]) + 1
)
# only update the time if its better than the best time and also not 0
if data.get("goal_time") > 0:
# get the current best goal time
best_time_trial = (
await self.data.item.get_time_trial_user_best_time_by_course_car(
self.version, user_id, course_id, style_car_id
)
)
if (
best_time_trial is None
or data.get("goal_time") < best_time_trial["goal_time"]
):
# now finally save the time trial with updated timestamp
data["play_dt"] = datetime.now()
await self.data.item.put_time_trial(self.version, user_id, data)
ver_str = self._headers_to_version(headers)
# update the timetrial event points
await self.data.item.put_timetrial_event(
user_id, self.timetrial_event_id[ver_str], event_point
)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id,
self.version,
{"timetrial_play_count": tips["timetrial_play_count"] + 1},
)
ver_str = self._headers_to_version(headers)
return {
"status_code": "0",
"course_rank": course_rank,
"course_car_rank": car_course_rank,
"location_course_store_rank": course_rank,
"car_use_count": [],
"maker_use_count": [],
"timetrial_event_data": {
"timetrial_event_id": self.timetrial_event_id[ver_str],
"point": event_point,
},
}
async def handle_user_updatestoryresult_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
ticket_data: List = data.pop("ticket_data")
reward_dist_data: Dict = data.pop("reward_dist_obj")
driver_debut_data = data.pop("driver_debut_obj")
rank_data: Dict = data.pop("mode_rank_obj")
# stamp_event_data = data.pop("stamp_event_data")
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save tickets in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save mode rank data in database
rank_data.update(reward_dist_data)
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
# save the current story progress in database
max_loop = data.get("chapter_loop_max")
chapter_id = data.get("chapter")
episode_id = data.get("episode")
difficulty = data.get("difficulty")
play_status = data.get("play_status")
# get the current loop from the database
story_data = await self.data.item.get_story(user_id, chapter_id)
# 1 = active, 2+ = cleared?
loop_count = 1
if story_data:
loop_count = story_data["loop_count"]
# if the played difficulty is smaller than loop_count you cannot clear
# (play_status = 2) the episode otherwise the following difficulties
# won't earn any EXP?
if difficulty < loop_count:
play_status = 1
# if the episode has already been cleared, set the play_status to 2
# so it won't be set to unplayed (play_status = 1)
episode_data = await self.data.item.get_story_episode(user_id, episode_id)
if episode_data:
if play_status < episode_data["play_status"]:
play_status = 2
# save the current episode progress in database
await self.data.item.put_story_episode(
user_id,
chapter_id,
{
"episode": episode_id,
"play_status": play_status,
},
)
if loop_count < max_loop and data.get("chapter_clear") == 1:
# increase the loop count
loop_count += 1
# for the current chapter set all episode play_status back to 1
await self.data.item.put_story_episode_play_status(user_id, chapter_id, 1)
await self.data.item.put_story(
user_id,
{
"story_type": data.get("story_type"),
"chapter": chapter_id,
"loop_count": loop_count,
},
)
# save the current episode difficulty progress in database
await self.data.item.put_story_episode_difficulty(
user_id,
episode_id,
{
"difficulty": difficulty,
"play_count": 1, # no idea where this comes from
"clear_count": 1, # no idea where this comes from
"play_status": data.get("play_status"),
"play_score": data.get("play_score"),
},
)
# get the use_count and story_use_count of the used car
style_car_id = data.get("style_car_id")
car_mileage = data.get("car_mileage")
used_car = await self.data.item.get_car(user_id, self.version, style_car_id)
used_car = used_car._asdict()
# increase the use_count and story_use_count of the used car
used_car["use_count"] += 1
used_car["story_use_count"] += 1
used_car["car_mileage"] = car_mileage
# save the used car in database
await self.data.item.put_car(user_id, self.version, used_car)
# get the profile data, update total_play and daily_play, and save it
profile = await self.data.profile.get_profile(user_id, self.version)
total_play = profile["total_play"] + 1
# save user profile in database
await self.data.profile.put_profile(
user_id,
self.version,
{
"total_play": total_play,
"last_play_date": datetime.now(),
"mileage": data.pop("mileage"),
"cash": data.pop("cash"),
"total_cash": data.pop("total_cash"),
"dressup_point": data.pop("dressup_point"),
"avatar_point": data.pop("avatar_point"),
"aura_id": data.pop("aura_id"),
"aura_color_id": data.pop("aura_color_id"),
"aura_line_id": data.pop("aura_line_id"),
},
)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"story_play_count": tips["story_play_count"] + 1}
)
return {
"status_code": "0",
"story_data": await self._generate_story_data(user_id),
"car_use_count": [],
"maker_use_count": [],
}
async def handle_user_updatespecialmoderesult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
ticket_data: List = data.pop("ticket_data")
reward_dist_data: Dict = data.pop("reward_dist_obj")
driver_debut_data = data.pop("driver_debut_obj")
rank_data: Dict = data.pop("mode_rank_obj")
# unused
hint_display_flag: int = data.pop("hint_display_flag")
# get the vs use count from database and update it
style_car_id = data.pop("style_car_id")
car_data = await self.data.item.get_car(user_id, self.version, style_car_id)
story_use_count = car_data["story_use_count"] + 1
# save car data in database
await self.data.item.put_car(
user_id,
self.version,
{
"style_car_id": style_car_id,
"car_mileage": data.pop("car_mileage"),
"story_use_count": story_use_count,
},
)
# get the profile data, update total_play and daily_play, and save it
profile = await self.data.profile.get_profile(user_id, self.version)
total_play = profile["total_play"] + 1
# save user profile in database
await self.data.profile.put_profile(
user_id,
self.version,
{
"total_play": total_play,
"last_play_date": datetime.now(),
"mileage": data.pop("mileage"),
"cash": data.pop("cash"),
"total_cash": data.pop("total_cash"),
"dressup_point": data.pop("dressup_point"),
"avatar_point": data.pop("avatar_point"),
"aura_id": data.pop("aura_id"),
"aura_color_id": data.pop("aura_color_id"),
"aura_line_id": data.pop("aura_line_id"),
},
)
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save ticket data in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save mode_rank and reward_dist data in database
rank_data.update(reward_dist_data)
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
# finally save the special mode with story_type=4 in database
await self.data.item.put_challenge(user_id, data)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id,
self.version,
{"special_play_count": tips["special_play_count"] + 1},
)
return {
"status_code": "0",
"special_mode_data": await self._generate_special_data(user_id, headers),
"car_use_count": [],
"maker_use_count": [],
}
async def handle_user_updatechallengemoderesult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
ticket_data: List = data.pop("ticket_data")
reward_dist_data: Dict = data.pop("reward_dist_obj")
driver_debut_data = data.pop("driver_debut_obj")
rank_data: Dict = data.pop("mode_rank_obj")
# get the vs use count from database and update it
style_car_id = data.get("style_car_id")
car_data = await self.data.item.get_car(user_id, self.version, style_car_id)
story_use_count = car_data["story_use_count"] + 1
# save car data in database
await self.data.item.put_car(
user_id,
self.version,
{
"style_car_id": style_car_id,
"car_mileage": data.pop("car_mileage"),
"story_use_count": story_use_count,
},
)
# get the profile data, update total_play and daily_play, and save it
profile = await self.data.profile.get_profile(user_id, self.version)
total_play = profile["total_play"] + 1
# save user profile in database
await self.data.profile.put_profile(
user_id,
self.version,
{
"total_play": total_play,
"last_play_date": datetime.now(),
"mileage": data.pop("mileage"),
"cash": data.pop("cash"),
"total_cash": data.pop("total_cash"),
"dressup_point": data.pop("dressup_point"),
"avatar_point": data.pop("avatar_point"),
"aura_id": data.pop("aura_id"),
"aura_color_id": data.pop("aura_color_id"),
"aura_line_id": data.pop("aura_line_id"),
},
)
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save ticket data in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save mode_rank and reward_dist data in database
rank_data.update(reward_dist_data)
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
# get the challenge mode data from database
challenge_data = await self.data.item.get_challenge(
user_id, data.get("vs_type"), data.get("play_difficulty")
)
if challenge_data:
# update play count
play_count = challenge_data["play_count"] + 1
data["play_count"] = play_count
# finally save the challenge mode with story_type=3 in database
await self.data.item.put_challenge(user_id, data)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id,
self.version,
{"challenge_play_count": tips["challenge_play_count"] + 1},
)
return {
"status_code": "0",
"challenge_mode_data": await self._generate_challenge_data(user_id),
"car_use_count": [],
"maker_use_count": [],
}
async def _generate_time_trial_data(
self, season_id: int, user_id: int
) -> List[Dict]:
# get the season time trial data from database
timetrial_data = []
courses = await self.data.item.get_courses(user_id)
if courses is None or len(courses) == 0:
return {"status_code": "0", "timetrial_data": timetrial_data}
for course in courses:
# grab the course id and course proeficiency
course_id = course["course_id"]
skill_level_exp = course["skill_level_exp"]
# get the best time for the current course for the current user
best_trial = await self.data.item.get_time_trial_best_ranking_by_course(
season_id, user_id, course_id
)
if not best_trial:
continue
goal_time = best_trial["goal_time"]
# get the rank for the current course
course_rank = await self.data.item.get_time_trial_ranking_by_course(
season_id, course_id, limit=None
)
course_rank = (
len([r for r in course_rank if r["goal_time"] < goal_time]) + 1
)
timetrial_data.append(
{
"style_car_id": best_trial["style_car_id"],
"course_id": course_id,
"skill_level_exp": skill_level_exp,
"goal_time": goal_time,
"rank": course_rank,
"rank_dt": int(best_trial["play_dt"].timestamp()),
}
)
return timetrial_data
async def handle_user_getpastseasontadata_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
season_id = data.get("season_id")
# so to get the season 1 data just subtract 1 from the season id
past_timetrial_data = await self._generate_time_trial_data(
season_id - 1, user_id
)
# TODO: get the current season timetrial data somehow, because after requesting
# GetPastSeasonTAData the game will NOT request GetTAData?!
return {
"status_code": "0",
"season_id": season_id,
"past_season_timetrial_data": past_timetrial_data,
}
def handle_user_getpastseasonrounddata_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
season_id = data.get("season_id")
# so to get the season 1 data just subtract 1 from the season id
past_timetrial_data = self._generate_time_trial_data(season_id - 1, user_id)
return {
"status_code": "0",
"season_id": season_id,
"past_season_round_event_data": [
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "DAC稼働記念 1stラウンド",
"round_id": 0,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 2ndラウンド",
"round_id": 1,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 3rdラウンド",
"round_id": 2,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 4thラウンド",
"round_id": 3,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 5thラウンド",
"round_id": 4,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 6thラウンド",
"round_id": 5,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 7thラウンド",
"round_id": 6,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 8thラウンド",
"round_id": 7,
},
{
"count": 0,
"win": 0,
"rank": 0,
"area_rank": 0,
"point": 0,
"total_round_point": 0,
"round_name": "シーズン1 9thラウンド",
"round_id": 8,
},
],
}
async def handle_user_gettadata_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
timetrial_data = await self._generate_time_trial_data(self.version, user_id)
# TODO: get the past season timetrial data somehow, because after requesting
# GetTAData the game will NOT request GetPastSeasonTAData?!
return {
"status_code": "0",
"timetrial_data": timetrial_data,
# "past_season_timetrial_data": timetrial_data,
}
async def handle_user_updatecartune_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
# full tune ticket
use_ticket = data.pop("use_ticket")
if use_ticket:
# get the user's tickets, full tune ticket id is 25
ticket = await self.data.item.get_ticket(user_id, ticket_id=25)
# update the ticket in the database
await self.data.item.put_ticket(
user_id,
{
"ticket_id": ticket["ticket_id"],
"ticket_cnt": ticket["ticket_cnt"] - 1,
},
)
# also set the tune_level to 16 (fully tuned)
data["tune_level"] = 16
await self.data.item.put_car(user_id, self.version, data)
return {
"status_code": "0",
"story_data": await self._generate_story_data(user_id),
"car_use_count": [],
"maker_use_count": [],
}
async def handle_log_saveplaylog_request(self, data: Dict, headers: Dict):
pass
async def handle_log_saveendlog_request(self, data: Dict, headers: Dict):
pass
async def handle_user_updatemoderesult_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
config_data: Dict = data.pop("config_obj")
stock_data: Dict = data.pop("stock_obj")
ticket_data: List = data.pop("ticket_data")
reward_dist_data: Dict = data.pop("reward_dist_obj")
# not required?
mode_id = data.pop("mode_id")
standby_play_flag = data.pop("standby_play_flag")
# save the tips list in database
tips_list = data.pop("tips_list")
await self.data.profile.put_profile_tips(
user_id, self.version, {"tips_list": tips_list}
)
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save tickets in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save rank dist data in database
await self.data.profile.put_profile_rank(
user_id, self.version, reward_dist_data
)
# update profile data and config in database
await self.data.profile.put_profile(user_id, self.version, data)
config_data["config_id"] = config_data.pop("id")
await self.data.profile.put_profile_config(user_id, config_data)
return {"status_code": "0", "server_status": 1}
async def _generate_theory_rival_data(
self, user_list: list, course_id: int, req_user_id: int
) -> list:
rival_data = []
for user_id in user_list:
# if not enough players are available just use the data from the req_user
if user_id == -1:
profile = await self.data.profile.get_profile(req_user_id, self.version)
profile = profile._asdict()
# set the name to CPU
profile["username"] = f""
# also reset stamps to default
profile["country"] = 9
profile["store"] = 0
profile["stamp_key_assign_0"] = 0
profile["stamp_key_assign_1"] = 1
profile["stamp_key_assign_2"] = 2
profile["stamp_key_assign_3"] = 3
profile["mytitle_id"] = 0
else:
profile = await self.data.profile.get_profile(user_id, self.version)
rank = await self.data.profile.get_profile_rank(
profile["user"], self.version
)
avatars = [
{
"sex": 0,
"face": 1,
"eye": 1,
"mouth": 1,
"hair": 1,
"glasses": 0,
"face_accessory": 0,
"body": 1,
"body_accessory": 0,
"behind": 0,
"bg": 1,
"effect": 0,
"special": 0,
},
{
"sex": 0,
"face": 1,
"eye": 1,
"mouth": 1,
"hair": 19,
"glasses": 0,
"face_accessory": 0,
"body": 2,
"body_accessory": 0,
"behind": 0,
"bg": 1,
"effect": 0,
"special": 0,
},
{
"sex": 1,
"face": 91,
"eye": 265,
"mouth": 13,
"hair": 369,
"glasses": 0,
"face_accessory": 0,
"body": 113,
"body_accessory": 0,
"behind": 0,
"bg": 1,
"effect": 0,
"special": 0,
},
{
"sex": 1,
"face": 91,
"eye": 265,
"mouth": 13,
"hair": 387,
"glasses": 0,
"face_accessory": 0,
"body": 114,
"body_accessory": 0,
"behind": 0,
"bg": 1,
"effect": 0,
"special": 0,
},
]
if user_id == -1:
# get a random avatar from the list and some random car from all users
avatar = choice(avatars)
car = await self.data.item.get_random_car(self.version)
else:
avatar = await self.data.profile.get_profile_avatar(profile["user"])
car = await self.data.item.get_random_user_car(
profile["user"], self.version
)
parts_list = []
for part in car["parts_list"]:
parts_list.append(part["parts"])
course = await self.data.item.get_theory_course(profile["user"], course_id)
powerhose_lv = 0
if course:
powerhose_lv = course["powerhouse_lv"]
theory_running = await self.data.item.get_theory_running_by_course(
profile["user"], course_id
)
# normally it's 127 after the first play so we set it to 128
attack = 128
defense = 128
safety = 128
runaway = 128
trick_flag = 0
if theory_running and user_id != -1:
attack = theory_running["attack"]
defense = theory_running["defense"]
safety = theory_running["safety"]
runaway = theory_running["runaway"]
trick_flag = theory_running["trick_flag"]
# get the time trial ranking medal
eval_id = 0
time_trial = await self.data.item.get_time_trial_best_ranking_by_course(
self.version, profile["user"], course_id
)
if time_trial:
eval_id = time_trial["eval_id"]
arcade = await self.data.arcade.get_arcade(profile["store"])
if arcade is None:
arcade = {}
arcade["name"] = self.core_cfg.server.name
rival_data.append(
{
"id": profile["user"],
"name": profile["username"],
"grade": rank["grade"],
# only needed for power match
"powerhouseLv": powerhose_lv,
"mytitleId": profile["mytitle_id"],
"country": profile["country"],
"auraId": profile["aura_id"],
"auraColor": profile["aura_color_id"],
"auraLine": profile["aura_line_id"],
# not sure?
"roundRanking": 0,
"storeName": arcade["name"],
"sex": avatar["sex"],
"face": avatar["face"],
"eye": avatar["eye"],
"mouth": avatar["mouth"],
"hair": avatar["hair"],
"glasses": avatar["glasses"],
"faceAccessory": avatar["face_accessory"],
"body": avatar["body"],
"bodyAccessory": avatar["body_accessory"],
"behind": avatar["behind"],
"bg": avatar["bg"],
"effect": avatar["effect"],
"special": avatar["special"],
"styleCarId": car["style_car_id"],
"color": car["color"],
"bureau": car["bureau"],
"kana": car["kana"],
"sNo": car["s_no"],
"lNo": car["l_no"],
"tuneLv": car["tune_level"],
"carFlag": car["car_flag"],
"tunePoint": car["tune_point"],
"infinityTune": car["infinity_tune"],
"tuneParts": car["tune_parts"],
"partsList": parts_list,
"partsCount": car["equip_parts_count"],
"stamp0": profile["stamp_key_assign_0"],
"stamp1": profile["stamp_key_assign_1"],
"stamp2": profile["stamp_key_assign_2"],
"stamp3": profile["stamp_key_assign_3"],
"attack": attack,
"defense": defense,
"safety": safety,
"runaway": runaway,
"trickFlg": trick_flag,
# time trial ranking medal
"taEval": eval_id,
}
)
return rival_data
async def handle_theory_matching_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
course_id = data.pop("course_id")
# no idea why thats needed?
grade = data.pop("grade")
# number of auto_matches and power_matches, official values are:
count_auto_match = 9
count_power_match = 3
# required for the power_match list?
powerhose_lv = data.pop("powerhouse_lv")
# get random profiles for auto match
profiles = await self.data.profile.get_different_random_profiles(
user_id, self.version, count=count_auto_match
)
user_list = [profile["user"] for profile in profiles]
# if user_list is not count_auto_match long, fill it up with -1
while len(user_list) < count_auto_match:
user_list.append(-1)
auto_match = await self._generate_theory_rival_data(
user_list, course_id, user_id
)
# get profiles with the same powerhouse_lv for power match
theory_courses = await self.data.item.get_theory_course_by_powerhouse_lv(
user_id, course_id, powerhose_lv, count=count_power_match
)
user_list = [course["user"] for course in theory_courses]
# if user_list is not count_power_match long, fill it up with -1
while len(user_list) < count_power_match:
user_list.append(-1)
power_match = await self._generate_theory_rival_data(
user_list, course_id, user_id
)
return {
"status_code": "0",
"server_status": 1,
"rival_data": {
"auto_match": auto_match,
"power_match": power_match,
},
}
async def handle_user_updatetheoryresult_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
ticket_data: List = data.pop("ticket_data")
reward_dist_data: Dict = data.pop("reward_dist_obj")
rank_data: Dict = data.pop("mode_rank_obj")
driver_debut_data: Dict = data.pop("driver_debut_obj")
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save tickets in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save rank dist data in database
rank_data.update(reward_dist_data)
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
# save the profile theory data in database
play_count = 1
play_count_multi = 1
win_count = 0
win_count_multi = 0
theory_data = await self.data.profile.get_profile_theory(user_id, self.version)
if theory_data:
play_count = theory_data["play_count"] + 1
play_count_multi = theory_data["play_count_multi"] + 1
win_count = theory_data["win_count"]
win_count_multi = theory_data["win_count_multi"]
# check all advantages and see if one of them is larger than 0
# if so, we won
if (
data.get("advantage_1") > 0
or data.get("advantage_2") > 0
or data.get("advantage_3") > 0
or data.get("advantage_4") > 0
):
win_count += 1
win_count_multi += 1
await self.data.profile.put_profile_theory(
user_id,
self.version,
{
"play_count": play_count,
"play_count_multi": play_count_multi,
"partner_id": data.get("partner_id"),
"partner_progress": data.get("partner_progress"),
"partner_progress_score": data.get("partner_progress_score"),
"practice_start_rank": data.get("practice_start_rank"),
"general_flag": data.get("general_flag"),
"vs_history": data.get("vs_history"),
# no idea?
"vs_history_multi": data.get("vs_history"),
"win_count": win_count,
"win_count_multi": win_count_multi,
},
)
# save theory course in database
await self.data.item.put_theory_course(
user_id,
{
"course_id": data.get("course_id"),
"max_victory_grade": data.get("max_victory_grade"),
# always add 1?
"run_count": 1,
"powerhouse_lv": data.get("powerhouse_lv"),
"powerhouse_exp": data.get("powerhouse_exp"),
# not sure if the played_powerhouse_lv is the same as powerhouse_lv
"played_powerhouse_lv": data.get("powerhouse_lv"),
},
)
# save the theory partner in database
await self.data.item.put_theory_partner(
user_id,
{
"partner_id": data.get("partner_id"),
"fellowship_lv": data.get("fellowship_lv"),
"fellowship_exp": data.get("fellowship_exp"),
},
)
# save the theory running in database?
await self.data.item.put_theory_running(
user_id,
{
"course_id": data.get("course_id"),
"attack": data.get("attack"),
"defense": data.get("defense"),
"safety": data.get("safety"),
"runaway": data.get("runaway"),
"trick_flag": data.get("trick_flag"),
},
)
# get the use_count and theory_use_count of the used car
style_car_id = data.get("style_car_id")
car_mileage = data.get("car_mileage")
used_car = await self.data.item.get_car(user_id, self.version, style_car_id)
used_car = used_car._asdict()
# increase the use_count and theory_use_count of the used car
used_car["use_count"] += 1
used_car["theory_use_count"] += 1
used_car["car_mileage"] = car_mileage
# save the used car in database
await self.data.item.put_car(user_id, self.version, used_car)
# get the profile data, update total_play and daily_play, and save it
profile = await self.data.profile.get_profile(user_id, self.version)
total_play = profile["total_play"] + 1
# save the profile in database
await self.data.profile.put_profile(
user_id,
self.version,
{
"total_play": total_play,
"last_play_date": datetime.now(),
"mileage": data.get("mileage"),
"aura_id": data.get("aura_id"),
"aura_color_id": data.get("aura_color_id"),
"aura_line_id": data.get("aura_line_id"),
"cash": data.get("cash"),
"total_cash": data.get("total_cash"),
"dressup_point": data.get("dressup_point"),
"avatar_point": data.get("avatar_point"),
},
)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"theory_play_count": tips["theory_play_count"] + 1}
)
return {
"status_code": "0",
"played_powerhouse_lv": data.get("powerhouse_lv"),
"car_use_count": [],
"maker_use_count": [],
"play_count": play_count,
"play_count_multi": play_count_multi,
"win_count": win_count,
"win_count_multi": win_count_multi,
}
async def handle_timetrial_getbestrecordprebattle_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
course_pickup_car_best_data = []
courses = await self.data.item.get_time_trial_courses(self.version)
for course in courses:
car_list = []
best_cars = await self.data.item.get_time_trial_best_cars_by_course(
self.version, course["course_id"], user_id
)
for i, car in enumerate(best_cars):
car_list.append(
{
"rank": i + 1,
# no clue
"member": user_id,
"value": car["goal_time"],
"store": self.core_cfg.server.name,
# use car_id from request?
"car_id": 0,
"style_car_id": car["style_car_id"],
"play_dt": car["play_dt"].timestamp(),
"section_time_1": car["section_time_1"],
"section_time_2": car["section_time_2"],
"section_time_3": car["section_time_3"],
"section_time_4": car["section_time_4"],
"mission": car["mission"],
}
)
course_pickup_car_best_data.append(
{
"course_id": course["course_id"],
"car_list": car_list,
}
)
return {
"status_code": "0",
"course_pickup_car_best_data": course_pickup_car_best_data,
}
async def handle_user_updateonlinebattle_request(self, data: Dict, headers: Dict):
# TODO: voiding cheaters' result. which will need to analysis historical battle results returned by the game.
return {
"status_code": "0",
"bothwin_penalty": 1,
}
async def handle_user_updateonlinebattleresult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
# save stock data in database
await self._save_stock_data(user_id, stock_data)
ticket_data: List = data.pop("ticket_data")
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
reward_dist_data: Dict = data.pop("reward_dist_obj")
rank_data: Dict = data.pop("mode_rank_obj")
# save rank dist data in database
rank_data.update(reward_dist_data)
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
driver_debut_data = data.pop("driver_debut_obj")
# get the use_count and net_vs_use_count of the used car
style_car_id = data.get("style_car_id")
car_mileage = data.pop("car_mileage")
used_car = await self.data.item.get_car(user_id, self.version, style_car_id)
used_car = used_car._asdict()
# increase the use_count and net_vs_use_count of the used car
used_car["use_count"] += 1
used_car["net_vs_use_count"] += 1
used_car["car_mileage"] = car_mileage
# save the used car in database
await self.data.item.put_car(user_id, self.version, used_car)
# get the profile data, update total_play and daily_play, and save it
profile = await self.data.profile.get_profile(user_id, self.version)
total_play = profile["total_play"] + 1
# save the profile in database
await self.data.profile.put_profile(
user_id,
self.version,
{
"total_play": total_play,
"last_play_date": datetime.now(),
"mileage": data.pop("mileage"),
"aura_id": data.pop("aura_id"),
"aura_color_id": data.pop("aura_color_id"),
"aura_line_id": data.pop("aura_line_id"),
"cash": data.pop("cash"),
"total_cash": data.pop("total_cash"),
"dressup_point": data.pop("dressup_point"),
"avatar_point": data.pop("avatar_point"),
},
)
vs_info = await self._update_vs_info(
user_id, IDACConstants.BATTLE_MODE_ONLINE, data
)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id,
self.version,
{"online_battle_play_count": tips["online_battle_play_count"] + 1},
)
round_info = await self._update_round_info(
user_id, self.round_event_id, data.pop("round_point"), data.pop("win_flg")
)
return {
"status_code": "0",
"vsinfo_data": vs_info,
"round_event": round_info,
"car_use_count": [],
"maker_use_count": [],
}
async def handle_user_updatestorebattleresult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj")
ticket_data: List = data.pop("ticket_data")
reward_dist_data: Dict = data.pop("reward_dist_obj")
rank_data: Dict = data.pop("mode_rank_obj")
driver_debut_data: Dict = data.pop("driver_debut_obj")
# save the received battle gift in database, hopefully that works
battle_gift_event_id = data.pop("battle_gift_event_id")
gift_id = data.pop("gift_id")
await self.data.item.put_battle_gift(
user_id,
{
"battle_gift_event_id": battle_gift_event_id,
"gift_id": gift_id,
"gift_status": 1, # aquired
},
)
# save stock data in database
await self._save_stock_data(user_id, stock_data)
# save tickets in database
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save rank dist data in database
rank_data.update(reward_dist_data)
await self.data.profile.put_profile_rank(user_id, self.version, rank_data)
# get the use_count and net_vs_use_count of the used car
style_car_id = data.get("style_car_id")
car_mileage = data.pop("car_mileage")
used_car = await self.data.item.get_car(user_id, self.version, style_car_id)
used_car = used_car._asdict()
# increase the use_count and net_vs_use_count of the used car
used_car["use_count"] += 1
used_car["vs_use_count"] += 1
used_car["car_mileage"] = car_mileage
# save the used car in database
await self.data.item.put_car(user_id, self.version, used_car)
# get the profile data, update total_play and daily_play, and save it
profile = await self.data.profile.get_profile(user_id, self.version)
total_play = profile["total_play"] + 1
# save the profile in database
await self.data.profile.put_profile(
user_id,
self.version,
{
"total_play": total_play,
"last_play_date": datetime.now(),
"mileage": data.pop("mileage"),
"aura_id": data.pop("aura_id"),
"aura_color_id": data.pop("aura_color_id"),
"aura_line_id": data.pop("aura_line_id"),
"cash": data.pop("cash"),
"total_cash": data.pop("total_cash"),
"dressup_point": data.pop("dressup_point"),
"avatar_point": data.pop("avatar_point"),
},
)
# save vs_info in database
vs_info = await self._update_vs_info(
user_id, IDACConstants.BATTLE_MODE_OFFLINE, data
)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id,
self.version,
{"store_battle_play_count": tips["store_battle_play_count"] + 1},
)
return {
"status_code": "0",
"vsinfo_data": vs_info,
"car_use_count": [],
"maker_use_count": [],
}
async def handle_factory_numberlotterybefore_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
win_list = []
lottery_count = 0
# retrieve the lottery data for the user
l = await self.data.factory.get_lottery(user_id, self.version)
if l:
# check if create_data is younger than 24 hours
if datetime.now() - l["create_date"] < timedelta(days=1):
lottery_count = l["lottery_count"]
saved_value = int(l["saved_value"])
# check each of the first 10 bits in saved_value
for number in range(10):
if saved_value & 1:
# if the least significant bit is 1, add to the win_list
win_list.append(
{"m_number_lottery_schedule_no": 1, "win_number": number * 1111}
)
# right shift saved_value to check the next bit
saved_value >>= 1
return {
"status_code": "0",
"lottery_info": {"lottery_count": lottery_count, "win_list": win_list},
}
async def handle_factory_numberlotteryresult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"]
win_number = data.pop("win_number")
lottery_count = data.pop("lottery_count")
style_car_id = data.pop("style_car_id")
license_no = data.pop("l_no")
is_end = data.pop("isEnd")
ticket_data = data.pop("ticket_data")
# retrieve the lottery data for the user
l = await self.data.factory.get_lottery(user_id, self.version)
create_date = l["create_date"] if l else datetime.now()
saved_value = l["saved_value"] if l else 0
if win_number != 10000:
# calculate the bit position to set based on the win_number
shifted = win_number // 1111
saved_value += 1 << shifted
# update the create_date timestamp when the last create_date is older than 24 hours
if l and datetime.now() - l["create_date"] > timedelta(days=1):
create_date = datetime.now()
# update the lottery data with the new saved_value and lottery_count
await self.data.factory.put_lottery(
user_id, self.version, saved_value, lottery_count, create_date
)
if license_no != 10000 and is_end == 1:
# ithe lottery is ended, save car data
await self.data.item.put_car(
user_id,
self.version,
{"style_car_id": style_car_id, "l_no": license_no},
)
# save each ticket data
for ticket in ticket_data:
await self.data.item.put_ticket(user_id, ticket)
# save remaining profile data
await self.data.profile.put_profile(user_id, self.version, data)
return {"status_code": "0"}