Initial D THE ARCADE Season 3 support (#1)

## Changes:

- [x] Added database upgrade script for season 3 tables
- [x] Completly implement season 3
- [x] Teams are fully functional as intended
- [x] Time Release is properly updated for season 3 (v2.10.00)
- [x] New accounts can be created and saved in db
- [x] Old 2eason 2 accounts can be transfered to season 3
- [x] Season independed config has been added inclusive version specific configs

Co-authored-by: puniru <puniru@posteo.jp>
Reviewed-on: https://gitea.tendokyu.moe/Dniel97/artemis-IDAC/pulls/1
Co-authored-by: Dniel97 <Dniel97@noreply.gitea.tendokyu.moe>
Co-committed-by: Dniel97 <Dniel97@noreply.gitea.tendokyu.moe>
This commit is contained in:
Dniel97
2026-04-01 18:18:09 +00:00
committed by Dniel97
co-authored by puniru
parent efc798a28a
commit 3ac8762868
131 changed files with 105994 additions and 27021 deletions
@@ -0,0 +1,308 @@
"""IDAC Season 3
Revision ID: 71fad2543ff7
Revises: 49c295e89cd4
Create Date: 2024-12-29 21:15:36.621390
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = "71fad2543ff7"
down_revision = "bdf710616ba4"
branch_labels = None
depends_on = None
PROFILE_STOCK_COLUMNS = [
"avatar_face_list",
"avatar_face_new_list",
"avatar_eye_list",
"avatar_eye_new_list",
"avatar_hair_list",
"avatar_hair_new_list",
"avatar_body_list",
"avatar_body_new_list",
"avatar_mouth_list",
"avatar_mouth_new_list",
"avatar_glasses_list",
"avatar_glasses_new_list",
"avatar_face_accessory_list",
"avatar_face_accessory_new_list",
"avatar_body_accessory_list",
"avatar_body_accessory_new_list",
"avatar_behind_list",
"avatar_behind_new_list",
"avatar_bg_list",
"avatar_bg_new_list",
"avatar_effect_list",
"avatar_effect_new_list",
"avatar_special_list",
"avatar_special_new_list",
"stamp_list",
"stamp_new_list",
"keyholder_list",
"keyholder_new_list",
"papercup_list",
"papercup_new_list",
"tachometer_list",
"tachometer_new_list",
"aura_list",
"aura_new_list",
"aura_color_list",
"aura_color_new_list",
"aura_line_list",
"aura_line_new_list",
"bgm_list",
"bgm_new_list",
"dx_color_list",
"dx_color_new_list",
"start_menu_bg_list",
"start_menu_bg_new_list",
"under_neon_list",
]
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"idac_team_info",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("name", sa.String(length=15), nullable=False),
sa.Column("mileage", sa.Integer(), nullable=False, server_default="0"),
sa.Column("sticker_id", sa.Integer(), nullable=False, server_default="1"),
sa.Column(
"sticker_stock",
sa.String(length=256),
nullable=False,
server_default="0111111111111".ljust(256, "0"),
),
sa.Column("country", sa.Integer(), nullable=False),
sa.Column("store_id", sa.Integer(), nullable=True),
sa.Column("password", sa.String(length=10), nullable=False),
sa.Column("auto_invite_flag", sa.Integer(), nullable=False, server_default="0"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("password", name="idac_team_info_password_uk"),
sa.UniqueConstraint("id", "version", name="idac_team_info_uk"),
)
op.create_table(
"idac_team_member",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("team_id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("role", sa.Integer(), nullable=False, server_default="0"),
sa.Column("daily_bonus", sa.Integer(), nullable=False, server_default="0"),
sa.Column("weekly_bonus", sa.Integer(), nullable=False, server_default="0"),
sa.Column(
"create_date_daily",
sa.TIMESTAMP(),
nullable=False,
server_default=sa.text("now()"),
),
sa.Column(
"create_date_weekly",
sa.TIMESTAMP(),
nullable=False,
server_default=sa.text("now()"),
),
sa.Column(
"join_dt", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=False
),
sa.ForeignKeyConstraint(
["team_id"], ["idac_team_info.id"], onupdate="cascade", ondelete="cascade"
),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.UniqueConstraint("user", "version", name="idac_team_member_uk"),
)
op.create_table(
"idac_league_info",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("round_id", sa.Integer(), nullable=False),
sa.Column("team_id", sa.Integer(), nullable=False),
sa.Column("league_rank_id", sa.Integer(), nullable=False, server_default="0"),
sa.Column("league_point", sa.Integer(), nullable=False, server_default="0"),
sa.ForeignKeyConstraint(
["team_id"], ["idac_team_info.id"], onupdate="cascade", ondelete="cascade"
),
sa.UniqueConstraint(
"version", "round_id", "team_id", name="idac_league_info_uk"
),
)
op.create_table(
"idac_user_league_info",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("team_id", sa.Integer(), nullable=False),
sa.Column("round_id", sa.Integer(), nullable=False),
sa.Column("point", sa.Integer(), nullable=False, server_default="0"),
sa.Column("rewards_claimed", sa.Boolean(), nullable=False, server_default="0"),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.UniqueConstraint(
"user", "version", "round_id", name="idac_user_league_info_uk"
),
)
op.drop_column("idac_user_car", "total_car_parts_count")
op.drop_column("idac_profile", "team_id")
op.drop_column("idac_profile_config", "config_id")
op.add_column(
"idac_profile",
sa.Column(
"total_car_parts_count", sa.Integer(), nullable=False, server_default="0"
),
)
op.add_column("idac_profile_config", sa.Column("favorite_bgm_list", sa.Text()))
op.add_column("idac_profile_config", sa.Column("break_flag", sa.INTEGER()))
op.add_column(
"idac_profile_config",
sa.Column("visible_my_stats", sa.INTEGER()),
)
op.add_column(
"idac_profile_config",
sa.Column("visible_rival_stats", sa.INTEGER()),
)
op.add_column("idac_profile_tips", sa.Column("last_team_join_dt", sa.TIMESTAMP))
op.create_table(
"idac_team_member_info",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("team_id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("action_id", sa.Integer(), nullable=False, server_default="2"),
sa.Column(
"action_dt", sa.DateTime(), server_default=sa.text("now()"), nullable=False
),
sa.ForeignKeyConstraint(
["team_id"], ["idac_team_info.id"], onupdate="cascade", ondelete="cascade"
),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
)
op.create_index(
"idx_trial_ranking",
"idac_user_time_trial",
["version", "course_id", "style_car_id", "user", "goal_time"],
)
op.alter_column(
"idac_user_battle_gift",
"gift_status",
new_column_name="gift_get_status",
existing_type=sa.Integer(),
existing_nullable=True,
)
for column_name in PROFILE_STOCK_COLUMNS:
op.alter_column(
"idac_profile_stock",
column_name,
existing_type=sa.String(length=255),
type_=sa.Text(),
existing_nullable=True,
)
op.alter_column(
"idac_profile_stock",
"mytitle_list",
existing_type=sa.String(length=1024),
type_=sa.Text(),
existing_nullable=True,
)
op.alter_column(
"idac_profile_stock",
"mytitle_new_list",
existing_type=sa.String(length=1024),
type_=sa.Text(),
existing_nullable=True,
)
op.create_unique_constraint(
"idac_round_info_uk",
"idac_round_info",
["round_id_in_json", "season"]
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("idac_team_member_info")
op.drop_table("idac_team_member")
op.drop_table("idac_league_info")
op.drop_table("idac_team_info")
op.drop_table("idac_user_league_info")
op.drop_column("idac_profile", "total_car_parts_count")
op.add_column(
"idac_user_car",
sa.Column(
"total_car_parts_count", sa.Integer(), nullable=False, server_default="0"
),
)
op.drop_column("idac_profile_config", "favorite_bgm_list")
op.drop_column("idac_profile_config", "visible_rival_stats")
op.drop_column("idac_profile_config", "visible_my_stats")
op.drop_column("idac_profile_config", "break_flag")
op.drop_column("idac_profile_tips", "last_team_join_dt")
op.drop_index("idx_trial_ranking", "idac_user_time_trial")
op.add_column(
"idac_profile",
sa.Column("team_id", sa.INTEGER(), server_default="0"),
)
op.add_column(
"idac_profile_config",
sa.Column("config_id", sa.INTEGER()),
)
op.alter_column(
"idac_user_battle_gift",
"gift_get_status",
new_column_name="gift_status",
existing_type=sa.Integer(),
existing_nullable=True,
)
for column_name in PROFILE_STOCK_COLUMNS:
op.alter_column(
"idac_profile_stock",
column_name,
existing_type=sa.Text(),
type_=sa.String(length=255),
existing_nullable=True,
)
op.alter_column(
"idac_profile_stock",
"mytitle_list",
existing_type=sa.Text(),
type_=sa.String(length=1024),
existing_nullable=True,
)
op.alter_column(
"idac_profile_stock",
"mytitle_new_list",
existing_type=sa.Text(),
type_=sa.String(length=1024),
existing_nullable=True,
)
op.drop_constraint(
"idac_round_info_uk",
"idac_round_info",
type_="unique"
)
# ### end Alembic commands ###
+6 -1
View File
@@ -1,6 +1,8 @@
from typing import Dict, List, Tuple, Any
import asyncio
import json
import logging, coloredlogs
from typing import Dict, List, Tuple, Any
from logging.handlers import TimedRotatingFileHandler
from starlette.requests import Request
from starlette.responses import Response
@@ -140,6 +142,9 @@ class TitleServlet:
if hasattr(handler_cls, "setup") and should_call_setup:
handler_cls.setup()
should_call_setup = False
if hasattr(handler_cls, "async_init"):
asyncio.create_task(handler_cls.async_init())
self.title_registry[code] = handler_cls
+77 -25
View File
@@ -11,33 +11,85 @@ server:
port_matching_p2p: 20003
timerelease:
timerelease_no: 3
timerelease_avatar_gacha_no: 3
S2:
timerelease_no: 4
timerelease_avatar_gacha_no: 3
S3:
timerelease_no: 6
timerelease_avatar_gacha_no: 4
collabo:
S2:
enable: True
collabo_id: 6
S3:
enable: True
collabo_id: 8
stamp:
enable: True
enabled_stamps: # max 3 play stamps
150:
- "touhou_remilia_scarlet"
- "touhou_flandre_scarlet"
- "touhou_sakuya_izayoi"
170:
- "touhou_remilia_scarlet"
- "touhou_flandre_scarlet"
- "touhou_sakuya_izayoi"
S2:
enable: False
open_dt: "2024-01-01 00:00:00"
close_dt: "2029-12-31 00:00:00"
enabled_stamps:
150:
- "touhou_remilia_scarlet"
- "touhou_flandre_scarlet"
- "touhou_sakuya_izayoi"
S3:
enable: False
open_dt: "2026-01-01 00:00:00"
close_dt: "2029-12-31 00:00:00"
enabled_stamps:
210:
- "touhou_remilia_scarlet"
- "touhou_flandre_scarlet"
- "touhou_sakuya_izayoi"
timetrial:
enable: True
enabled_timetrial:
150: "touhou_remilia_scarlet"
170: "touhou_remilia_scarlet"
S2:
enable: TFalsee
open_dt: "2024-01-01 00:00:00"
close_dt: "2029-12-31 00:00:00"
enabled_timetrials:
150: "touhou_remilia_scarlet"
S3:
enable: False
open_dt: "2024-01-01 00:00:00"
close_dt: "2029-12-31 00:00:00"
enabled_timetrials:
210: "touhou_remilia_scarlet"
battle_event:
enable: True
enabled_battle_event:
170: "touhou_1st"
battle_gift:
S2:
enable: False
open_dt: "2024-01-01 00:00:00"
close_dt: "2029-12-31 00:00:00"
enabled_battle_gifts:
150: "touhou_1st"
S3:
enable: False
open_dt: "2024-01-01 00:00:00"
close_dt: "2029-12-31 00:00:00"
enabled_battle_gifts:
210: "touhou_1st"
round_event:
enable: True
enabled_round: "S2R2"
last_round: "S2R1"
round:
S2:
enable: True
open_dt: "2024-01-01 00:00:00"
close_dt: "2029-02-28 00:00:00"
enabled_round: "round_S2R2"
last_round: "round_S2R1"
S3:
enable: True
open_dt: "2024-01-01 00:00:00"
close_dt: "2029-02-28 00:00:00"
open_round: True
enabled_round: "round_S3R1"
last_round: ""
league_reward:
S3:
enable: True
enabled_league_reward: "league_reward_S3"
+1
View File
@@ -22,3 +22,4 @@ asyncio
uvicorn
alembic
python-multipart
pydantic
+12 -1
View File
@@ -9,8 +9,19 @@ from titles.idac.database import IDACData
class IDACBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: IDACConfig) -> None:
self.core_cfg = core_cfg
self.game_config = game_cfg
self.server_cfg = game_cfg.server
self.game_cfg = game_cfg.season1
self.game = IDACConstants.GAME_CODE
self.version = IDACConstants.VER_IDAC_SEASON_1
self.data = IDACData(core_cfg)
self.logger = logging.getLogger("idac")
self.controllers = dict()
async def async_init(self):
"""
Allows initialization of async functions such as database transactions
"""
pass
+16 -143
View File
@@ -1,5 +1,8 @@
from typing import Dict
from core.config import CoreConfig
from titles.idac.gameconfig.season1_config import IDACSeason1Config
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.gameconfig.season4_config import IDACSeason4Config
class IDACServerConfig:
@@ -67,150 +70,20 @@ class IDACServerConfig:
return CoreConfig.get_config_field(
self.__config, "idac", "server", "port_matching_p2p", default=20003
)
class IDACTimereleaseConfig:
def __init__(self, parent: "IDACConfig") -> None:
self.__config = parent
@property
def timerelease_no(self) -> int:
return CoreConfig.get_config_field(
self.__config, "idac", "timerelease", "timerelease_no", default=1
)
@property
def timerelease_avatar_gacha_no(self) -> int:
return CoreConfig.get_config_field(
self.__config, "idac", "timerelease", "timerelease_avatar_gacha_no", default=1
)
class IDACStampConfig:
def __init__(self, parent: "IDACConfig") -> None:
self.__config = parent
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "idac", "stamp", "enable", default=True
)
@property
def enabled_stamps(self) -> Dict:
"""
In the form of:
<version as int>:
- <stamp name 1>
- <stamp name 2>
- <stamp name 3>
max 3 stamps per version
f.e.:
150:
- "touhou_remilia_scarlet"
- "touhou_flandre_scarlet"
- "touhou_sakuya_izayoi"
"""
return CoreConfig.get_config_field(
self.__config,
"idac",
"stamp",
"enabled_stamps",
default={},
)
class IDACTimetrialConfig:
def __init__(self, parent: "IDACConfig") -> None:
self.__config = parent
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "idac", "timetrial", "enable", default=True
)
@property
def enabled_timetrial(self) -> Dict:
"""
In the form of:
<version as int>: <timetrial name>
f.e.:
150: "touhou_remilia_scarlet"
"""
return CoreConfig.get_config_field(
self.__config,
"idac",
"timetrial",
"enabled_timetrial",
default={},
)
class IDACTBattleGiftConfig:
def __init__(self, parent: "IDACConfig") -> None:
self.__config = parent
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "idac", "battle_event", "enable", default=True
)
@property
def enabled_battle_event(self) -> Dict:
"""
In the form of:
<version as int>: <battle_event name>
f.e.:
170: "touhou_1st"
"""
return CoreConfig.get_config_field(
self.__config,
"idac",
"battle_event",
"enabled_battle_event",
default={},
)
class IDACRoundConfig:
def __init__(self, parent: "IDACConfig") -> None:
self.__config = parent
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "idac", "round_event", "enable", default=True
)
@property
def enabled_round(self) -> str:
return CoreConfig.get_config_field(
self.__config,
"idac",
"round_event",
"enabled_round",
default="S2R2",
)
@property
def last_round(self) -> str:
return CoreConfig.get_config_field(
self.__config,
"idac",
"round_event",
"last_round",
default="S2R1",
)
class IDACConfig(dict):
def __init__(self) -> None:
self.server = IDACServerConfig(self)
self.timerelease = IDACTimereleaseConfig(self)
self.stamp = IDACStampConfig(self)
self.timetrial = IDACTimetrialConfig(self)
self.battle_event = IDACTBattleGiftConfig(self)
self.round_event = IDACRoundConfig(self)
self.season1 = IDACSeason1Config(self)
self.season2 = IDACSeason2Config(self)
self.season3 = IDACSeason3Config(self)
self.season4 = IDACSeason4Config(self)
def update(self, *args, **kwargs):
super().update(*args, **kwargs)
self.season1.load_events()
self.season2.load_events()
self.season3.load_events()
self.season4.load_events()
+302 -1
View File
@@ -1,10 +1,192 @@
class IDACConstants():
from enum import IntEnum
from typing import Dict, List, Optional
from pydantic import BaseModel
class RewardCategoryEnum(IntEnum):
NONE = 0
D_COIN = 1
CAR_DRESSUP_TOKEN = 3
AVATAR_TOKEN = 5
TACHOMETER = 6
AURA = 7
AURA_COLOR = 8
AVATAR_FACE = 9
AVATAR_EYE = 10
AVATAR_MOUTH = 11
AVATAR_HAIR = 12
AVATAR_GLASSES = 13
AVATAR_ACCESSORIES = 14
AVATAR_BODY = 15
AVATAR_BACKGROUNDS = 18
CHATSTAMP = 21
KEYCHAIN = 22
TITLE = 24
FULLTUNE_TICKET = 25
PAPER_CUP = 26
BGM = 27
ONOMATOPE = 28
MAIN_MENU_BG = 31
CAR_PAINT = 32
AURA_LEVEL = 33
FT_TOKEN_PIECES = 34
UNDERNEONS = 35
class LayoutEnum(IntEnum):
TECHNICAL = 0
HIGHSPEED = 1
class StyleEnum(IntEnum):
DH = 0
HC = 1
AR = 2
class CarMakerEnum(IntEnum):
TOYOTA = 0
NISSAN = 1
HONDA = 2
MAZDA = 3
SUBARU = 4
MITSUBISHI = 5
SUZUKI = 6
INITIALD = 7
COMPLETE = 8
PORSCHE = 9
class StoryTypeEnum(IntEnum):
NONE = 0
STORY = 1
MFG = 2
BUNTA = 3
TOUHOU_1ST = 4
MIKU = 5
HOT_VERSION = 6
class CollaboEnum(IntEnum):
NONE = 0
WMN = 1
TOHO = 2
LIFEGUARD = 3
MFG = 4
ANNIVERSARY_20TH = 5
TOHO_2 = 6
MIKU = 7
HOT_VERSION = 8
TOUHOU_3 = 9
class StoryPlayStatusEnum(IntEnum):
NEW = 0
PLAYED = 1
CLEARED = 2
MAX = 3 # not used?
class NameChangeCategoryEnum(IntEnum):
PAYMET = 0
CREDIT_RETRY = 1
INHERIT = 2
DENIED = 3
class BattleGiftStatusEnum(IntEnum):
NONE = 0
AQUIRED = 1
NEW = 2
class LeagueRankEnum(IntEnum):
OPEN = 0
BASIC = 1
BRONZE = 2
SILVER = 3
GOLD = 4
PLATINUM = 5
MASTER = 6
class VSInfoBattleModeEnum(IntEnum):
ONLINE = 0
INSTORE = 1
class PointRule(BaseModel):
upper_points: int
lower_points: int
destination_league_rank_id: LeagueRankEnum
destination_point: int
class RankRule(BaseModel):
destination_league_rank_id: LeagueRankEnum
destination_rank: int
demote_rank: Optional[int] = None
class PointRankingRule(BaseModel):
points_upper_rank: int
upper_points: int
point_rules: List[PointRule]
class OpenRankingRule(BaseModel):
default_rank_id: LeagueRankEnum
default_rank: int
rules: List[RankRule]
class NormalRankingRule(BaseModel):
default_rank_id: LeagueRankEnum
default_rank: int
rules: Dict[LeagueRankEnum, RankRule]
class TeamPointsRankingRules(BaseModel):
"""
TeamPointsRankingRules is a dictionary of PointRankingRule objects, where the key is the version (season)
"""
RootModel: Dict[int, PointRankingRule]
def __getitem__(self, version: int) -> "PointRankingRule":
return self.RootModel[version]
class TeamRankingRulesOpenRound(BaseModel):
"""
TeamRankingRulesOpenRound is a dictionary of RankingRule objects, where the key is the version (season)
"""
RootModel: Dict[int, OpenRankingRule]
def __getitem__(self, version: int) -> "OpenRankingRule":
return self.RootModel[version]
class TeamRankingRulesNormalRound(BaseModel):
"""
TeamRankingRulesNormalRound is a dictionary of RankingRule objects, where the key is the version (season)
"""
RootModel: Dict[int, NormalRankingRule]
def __getitem__(self, version: int) -> "NormalRankingRule":
return self.RootModel[version]
class IDACConstants:
GAME_CODE = "SDGT"
CONFIG_NAME = "idac.yaml"
VER_IDAC_SEASON_1 = 0
VER_IDAC_SEASON_2 = 1
VER_IDAC_SEASON_3 = 2
BATTLE_MODE_ONLINE = 0
BATTLE_MODE_OFFLINE = 1
@@ -12,6 +194,125 @@ class IDACConstants():
VERSION_STRING = (
"Initial D THE ARCADE Season 1",
"Initial D THE ARCADE Season 2",
"Initial D THE ARCADE Season 3",
)
MAX_GRADE = {
0: 54,
1: 72,
2: 72,
3: 81,
}
CAR_LAYOUT_MAPPING_FULL = {
0: LayoutEnum.TECHNICAL,
1: LayoutEnum.TECHNICAL,
2: LayoutEnum.TECHNICAL,
3: LayoutEnum.TECHNICAL,
4: LayoutEnum.TECHNICAL,
5: LayoutEnum.TECHNICAL,
9: LayoutEnum.TECHNICAL,
10: LayoutEnum.TECHNICAL,
13: LayoutEnum.TECHNICAL,
14: LayoutEnum.TECHNICAL,
258: LayoutEnum.TECHNICAL,
259: LayoutEnum.TECHNICAL,
261: LayoutEnum.TECHNICAL,
512: LayoutEnum.TECHNICAL,
513: LayoutEnum.TECHNICAL,
514: LayoutEnum.TECHNICAL,
515: LayoutEnum.TECHNICAL,
770: LayoutEnum.TECHNICAL,
771: LayoutEnum.TECHNICAL,
772: LayoutEnum.TECHNICAL,
1280: LayoutEnum.TECHNICAL,
1281: LayoutEnum.TECHNICAL,
1283: LayoutEnum.TECHNICAL,
1285: LayoutEnum.TECHNICAL,
1286: LayoutEnum.TECHNICAL,
1536: LayoutEnum.TECHNICAL,
1537: LayoutEnum.TECHNICAL,
1792: LayoutEnum.TECHNICAL,
}
TEAM_POINTS_RANKING_RULES = TeamPointsRankingRules(
RootModel={
VER_IDAC_SEASON_3: PointRankingRule(
points_upper_rank=2,
upper_points=5000,
point_rules=[
PointRule(
lower_points=0,
upper_points=1000,
destination_league_rank_id=LeagueRankEnum.BASIC,
destination_point=1000,
),
PointRule(
lower_points=1000,
upper_points=5000,
destination_league_rank_id=LeagueRankEnum.BRONZE,
destination_point=5000,
),
],
),
}
)
TEAM_RANKING_RULES_OPEN_ROUND = TeamRankingRulesOpenRound(
RootModel={
VER_IDAC_SEASON_3: OpenRankingRule(
default_rank_id=LeagueRankEnum.SILVER,
default_rank=2200,
rules=[
RankRule(
destination_league_rank_id=LeagueRankEnum.GOLD,
destination_rank=2200,
demote_rank=None,
),
RankRule(
destination_league_rank_id=LeagueRankEnum.PLATINUM,
destination_rank=700,
demote_rank=None,
),
RankRule(
destination_league_rank_id=LeagueRankEnum.MASTER,
destination_rank=200,
demote_rank=None,
),
],
)
}
)
TEAM_RANKING_RULES_NORMAL_ROUND = TeamRankingRulesNormalRound(
RootModel={
VER_IDAC_SEASON_3: NormalRankingRule(
default_rank_id=LeagueRankEnum.SILVER,
default_rank=500,
rules={
LeagueRankEnum.SILVER: RankRule(
destination_league_rank_id=LeagueRankEnum.GOLD,
destination_rank=500,
demote_rank=None,
),
LeagueRankEnum.GOLD: RankRule(
destination_league_rank_id=LeagueRankEnum.PLATINUM,
destination_rank=200,
demote_rank=1001,
),
LeagueRankEnum.PLATINUM: RankRule(
destination_league_rank_id=LeagueRankEnum.MASTER,
destination_rank=50,
demote_rank=301,
),
LeagueRankEnum.MASTER: RankRule(
destination_league_rank_id=LeagueRankEnum.MASTER,
destination_rank=50,
demote_rank=151,
),
},
)
}
)
@classmethod
@@ -0,0 +1,47 @@
import logging
from typing import Dict
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.model.season2.advertise_model import (
RequestSeason2AdvertiseGetRankingData,
ResponseSeason2AdvertiseGetRankingData,
)
from titles.idac.model.season2.common_model import RequestSeason2Headers
from titles.idac.service.season2.advertise_service import IDACSeason2AdvertiseService
class IDACSeason2AdvertiseController:
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
advertise_service: IDACSeason2AdvertiseService,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_config = game_cfg
self.game = IDACConstants.GAME_CODE
self.version = IDACConstants.VER_IDAC_SEASON_2
self.logger = logging.getLogger("idac")
self.advertise_service = advertise_service
async def handle_get_ranking_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2AdvertiseGetRankingData:
req = RequestSeason2AdvertiseGetRankingData.model_validate(data)
req_headers = RequestSeason2Headers.model_validate(headers)
national_best_data = await self.advertise_service.get_national_best_data(
req.last_update_date
)
return ResponseSeason2AdvertiseGetRankingData(
national_best_data=national_best_data
)
@@ -0,0 +1,31 @@
from datetime import datetime
from typing import Dict
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.model.season2.alive_model import ResponseSeason2AliveGet
class IDACSeason2AliveController:
ResponseAliveGet = ResponseSeason2AliveGet
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_cfg = game_cfg
self.version = IDACConstants.VER_IDAC_SEASON_2
async def handle_get_request(self, data: Dict, headers: Dict) -> ResponseAliveGet:
return self.ResponseAliveGet(
server_status=1,
force_reboot_time=int(datetime.now().timestamp()) - 86400,
)
@@ -0,0 +1,311 @@
import logging
import os
from collections import defaultdict
from datetime import datetime
from typing import Dict
from core.config import CoreConfig
from core.utils import Utils
from titles.idac.config import IDACServerConfig
from titles.idac.const import (
CollaboEnum,
IDACConstants,
RewardCategoryEnum,
StoryTypeEnum,
)
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.model.common_model import ResponseGeneric, Reward
from titles.idac.model.season2.boot_model import (
ResponseSeason2BootGetConfigData,
ResponseSeason2BootGetGachaData,
ResponseSeason2BootGetTimeReleaseData,
)
class IDACSeason2BootController:
ResponseBootGetConfigData = ResponseSeason2BootGetConfigData
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_cfg = game_cfg
self.game = IDACConstants.GAME_CODE
self.version = IDACConstants.VER_IDAC_SEASON_2
self.logger = logging.getLogger("idac")
# load timerelease numbers from config
self.timerelease_no = self.game_cfg.timerelease.timerelease_no
self.timerelease_avatar_gacha_no = (
self.game_cfg.timerelease.timerelease_avatar_gacha_no
)
self.collabo_id = CollaboEnum.NONE
self.stamp_info: dict[int, list[ResponseSeason2BootGetConfigData.StampInfo]] = (
defaultdict(list)
)
self.timetrial_event: dict[
int, ResponseSeason2BootGetConfigData.TimetrialEventData
] = defaultdict()
self.battle_gift_event: dict[
int, ResponseSeason2BootGetConfigData.BattleGiftEventMaster
] = defaultdict()
self.round_event: list[ResponseSeason2BootGetConfigData.RoundEvent_2] = []
self.last_round_event: list[ResponseSeason2BootGetConfigData.RoundEvent_2] = []
self.last_round_event_ranking: list[
ResponseSeason2BootGetConfigData.LastRoundEventRanking
] = []
if self.game_cfg.collabo.enable:
self.collabo_id = CollaboEnum(self.game_cfg.collabo.collabo_id)
if self.game_cfg.stamp.enable:
self.stamp_info = self.game_cfg.stamp_info
if self.game_cfg.timetrial.enable:
self.timetrial_event = self.game_cfg.timetrial_info
if self.game_cfg.battle_gift.enable:
self.battle_gift_event = self.game_cfg.battle_gift_info
if self.game_cfg.round.enable:
self.round_event = [self.game_cfg.round_info]
if self.game_cfg.last_round_info:
self.last_round_event = [self.game_cfg.last_round_info]
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) -> StoryTypeEnum:
ver = self._headers_to_version(headers)
return {
150: StoryTypeEnum.TOUHOU_1ST,
170: StoryTypeEnum.MIKU,
210: StoryTypeEnum.HOT_VERSION,
}.get(ver, 0)
async def handle_get_config_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2BootGetConfigData:
ver_str = self._headers_to_version(headers)
# TODO: properly set the domain name
hostname = "sega-initiald.net"
if self.core_cfg.server.is_using_proxy:
domain_api_game = f"http://{hostname}/{ver_str}/"
else:
domain_api_game = (
f"http://{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 = None
special_story_type = self._special_story_type(headers)
if special_story_type != StoryTypeEnum.NONE:
special_mode_data = ResponseSeason2BootGetConfigData.SpecialModeData(
start_dt=datetime(2023, 1, 1),
end_dt=datetime(2029, 12, 31),
story_type=special_story_type,
)
battle_gift_event = []
if ver_str in self.battle_gift_event:
battle_gift_event_data = self.battle_gift_event.get(ver_str, {})
# check if battle gift event time is valid
start_dt = battle_gift_event_data.start_dt
end_dt = battle_gift_event_data.end_dt
if start_dt < datetime.now() < end_dt:
battle_gift_event = [battle_gift_event_data]
stamp_info: list[ResponseSeason2BootGetConfigData.StampInfo] = []
if ver_str in self.stamp_info:
stamp_info_data = self.stamp_info.get(ver_str, [])
# check if stamp event time is valid
start_dt = self.game_cfg.stamp.open_dt
end_dt = self.game_cfg.stamp.close_dt
if start_dt < datetime.now() < end_dt:
stamp_info = stamp_info_data
number_lottery_data: list[
ResponseSeason2BootGetConfigData.NumberLotteryData
] = [
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=10,
m_number_lottery_schedule_no=1,
win_number=0,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=11,
m_number_lottery_schedule_no=1,
win_number=1111,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=12,
m_number_lottery_schedule_no=1,
win_number=2222,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=13,
m_number_lottery_schedule_no=1,
win_number=3333,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=14,
m_number_lottery_schedule_no=1,
win_number=4444,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=15,
m_number_lottery_schedule_no=1,
win_number=5555,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=16,
m_number_lottery_schedule_no=1,
win_number=6666,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=17,
m_number_lottery_schedule_no=1,
win_number=7777,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=18,
m_number_lottery_schedule_no=1,
win_number=8888,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
self.ResponseBootGetConfigData.NumberLotteryData(
m_number_lottery_win_number_no=19,
m_number_lottery_schedule_no=1,
win_number=9999,
reward_category=RewardCategoryEnum.FT_TOKEN_PIECES,
reward_type=1,
end_dt=0,
),
]
return self.ResponseBootGetConfigData(
domain_api_game=domain_api_game,
domain_matching=f"{domain_api_game}initiald-matching/",
domain_echo1=f"{hostname}:{self.server_cfg.echo1}",
domain_echo2=f"{hostname}:{self.server_cfg.echo2}",
domain_ping=f"{hostname}",
battle_gift_event_master=battle_gift_event,
# 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,
stamp_info=stamp_info,
# 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,
timetrial_disp_date=int(
datetime.strptime("2023-10-01", "%Y-%m-%d").timestamp()
),
collabo_id=self.collabo_id,
theory_open_version="1.40.00",
# unlocks the version specific special mode
special_mode_data=special_mode_data,
timetrial_event_data=self.timetrial_event.get(ver_str, None),
number_lottery_data=number_lottery_data,
)
async def handle_bookkeep_request(
self, data: Dict, headers: Dict
) -> ResponseGeneric:
return ResponseGeneric()
async def handle_get_gacha_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2BootGetGachaData:
# TODO: add support for multiple seasons (maybe even multiple vesions of the same season?)
with open("./titles/idac/data/avatarGacha.json", encoding="UTF-8") as f:
avatar_gacha_data = f.read()
resp = ResponseSeason2BootGetGachaData.model_validate_json(avatar_gacha_data)
return resp
async def handle_get_timerelease_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2BootGetTimeReleaseData:
"""
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", encoding="utf-8") as f:
time_release_data = f.read()
resp = ResponseSeason2BootGetTimeReleaseData.model_validate_json(
time_release_data
)
return resp
@@ -0,0 +1,500 @@
import logging
from typing import Dict
from datetime import timedelta
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants, NameChangeCategoryEnum
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.model.season2.common_model import RequestSeason2UserHeaders
from titles.idac.model.season2.factory_model import *
from titles.idac.model.season2.item_model import TicketDto
from titles.idac.model.season2.user_model import ProfileDto
from titles.idac.service.season2.factory_service import IDACSeason2FactoryService
from titles.idac.service.season2.user_service import IDACSeason2UserService
class IDACSeason2FactoryController:
RequestFactoryGetCarData = RequestSeason2FactoryGetCarData
ResponseFactoryGetCarData = ResponseSeason2FactoryGetCarData
RequestFactoryAvatarGachaResult = RequestSeason2FactoryAvatarGachaResult
ResponseFactoryAvatarGachaResult = ResponseSeason2FactoryAvatarGachaResult
RequestFactoryBuyCarResult = RequestSeason2FactoryBuyCarResult
ResponseFactoryBuyCarResult = ResponseSeason2FactoryBuyCarResult
RequestFactorySaveFavoriteCar = RequestSeason2FactorySaveFavoriteCar
ResponseFactorySaveFavoriteCar = ResponseSeason2FactorySaveFavoriteCar
RequestFactoryUpdateCustomizeResult = RequestSeason2FactoryUpdateCustomizeResult
ResponseFactoryUpdateCustomizeResult = ResponseSeason2FactoryUpdateCustomizeResult
RequestFactoryUpdateMultipleCustomizeResult = (
RequestSeason2FactoryUpdateMultipleCustomizeResult
)
ResponseFactoryUpdateMultipleCustomizeResult = (
ResponseSeason2FactoryUpdateMultipleCustomizeResult
)
RequestFactoryUpdateCustomizeUser = RequestSeason2FactoryUpdateCustomizeUser
ResponseFactoryUpdateCustomizeUser = ResponseSeason2FactoryUpdateCustomizeUser
RequestFactoryUpdateCustomizeAvatar = RequestSeason2FactoryUpdateCustomizeAvatar
ResponseFactoryUpdateCustomizeAvatar = ResponseSeason2FactoryUpdateCustomizeAvatar
RequestFactoryRenameBefore = RequestSeason2FactoryRenameBefore
ResponseFactoryRenameBefore = ResponseSeason2FactoryRenameBefore
RequestFactoryRenameResult = RequestSeason2FactoryRenameResult
ResponseFactoryRenameResult = ResponseSeason2FactoryRenameResult
RequestFactoryNumberLotteryBefore = RequestSeason2FactoryNumberLotteryBefore
ResponseFactoryNumberLotteryBefore = ResponseSeason2FactoryNumberLotteryBefore
RequestFactoryNumberLotteryResult = RequestSeason2FactoryNumberLotteryResult
ResponseFactoryNumberLotteryResult = ResponseSeason2FactoryNumberLotteryResult
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
user_service: IDACSeason2UserService,
factory_service: IDACSeason2FactoryService,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_cfg = game_cfg
self.game = IDACConstants.GAME_CODE
self.version = IDACConstants.VER_IDAC_SEASON_2
self.logger = logging.getLogger("idac")
self.user_service = user_service
self.factory_service = factory_service
async def handle_get_car_data_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryGetCarData:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryGetCarData.model_validate(data)
user_id = req_headers.session
car_dto_list = await self.factory_service.get_profile_cars(user_id)
factory_car_dto_list = [
ResponseSeason2FactoryGetCarData.FactoryCarData(
**car.model_dump(), id=user_id
)
for car in car_dto_list
]
return self.ResponseFactoryGetCarData(car_data=factory_car_dto_list)
async def handle_avatar_gacha_result_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryAvatarGachaResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryAvatarGachaResult.model_validate(data)
user_id = req_headers.session
# save user's stock
await self.user_service.put_profile_stock(
user_id, StockData.model_validate(req.stock_obj)
)
# deduct used tickets
ticket_dto = await self.user_service.get_profile_ticket(
user_id, RewardCategoryEnum.AVATAR_TOKEN
)
ticket_dto.ticket_cnt -= req.use_ticket_cnt
# save tickets
await self.user_service.put_profile_tickets(
user_id, [TicketDto.model_validate(ticket_dto)]
)
ticket_dto_list = await self.user_service.get_profile_tickets(user_id)
return self.ResponseFactoryAvatarGachaResult(ticket_data=ticket_dto_list)
async def handle_buy_car_result_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryBuyCarResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryBuyCarResult.model_validate(data)
user_id = req_headers.session
# get the user's cars
car_dto_list = await self.factory_service.get_profile_cars(user_id)
new_car_dto = CarDto(
car_id=req.car_id,
style_car_id=req.style_car_id,
color=req.color,
bureau=req.bureau,
kana=req.kana,
s_no=req.s_no,
l_no=req.l_no,
car_flag=req.car_flag,
tune_point=req.tune_point,
tune_level=16 if req.use_ticket else 1,
tune_parts=req.tune_parts,
color_stock_list=req.color_stock_list,
color_stock_new_list=req.color_stock_new_list,
parts_stock_list=req.parts_stock_list,
parts_stock_new_list=req.parts_stock_new_list,
parts_set_equip_list=req.parts_set_equip_list,
parts_list=req.parts_list,
equip_parts_count=req.equip_parts_count,
pickup_seq=next(
(
car.pickup_seq
for car in req.pickup_on_car_ids
if car.style_car_id == req.style_car_id
),
0,
),
purchase_seq=max((car.purchase_seq for car in car_dto_list), default=0) + 1,
)
# add the new car dto to the list
car_dto_list.append(new_car_dto)
# save new car
await self.factory_service.put_profile_car(user_id, new_car_dto)
# save favorite cars (pickup on)
for pickup_car in req.pickup_on_car_ids:
if pickup_car.style_car_id != req.style_car_id:
await self.factory_service.set_car_favorite(
user_id,
pickup_car.style_car_id,
pickup_car.pickup_seq,
car_dto_list,
)
# remove from favorites (pickup off)
for pickup_off_car in req.pickup_off_car_ids:
await self.factory_service.unset_car_favorite(
user_id, pickup_off_car.style_car_id, car_dto_list
)
# handle full tune ticket
if req.use_ticket:
ticket_dto = await self.user_service.get_profile_ticket(
user_id, RewardCategoryEnum.FULLTUNE_TICKET
)
ticket_dto.ticket_cnt -= 1
await self.user_service.put_profile_tickets(user_id, [ticket_dto])
ticket_dto_list = await self.user_service.get_profile_tickets(user_id)
# get the user profile for total_car_parts_count
profile_dto = await self.user_service.get_profile(user_id)
# calculate the different counts for cars
fulltune_count = self.factory_service.calculate_fulltune_count(car_dto_list)
# those are needed for titles
car_layout_count = self.factory_service.calculate_car_layout_count(car_dto_list)
car_style_count = self.factory_service.calculate_car_style_count(car_dto_list)
return self.ResponseFactoryBuyCarResult(
ticket_data=ticket_dto_list,
fulltune_count=fulltune_count,
total_car_parts_count=profile_dto.total_car_parts_count,
car_layout_count=car_layout_count,
car_style_count=car_style_count,
)
async def handle_save_favorite_car_request(
self, data: Dict, headers: Dict
) -> ResponseFactorySaveFavoriteCar:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactorySaveFavoriteCar.model_validate(data)
user_id = req_headers.session
# get the user's car
car_dto_list = await self.factory_service.get_profile_cars(user_id)
# save favorite cars (pickup on)
for pickup_car in req.pickup_on_car_ids:
await self.factory_service.set_car_favorite(
user_id, pickup_car.style_car_id, pickup_car.pickup_seq, car_dto_list
)
# remove from favorites (pickup off)
for pickup_off_car in req.pickup_off_car_ids:
await self.factory_service.unset_car_favorite(
user_id, pickup_off_car.style_car_id, car_dto_list
)
return self.ResponseFactorySaveFavoriteCar()
async def handle_update_customize_result_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryUpdateCustomizeResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryUpdateCustomizeResult.model_validate(data)
user_id = req_headers.session
# save total_car_parts_count
self.user_service.put_profile(
user_id, ProfileDto(total_car_parts_count=req.total_car_parts_count)
)
# get the user's car
car_dto_list = await self.factory_service.get_profile_cars(user_id)
# Find the car being customized
car_dto = self.factory_service._get_used_car(req.style_car_id, car_dto_list)
if not car_dto:
raise ValueError(f"Car {req.style_car_id} not found for user {user_id}")
car_dto.color = req.color
car_dto.color_stock_list = req.color_stock_list
car_dto.color_stock_new_list = req.color_stock_new_list
car_dto.parts_stock_list = req.parts_stock_list
car_dto.parts_stock_new_list = req.parts_stock_new_list
car_dto.parts_set_equip_list = req.parts_set_equip_list
car_dto.parts_list = req.parts_list
car_dto.equip_parts_count = req.equip_parts_count
# save updated car
await self.factory_service.put_profile_car(
user_id, CarDto.model_validate(car_dto)
)
# save tickets
await self.user_service.put_profile_tickets(
user_id, [TicketDto.model_validate(ticket) for ticket in req.ticket_data]
)
return self.ResponseFactoryUpdateCustomizeResult()
async def handle_update_multiple_customize_result_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryUpdateMultipleCustomizeResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryUpdateMultipleCustomizeResult.model_validate(data)
user_id = req_headers.session
# save total_car_parts_count
self.user_service.put_profile(
user_id, ProfileDto(total_car_parts_count=req.total_car_parts_count)
)
# get the user's car list
car_dto_list = await self.factory_service.get_profile_cars(user_id)
# update each car's customization
for factory_car in req.car_list:
car_dto = self.factory_service._get_used_car(
factory_car.style_car_id, car_dto_list
)
if not car_dto:
raise ValueError(
f"Car {factory_car.style_car_id} not found for user {user_id}"
)
# update customization fields
car_dto.color = factory_car.color
car_dto.color_stock_list = factory_car.color_stock_list
car_dto.color_stock_new_list = factory_car.color_stock_new_list
car_dto.parts_stock_list = factory_car.parts_stock_list
car_dto.parts_stock_new_list = factory_car.parts_stock_new_list
car_dto.parts_set_equip_list = factory_car.parts_set_equip_list
car_dto.parts_list = factory_car.parts_list
car_dto.equip_parts_count = factory_car.equip_parts_count
# save updated car
await self.factory_service.put_profile_car(
user_id, CarDto.model_validate(car_dto)
)
# save tickets
await self.user_service.put_profile_tickets(
user_id, [TicketDto.model_validate(ticket) for ticket in req.ticket_data]
)
return self.ResponseFactoryUpdateMultipleCustomizeResult()
async def handle_update_customize_user_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryUpdateCustomizeUser:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryUpdateCustomizeUser.model_validate(data)
user_id = req_headers.session
# get the user's profile
profile_dto = await self.user_service.get_profile(user_id)
# update profile fields
profile_dto.mytitle_id = req.mytitle_id
profile_dto.mytitle_efffect_id = req.mytitle_efffect_id
profile_dto.papercup_id = req.papercup_id
profile_dto.tachometer_id = req.tachometer_id
profile_dto.aura_id = req.aura_id
profile_dto.aura_color_id = req.aura_color_id
profile_dto.aura_line_id = req.aura_line_id
profile_dto.bgm_id = req.bgm_id
profile_dto.keyholder_id = req.keyholder_id
profile_dto.start_menu_bg_id = req.start_menu_bg_id
profile_dto.stamp_key_assign_0 = req.stamp_key_assign_0
profile_dto.stamp_key_assign_1 = req.stamp_key_assign_1
profile_dto.stamp_key_assign_2 = req.stamp_key_assign_2
profile_dto.stamp_key_assign_3 = req.stamp_key_assign_3
# save user's profile customization
await self.user_service.put_profile(
user_id, ProfileDto.model_validate(profile_dto)
)
# save user's stock
await self.user_service.put_profile_stock(
user_id, StockData.model_validate(req.stock_obj)
)
return self.ResponseFactoryUpdateCustomizeUser()
async def handle_update_customize_avatar_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryUpdateCustomizeAvatar:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryUpdateCustomizeAvatar.model_validate(data)
user_id = req_headers.session
# save user's stock
await self.user_service.put_profile_stock(
user_id, StockData.model_validate(req.stock_obj)
)
# save user's avatar
await self.user_service.put_profile_avatar(
user_id, AvatarData.model_validate(req.avatar_obj)
)
return self.ResponseFactoryUpdateCustomizeAvatar()
async def handle_rename_before_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryRenameBefore:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryRenameBefore.model_validate(data)
user_id = req_headers.session
profile_dto = await self.user_service.get_profile(user_id)
# set the profile name change category to credit retry
if profile_dto.name_change_category == NameChangeCategoryEnum.DENIED:
return self.ResponseFactoryRenameBefore(
status_code=StatusCodeEnum.BAD_REQUEST
)
profile_dto.name_change_category = NameChangeCategoryEnum.CREDIT_RETRY
await self.user_service.put_profile(user_id, profile_dto)
return self.ResponseFactoryRenameBefore()
async def handle_rename_result_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryRenameResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryRenameResult.model_validate(data)
user_id = req_headers.session
profile_dto = await self.user_service.get_profile(user_id)
if profile_dto.name_change_category == NameChangeCategoryEnum.DENIED:
return self.ResponseFactoryRenameBefore(
status_code=StatusCodeEnum.BAD_REQUEST
)
# save user's new name and name change category to denied
profile_dto.username = req.username
profile_dto.name_change_category = NameChangeCategoryEnum.DENIED
await self.user_service.put_profile(user_id, profile_dto)
return self.ResponseFactoryRenameResult()
async def handle_number_lottery_before_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryNumberLotteryBefore:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryNumberLotteryBefore.model_validate(data)
user_id = req_headers.session
lottery_info = await self.factory_service.calculate_lottery_info(user_id)
return self.ResponseFactoryNumberLotteryBefore(lottery_info=lottery_info)
async def handle_number_lottery_result_request(
self, data: Dict, headers: Dict
) -> ResponseFactoryNumberLotteryResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestFactoryNumberLotteryResult.model_validate(data)
user_id = req_headers.session
# get lottery data
lottery_dto = await self.factory_service.get_lottery(user_id)
if not lottery_dto:
lottery_dto = LotteryDto(
create_date=datetime.now(),
saved_value=0,
lottery_count=req.lottery_count,
)
if req.win_number != 10000:
shifted = req.win_number // 1111
lottery_dto.saved_value += 1 << shifted
if lottery_dto and datetime.now() - lottery_dto.create_date > timedelta(days=1):
lottery_dto.create_date = datetime.now()
await self.factory_service.put_lottery(
user_id, LotteryDto.model_validate(lottery_dto)
)
# get the user's car
car_dto_list = await self.factory_service.get_profile_cars(user_id)
# find the car for lottery
car_dto = self.factory_service._get_used_car(req.style_car_id, car_dto_list)
if req.l_no != 10000 and req.isEnd == 1:
car_dto.l_no = req.l_no
await self.factory_service.put_profile_car(
user_id, CarDto.model_validate(car_dto)
)
if req.ticket_data:
await self.user_service.put_profile_tickets(
user_id,
[TicketDto.model_validate(ticket) for ticket in req.ticket_data],
)
# get the user's profile and update profile data
profile_dto = ProfileDto(
cash=req.cash,
total_cash=req.total_cash,
dressup_point=req.dressup_point,
avatar_point=req.avatar_point,
)
await self.user_service.put_profile(user_id, profile_dto)
return self.ResponseFactoryNumberLotteryResult()
@@ -0,0 +1,50 @@
from typing import Dict
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.model.season2.common_model import RequestSeason2UserHeaders
from titles.idac.model.season2.log_model import (
RequestSeason2LogSavePlayLog,
ResponseSeason2LogSavePlayLog,
RequestSeason2LogSaveEndLog,
ResponseSeason2LogSaveEndLog,
)
class IDACSeason2LogController:
RequestLogSavePlayLog = RequestSeason2LogSavePlayLog
ResponseLogSavePlayLog = ResponseSeason2LogSavePlayLog
RequestLogSaveEndLog = RequestSeason2LogSaveEndLog
ResponseLogSaveEndLog = ResponseSeason2LogSaveEndLog
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_cfg = game_cfg
self.version = IDACConstants.VER_IDAC_SEASON_2
async def handle_save_play_log_request(
self, data: Dict, headers: Dict
) -> ResponseLogSavePlayLog:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestLogSavePlayLog.model_validate(data)
return self.ResponseLogSavePlayLog()
async def handle_save_end_log_request(
self, data: Dict, headers: Dict
) -> RequestLogSaveEndLog:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestLogSaveEndLog.model_validate(data)
return self.ResponseLogSaveEndLog()
@@ -0,0 +1,128 @@
import logging
from datetime import datetime
from typing import Dict
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.model.common_model import LockResultEnum
from titles.idac.model.season2.common_model import RequestSeason2Headers
from titles.idac.model.season2.login_model import *
from titles.idac.service.season2.login_service import IDACSeason2LoginService
from titles.idac.service.season2.user_service import IDACSeason2UserService
class IDACSeason2LoginController:
RequestLoginCheckLock = RequestSeason2LoginCheckLock
ResponseLoginCheckLock = ResponseSeason2LoginCheckLock
RequestLoginUnLock = RequestSeason2LoginUnLock
ResponseLoginUnLock = ResponseSeason2LoginUnLock
RequestLoginReLock = RequestSeason2LoginReLock
ResponseLoginReLock = ResponseSeason2LoginReLock
RequestLoginGuestPlay = RequestSeason2LoginGuestPlay
ResponseLoginGuestPlay = ResponseSeason2LoginGuestPlay
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
login_service: IDACSeason2LoginService,
user_service: IDACSeason2UserService,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_config = game_cfg
self.game = IDACConstants.GAME_CODE
self.version = IDACConstants.VER_IDAC_SEASON_2
self.logger = logging.getLogger("idac")
self.login_service = login_service
self.user_service = user_service
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_check_lock_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2LoginCheckLock:
req = self.RequestLoginCheckLock.model_validate(data)
req_headers = RequestSeason2Headers.model_validate(headers)
user_id = req.id
lock_result = LockResultEnum.IN_USE
is_new_player = 0
# check that the user_id from access_code matches the user_id
if await self.login_service.is_valid_user_id(user_id, req.accesscode):
lock_result = LockResultEnum.OK
# check if an IDAC profile already exists
p = await self.user_service.get_profile(user_id)
is_new_player = 1 if p is None else 0
db_version = p.device_version if p is not None else "1.00.00"
# check if the client version is valid
if not self._is_valid_version(db_version, req_headers.device_version):
lock_result = LockResultEnum.NEWER_DATA
else:
user_id = ""
takeover_data = []
return self.ResponseLoginCheckLock(
lock_result=lock_result,
lock_date=datetime.now(),
# TODO?
daily_play=0,
session=f"{user_id}",
shared_security_key="a",
session_procseq=1,
new_player=is_new_player,
server_status=1,
takeover_data=takeover_data,
)
async def handle_un_lock_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2LoginUnLock:
req = self.RequestLoginUnLock.model_validate(data)
req_headers = RequestSeason2Headers.model_validate(headers)
return self.ResponseLoginUnLock(lock_result=LockResultEnum.OK)
async def handle_re_lock_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2LoginReLock:
req = self.RequestLoginReLock.model_validate(data)
req_headers = RequestSeason2Headers.model_validate(headers)
return self.ResponseLoginReLock(
lock_result=LockResultEnum.OK, lock_date=int(datetime.now().timestamp())
)
async def handle_guest_play_request(
self, data: Dict, headers: Dict
) -> ResponseSeason2LoginGuestPlay:
req = self.RequestLoginGuestPlay.model_validate(data)
req_headers = RequestSeason2Headers.model_validate(headers)
return self.ResponseLoginGuestPlay(
session="-1", shared_security_key="Guest", session_procseq=0
)
@@ -0,0 +1,66 @@
import logging
from typing import Dict
from pydantic_core import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.gameconfig.season2_config import IDACSeason2Config
from titles.idac.model.season2.common_model import *
from titles.idac.model.season2.user_model import *
from titles.idac.model.season2.theory_model import *
from titles.idac.service.season2.factory_service import IDACSeason2FactoryService
from titles.idac.service.season2.theory_service import IDACSeason2TheoryService
from titles.idac.service.season2.user_service import IDACSeason2UserService
class IDACSeason2TheoryController:
RequestTheoryMatching = RequestSeason2TheoryMatching
ResponseTheoryMatching = ResponseSeason2TheoryMatching
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
user_service: IDACSeason2UserService,
theory_service: IDACSeason2TheoryService,
factory_service: IDACSeason2FactoryService,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_cfg = game_cfg
self.version = IDACConstants.VER_IDAC_SEASON_2
self.game = IDACConstants.GAME_CODE
self.logger = logging.getLogger("idac")
self.user_service = user_service
self.theory_service = theory_service
self.factory_service = factory_service
async def handle_matching_request(
self, data: Dict, headers: Dict
) -> ResponseTheoryMatching:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestTheoryMatching.model_validate(data)
power_match: list[Season2PowerMatch] = []
user_id = req_headers.session
auto_match = await self.theory_service.generate_auto_match(
user_id, req.grade, req.course_id, req_headers.device_version
)
# only get the power_match if requested
if req.powerhouse_lv > 0:
power_match = await self.theory_service.generate_power_match(
user_id, req.grade, req.course_id, req.powerhouse_lv, req_headers.device_version
)
return self.ResponseTheoryMatching(
rival_data=ResponseSeason2TheoryMatching.RivalData(
auto_match=auto_match,
power_match=power_match,
)
)
@@ -0,0 +1,107 @@
import logging
from typing import Dict
from core.config import CoreConfig
from titles.idac.const import IDACConstants
from titles.idac.model.season2.common_model import RequestSeason2UserHeaders
from titles.idac.model.season2.time_trial_model import (
RequestSeason2TimeTrialGetBestRecordPreTA,
ResponseSeason2TimeTrialGetBestRecordPreTA,
)
from titles.idac.service.season2.time_trial_service import *
class IDACSeason2TimeTrialController:
RequestTimeTrialGetBestRecordPreTA = RequestSeason2TimeTrialGetBestRecordPreTA
ResponseTimeTrialGetBestRecordPreTA = ResponseSeason2TimeTrialGetBestRecordPreTA
RequestTimeTrialGetBestRecordPreRace = RequestSeason2TimeTrialGetBestRecordPreRace
ResponseTimeTrialGetBestRecordPreRace = ResponseSeason2TimeTrialGetBestRecordPreRace
RequestTimeTrialGetBestRecordPreBattle = (
RequestSeason2TimeTrialGetBestRecordPreBattle
)
ResponseTimeTrialGetBestRecordPreBattle = (
ResponseSeason2TimeTrialGetBestRecordPreBattle
)
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason2Config,
time_trial_service: IDACSeason2TimeTrialService,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_cfg = game_cfg
self.version = IDACConstants.VER_IDAC_SEASON_2
self.logger = logging.getLogger("idac")
self.time_trial_service = time_trial_service
async def handle_get_best_record_pre_ta_request(
self, data: Dict, headers: Dict
) -> ResponseTimeTrialGetBestRecordPreTA:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestTimeTrialGetBestRecordPreTA.model_validate(data)
user_id = req_headers.session
course_mybest_data = await self.time_trial_service.get_course_mybest_data(
user_id
)
style_car_id_list = [car.style_car_id for car in req.car_ids]
course_pickup_car_best_data = (
await self.time_trial_service.get_course_pickup_car_best_data(
user_id, style_car_id_list
)
)
return self.ResponseTimeTrialGetBestRecordPreTA(
course_mybest_data=course_mybest_data,
course_pickup_car_best_data=course_pickup_car_best_data,
)
async def handle_get_best_record_pre_race_request(
self, data: Dict, headers: Dict
) -> ResponseTimeTrialGetBestRecordPreRace:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestTimeTrialGetBestRecordPreRace.model_validate(data)
style_car_id_list = [car.style_car_id for car in req.car_ids]
course_id = req.course_id
course_car_best_data = await self.time_trial_service.get_course_car_best_data(
course_id, style_car_id_list
)
course_best_data = await self.time_trial_service.get_course_best_data(course_id)
return self.ResponseTimeTrialGetBestRecordPreRace(
course_car_best_data=course_car_best_data, course_best_data=course_best_data
)
async def handle_get_best_record_pre_battle_request(
self, data: Dict, headers: Dict
) -> ResponseTimeTrialGetBestRecordPreRace:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestTimeTrialGetBestRecordPreBattle.model_validate(data)
user_id = req_headers.session
style_car_id_list = [car.style_car_id for car in req.car_ids]
course_pickup_car_best_data = (
await self.time_trial_service.get_course_pickup_car_best_data(
user_id, style_car_id_list
)
)
return self.ResponseTimeTrialGetBestRecordPreBattle(
course_pickup_car_best_data=course_pickup_car_best_data
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.controller.season2.advertise_controller import (
IDACSeason2AdvertiseController,
)
from titles.idac.service.season3.advertise_service import IDACSeason3AdvertiseService
class IDACSeason3AdvertiseController(IDACSeason2AdvertiseController):
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
advertise_service: IDACSeason3AdvertiseService,
) -> None:
super().__init__(core_cfg, server_cfg, game_cfg, advertise_service)
self.version = IDACConstants.VER_IDAC_SEASON_3
@@ -0,0 +1,17 @@
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.controller.season2.alive_controller import IDACSeason2AliveController
from titles.idac.gameconfig.season3_config import IDACSeason3Config
class IDACSeason3AliveController(IDACSeason2AliveController):
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
) -> None:
super().__init__(core_cfg, server_cfg, game_cfg)
self.version = IDACConstants.VER_IDAC_SEASON_3
@@ -0,0 +1,39 @@
from typing import Dict
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import CollaboEnum, IDACConstants
from titles.idac.controller.season2.boot_controller import IDACSeason2BootController
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.model.season3.boot_model import ResponseSeason3BootGetConfigData
class IDACSeason3BootController(IDACSeason2BootController):
ResponseBootGetConfigData = ResponseSeason3BootGetConfigData
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
) -> None:
super().__init__(core_cfg, server_cfg, game_cfg)
self.game_cfg = game_cfg
self.version = IDACConstants.VER_IDAC_SEASON_3
async def handle_get_config_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3BootGetConfigData:
resp: ResponseSeason3BootGetConfigData = (
await super().handle_get_config_data_request(data, headers)
)
resp.collabo_id = CollaboEnum.HOT_VERSION
resp.theory_open_version = "2.00.00"
resp.league_rewards = self.game_cfg.league_reward_info
resp.league_flag = 0 # 0 = open league?
return resp
@@ -0,0 +1,25 @@
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.controller.season2.factory_controller import (
IDACSeason2FactoryController,
)
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.model.season3.user_model import *
from titles.idac.service.season3.factory_service import IDACSeason3FactoryService
from titles.idac.service.season3.user_service import IDACSeason3UserService
class IDACSeason3FactoryController(IDACSeason2FactoryController):
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
user_service: IDACSeason3UserService,
factory_service: IDACSeason3FactoryService,
) -> None:
super().__init__(core_cfg, server_cfg, game_cfg, user_service, factory_service)
self.version = IDACConstants.VER_IDAC_SEASON_3
@@ -0,0 +1,18 @@
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.controller.season2.log_controller import IDACSeason2LogController
from titles.idac.gameconfig.season3_config import IDACSeason3Config
class IDACSeason3LogController(IDACSeason2LogController):
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
) -> None:
super().__init__(core_cfg, server_cfg, game_cfg)
self.version = IDACConstants.VER_IDAC_SEASON_3
@@ -0,0 +1,22 @@
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.controller.season2.login_controller import IDACSeason2LoginController
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.service.season3.login_service import IDACSeason3LoginService
from titles.idac.service.season3.user_service import IDACSeason3UserService
class IDACSeason3LoginController(IDACSeason2LoginController):
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
login_service: IDACSeason3LoginService,
user_service: IDACSeason3UserService,
) -> None:
super().__init__(core_cfg, server_cfg, game_cfg, login_service, user_service)
self.version = IDACConstants.VER_IDAC_SEASON_3
@@ -0,0 +1,330 @@
import logging
from typing import Dict
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.model.season2.common_model import RequestSeason2UserHeaders
from titles.idac.model.season3.common_model import *
from titles.idac.model.season3.result_model import *
from titles.idac.model.season3.team_model import *
from titles.idac.service.season3.team_service import IDACSeason3TeamService
from titles.idac.service.season3.user_service import IDACSeason3UserService
class IDACSeason3TeamController:
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
user_service: IDACSeason3UserService,
team_service: IDACSeason3TeamService,
) -> None:
self.core_cfg = core_cfg
self.server_cfg = server_cfg
self.game_cfg = game_cfg
self.version = IDACConstants.VER_IDAC_SEASON_3
self.logger = logging.getLogger("idac")
self.user_service = user_service
self.team_service = team_service
async def handle_create_team_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3CreateTeam:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = RequestSeason3CreateTeam.model_validate(data)
user_id = req_headers.session
country = req_headers.a_country
store_id = req_headers.a_store
team_info = await self.team_service.create_team(
user_id, req.team_name, country, store_id, req.auto_invite_flag
)
return ResponseSeason3CreateTeam(
team_info=team_info,
auto_invite_flag=req.auto_invite_flag,
)
async def handle_left_team_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3LeftTeam:
user_id = headers["session"]
left_result = await self.team_service.leave_team(user_id)
return ResponseSeason3LeftTeam(
left_status=0 if left_result else 1,
)
async def handle_pre_join_team_request(self, data: Dict, headers: Dict):
req = RequestSeason3PreJoinTeam.model_validate(data)
team_info = await self.team_service.pre_join_team(req.password)
if not team_info:
# failed to find team for the given D number (password)
return ResponseSeason3PreJoinTeam(
join_status=JoinStatusEnum.INVALID_PASSWORD
)
# check if team already has 10 members
team_member_list = await self.team_service.get_all_team_member_dto(
team_info.team_id
)
if len(team_member_list) >= 10:
return ResponseSeason3PreJoinTeam(join_status=JoinStatusEnum.TEAM_FULL)
# successfully found team
return ResponseSeason3PreJoinTeam(
join_status=JoinStatusEnum.SUCCESS,
team_info=team_info,
)
async def handle_join_team_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3JoinTeam:
req = RequestSeason3JoinTeam.model_validate(data)
user_id = headers["session"]
response_prejoin = await self.handle_pre_join_team_request(data, headers)
if response_prejoin.join_status != JoinStatusEnum.SUCCESS:
return ResponseSeason3JoinTeam(
join_status=response_prejoin.join_status,
)
team_info = response_prejoin.team_info
team_id = team_info.team_id
join_result = await self.team_service.join_team(user_id, team_id)
if not join_result:
# failed to join team
return ResponseSeason3JoinTeam(join_status=JoinStatusEnum.UNEXPECTED_ERROR)
league_info = await self.team_service.get_league_info(team_id, user_id)
team_member_cars = await self.team_service.get_team_member_cars(user_id)
# update the tips last team join timestamp
await self.user_service.update_tips_team_join(user_id)
return ResponseSeason3JoinTeam(
join_status=JoinStatusEnum.SUCCESS,
team_info=team_info,
league_info=league_info,
team_member_cars=team_member_cars,
)
async def handle_get_team_manage_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3GetTeamManageData:
user_id = headers["session"]
# return wrong response if the user is not a leader
if not await self.team_service.is_team_leader(user_id):
return ResponseSeason3GetTeamManageData(
member_data=[],
password="",
auto_invite_flag=0,
)
team_info_dto = await self.team_service.get_team_info_dto(user_id)
member_data = await self.team_service.get_team_member_data(user_id)
return ResponseSeason3GetTeamManageData(
member_data=member_data,
password=team_info_dto.password,
auto_invite_flag=team_info_dto.auto_invite_flag,
)
async def handle_kick_member_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3TeamKickMember:
req = RequestSeason3TeamKickMember.model_validate(data)
user_id = headers["session"]
# this could use teh function is_team_leader but we need to check if the user is a member of the team first
leader_member = await self.team_service.get_team_member_dto(user_id)
if not leader_member or leader_member.role != TeamRoleEnum.LEADER:
# check if the user is a team leader
self.logger.warning(
f"User {user_id} tried to kick member from team but is not a member or leader!"
)
return ResponseSeason3TeamKickMember(
control_result=1,
team_point=0,
)
# check if the user is trying to kick themselves
if req.id == user_id:
self.logger.warning(
f"User {user_id} tried to kick themselves from the team!"
)
return ResponseSeason3TeamKickMember(
control_result=1,
team_point=0,
)
# check if the user is in the same team as the leader
team_member = await self.team_service.get_team_member_dto(req.id)
if not team_member or team_member.team_id != leader_member.team_id:
self.logger.warning(
f"User {user_id} tried to kick member from team but the member is not in the same team!"
)
return ResponseSeason3TeamKickMember(
control_result=1,
team_point=0,
)
left_result = await self.team_service.leave_team(req.id)
if not left_result:
# failed to kick member from team
return ResponseSeason3TeamKickMember(
control_result=1,
team_point=0,
)
# return the new leage info team points
league_info = await self.team_service.get_league_info_dto(user_id)
return ResponseSeason3TeamKickMember(
control_result=0,
team_point=league_info.league_point,
)
async def handle_change_leader_request(
self, data: Dict, headers: Dict
) -> ResponsSeason3TeamChangeLeader:
req = RequestSeason3TeamChangeLeader.model_validate(data)
user_id = headers["session"]
if not await self.team_service.is_team_leader(user_id):
self.logger.warning(
f"User {user_id} tried to change team leader but is not the leader!"
)
return ResponsSeason3TeamChangeLeader(control_result=1)
success = await self.team_service.change_team_leader(user_id, req.id)
return ResponsSeason3TeamChangeLeader(
control_result=0 if success else 1,
)
async def handle_change_password_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3TeamChangePassword:
user_id = headers["session"]
if not await self.team_service.is_team_leader(user_id):
self.logger.warning(
f"User {user_id} tried to change team password but is not the leader!"
)
return ResponseSeason3TeamChangePassword(
control_result=1,
password="",
)
password = await self.team_service.change_team_password(user_id)
return ResponseSeason3TeamChangePassword(
control_result=0,
password=password,
)
async def handle_get_team_info_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3GetTeamInfoData:
user_id = headers["session"]
member_data = await self.team_service.get_team_member_data(user_id)
past_league_data = await self.team_service.get_past_league_data(user_id)
return ResponseSeason3GetTeamInfoData(
member_data=member_data,
past_league_data=past_league_data,
)
async def handle_get_team_home_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3GetTeamHomeData:
user_id = headers["session"]
team_member_cars = await self.team_service.get_team_member_cars(user_id)
return ResponseSeason3GetTeamHomeData(team_member_cars=team_member_cars)
async def handle_get_sticker_stock_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3TeamGetTeamLogoStock:
user_id = headers["session"]
sticker_stock = await self.team_service.get_team_logo_stock(user_id)
return ResponseSeason3TeamGetTeamLogoStock(
sticker_stock=sticker_stock,
)
async def handle_update_sticker_data_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3TeamUpdateTeamLogoData:
req = RequestSeason3TeamUpdateTeamLogoData.model_validate(data)
user_id = headers["session"]
if not await self.team_service.is_team_leader(user_id):
self.logger.warning(
f"User {user_id} tried to update team logo but is not the leader!"
)
return ResponseSeason3TeamUpdateTeamLogoData(
control_result=1,
sticker_id=0,
)
sticker_id = await self.team_service.update_team_logo(user_id, req.sticker_id)
return ResponseSeason3TeamUpdateTeamLogoData(
control_result=0,
sticker_id=sticker_id,
)
async def handle_get_recommend_team_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3TeamGetRecommendTeam:
user_id = int(headers["session"])
recommend_teams = await self.team_service.get_recommend_teams(user_id)
return ResponseSeason3TeamGetRecommendTeam(
join_status=JoinStatusEnum.SUCCESS,
recommend_team_list=recommend_teams,
)
async def handle_update_team_option_request(
self, data: Dict, headers: Dict
) -> ResponseSeason3TeamUpdateTeamOption:
req = RequestSeason3TeamUpdateTeamOption.model_validate(data)
user_id = headers["session"]
if not await self.team_service.is_team_leader(user_id):
self.logger.warning(
f"User {user_id} tried to update team options but is not the leader!"
)
return ResponseSeason3TeamUpdateTeamOption(
control_result=1,
auto_invite_flag=0,
)
auto_invite_flag = await self.team_service.update_team_option(
user_id, req.auto_invite_flag
)
return ResponseSeason3TeamUpdateTeamOption(
control_result=0,
auto_invite_flag=auto_invite_flag,
)
@@ -0,0 +1,80 @@
from typing import Dict
from pydantic_core import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.controller.season2.theory_controller import IDACSeason2TheoryController
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.model.season3.common_model import RequestSeason3UserHeaders
from titles.idac.model.season3.theory_model import (
RequestSeason3TheoryMatching,
ResponseSeason3TheoryMatching,
Season3PowerMatch,
)
from titles.idac.service.season3.factory_service import IDACSeason3FactoryService
from titles.idac.service.season3.team_service import IDACSeason3TeamService
from titles.idac.service.season3.theory_service import IDACSeason3TheoryService
from titles.idac.service.season3.user_service import IDACSeason3UserService
class IDACSeason3TheoryController(IDACSeason2TheoryController):
RequestTheoryMatching = RequestSeason3TheoryMatching
ResponseTheoryMatching = ResponseSeason3TheoryMatching
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
user_service: IDACSeason3UserService,
theory_service: IDACSeason3TheoryService,
factory_service: IDACSeason3FactoryService,
team_service: IDACSeason3TeamService,
) -> None:
super().__init__(
core_cfg,
server_cfg,
game_cfg,
user_service,
theory_service,
factory_service,
)
self.version = IDACConstants.VER_IDAC_SEASON_3
self.team_service = team_service
async def handle_matching_request(
self, data: Dict, headers: Dict
) -> ResponseTheoryMatching:
req_headers = RequestSeason3UserHeaders.model_validate(headers)
req = self.RequestTheoryMatching.model_validate(data)
resp: ResponseSeason3TheoryMatching = await super().handle_matching_request(
data, headers
)
# iterate over all auto match rivals
await self.set_team_to_rival_data(resp.rival_data.auto_match)
# iterate over all power match data
await self.set_team_to_rival_data(resp.rival_data.power_match)
return resp
async def set_team_to_rival_data(self, rival_data: list[Season3PowerMatch]):
for rival in rival_data:
rival_id = rival.id
# do not set a team for a CPU rival
if rival_id == -1:
continue
# check if they part of a team
team_dto = await self.team_service.get_team_info_dto(rival_id)
if team_dto:
rival.stickerid = team_dto.sticker_id
rival.teamName = team_dto.name
# get the league rank
league_info_dto = await self.team_service.get_league_info_dto(rival_id)
rival.league_rank_id = league_info_dto.league_rank_id
@@ -0,0 +1,21 @@
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.controller.season2.time_trial_controller import (
IDACSeason2TimeTrialController,
)
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.service.season3.time_trial_service import IDACSeason3TimeTrialService
class IDACSeason3TimeTrialController(IDACSeason2TimeTrialController):
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
time_trial_service: IDACSeason3TimeTrialService,
) -> None:
super().__init__(core_cfg, server_cfg, game_cfg, time_trial_service)
self.version = IDACConstants.VER_IDAC_SEASON_3
@@ -0,0 +1,296 @@
from typing import Dict
from core.config import CoreConfig
from titles.idac.config import IDACServerConfig
from titles.idac.const import IDACConstants
from titles.idac.controller.season2.user_controller import IDACSeason2UserController
from titles.idac.gameconfig.season3_config import IDACSeason3Config
from titles.idac.model.season2.common_model import RequestSeason2UserHeaders
from titles.idac.model.season3.common_model import RequestSeason3UserHeaders
from titles.idac.model.season3.user_model import *
from titles.idac.service.season2.factory_service import IDACSeason2FactoryService
from titles.idac.service.season3.arcade_service import IDACSeason3ArcadeService
from titles.idac.service.season3.team_service import IDACSeason3TeamService
from titles.idac.service.season3.time_trial_service import IDACSeason3TimeTrialService
from titles.idac.service.season3.user_service import IDACSeason3UserService
class IDACSeason3UserController(IDACSeason2UserController):
RequestUserCreateAccount = RequestSeason3UserCreateAccount
ResponseUserCreateAccount = ResponseSeason3UserCreateAccount
RequestUserGetData = RequestSeason3UserGetData
ResponseUserGetData = ResponseSeason3UserGetData
RequestUserUpdateLogin = RequestSeason3UserUpdateLogin
ResponseUserUpdateLogin = ResponseSeason3UserUpdateLogin
RequestUserUpdateTimeTrialResult = RequestSeason3UserUpdateTimeTrialResult
ResponseUserUpdateTimeTrialResult = ResponseSeason3UserUpdateTimeTrialResult
RequestUserUpdateStoryResult = RequestSeason3UserUpdateStoryResult
ResponseUserUpdateStoryResult = ResponseSeason3UserUpdateStoryResult
RequestUserUpdateSpecialResult = RequestSeason3UserUpdateSpecialResult
ResponseUserUpdateSpecialResult = ResponseSeason3UserUpdateSpecialResult
RequestUserUpdateTheoryResult = RequestSeason3UserUpdateTheoryResult
ResponseUserUpdateTheoryResult = ResponseSeason3UserUpdateTheoryResult
RequestUserUpdateChallengeModeResult = RequestSeason3UserUpdateChallengeModeResult
ResponseUserUpdateChallengeModeResult = ResponseSeason3UserUpdateChallengeModeResult
RequestUserUpdateStoreBattleResult = RequestSeason3UserUpdateStoreBattleResult
ResponseUserUpdateStoreBattleResult = ResponseSeason3UserUpdateStoreBattleResult
RequestUserUpdateOnlineBattleResult = RequestSeason3UserUpdateOnlineBattleResult
ResponseUserUpdateOnlineBattleResult = ResponseSeason3UserUpdateOnlineBattleResult
def __init__(
self,
core_cfg: CoreConfig,
server_cfg: IDACServerConfig,
game_cfg: IDACSeason3Config,
arcade_service: IDACSeason3ArcadeService,
user_service: IDACSeason3UserService,
time_trial_service: IDACSeason3TimeTrialService,
factory_service: IDACSeason2FactoryService,
team_service: IDACSeason3TeamService,
) -> None:
super().__init__(
core_cfg,
server_cfg,
game_cfg,
arcade_service,
user_service,
time_trial_service,
factory_service,
)
self.version = IDACConstants.VER_IDAC_SEASON_3
self.team_service = team_service
def _special_story_type(self, headers: RequestSeason2UserHeaders) -> StoryTypeEnum:
ver = self._headers_to_version(headers)
return {210: StoryTypeEnum.HOT_VERSION}.get(ver, StoryTypeEnum.NONE)
async def handle_get_data_request(
self, data: Dict, headers: Dict
) -> ResponseUserGetData:
req = self.RequestUserGetData.model_validate(data)
req_headers = RequestSeason3UserHeaders.model_validate(headers)
resp: ResponseSeason3UserGetData = await super().handle_get_data_request(
data, headers
)
user_id = req_headers.session
team_info = await self.team_service.get_team_info(user_id)
league_info = None
last_league_info = []
league_rewards = []
if team_info:
league_info = await self.team_service.get_league_info(
team_info.team_id, user_id
)
# check if the user already claimed the last league rewards
claimed_rewards = await self.team_service.claimed_league_rewards(user_id)
if not claimed_rewards:
# get past league info
last_league_info = await self.team_service.get_past_league_info(team_info.team_id, user_id)
# generate league rewards based on the last league
league_rewards = self.team_service.generate_league_rewards(last_league_info)
resp.team_info = team_info
resp.league_info = league_info
resp.league_rewards = league_rewards
if last_league_info:
# needs to be alist
resp.last_league_info = [last_league_info]
return resp
async def handle_update_login_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateLogin:
req_headers = RequestSeason3UserHeaders.model_validate(headers)
req = self.RequestUserUpdateLogin.model_validate(data)
resp: ResponseSeason3UserUpdateLogin = (
await super().handle_update_login_request(data, headers)
)
user_id = req_headers.session
# mark league rewards as received
if len(req.league_rewards_id_arr) > 1:
await self.team_service.update_past_league_rewards(user_id)
return resp
async def handle_update_time_trial_result_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateTimeTrialResult:
req = self.RequestUserUpdateTimeTrialResult.model_validate(data)
req_headers = RequestSeason3UserHeaders.model_validate(headers)
resp: ResponseSeason3UserUpdateTimeTrialResult = (
await super().handle_update_time_trial_result_request(data, headers)
)
user_id = req_headers.session
update_result = await self.team_service.update_league_info(
user_id, Season3UpdateRequestTeam.model_validate(req)
)
resp.league_info = update_result.league_info
resp.promote_effect_param = update_result.promote_effect_param
resp.team_daily_bonus = update_result.team_daily_bonus
resp.team_weekly_bonus = update_result.team_weekly_bonus
return resp
async def handle_update_story_result_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateStoryResult:
req_headers = RequestSeason3UserHeaders.model_validate(headers)
req = self.RequestUserUpdateStoryResult.model_validate(data)
resp: ResponseSeason3UserUpdateStoryResult = (
await super().handle_update_story_result_request(data, headers)
)
user_id = req_headers.session
update_result = await self.team_service.update_league_info(
user_id, Season3UpdateRequestTeam.model_validate(req)
)
resp.league_info = update_result.league_info
resp.promote_effect_param = update_result.promote_effect_param
resp.team_daily_bonus = update_result.team_daily_bonus
resp.team_weekly_bonus = update_result.team_weekly_bonus
return resp
async def handle_update_special_mode_result_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateSpecialResult:
req_headers = RequestSeason3UserHeaders.model_validate(headers)
req = self.RequestUserUpdateSpecialResult.model_validate(data)
resp: ResponseSeason3UserUpdateSpecialResult = (
await super().handle_update_special_mode_result_request(data, headers)
)
user_id = req_headers.session
# rental flag is actually set in season 2 to not duplicate code here
update_result = await self.team_service.update_league_info(
user_id, Season3UpdateRequestTeam.model_validate(req)
)
resp.league_info = update_result.league_info
resp.promote_effect_param = update_result.promote_effect_param
resp.team_daily_bonus = update_result.team_daily_bonus
resp.team_weekly_bonus = update_result.team_weekly_bonus
return resp
async def handle_update_theory_result_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateTheoryResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestUserUpdateTheoryResult.model_validate(data)
resp: ResponseSeason3UserUpdateTheoryResult = (
await super().handle_update_theory_result_request(data, headers)
)
user_id = req_headers.session
update_result = await self.team_service.update_league_info(
user_id, Season3UpdateRequestTeam.model_validate(req)
)
resp.league_info = update_result.league_info
resp.promote_effect_param = update_result.promote_effect_param
resp.team_daily_bonus = update_result.team_daily_bonus
resp.team_weekly_bonus = update_result.team_weekly_bonus
return resp
async def handle_update_challenge_mode_result_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateChallengeModeResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestUserUpdateChallengeModeResult.model_validate(data)
resp: ResponseSeason3UserUpdateChallengeModeResult = (
await super().handle_update_challenge_mode_result_request(data, headers)
)
user_id = req_headers.session
update_result = await self.team_service.update_league_info(
user_id, Season3UpdateRequestTeam.model_validate(req)
)
resp.league_info = update_result.league_info
resp.promote_effect_param = update_result.promote_effect_param
resp.team_daily_bonus = update_result.team_daily_bonus
resp.team_weekly_bonus = update_result.team_weekly_bonus
return resp
async def handle_update_store_battle_result_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateStoreBattleResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestUserUpdateStoreBattleResult.model_validate(data)
resp: ResponseSeason3UserUpdateStoreBattleResult = (
await super().handle_update_store_battle_result_request(data, headers)
)
user_id = req_headers.session
update_result = await self.team_service.update_league_info(
user_id, Season3UpdateRequestTeam.model_validate(req)
)
resp.league_info = update_result.league_info
resp.promote_effect_param = update_result.promote_effect_param
resp.team_daily_bonus = update_result.team_daily_bonus
resp.team_weekly_bonus = update_result.team_weekly_bonus
return resp
async def handle_update_online_battle_result_request(
self, data: Dict, headers: Dict
) -> ResponseUserUpdateOnlineBattleResult:
req_headers = RequestSeason2UserHeaders.model_validate(headers)
req = self.RequestUserUpdateOnlineBattleResult.model_validate(data)
resp: ResponseSeason3UserUpdateOnlineBattleResult = (
await super().handle_update_online_battle_result_request(data, headers)
)
user_id = req_headers.session
update_result = await self.team_service.update_league_info(
user_id, Season3UpdateRequestTeam.model_validate(req)
)
resp.league_info = update_result.league_info
resp.promote_effect_param = update_result.promote_effect_param
resp.team_daily_bonus = update_result.team_daily_bonus
resp.team_weekly_bonus = update_result.team_weekly_bonus
return resp
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,632 @@
[
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 6000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 12000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 18000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 24000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 30000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 40000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 60000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 80000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 0,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 100000,
"reward_category": 34,
"reward_type": 2,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 6000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 12000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 18000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 24000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 30000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 40000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 60000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 80000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 1,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 100000,
"reward_category": 34,
"reward_type": 2,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 6000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 12000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 18000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 24000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 30000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 40000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 60000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 80000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 2,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 100000,
"reward_category": 34,
"reward_type": 2,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 6000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 12000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 18000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 24000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 30000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 40000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 60000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 80000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 3,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 100000,
"reward_category": 34,
"reward_type": 2,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 6000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 12000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 18000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 24000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 30000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 40000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 60000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 80000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 4,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 100000,
"reward_category": 34,
"reward_type": 2,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 6000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 12000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 18000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 24000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 30000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 40000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 60000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 80000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 5,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 100000,
"reward_category": 34,
"reward_type": 2,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 6000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 12000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 18000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 24000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 30000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 40000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 60000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 80000,
"reward_category": 34,
"reward_type": 1,
"reward_reason": "チームポイント報酬"
},
{
"league_rank_id": 6,
"target_type": 0,
"rank_start": 0,
"rank_end": 0,
"team_point": 100000,
"reward_category": 34,
"reward_type": 2,
"reward_reason": "チームポイント報酬"
}
]
+258
View File
@@ -0,0 +1,258 @@
{
"round_event_id": 31,
"round_event_nm": "シーズン3 OPENラウンド",
"round_start_rank": 11,
"random_hidden_flag": 0,
"save_filename": "230417_round_01_S3_1st",
"vscount": [
{
"reward_upper_limit": 10,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 21,
"reward_type": 34
}
]
},
{
"reward_upper_limit": 40,
"reward_lower_limit": 40,
"reward": [
{
"reward_category": 21,
"reward_type": 35
}
]
},
{
"reward_upper_limit": 60,
"reward_lower_limit": 60,
"reward": [
{
"reward_category": 7,
"reward_type": 9
}
]
},
{
"reward_upper_limit": 120,
"reward_lower_limit": 120,
"reward": [
{
"reward_category": 21,
"reward_type": 317
}
]
},
{
"reward_upper_limit": 160,
"reward_lower_limit": 160,
"reward": [
{
"reward_category": 21,
"reward_type": 370
}
]
}
],
"rank": [
{
"reward_upper_limit": 1,
"reward_lower_limit": 1,
"reward": [
{
"reward_category": 24,
"reward_type": 4627
}
]
},
{
"reward_upper_limit": 2,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 24,
"reward_type": 4628
}
]
},
{
"reward_upper_limit": 11,
"reward_lower_limit": 50,
"reward": [
{
"reward_category": 24,
"reward_type": 4629
}
]
},
{
"reward_upper_limit": 51,
"reward_lower_limit": 100,
"reward": [
{
"reward_category": 24,
"reward_type": 4630
}
]
},
{
"reward_upper_limit": 101,
"reward_lower_limit": 1000,
"reward": [
{
"reward_category": 24,
"reward_type": 4631
}
]
}
],
"point": [],
"playable_course_list": [
{
"course_id": 8,
"course_day": 0
},
{
"course_id": 8,
"course_day": 1
},
{
"course_id": 10,
"course_day": 0
},
{
"course_id": 10,
"course_day": 1
},
{
"course_id": 12,
"course_day": 0
},
{
"course_id": 12,
"course_day": 1
},
{
"course_id": 14,
"course_day": 0
},
{
"course_id": 14,
"course_day": 1
},
{
"course_id": 16,
"course_day": 0
},
{
"course_id": 16,
"course_day": 1
},
{
"course_id": 18,
"course_day": 0
},
{
"course_id": 18,
"course_day": 1
},
{
"course_id": 20,
"course_day": 0
},
{
"course_id": 20,
"course_day": 1
},
{
"course_id": 22,
"course_day": 0
},
{
"course_id": 22,
"course_day": 1
},
{
"course_id": 36,
"course_day": 0
},
{
"course_id": 36,
"course_day": 1
},
{
"course_id": 38,
"course_day": 0
},
{
"course_id": 38,
"course_day": 1
},
{
"course_id": 40,
"course_day": 0
},
{
"course_id": 40,
"course_day": 1
},
{
"course_id": 42,
"course_day": 0
},
{
"course_id": 42,
"course_day": 1
},
{
"course_id": 44,
"course_day": 0
},
{
"course_id": 44,
"course_day": 1
},
{
"course_id": 46,
"course_day": 0
},
{
"course_id": 46,
"course_day": 1
},
{
"course_id": 56,
"course_day": 0
},
{
"course_id": 56,
"course_day": 1
},
{
"course_id": 58,
"course_day": 0
},
{
"course_id": 58,
"course_day": 1
},
{
"course_id": 60,
"course_day": 0
},
{
"course_id": 60,
"course_day": 1
},
{
"course_id": 62,
"course_day": 0
},
{
"course_id": 62,
"course_day": 1
}
]
}
+242
View File
@@ -0,0 +1,242 @@
{
"round_event_id": 32,
"round_event_nm": "シーズン3 2ndラウンド",
"round_start_rank": 11,
"random_hidden_flag": 0,
"save_filename": "230511_round_01_S3_2nd",
"vscount": [
{
"reward_upper_limit": 10,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 21,
"reward_type": 388
}
]
},
{
"reward_upper_limit": 40,
"reward_lower_limit": 40,
"reward": [
{
"reward_category": 21,
"reward_type": 520
}
]
},
{
"reward_upper_limit": 60,
"reward_lower_limit": 60,
"reward": [
{
"reward_category": 7,
"reward_type": 11
}
]
},
{
"reward_upper_limit": 120,
"reward_lower_limit": 120,
"reward": [
{
"reward_category": 21,
"reward_type": 117
}
]
},
{
"reward_upper_limit": 160,
"reward_lower_limit": 160,
"reward": [
{
"reward_category": 21,
"reward_type": 145
}
]
}
],
"rank": [
{
"reward_upper_limit": 1,
"reward_lower_limit": 1,
"reward": [
{
"reward_category": 24,
"reward_type": 4632
}
]
},
{
"reward_upper_limit": 2,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 24,
"reward_type": 4633
}
]
},
{
"reward_upper_limit": 11,
"reward_lower_limit": 50,
"reward": [
{
"reward_category": 24,
"reward_type": 4634
}
]
},
{
"reward_upper_limit": 51,
"reward_lower_limit": 100,
"reward": [
{
"reward_category": 24,
"reward_type": 4635
}
]
},
{
"reward_upper_limit": 101,
"reward_lower_limit": 1000,
"reward": [
{
"reward_category": 24,
"reward_type": 4636
}
]
}
],
"point": [],
"playable_course_list": [
{
"course_id": 4,
"course_day": 0
},
{
"course_id": 4,
"course_day": 1
},
{
"course_id": 6,
"course_day": 0
},
{
"course_id": 6,
"course_day": 1
},
{
"course_id": 12,
"course_day": 0
},
{
"course_id": 12,
"course_day": 1
},
{
"course_id": 14,
"course_day": 0
},
{
"course_id": 14,
"course_day": 1
},
{
"course_id": 16,
"course_day": 0
},
{
"course_id": 16,
"course_day": 1
},
{
"course_id": 18,
"course_day": 0
},
{
"course_id": 18,
"course_day": 1
},
{
"course_id": 28,
"course_day": 0
},
{
"course_id": 28,
"course_day": 1
},
{
"course_id": 30,
"course_day": 0
},
{
"course_id": 30,
"course_day": 1
},
{
"course_id": 56,
"course_day": 0
},
{
"course_id": 56,
"course_day": 1
},
{
"course_id": 58,
"course_day": 0
},
{
"course_id": 58,
"course_day": 1
},
{
"course_id": 60,
"course_day": 0
},
{
"course_id": 60,
"course_day": 1
},
{
"course_id": 62,
"course_day": 0
},
{
"course_id": 62,
"course_day": 1
},
{
"course_id": 68,
"course_day": 0
},
{
"course_id": 68,
"course_day": 1
},
{
"course_id": 70,
"course_day": 0
},
{
"course_id": 70,
"course_day": 1
},
{
"course_id": 72,
"course_day": 0
},
{
"course_id": 72,
"course_day": 1
},
{
"course_id": 74,
"course_day": 0
},
{
"course_id": 74,
"course_day": 1
}
]
}
File diff suppressed because it is too large Load Diff
+20 -8
View File
@@ -1,16 +1,28 @@
from core.data import Data
from core.config import CoreConfig
from titles.idac.schema.profile import IDACProfileData
from titles.idac.schema.item import IDACItemData
from titles.idac.schema.rounds import IDACOnlineRounds
from titles.idac.schema.factory import IDACFactoryData
from titles.idac.schema.challenge_db import IDACChallengeDb
from titles.idac.schema.profile_db import IDACProfileDb
from titles.idac.schema.item_db import IDACItemDb
from titles.idac.schema.round_db import IDACRoundDb
from titles.idac.schema.factory_db import IDACFactoryDb
from titles.idac.schema.story_db import IDACStoryDb
from titles.idac.schema.team_db import IDACTeamDb
from titles.idac.schema.theory_db import IDACTheoryDb
from titles.idac.schema.time_trial_db import IDACTimeTrialDb
from titles.idac.schema.vs_info_db import IDACVsInfoDb
class IDACData(Data):
def __init__(self, cfg: CoreConfig) -> None:
super().__init__(cfg)
self.profile = IDACProfileData(cfg, self.session)
self.item = IDACItemData(cfg, self.session)
self.rounds = IDACOnlineRounds(cfg, self.session)
self.factory = IDACFactoryData(cfg, self.session)
self.profile = IDACProfileDb(cfg, self.session)
self.story = IDACStoryDb(cfg, self.session)
self.time_trial = IDACTimeTrialDb(cfg, self.session)
self.vs_info = IDACVsInfoDb(cfg, self.session)
self.theory = IDACTheoryDb(cfg, self.session)
self.challenge = IDACChallengeDb(cfg, self.session)
self.item = IDACItemDb(cfg, self.session)
self.round = IDACRoundDb(cfg, self.session)
self.factory = IDACFactoryDb(cfg, self.session)
self.team = IDACTeamDb(cfg, self.session)
+1 -1
View File
@@ -14,7 +14,7 @@ class IDACEchoUDP:
self.transport = transport
def datagram_received(self, data, addr):
logging.getLogger('idz').debug(f'Received echo from {addr}')
logging.getLogger('idac').debug(f'Received echo from {addr}')
self.transport.sendto(data, addr)
+348 -254
View File
@@ -3,7 +3,10 @@ import yaml
import jinja2
from os import path
from typing import List, Any, Type
from pydantic import BaseModel, Field, ValidationError, model_validator
from typing import Dict, List, Any, Optional, Tuple
from sqlalchemy import select
from sqlalchemy.engine import Row
from starlette.routing import Route, Mount
from starlette.responses import Response, RedirectResponse, JSONResponse
from starlette.requests import Request
@@ -11,102 +14,98 @@ from starlette.requests import Request
from core.frontend import FE_Base, UserSession
from core.config import CoreConfig
from titles.idac.database import IDACData
from titles.idac.schema.profile import *
from titles.idac.schema.item import *
from titles.idac.config import IDACConfig
from titles.idac.const import IDACConstants
from titles.idac.schema.model.challenge import ChallengeDb
from titles.idac.schema.model.factory import CarDb
from titles.idac.schema.model.item import (
BattleGiftDb,
StampDb,
TicketDb,
TimeTrialEventDb,
)
from titles.idac.schema.model.profile import (
ProfileAvatarDb,
ProfileConfigDb,
ProfileDb,
ProfileRankDb,
ProfileStockDb,
ProfileTheoryDb,
ProfileTipsDb,
)
from titles.idac.schema.model.story import (
StoryDb,
StoryEpisodeDb,
StoryEpisodeDifficultyDb,
)
from titles.idac.schema.model.theory import (
TheoryCourseDb,
TheoryPartnerDb,
TheoryRunningDb,
)
from titles.idac.schema.model.time_trial import CourseDb, TrialDb
from titles.idac.schema.model.vs_info import VsInfoDb
from titles.idac.service.season3.time_trial_service import IDACSeason3TimeTrialService
class RankingData:
def __init__(
self,
rank: int,
name: str,
record: int,
eval_id: int,
store: str,
style_car_id: int,
update_date: str,
) -> None:
self.rank: int = rank
self.name: str = name
self.record: str = record
self.store: str = store
self.eval_id: int = eval_id
self.style_car_id: int = style_car_id
self.update_date: str = update_date
def make(self):
return vars(self)
class RankingData(BaseModel):
rank: int
name: str
record: str
eval_id: int
store: str
style_car_id: int
update_date: str
class RequestValidator:
def __init__(self) -> None:
self.success: bool = True
self.error: str = ""
def validate_param(
self,
request_args: Dict[bytes, bytes],
param_name: str,
param_type: Type[None],
default=None,
required: bool = True,
) -> None:
# Check if the parameter is missing
if param_name not in request_args:
if required:
self.success = False
self.error += f"Missing parameter: '{param_name}'. "
else:
# If the parameter is not required,
# return the default value if it exists
return default
return None
param_value = request_args[param_name]
# Check if the parameter type is not empty
if param_type:
try:
# Attempt to convert the parameter value to the specified type
param_value = param_type(param_value)
except ValueError:
# If the conversion fails, return an error
self.success = False
self.error += f"Invalid parameter type for '{param_name}'. "
return None
return param_value
class RankingRequest(BaseModel):
course_id: int = Field(alias="courseId")
page_number: int = Field(default=1, alias="pageNumber")
version: int = Field(default=1, alias="season")
class RankingRequest(RequestValidator):
def __init__(self, request_args: Dict[bytes, bytes]) -> None:
super().__init__()
class RankingResponse(BaseModel):
success: bool = False
error: str = ""
total_pages: int = 0
total_records: int = 0
updated_at: str = ""
ranking: List[RankingData] = Field(default_factory=list)
self.course_id: int = self.validate_param(request_args, "courseId", int)
self.page_number: int = self.validate_param(
request_args, "pageNumber", int, default=1, required=False
)
@model_validator(mode="after")
def set_default_error(self) -> "RankingResponse":
# Emulates your original error fallback logic
if not self.success and not self.error:
self.error = "Unknown error."
return self
class RankingResponse:
def __init__(self) -> None:
self.success: bool = False
self.error: str = ""
self.total_pages: int = 0
self.total_records: int = 0
self.updated_at: str = ""
self.ranking: list[RankingData] = []
class ProfileRankingData(BaseModel):
course: int
rank: int
record: str
eval_id: int
style_car_id: int
update_date: str
def make(self):
ret = vars(self)
self.error = (
"Unknown error." if not self.success and self.error == "" else self.error
)
ret["ranking"] = [rank.make() for rank in self.ranking]
return ret
class ProfileRankingRequest(BaseModel):
version: int = Field(default=1, alias="season")
class ProfileRankingResponse(BaseModel):
success: bool = False
error: str = ""
updated_at: str = ""
ranking: List[ProfileRankingData] = Field(default_factory=list)
@model_validator(mode="after")
def set_default_error(self) -> "RankingResponse":
# Emulates your original error fallback logic
if not self.success and not self.error:
self.error = "Unknown error."
return self
class IDACFrontend(FE_Base):
isLeaf = False
@@ -119,35 +118,43 @@ class IDACFrontend(FE_Base):
self.data = IDACData(cfg)
self.core_cfg = cfg
self.game_cfg = IDACConfig()
if path.exists(f"{cfg_dir}/{IDACConstants.CONFIG_NAME}"):
self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{IDACConstants.CONFIG_NAME}"))
)
self.nav_name = "頭文字D THE ARCADE"
# self.nav_name = "IDAC"
# TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2
self.profile = IDACProfileFrontend(cfg, self.environment)
self.ranking = IDACRankingFrontend(cfg, self.environment)
self.profile = IDACProfileFrontend(cfg, self.game_cfg, self.environment)
self.ranking = IDACRankingFrontend(cfg, self.game_cfg, self.environment)
def get_routes(self) -> List[Route]:
return [
Route("/", self.render_GET),
Mount("/profile", routes=[
Route("/", self.profile.render_GET),
# dirty hack
Route("/export.get", self.profile.render_GET),
]),
Mount("/ranking", routes=[
Route("/", self.ranking.render_GET),
# dirty hack
Route("/const.get", self.ranking.render_GET),
Route("/ranking.get", self.ranking.render_GET),
]),
Mount(
"/profile",
routes=[
Route("/", self.profile.render_GET),
Route("/ranking.get", self.profile.handle_get_profile_ranking),
Route("/export.get", self.profile.handle_export_profile),
],
),
Mount(
"/ranking",
routes=[
Route("/", self.ranking.render_GET),
Route("/const.get", self.ranking.handle_get_const),
Route("/ranking.get", self.ranking.handle_get_ranking),
],
),
]
async def render_GET(self, request: Request) -> bytes:
async def render_GET(self, request: Request) -> Response:
uri: str = request.url.path
template = self.environment.get_template(
@@ -161,129 +168,153 @@ class IDACFrontend(FE_Base):
if uri.startswith("/game/idac"):
return RedirectResponse("/game/idac/ranking", 303)
return Response(template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
active_page="idac",
), media_type="text/html; charset=utf-8")
return Response(
template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
active_page="idac",
),
media_type="text/html; charset=utf-8",
)
async def render_POST(self, request: Request) -> bytes:
pass
class IDACRankingFrontend(FE_Base):
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
def __init__(
self, cfg: CoreConfig, game_cfg: IDACConfig, environment: jinja2.Environment
) -> None:
super().__init__(cfg, environment)
self.data = IDACData(cfg)
self.core_cfg = cfg
self.game_cfg = game_cfg
self.nav_name = "頭文字D THE ARCADE"
# TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2
async def render_GET(self, request: Request) -> bytes:
uri: str = request.url.path
self.const = {"car": [], "course": []}
with open("titles/idac/templates/const.json", "r", encoding="utf-8") as f:
self.const = json.load(f)
self.available_seasons = [
{"value": i, "name": season[season.find("Season") :]}
for i, season in enumerate(IDACConstants.VERSION_STRING[1:], start=1)
]
self.version = IDACConstants.VER_IDAC_SEASON_3
async def render_GET(self, request: Request) -> Response:
template = self.environment.get_template(
"titles/idac/templates/ranking/index.jinja"
)
usr_sesh = self.validate_session(request)
if not usr_sesh:
usr_sesh = UserSession()
user_id = usr_sesh.user_id
# IDAC constants
if uri.startswith("/game/idac/ranking/const.get"):
# get the constants
with open("titles/idac/templates/const.json", "r", encoding="utf-8") as f:
constants = json.load(f)
return Response(
template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
active_page="idac",
active_tab="ranking",
available_seasons=self.available_seasons,
current_season=self.version,
),
media_type="text/html; charset=utf-8",
)
return JSONResponse(constants)
async def handle_get_const(self, request: Request) -> JSONResponse:
return JSONResponse(self.const)
# leaderboard ranking
elif uri.startswith("/game/idac/ranking/ranking.get"):
req = RankingRequest(request.query_params._dict)
resp = RankingResponse()
async def handle_get_ranking(self, request: Request) -> JSONResponse:
resp = RankingResponse()
if not req.success:
resp.error = req.error
return JSONResponse(resp.make())
try:
# Let Pydantic validate the Starlette query parameters automatically
req = RankingRequest(**request.query_params)
except ValidationError as e:
# If required params are missing or wrong types, catch it here
resp.error = "Invalid or missing parameters."
return JSONResponse(resp.model_dump())
# get the total number of records
total_records = await self.data.item.get_time_trial_ranking_by_course_total(
self.version, req.course_id
# get the total number of records
total_records = (
await self.data.time_trial.get_time_trial_ranking_by_course_total(
req.version, req.course_id
)
# return an error if there are no records
if total_records is None or total_records == 0:
resp.error = "No records found."
return JSONResponse(resp.make())
)
# get the total number of records
total = total_records["count"]
# return an error if there are no records
if total_records == 0:
resp.error = "No records found."
return JSONResponse(resp.model_dump())
limit = 50
offset = (req.page_number - 1) * limit
total = total_records
limit = 50
offset = (req.page_number - 1) * limit
ranking = await self.data.item.get_time_trial_ranking_by_course(
self.version,
req.course_id,
limit=limit,
offset=offset,
)
ranking = await self.data.time_trial.get_time_trial_ranking_by_course(
req.version,
req.course_id,
limit=limit,
offset=offset,
)
for i, rank in enumerate(ranking):
user_id = rank["user"]
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"])
# get the username, country and store from the profile
profile = await self.data.profile.get_profile(user_id, req.version)
arcade = await self.data.arcade.get_arcade(profile.store)
if arcade is None:
arcade = {}
arcade["name"] = self.core_config.server.name
if arcade is None:
arcade = {"name": self.core_config.server.name}
# should never happen
if profile is None:
continue
if profile is None:
continue
resp.ranking.append(
RankingData(
rank=offset + i + 1,
name=profile["username"],
record=rank["goal_time"],
store=arcade["name"],
eval_id=rank["eval_id"],
style_car_id=rank["style_car_id"],
update_date=str(rank["play_dt"]),
)
resp.ranking.append(
RankingData(
rank=offset + i + 1,
name=profile.username,
record=str(rank.goal_time), # cast to str just to be safe
store=arcade["name"],
eval_id=rank.eval_id,
style_car_id=rank.style_car_id,
update_date=str(rank.play_dt),
)
)
# now return the json data, with the total number of pages and records
# round up the total pages
resp.success = True
resp.total_pages = (total // limit) + 1
resp.total_records = total
return JSONResponse(resp.make())
# finalize and serialize the response using model_dump()
resp.success = True
resp.total_pages = (total // limit) + 1
resp.total_records = total
return Response(template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
active_page="idac",
active_tab="ranking",
), media_type="text/html; charset=utf-8")
return JSONResponse(resp.model_dump())
class IDACProfileFrontend(FE_Base):
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
def __init__(
self, cfg: CoreConfig, game_cfg: IDACConfig, environment: jinja2.Environment
) -> None:
super().__init__(cfg, environment)
self.data = IDACData(cfg)
self.core_cfg = cfg
self.game_cfg = game_cfg
self.nav_name = "頭文字D THE ARCADE"
# TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2
self.no_user_redirect = "/user/"
self.available_seasons = [
{"value": i, "name": season[season.find("Season") :]}
for i, season in enumerate(IDACConstants.VERSION_STRING[1:], start=1)
]
self.version = IDACConstants.VER_IDAC_SEASON_3
self.ticket_names = {
3: "car_dressup_points",
@@ -292,87 +323,89 @@ class IDACProfileFrontend(FE_Base):
34: "full_tune_fragments",
}
self.time_trial_service = IDACSeason3TimeTrialService(
self.core_cfg, self.game_cfg.server, self.game_cfg.season3
)
async def get_validated_user(self, request: Request) -> Tuple[UserSession, Optional[Row]]:
"""Helper to get session and user, returning None for user if not found."""
usr_sesh = self.validate_session(request) or UserSession()
user_id = usr_sesh.user_id
user = await self.data.user.get_user(user_id)
if user is None:
self.logger.debug(f"User {user_id} not found")
return usr_sesh, None
return usr_sesh, user
async def generate_all_tables_json(self, user_id: int):
json_export = {}
idac_tables = {
profile,
config,
avatar,
rank,
stock,
theory,
car,
ticket,
story,
episode,
difficulty,
course,
trial,
challenge,
theory_course,
theory_partner,
theory_running,
vs_info,
stamp,
timetrial_event,
ProfileDb,
ProfileConfigDb,
ProfileAvatarDb,
ProfileRankDb,
ProfileStockDb,
ProfileTheoryDb,
ProfileTipsDb,
CarDb,
TicketDb,
StoryDb,
StoryEpisodeDb,
StoryEpisodeDifficultyDb,
CourseDb,
TrialDb,
ChallengeDb,
TheoryCourseDb,
TheoryPartnerDb,
TheoryRunningDb,
VsInfoDb,
StampDb,
TimeTrialEventDb,
BattleGiftDb,
}
for table in idac_tables:
sql = select(table).where(
table.c.user == user_id,
for model in idac_tables:
sql = select(model).where(
model.user == user_id,
)
# check if the table has a version column
if "version" in table.c:
sql = sql.where(table.c.version == self.version)
# check if the model has a 'version' attribute
if hasattr(model, "version"):
sql = sql.where(model.version == self.version)
# lol use the profile connection for items, dirty hack
result = await self.data.profile.execute(sql)
data_list = result.fetchall()
# add the list to the json export with the correct table name
json_export[table.name] = []
for data in data_list:
tmp = data._asdict()
tmp.pop("id")
tmp.pop("user")
json_export[table.name].append(tmp)
data_list = result.scalars().all()
table_name = model.__tablename__
json_export[table_name] = []
for obj in data_list:
# obj.__dict__ contains SQLAlchemy's internal state (_sa_instance_state),
# so we filter out any keys starting with "_sa_"
tmp = {
k: v for k, v in obj.__dict__.items() if not k.startswith("_sa_")
}
tmp.pop("id", None)
tmp.pop("user", None)
json_export[table_name].append(tmp)
return json.dumps(json_export, indent=4, default=str, ensure_ascii=False)
async def render_GET(self, request: Request) -> bytes:
uri: str = request.url.path
async def render_GET(self, request: Request) -> Response:
template = self.environment.get_template(
"titles/idac/templates/profile/index.jinja"
)
usr_sesh = self.validate_session(request)
if not usr_sesh:
usr_sesh = UserSession()
user_id = usr_sesh.user_id
user = await self.data.user.get_user(user_id)
usr_sesh, user = await self.get_validated_user(request)
if user is None:
self.logger.debug(f"User {user_id} not found")
return RedirectResponse("/user/", 303)
# profile export
if uri.startswith("/game/idac/profile/export.get"):
if user_id == 0:
return RedirectResponse("/game/idac", 303)
# set the file name, content type and size to download the json
content = await self.generate_all_tables_json(user_id)
self.logger.info(f"User {user_id} exported their IDAC data")
return Response(
content.encode("utf-8"),
200,
{'content-disposition': 'attachment; filename=idac_profile.json'},
"application/octet-stream"
)
return RedirectResponse(self.no_user_redirect, 303)
user_id = usr_sesh.user_id
profile_data, tickets, rank = None, None, None
if user_id > 0:
@@ -382,18 +415,79 @@ class IDACProfileFrontend(FE_Base):
if ticket_data:
tickets = {
self.ticket_names[ticket["ticket_id"]]: ticket["ticket_cnt"]
self.ticket_names[ticket.ticket_id]: ticket.ticket_cnt
for ticket in ticket_data
}
return Response(template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
profile=profile_data,
tickets=tickets,
rank=rank,
sesh=vars(usr_sesh),
username=user["username"],
active_page="idac",
active_tab="profile",
), media_type="text/html; charset=utf-8")
return Response(
template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
profile=profile_data,
tickets=tickets,
rank=rank,
sesh=vars(usr_sesh),
username=user["username"],
available_seasons=self.available_seasons,
current_season=self.version,
active_page="idac",
active_tab="profile",
),
media_type="text/html; charset=utf-8",
)
async def handle_get_profile_ranking(
self, request: Request
) -> JSONResponse:
resp = ProfileRankingResponse()
usr_sesh, user = await self.get_validated_user(request)
if user is None:
return RedirectResponse(self.no_user_redirect, 303)
user_id = usr_sesh.user_id
try:
# Let Pydantic validate the Starlette query parameters automatically
req = ProfileRankingRequest(**request.query_params)
except ValidationError as e:
# If required params are missing or wrong types, catch it here
resp.error = "Invalid or missing parameters."
return JSONResponse(resp.model_dump())
season_time_trial_data = (
await self.time_trial_service.generate_season_time_trial_data(
user_id, req.version
)
)
resp.success = True
resp.ranking = [
ProfileRankingData(
course=time_trial.course_id,
rank=time_trial.rank,
record=str(time_trial.goal_time),
eval_id=time_trial.eval_id,
style_car_id=time_trial.style_car_id,
update_date=str(time_trial.rank_dt),
)
for time_trial in season_time_trial_data
]
return JSONResponse(resp.model_dump())
async def handle_export_profile(self, request: Request) -> Response:
usr_sesh, user = await self.get_validated_user(request)
if user is None:
return RedirectResponse(self.no_user_redirect, 303)
user_id = usr_sesh.user_id
# set the file name, content type and size to download the json
content = await self.generate_all_tables_json(user_id)
self.logger.info(f"User {user_id} exported their IDAC data")
return Response(
content.encode("utf-8"),
200,
{"content-disposition": "attachment; filename=idac_profile.json"},
"application/octet-stream",
)
+31
View File
@@ -0,0 +1,31 @@
from typing import Dict, List, Optional
from datetime import datetime
from core.config import CoreConfig
class IDACBaseFeature:
"""Base class that binds a specific feature key and season."""
def __init__(self, config: dict, feature_key: str, season: str) -> None:
self._config = config
self._feature_key = feature_key
self._season = season
self._dt_format = "%Y-%m-%d %H:%M:%S"
def _get_val(self, *keys, default=None):
return CoreConfig.get_config_field(
self._config,
"idac",
self._feature_key,
self._season,
*keys,
default=default
)
def _get_dt(self, key: str, default_str: str) -> datetime:
dt_str = self._get_val(key, default=default_str)
try:
return datetime.strptime(dt_str, self._dt_format)
except (ValueError, TypeError):
return datetime.strptime(default_str, self._dt_format)
+276
View File
@@ -0,0 +1,276 @@
from datetime import datetime, timedelta
import json
import logging
import os
from collections import defaultdict
from typing import Optional
from titles.idac.gameconfig.common_config import IDACBaseFeature
from titles.idac.model.season2.config_model import (
BattleGiftConfig,
RoundConfig,
StampInfoConfig,
TimetrialConfig,
)
SEASON = "S1"
class IDACSeason1TimereleaseConfig(IDACBaseFeature):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, "timerelease", season)
@property
def timerelease_no(self) -> int:
return self._get_val("timerelease_no", default=1)
@property
def timerelease_avatar_gacha_no(self) -> int:
return self._get_val("timerelease_avatar_gacha_no", default=1)
class IDACSeason1CollaboConfig(IDACBaseFeature):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, "collabo", season)
@property
def enable(self) -> bool:
return self._get_val("enable", default=False)
@property
def collabo_id(self) -> int:
return self._get_val("collabo_id", default=0)
class IDACSeason1RoundConfig(IDACBaseFeature):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, "round", season)
@property
def enable(self) -> bool:
return self._get_val("enable", default=False)
@property
def open_dt(self) -> datetime:
return self._get_dt("open_dt", "2000-01-01 00:00:00")
@property
def close_dt(self) -> datetime:
return self._get_dt("close_dt", "2099-12-31 23:59:59")
@property
def enabled_round(self) -> str:
return self._get_val("enabled_round", default="")
@property
def last_round(self) -> str:
return self._get_val("last_round", default="")
class IDACSeason1StampConfig(IDACBaseFeature):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, "stamp", season)
@property
def enable(self) -> bool:
return self._get_val("enable", default=False)
@property
def open_dt(self) -> datetime:
return self._get_dt("open_dt", "2000-01-01 00:00:00")
@property
def close_dt(self) -> datetime:
return self._get_dt("close_dt", "2099-12-31 23:59:59")
@property
def enabled_stamps(self) -> dict:
return self._get_val("enabled_stamps", default={})
class IDACSeason1TimeTrialConfig(IDACBaseFeature):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, "timetrial", season)
@property
def enable(self) -> bool:
return self._get_val("enable", default=False)
@property
def open_dt(self) -> datetime:
return self._get_dt("open_dt", "2000-01-01 00:00:00")
@property
def close_dt(self) -> datetime:
return self._get_dt("close_dt", "2099-12-31 23:59:59")
@property
def enabled_timetrials(self) -> Optional[str]:
return self._get_val("enabled_timetrials", default={})
class IDACSeason1BattleGiftConfig(IDACBaseFeature):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, "battle_gift", season)
@property
def enable(self) -> bool:
return self._get_val("enable", default=False)
@property
def open_dt(self) -> datetime:
return self._get_dt("open_dt", "2000-01-01 00:00:00")
@property
def close_dt(self) -> datetime:
return self._get_dt("close_dt", "2099-12-31 23:59:59")
@property
def enabled_battle_gifts(self) -> dict:
return self._get_val("enabled_battle_gifts", default={})
class IDACSeason1Config(dict):
def __init__(self, parent) -> None:
self.logger = logging.getLogger("idac")
self.timerelease = IDACSeason1TimereleaseConfig(parent)
self.collabo = IDACSeason1CollaboConfig(parent)
self.stamp = IDACSeason1StampConfig(parent)
self.timetrial = IDACSeason1TimeTrialConfig(parent)
self.battle_gift = IDACSeason1BattleGiftConfig(parent)
self.round = IDACSeason1RoundConfig(parent)
self.stamp_info: dict[int, list[StampInfoConfig]] = defaultdict(list)
self.timetrial_info: dict[int, TimetrialConfig] = defaultdict()
self.battle_gift_info: dict[int, BattleGiftConfig] = defaultdict()
self.round_info: Optional[RoundConfig] = None
self.last_round_info: Optional[RoundConfig] = None
def load_events(self):
self.stamp_info = self.load_stamp_events()
self.timetrial_info = self.load_time_trial_events()
self.battle_gift_info = self.load_battle_gift_events()
self.round_info = self.load_round_event()
self.last_round_info = self.load_last_round_event()
def load_stamp_events(self) -> dict[int, list[StampInfoConfig]]:
stamp_info: dict[int, list[StampInfoConfig]] = defaultdict(list)
if not self.stamp.enable:
return stamp_info
for game_ver in self.stamp.enabled_stamps:
for stamp in self.stamp.enabled_stamps[game_ver][:3]:
file_path = f"./titles/idac/data/stamp/{stamp}.json"
if not os.path.exists(file_path):
self.logger.warning(f"Stamp {stamp} is enabled but does not exist!")
continue
with open(file_path, encoding="UTF-8") as f:
self.logger.debug(f"Loading stamp {stamp}")
stamp_info_config = StampInfoConfig.model_validate(json.load(f))
stamp_info_config.start_dt = self.stamp.open_dt
stamp_info_config.end_dt = self.stamp.close_dt
stamp_info[game_ver].append(stamp_info_config)
return stamp_info
def load_time_trial_events(self) -> dict[int, TimetrialConfig]:
timetrial_info: dict[int, TimetrialConfig] = defaultdict()
if not self.timetrial.enable:
return timetrial_info
for game_ver in self.timetrial.enabled_timetrials.keys():
timetrial = self.timetrial.enabled_timetrials[game_ver]
file_path = f"./titles/idac/data/timetrial/{timetrial}.json"
if not os.path.exists(file_path):
self.logger.warning(
f"Timetrial {timetrial} is enabled but does not exist!"
)
continue
with open(file_path, encoding="UTF-8") as f:
self.logger.debug(f"Loading Timetrial {timetrial}")
timetrial_config = TimetrialConfig.model_validate(json.load(f))
timetrial_config.start_dt = self.timetrial.open_dt
timetrial_config.end_dt = self.timetrial.close_dt
timetrial_info[game_ver] = timetrial_config
return timetrial_info
def load_battle_gift_events(self) -> dict[int, BattleGiftConfig]:
battle_gift_info: dict[int, BattleGiftConfig] = defaultdict()
if not self.battle_gift.enable:
return battle_gift_info
for game_ver in self.battle_gift.enabled_battle_gifts.keys():
battle_gift = self.battle_gift.enabled_battle_gifts[game_ver]
file_path = f"./titles/idac/data/battle_gift/{battle_gift}.json"
if not os.path.exists(file_path):
self.logger.warning(
f"Battle Gift {battle_gift} is enabled but does not exist!"
)
continue
with open(file_path, encoding="UTF-8") as f:
self.logger.debug(f"Loading Battle Gift {battle_gift}")
battle_gift_config = BattleGiftConfig.model_validate(json.load(f))
battle_gift_config.start_dt = self.battle_gift.open_dt
battle_gift_config.end_dt = self.battle_gift.close_dt
battle_gift_info[game_ver] = battle_gift_config
return battle_gift_info
def load_round_event(self) -> Optional[RoundConfig]:
round_info: Optional[RoundConfig] = None
if not self.round.enable:
return round_info
round = self.round.enabled_round
file_path = f"./titles/idac/data/round/{round}.json"
if not os.path.exists(file_path):
self.logger.warning(f"Round {round} is enabled but does not exist!")
return round_info
with open(file_path, encoding="UTF-8") as f:
self.logger.debug(f"Loading Round {round}")
round_info = RoundConfig.model_validate(json.load(f))
round_info.start_dt = self.round.open_dt
round_info.end_dt = self.round.close_dt
return round_info
def load_last_round_event(self) -> Optional[RoundConfig]:
round_info: Optional[RoundConfig] = None
if not self.round.enable:
return round_info
round = self.round.last_round
if not round:
return
file_path = f"./titles/idac/data/round/{round}.json"
if not os.path.exists(file_path):
self.logger.warning(f"Last Round {round} is enabled but does not exist!")
return round_info
with open(file_path, encoding="UTF-8") as f:
self.logger.debug(f"Loading Last Round {round}")
round_info = RoundConfig.model_validate(json.load(f))
round_info.start_dt = self.round.open_dt - timedelta(weeks=4)
round_info.end_dt = self.round.open_dt - timedelta(days=1)
return round_info
+58
View File
@@ -0,0 +1,58 @@
import logging
from titles.idac.gameconfig.season1_config import (
IDACSeason1BattleGiftConfig,
IDACSeason1CollaboConfig,
IDACSeason1Config,
IDACSeason1RoundConfig,
IDACSeason1StampConfig,
IDACSeason1TimeTrialConfig,
IDACSeason1TimereleaseConfig,
)
SEASON = "S2"
class IDACSeason2TimereleaseConfig(IDACSeason1TimereleaseConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason2CollaboConfig(IDACSeason1CollaboConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason2StampConfig(IDACSeason1StampConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason2TimeTrialConfig(IDACSeason1TimeTrialConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason2BattleGiftConfig(IDACSeason1BattleGiftConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason2RoundConfig(IDACSeason1RoundConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason2Config(IDACSeason1Config):
def __init__(self, parent) -> None:
super().__init__(parent)
self.logger = logging.getLogger("idac")
self.season_name = SEASON
self.timerelease = IDACSeason2TimereleaseConfig(parent, self.season_name)
self.collabo = IDACSeason2CollaboConfig(parent, self.season_name)
self.stamp = IDACSeason2StampConfig(parent, self.season_name)
self.timetrial = IDACSeason2TimeTrialConfig(parent, self.season_name)
self.battle_gift = IDACSeason2BattleGiftConfig(parent, self.season_name)
self.round = IDACSeason2RoundConfig(parent, self.season_name)
+106
View File
@@ -0,0 +1,106 @@
import json
import os
from titles.idac.gameconfig.common_config import IDACBaseFeature
from titles.idac.gameconfig.season2_config import (
IDACSeason2BattleGiftConfig,
IDACSeason2CollaboConfig,
IDACSeason2Config,
IDACSeason2RoundConfig,
IDACSeason2StampConfig,
IDACSeason2TimeTrialConfig,
IDACSeason2TimereleaseConfig,
)
from titles.idac.model.season2.config_model import (
LeagueRewardConfig,
)
SEASON = "S3"
class IDACSeason3TimereleaseConfig(IDACSeason2TimereleaseConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason3CollaboConfig(IDACSeason2CollaboConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason3StampConfig(IDACSeason2StampConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason3TimeTrialConfig(IDACSeason2TimeTrialConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason3BattleGiftConfig(IDACSeason2BattleGiftConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason3RoundConfig(IDACSeason2RoundConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
@property
def open_league(self) -> bool:
return self._get_val("open_league", default=False)
class IDACSeason3LeagueRewardConfig(IDACBaseFeature):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, "league_reward", season)
@property
def enable(self) -> bool:
return self._get_val("enable", default=False)
@property
def enabled_league_reward(self) -> str:
return self._get_val("enabled_league_reward", default="")
class IDACSeason3Config(IDACSeason2Config):
def __init__(self, parent) -> None:
super().__init__(parent)
self.timerelease = IDACSeason3TimereleaseConfig(parent)
self.collabo = IDACSeason3CollaboConfig(parent)
self.stamp = IDACSeason3StampConfig(parent)
self.timetrial = IDACSeason3TimeTrialConfig(parent)
self.battle_gift = IDACSeason3BattleGiftConfig(parent)
self.round = IDACSeason3RoundConfig(parent)
self.league_reward = IDACSeason3LeagueRewardConfig(parent)
self.league_reward_info: list[LeagueRewardConfig] = []
def load_events(self):
super().load_events()
self.league_reward_info = self.load_league_reward_event()
def load_league_reward_event(self) -> list[LeagueRewardConfig]:
league_reward_info: list[LeagueRewardConfig] = []
if not self.league_reward.enable:
return league_reward_info
league_reward = self.league_reward.enabled_league_reward
file_path = f"./titles/idac/data/league_reward/{league_reward}.json"
if not os.path.exists(file_path):
self.logger.warning(
f"League Reward {league_reward} is enabled but does not exist!"
)
return league_reward_info
with open(file_path, encoding="UTF-8") as f:
self.logger.debug(f"Loading League Reward {league_reward}")
for league in json.load(f):
league_reward_info.append(LeagueRewardConfig.model_validate(league))
return league_reward_info
+61
View File
@@ -0,0 +1,61 @@
from titles.idac.gameconfig.season3_config import (
IDACSeason3BattleGiftConfig,
IDACSeason3CollaboConfig,
IDACSeason3Config,
IDACSeason3LeagueRewardConfig,
IDACSeason3RoundConfig,
IDACSeason3StampConfig,
IDACSeason3TimeTrialConfig,
IDACSeason3TimereleaseConfig,
)
SEASON = "S4"
class IDACSeason4TimereleaseConfig(IDACSeason3TimereleaseConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason4CollaboConfig(IDACSeason3CollaboConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason4StampConfig(IDACSeason3StampConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason4TimeTrialConfig(IDACSeason3TimeTrialConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason4BattleGiftConfig(IDACSeason3BattleGiftConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason4RoundConfig(IDACSeason3RoundConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason4LeagueRewardConfig(IDACSeason3LeagueRewardConfig):
def __init__(self, parent: dict, season: str = SEASON) -> None:
super().__init__(parent, season)
class IDACSeason4Config(IDACSeason3Config):
def __init__(self, parent) -> None:
super().__init__(parent)
self.timerelease = IDACSeason4TimereleaseConfig(parent)
self.collabo = IDACSeason4CollaboConfig(parent)
self.stamp = IDACSeason4StampConfig(parent)
self.timetrial = IDACSeason4TimeTrialConfig(parent)
self.battle_gift = IDACSeason4BattleGiftConfig(parent)
self.round = IDACSeason4RoundConfig(parent)
self.league_reward = IDACSeason4LeagueRewardConfig(parent)
+79 -32
View File
@@ -1,5 +1,6 @@
import json
import traceback
import inflection
import yaml
import logging
import coloredlogs
@@ -16,11 +17,14 @@ from core.config import CoreConfig
from core.title import BaseServlet, JSONResponseNoASCII
from core.utils import Utils
from titles.idac.base import IDACBase
from titles.idac.model.common_model import ResponseGeneric
from titles.idac.season2 import IDACSeason2
from titles.idac.season3 import IDACSeason3
from titles.idac.config import IDACConfig
from titles.idac.const import IDACConstants
from titles.idac.echo import IDACEchoUDP
class IDACServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
@@ -32,6 +36,8 @@ class IDACServlet(BaseServlet):
self.logger = logging.getLogger("idac")
self.queue = 0
if not hasattr(self.logger, "inited"):
log_fmt_str = "[%(asctime)s] IDAC | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
@@ -55,14 +61,17 @@ class IDACServlet(BaseServlet):
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.inited = True
self.versions = [
self.versions: list[IDACBase] = [
IDACBase(core_cfg, self.game_cfg),
IDACSeason2(core_cfg, self.game_cfg)
IDACSeason2(core_cfg, self.game_cfg),
IDACSeason3(core_cfg, self.game_cfg),
]
@classmethod
def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool:
def is_game_enabled(
cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str
) -> bool:
game_cfg = IDACConfig()
if path.exists(f"{cfg_dir}/{IDACConstants.CONFIG_NAME}"):
@@ -72,39 +81,55 @@ class IDACServlet(BaseServlet):
if not game_cfg.server.enable:
return False
return True
def get_routes(self) -> List[Route]:
return [
Route("/{version:int}/initiald/{category:str}/{endpoint:str}", self.render_POST, methods=["POST"]),
Route("/{version:int}/initiald-matching/{endpoint:str}", self.render_matching, methods=["POST"]),
Route(
"/{version:int}/initiald/{category:str}/{endpoint:str}",
self.render_POST,
methods=["POST"],
),
Route(
"/{version:int}/initiald-matching/{endpoint:str}",
self.render_matching,
methods=["POST"],
),
]
def get_allnet_info(
self, game_code: str, game_ver: int, keychip: str
) -> Tuple[bool, str, str]:
title_port_int = Utils.get_title_port(self.core_cfg)
t_port = f":{title_port_int}" if title_port_int and not self.core_cfg.server.is_using_proxy else ""
t_port = (
f":{title_port_int}"
if title_port_int and not self.core_cfg.server.is_using_proxy
else ""
)
return (
f"",
# requires http or else it defaults to https
f"http://{self.core_cfg.server.hostname}{t_port}/{game_ver}/",
# f"http://{self.core_cfg.server.hostname}{t_port}/{game_ver}/",
f"http://sega-initiald.net/{game_ver}/",
)
async def render_POST(self, request: Request) -> bytes:
req_raw = await request.body()
internal_ver = 0
version: int = request.path_params.get('version')
category: str = request.path_params.get('category')
endpoint: str = request.path_params.get('endpoint')
version: int = request.path_params.get("version")
category: str = request.path_params.get("category").lower()
endpoint: str = request.path_params.get("endpoint")
client_ip = Utils.get_ip_addr(request)
internal_ver = IDACConstants.VER_IDAC_SEASON_1
if version >= 100 and version < 140: # IDAC Season 1
internal_ver = IDACConstants.VER_IDAC_SEASON_1
elif version >= 140 and version < 171: # IDAC Season 2
internal_ver = IDACConstants.VER_IDAC_SEASON_2
elif version >= 200 and version < 240: # IDAC Season 3
internal_ver = IDACConstants.VER_IDAC_SEASON_3
header_application = self.decode_header(request.headers.get("application", ""))
@@ -114,19 +139,33 @@ class IDACServlet(BaseServlet):
self.logger.debug(f"Headers: {header_application}")
self.logger.debug(req_data)
# func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
func_to_find = "handle_"
func_to_find += f"{category.lower()}_" if not category == "" else ""
func_to_find += f"{endpoint.lower()}_request"
if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}")
return JSONResponse('{"status_code": "0"}')
# if not hasattr(self.versions[internal_ver], func_to_find):
# self.logger.warning(f"Unhandled v{version} request {func_to_find}")
# return JSONResponse('{"status_code": "0"}')
resp = None
try:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = await handler(req_data, header_application)
if internal_ver == IDACConstants.VER_IDAC_SEASON_2:
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
controller: IDACSeason2 = self.versions[internal_ver].controllers[
category
]
handler = getattr(controller, func_to_find)
resp = await handler(req_data, header_application)
elif internal_ver == IDACConstants.VER_IDAC_SEASON_3:
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
controller: IDACSeason3 = self.versions[internal_ver].controllers[
category
]
handler = getattr(controller, func_to_find)
resp = await handler(req_data, header_application)
else:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = await handler(req_data, header_application)
except Exception as e:
traceback.print_exc()
@@ -136,6 +175,9 @@ class IDACServlet(BaseServlet):
if resp is None:
resp = {"status_code": "0"}
if isinstance(resp, ResponseGeneric):
resp = resp.model_dump_json_safe(exclude_none=True)
self.logger.debug(f"Response {resp}")
return JSONResponseNoASCII(resp)
@@ -144,24 +186,20 @@ class IDACServlet(BaseServlet):
ver: int = request.path_params.get("version")
client_ip = Utils.get_ip_addr(request)
req_data = await request.json()
header_application = self.decode_header(request.headers.get('application', ''))
header_application = self.decode_header(request.headers.get("application", ""))
user_id = int(header_application["session"])
# self.getMatchingStatus(user_id)
self.logger.info(
f"IDAC Matching request from {client_ip}: {url} - {req_data}"
)
self.logger.info(f"IDAC Matching request from {client_ip}: {url} - {req_data}")
resp = {"status_code": "0"}
if url == "/regist":
if url == "regist":
self.queue = self.queue + 1
elif url == "/status":
elif url == "status":
if req_data.get("cancel_flag"):
self.queue = self.queue - 1
self.logger.info(
f"IDAC Matching endpoint {client_ip} had quited"
)
self.logger.info(f"IDAC Matching endpoint {client_ip} had quited")
resp = {
"status_code": "0",
@@ -190,9 +228,18 @@ class IDACServlet(BaseServlet):
asyncio.create_task(
loop.create_datagram_endpoint(
lambda: IDACEchoUDP(),
local_addr=(self.core_cfg.server.listen_address, self.game_cfg.server.echo1)
local_addr=(
self.core_cfg.server.listen_address,
self.game_cfg.server.echo1,
),
)
)
self.logger.info(f"Matching listening on {self.game_cfg.server.matching} with echo on {self.game_cfg.server.echo1}")
self.logger.info(
f"Matching listening on {self.game_cfg.server.matching} with echo on {self.game_cfg.server.echo1}"
)
async def async_init(self):
if self.game_cfg.server.enable:
for season in self.versions:
await season.async_init()
+16
View File
@@ -0,0 +1,16 @@
from titles.idac.model.season2.challenge_model import *
from titles.idac.schema.model.challenge import *
class IDACChallengeMapper:
def challenge_to_dto(self, challenge: ChallengeDb) -> ChallengeDto:
return ChallengeDto.model_validate(challenge)
def challenges_to_dto(self, challenges: list[ChallengeDb]) -> list[ChallengeDto]:
return [self.challenge_to_dto(challenge) for challenge in challenges]
def row_to_best_challenge_dto(self, row) -> BestChallengeByVsTypeDto:
return BestChallengeByVsTypeDto.model_validate(row)
def rows_to_best_challenge_dtos(self, rows: list) -> list[BestChallengeByVsTypeDto]:
return [self.row_to_best_challenge_dto(row) for row in rows]
+13
View File
@@ -0,0 +1,13 @@
from titles.idac.model.season2.factory_model import CarDto, LotteryDto
from titles.idac.schema.model.factory import CarDb, LotteryDb
class IDACFactoryMapper:
def lottery_to_dto(self, lottery: LotteryDb) -> LotteryDto:
return LotteryDto.model_validate(lottery)
def car_to_dto(self, car: CarDb) -> CarDto:
return CarDto.model_validate(car)
def cars_to_dto_list(self, cars_db: list[CarDb]) -> list[CarDto]:
return [self.car_to_dto(car) for car in cars_db]
+32
View File
@@ -0,0 +1,32 @@
from typing import List
from titles.idac.model.season2.item_model import *
from titles.idac.schema.model.item import *
class IDACItemMapper:
def ticket_to_dto(self, ticket: TicketDb) -> TicketDto:
return TicketDto.model_validate(ticket)
def tickets_to_dto(self, tickets: List[TicketDb]) -> List[TicketDto]:
return [self.ticket_to_dto(ticket) for ticket in tickets]
def stamp_to_dto(self, stamp: StampDb) -> StampDto:
return StampDto.model_validate(stamp)
def stamps_to_dto(self, stamps: List[StampDb]) -> List[StampDto]:
return [self.stamp_to_dto(stamp) for stamp in stamps]
def timetrial_event_to_dto(self, event: TimeTrialEventDb) -> TimeTrialEventDto:
return TimeTrialEventDto.model_validate(event)
def timetrial_events_to_dto(
self, events: list[TimeTrialEventDb]
) -> list[TimeTrialEventDto]:
return [self.timetrial_event_to_dto(event) for event in events]
def battle_gift_to_dto(self, gift: BattleGiftDb) -> BattleGiftDto:
return BattleGiftDto.model_validate(gift)
def battle_gifts_to_dto(self, gifts: List[BattleGiftDb]) -> List[BattleGiftDto]:
return [self.battle_gift_to_dto(gift) for gift in gifts]
+32
View File
@@ -0,0 +1,32 @@
from sqlalchemy.engine import Row
from titles.idac.model.season2.user_model import *
from titles.idac.schema.model.profile import *
class IDACProfileMapper:
def profile_to_dto(self, profile: ProfileDb) -> ProfileDto:
return ProfileDto.model_validate(profile)
def profile_config_to_dto(self, config: ProfileConfigDb) -> ProfileConfigDto:
return ProfileConfigDto.model_validate(config)
def profile_avatar_to_dto(self, avatar: ProfileAvatarDb) -> ProfileAvatarDto:
return ProfileAvatarDto.model_validate(avatar)
def profile_rank_to_dto(self, rank: ProfileRankDb) -> ProfileRankDto:
return ProfileRankDto.model_validate(rank)
def profile_stock_to_dto(self, stock: ProfileStockDb) -> ProfileStockDto:
return ProfileStockDto.model_validate(stock)
def profile_theory_to_dto(self, theory: ProfileTheoryDb) -> ProfileTheoryDto:
return ProfileTheoryDto.model_validate(theory)
def profile_tips_to_dto(self, tips: ProfileTipsDb) -> ProfileTipsDto:
return ProfileTipsDto.model_validate(tips)
def profile_config_dto_to_config_data(
self, config: ProfileConfigDto
) -> Season2ConfigData:
return Season2ConfigData.model_validate(config)
+36
View File
@@ -0,0 +1,36 @@
from sqlalchemy.engine import Row
from titles.idac.model.season2.round_model import (
RoundInfoDto,
UserRoundInfoDto,
UserRoundInfoWithRankDto,
)
from titles.idac.schema.model.round import RoundInfoDb, UserRoundInfoDb
class IDACRoundMapper:
def round_info_list_to_dto_list(
self, round_info_list: list[RoundInfoDb]
) -> list[RoundInfoDto]:
return [self.round_info_to_dto(round) for round in round_info_list]
def round_info_to_dto(self, round_info: RoundInfoDb) -> RoundInfoDto:
return RoundInfoDto.model_validate(round_info)
def user_round_info_to_dto(
self, user_round_info: UserRoundInfoDb
) -> UserRoundInfoDto:
return UserRoundInfoDto.model_validate(user_round_info)
def user_round_info_with_rank_list_to_dto_list(
self, user_round_info_list: list[Row]
) -> list[UserRoundInfoWithRankDto]:
return [
self.user_round_info_with_rank_to_dto(user_round)
for user_round in user_round_info_list
]
def user_round_info_with_rank_to_dto(
self, user_round_info: Row
) -> UserRoundInfoWithRankDto:
return UserRoundInfoWithRankDto.model_validate(user_round_info)
+31
View File
@@ -0,0 +1,31 @@
from titles.idac.model.season2.story_model import *
from titles.idac.schema.model.story import *
class IDACStoryMapper:
def story_to_dto(self, story: StoryDb) -> StoryDto:
return StoryDto.model_validate(story)
def stories_to_dto(self, stories: list[StoryDb]) -> list[StoryDto]:
return [self.story_to_dto(story) for story in stories]
def story_episode_to_dto(self, episode: StoryEpisodeDb) -> StoryEpisodeDto:
return StoryEpisodeDto.model_validate(episode)
def story_episodes_to_dto(
self, episodes: list[StoryEpisodeDb]
) -> list[StoryEpisodeDto]:
return [self.story_episode_to_dto(episode) for episode in episodes]
def story_episode_difficulty_to_dto(
self, difficulty: StoryEpisodeDifficultyDb
) -> StoryEpisodeDifficultyDto:
return StoryEpisodeDifficultyDto.model_validate(difficulty)
def story_episode_difficulties_to_dto(
self, difficulties: list[StoryEpisodeDifficultyDb]
) -> list[StoryEpisodeDifficultyDto]:
return [
self.story_episode_difficulty_to_dto(difficulty)
for difficulty in difficulties
]
+98
View File
@@ -0,0 +1,98 @@
from sqlalchemy.engine import Row
from titles.idac.model.season3.team_model import *
from titles.idac.schema.model.team import *
class IDACTeamMapper:
def team_info_to_db(self, team_info: TeamInfoDto) -> TeamInfoDb:
return TeamInfoDb(
id=team_info.id,
version=team_info.version,
name=team_info.name,
mileage=team_info.mileage,
sticker_id=team_info.sticker_id,
sticker_stock=team_info.sticker_stock,
country=team_info.country,
store_id=team_info.store_id,
password=team_info.password,
auto_invite_flag=team_info.auto_invite_flag,
)
def team_info_list_to_dto_list(
self, team_info_list: list[TeamInfoDb]
) -> list[TeamInfoDto]:
return [self.team_info_to_dto(team) for team in team_info_list]
def team_info_to_dto(self, team_info: TeamInfoDb) -> TeamInfoDto:
return TeamInfoDto.model_validate(team_info)
def team_member_to_db(self, team_member: TeamMemberDto) -> TeamMemberDb:
return TeamMemberDb(
team_id=team_member.team_id,
user=team_member.user,
version=team_member.version,
role=team_member.role,
join_dt=team_member.join_dt,
)
def team_member_to_dto(self, team_member: TeamMemberDb) -> TeamMemberDto:
return TeamMemberDto.model_validate(team_member)
def team_member_with_ranking_to_dto(
self, team_member: Row
) -> TeamMemberWithRankingDto:
return TeamMemberWithRankingDto.model_validate(team_member)
def all_team_member_with_ranking_to_dto(
self, team_member: list[dict]
) -> list[TeamMemberWithRankingDto]:
return [self.team_member_with_ranking_to_dto(member) for member in team_member]
def all_team_member_to_dto(
self, team_member: list[TeamMemberDb]
) -> list[TeamMemberDto]:
return [self.team_member_to_dto(member) for member in team_member]
def team_member_info_to_dto(
self, team_member_info: TeamMemberInfoDb
) -> TeamMemberInfoDto:
return TeamMemberInfoDto.model_validate(team_member_info)
def all_team_member_info_to_dto(
self, team_member_info: list[TeamMemberInfoDb]
) -> list[TeamMemberInfoDto]:
return [self.team_member_info_to_dto(member) for member in team_member_info]
def user_league_info_to_dto(
self, user_league_info: UserLeagueInfoDb
) -> UserLeagueInfoDto:
return UserLeagueInfoDto.model_validate(user_league_info)
def league_info_list_to_dto_list(self, league_info_list: list[LeagueInfoDb]) -> list[LeagueInfoDto]:
return [self.league_info_to_dto(league) for league in league_info_list]
def league_info_to_dto(self, league_info: LeagueInfoDb) -> LeagueInfoDto:
return LeagueInfoDto.model_validate(league_info)
def league_info_with_ranking_to_dto(
self, league_info: Row
) -> LeagueInfoWithRankingDto:
return LeagueInfoWithRankingDto.model_validate(league_info)
def random_team_info_to_dto(self, team_info: Row) -> RecommendTeamDto:
team_info_db: TeamInfoDb = team_info["TeamInfoDb"]
league_info_db: LeagueInfoDb = team_info["LeagueInfoDb"]
return RecommendTeamDto(
team_id=team_info_db.id,
password=team_info_db.password,
team_name=team_info_db.name,
team_sticker_id=team_info_db.sticker_id,
country=team_info_db.country,
store_id=team_info_db.store_id,
league_rank_id=league_info_db.league_rank_id,
team_point=league_info_db.league_point,
)
def random_team_infos_to_dto(self, team_infos: list[Row]) -> list[RecommendTeamDto]:
return [self.random_team_info_to_dto(team_info) for team_info in team_infos]
+32
View File
@@ -0,0 +1,32 @@
from titles.idac.model.season2.theory_model import *
from titles.idac.schema.model.profile import ProfileDb
from titles.idac.schema.model.theory import *
class IDACTheoryMapper:
def theory_course_to_dto(self, course: TheoryCourseDb) -> TheoryCourseDto:
return TheoryCourseDto.model_validate(course)
def theory_courses_to_dto(
self, courses: list[TheoryCourseDb]
) -> list[TheoryCourseDto]:
return [self.theory_course_to_dto(course) for course in courses]
def theory_partner_to_dto(self, partner: TheoryPartnerDb) -> TheoryPartnerDto:
return TheoryPartnerDto.model_validate(partner)
def theory_partners_to_dto(
self, partners: list[TheoryPartnerDb]
) -> list[TheoryPartnerDto]:
return [self.theory_partner_to_dto(partner) for partner in partners]
def theory_running_to_dto(self, running: TheoryRunningDb) -> TheoryRunningDto:
return TheoryRunningDto.model_validate(running)
def theory_runnings_to_dto(
self, runnings: list[TheoryRunningDb]
) -> list[TheoryRunningDto]:
return [self.theory_running_to_dto(running) for running in runnings]
def theory_profile_to_dto(self, profile: ProfileDb) -> TheoryProfileDto:
return TheoryProfileDto.model_validate(profile)
+13
View File
@@ -0,0 +1,13 @@
from titles.idac.model.season2.time_trial_model import CourseDto, TrialDto
from titles.idac.schema.model.time_trial import CourseDb, TrialDb
class IDACTimeTrialMapper:
def course_to_dto(self, course: CourseDb) -> CourseDto:
return CourseDto.model_validate(course)
def courses_to_dto_list(self, courses_db: list[CourseDb]) -> list[CourseDto]:
return [self.course_to_dto(course) for course in courses_db]
def trial_to_dto(self, trial: TrialDb) -> TrialDto:
return TrialDto.model_validate(trial)
+18
View File
@@ -0,0 +1,18 @@
from titles.idac.model.season2.vs_info_model import *
from titles.idac.schema.model.vs_info import *
class IDACVsInfoMapper:
def vs_info_to_dto(self, vs_info: VsInfoDb) -> VsInfoDto:
return VsInfoDto.model_validate(vs_info)
def vs_infos_to_dto(self, vs_infos: list[VsInfoDb]) -> list[VsInfoDto]:
return [self.vs_info_to_dto(vs_info) for vs_info in vs_infos]
def vs_course_info_to_dto(self, course_info: VsCourseInfoDb) -> VsCourseInfoDto:
return VsCourseInfoDto.model_validate(course_info)
def vs_course_infos_to_dto(
self, course_infos: list[VsCourseInfoDb]
) -> list[VsCourseInfoDto]:
return [self.vs_course_info_to_dto(course_info) for course_info in course_infos]
View File
+433
View File
@@ -0,0 +1,433 @@
from datetime import datetime
from enum import Enum, IntEnum
from typing import Optional
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, field_serializer
from titles.idac.const import (
BattleGiftStatusEnum,
CarMakerEnum,
LayoutEnum,
RewardCategoryEnum,
StoryPlayStatusEnum,
StoryTypeEnum,
StyleEnum,
)
class DtoModel(BaseModel):
model_config = ConfigDict(
from_attributes=True,
)
def model_dump_json_safe(self, **kwargs) -> dict:
"""Dump model with datetime converted to timestamps for JSON serialization."""
data = self.model_dump(**kwargs)
return self._serialize_datetimes(data)
def model_dump_db(self, **kwargs) -> dict:
"""
Dump model with None values excluded and IntEnums converted to ints
for SQLAlchemy.
"""
data = self.model_dump(exclude_none=True, **kwargs)
return self._serialize_enums(data)
def _serialize_datetimes(self, obj):
"""Recursively convert datetime objects to timestamps."""
if isinstance(obj, datetime):
return int(obj.timestamp())
elif isinstance(obj, dict):
return {key: self._serialize_datetimes(value) for key, value in obj.items()}
elif isinstance(obj, list):
return [self._serialize_datetimes(item) for item in obj]
elif isinstance(obj, BaseModel):
return self._serialize_datetimes(obj.model_dump())
return obj
def _serialize_enums(self, obj):
"""Recursively convert datetime objects to timestamps."""
if isinstance(obj, IntEnum):
return int(obj)
if isinstance(obj, dict):
return {k: self._serialize_enums(v) for k, v in obj.items()}
if isinstance(obj, list):
return [self._serialize_enums(v) for v in obj]
return obj
class Course(DtoModel):
course_id: int
course_day: int
class Reward(DtoModel):
reward_category: RewardCategoryEnum
reward_type: int
class Ticket(DtoModel):
ticket_id: int
ticket_cnt: int
class CarUseCount(DtoModel):
car_id: int
count: int
class MakerUseCount(DtoModel):
maker_id: CarMakerEnum
count: int
class TimetrialEventData_2(DtoModel):
timetrial_event_id: int
point: int
class Part(DtoModel):
parts: int
class LastUpdateDate(DtoModel):
course_id: int
# UNIX timestamp
ranking_date: int
class RewardDist(DtoModel):
grade_reward_dist: int
story_rank_reward_dist: int
time_trial_rank_reward_dist: int
online_battle_rank_reward_dist: int
store_battle_rank_reward_dist: int
theory_rank_reward_dist: int
class DriverDebut(DtoModel):
play_count: int
daily_play: int
last_play_dt: datetime
use_start_date: datetime
use_end_date: datetime
ticket_cnt: int
ticket_get_bit: int
class AvatarData(DtoModel):
sex: int
face: int
eye: int
mouth: int
hair: int
glasses: int
face_accessory: int
body: int
body_accessory: int
behind: int
bg: int
effect: int
special: int
class StockData(DtoModel):
mytitle_list: Optional[str] = None
mytitle_new_list: Optional[str] = None
avatar_face_list: Optional[str] = None
avatar_face_new_list: Optional[str] = None
avatar_eye_list: Optional[str] = None
avatar_eye_new_list: Optional[str] = None
avatar_hair_list: Optional[str] = None
avatar_hair_new_list: Optional[str] = None
avatar_body_list: Optional[str] = None
avatar_body_new_list: Optional[str] = None
avatar_mouth_list: Optional[str] = None
avatar_mouth_new_list: Optional[str] = None
avatar_glasses_list: Optional[str] = None
avatar_glasses_new_list: Optional[str] = None
avatar_face_accessory_list: Optional[str] = None
avatar_face_accessory_new_list: Optional[str] = None
avatar_body_accessory_list: Optional[str] = None
avatar_body_accessory_new_list: Optional[str] = None
avatar_behind_list: Optional[str] = None
avatar_behind_new_list: Optional[str] = None
avatar_bg_list: Optional[str] = None
avatar_bg_new_list: Optional[str] = None
avatar_effect_list: Optional[str] = None
avatar_effect_new_list: Optional[str] = None
avatar_special_list: Optional[str] = None
avatar_special_new_list: Optional[str] = None
stamp_list: Optional[str] = None
stamp_new_list: Optional[str] = None
keyholder_list: Optional[str] = None
keyholder_new_list: Optional[str] = None
papercup_list: Optional[str] = None
papercup_new_list: Optional[str] = None
tachometer_list: Optional[str] = None
tachometer_new_list: Optional[str] = None
aura_list: Optional[str] = None
aura_new_list: Optional[str] = None
aura_color_list: Optional[str] = None
aura_color_new_list: Optional[str] = None
aura_line_list: Optional[str] = None
aura_line_new_list: Optional[str] = None
bgm_list: Optional[str] = None
bgm_new_list: Optional[str] = None
dx_color_list: Optional[str] = None
dx_color_new_list: Optional[str] = None
start_menu_bg_list: Optional[str] = None
start_menu_bg_new_list: Optional[str] = None
under_neon_list: Optional[str] = None
class ModeRank(DtoModel):
story_rank: int
story_rank_exp: int
time_trial_rank: int
time_trial_rank_exp: int
online_battle_rank: int
online_battle_rank_exp: int
store_battle_rank: int
store_battle_rank_exp: int
theory_rank: int
theory_exp: int
pride_group_id: int
pride_point: int
is_last_max: int
grade: int
grade_exp: int
class ModeRankData(DtoModel):
story_rank_exp: int
story_rank: int
time_trial_rank_exp: int
time_trial_rank: int
online_battle_rank_exp: int
online_battle_rank: int
store_battle_rank_exp: int
store_battle_rank: int
theory_exp: int
theory_rank: int
pride_group_id: int
pride_point: int
grade_exp: int
grade: int
grade_reward_dist: int
story_rank_reward_dist: int
time_trial_rank_reward_dist: int
online_battle_rank_reward_dist: int
store_battle_rank_reward_dist: int
theory_rank_reward_dist: int
max_attained_online_battle_rank: int
max_attained_pride_point: int
is_last_max: int
class CarData(DtoModel):
id: int
car_id: Optional[int] = None
style_car_id: int
color: int
bureau: int
kana: int
s_no: int
l_no: int
car_flag: int
tune_point: int
tune_parts: datetime
infinity_tune: int
online_vs_win: int
pickup_seq: int
purchase_seq: int
color_stock_list: str
color_stock_new_list: str
parts_stock_list: str
parts_stock_new_list: str
parts_set_equip_list: str
use_count: int
story_use_count: int
timetrial_use_count: int
vs_use_count: int
net_vs_use_count: int
theory_use_count: int
car_mileage: int
parts_list: list[Part]
equip_parts_count: int
class BestData(DtoModel):
class RankingData(DtoModel):
course_id: int
rank: int
username: str
value: int
store: str
country: int
style_car_id: int
play_dt: datetime
section_time_1: int
section_time_2: int
section_time_3: int
section_time_4: int
mission: int
course_id: int
ranking_data: list[RankingData]
class Story(DtoModel):
class Episode(DtoModel):
class Difficulty(DtoModel):
difficulty: int
play_count: int = 0
clear_count: int = 0
play_status: StoryPlayStatusEnum = StoryPlayStatusEnum.NEW
play_score: int = 0
episode: int
play_status: StoryPlayStatusEnum = StoryPlayStatusEnum.NEW
difficulty_data: list[Difficulty]
story_type: StoryTypeEnum = StoryTypeEnum.STORY
chapter: int
loop_count: int = 1
episode_data: list[Episode]
class VsInfo(DtoModel):
class VsinfoCourseData(DtoModel):
course_id: int
vs_cnt: int
vs_win: int
battle_mode: int
vs_cnt: int
vs_win: int
invalid: int
str: int
str_now: int
lose_now: int
vs_history: int
course_select_priority: int
break_count: int
break_penalty_flag: bool
vsinfo_course_data: list[VsinfoCourseData]
class BattleGift(DtoModel):
class Gift(DtoModel):
gift_id: int
gift_get_status: BattleGiftStatusEnum = BattleGiftStatusEnum.NEW
first_distribution_flag: int = 1
gift_data: list[Gift]
class RoundEvent(DtoModel):
count: int
win: int
rank: int
point: int
total_round_point: int
class PastRoundEvent(DtoModel):
count: int
win: int
rank: int
area_rank: int
point: int
total_round_point: int
round_name: str
round_id: int
class StampEventData(DtoModel):
m_stamp_event_id: int
select_flag: int
stamp_masu: int
daily_bonus: int
weekly_bonus: int
weekday_bonus: int
weekend_bonus: int
total_bonus: int
day_total_bonus: int
store_battle_bonus: int
story_bonus: int
online_battle_bonus: int
timetrial_bonus: int
fasteststreetlegaltheory_bonus: int
collaboration_bonus: int
add_bonus_daily_flag_1: int
add_bonus_daily_flag_2: int
add_bonus_daily_flag_3: int
new_flag: bool
class SpecialMode(DtoModel):
story_type: StoryTypeEnum
vs_type: int
max_clear_lv: int = Field(
..., validation_alias=AliasChoices("max_clear_lv", "cleared_difficulty")
)
last_play_lv: int = Field(
..., validation_alias=AliasChoices("last_play_lv", "play_difficulty")
)
last_play_course_id: int
class ChallengeModeData(DtoModel):
story_type: StoryTypeEnum = StoryTypeEnum.BUNTA
vs_type: int
max_clear_lv: int
last_play_lv: int
last_play_course_id: int
play_count: int
class TimetrialEventData2(DtoModel):
timetrial_event_id: int
point: int
class CarLayoutCount(DtoModel):
layout_id: LayoutEnum
count: int
class CarStyleCount(DtoModel):
style_id: StyleEnum
count: int
class CarUseCount(DtoModel):
car_id: int
count: int
class LockResultEnum(IntEnum):
IN_USE = 0
OK = 1
NEWER_DATA = 2
class StatusCodeEnum(str, Enum):
OK = "0" # Success
BAD_SESSION = "E1001" # Unknown requested session in headers
REQUEST_FALSIFICATION = "E2002" # detect request Falsification
UNKNOWN = "E3004" # UNKNOWN API
BAD_HEADER = "E4001" # BAD HEADER
BAD_REQUEST = "E4002" # BAD REQUEST FILED
SERVER_ERROR = "E4003" # SERVER SIDE ERROR WHEN REQUESTING ID (OR TIMEOUT)
OUT_OF_SERVICE = "E4004" # NOT YET SERVICING
TIMEOUT = "E4005" # TIMEOUT/DEADLOCK
SYSTEM_ERROR = "E9999" # SYSTEM ERROR
class RequestGeneric(DtoModel):
pass
class ResponseGeneric(DtoModel):
status_code: StatusCodeEnum = StatusCodeEnum.OK
@@ -0,0 +1,18 @@
from pydantic import BaseModel
from titles.idac.model.common_model import (
BestData,
LastUpdateDate,
RequestGeneric,
ResponseGeneric,
)
class RequestSeason2AdvertiseGetRankingData(RequestGeneric):
last_update_date: list[LastUpdateDate]
class ResponseSeason2AdvertiseGetRankingData(ResponseGeneric):
national_best_data: list[BestData] = []
shop_best_data: list[BestData] = []
rank_management_flag: int = 0
+10
View File
@@ -0,0 +1,10 @@
from datetime import datetime
from titles.idac.model.common_model import RequestGeneric, ResponseGeneric
RequestSeason2AliveGet = RequestGeneric
class ResponseSeason2AliveGet(ResponseGeneric):
# 1 Success, 0 = Failed
server_status: int = 1
force_reboot_time: datetime
+618
View File
@@ -0,0 +1,618 @@
from datetime import datetime
from typing import Optional
from titles.idac.const import CollaboEnum, StoryTypeEnum
from titles.idac.model.common_model import *
RequestSeason2BootGetGachaData = RequestGeneric
class ResponseSeason2BootGetGachaData(ResponseGeneric):
class AvatarGachaData(DtoModel):
class GachaReward(DtoModel):
reward_id: int
reward_category: int
reward_type: int
rate: int
pickup_flag: int
avatar_gacha_id: int
avatar_gacha_nm: str
gacha_type: int
save_filename: str
use_ticket_cnt: int
start_dt: datetime
end_dt: datetime
gacha_reward: list[GachaReward]
avatar_gacha_data: list[AvatarGachaData]
RequestSeason2BootGetTimeReleaseData = RequestGeneric
class ResponseSeason2BootGetTimeReleaseData(ResponseGeneric):
class Car(DtoModel):
style_car_id: int
release_status: int
recommend: int
new_status: int
class Chapter(DtoModel):
timerelease_story: int
timerelease_chapter: int
class AnnaunceImage(DtoModel):
save_filename: str
url: str
# UNIX timestamp
open_dt: int
# UNIX timestamp
close_dt: int
display_id: int
target_id: int
page: int
time: int
class VsBalanceCar(DtoModel):
style_car_id: int
course_id: int
vs_balance_param1: int
vs_balance_param2: int
vs_balance_param3: int
vs_balance_param4: int
vs_balance_param5: int
class VsBalanceParam(DtoModel):
course_id: int
boost0: int
boost1: int
boost2: int
boost3: int
boost4: int
boost5: int
boost6: int
class MenuInfo(DtoModel):
dressup_shop: int
avatar_gacha: int
car_dressup: int
car_keyholder: int
meter_customize: int
aura_customize: int
papercup_customize: int
avatar_customize: int
chat_stamp_customize: int
mytitle_customize: int
start_menu_customize: int
name_change: int
class PartsList(DtoModel):
car_id: int
parts_id: int
display_timerelease_id: str
car_list: list[Car]
course_list: list[Course]
chapter_list: list[Chapter]
annaunce_image: list[AnnaunceImage]
vs_balance_car: list[VsBalanceCar]
vs_balance_param4: list[VsBalanceParam]
vs_balance_param5: list[VsBalanceParam]
vs_balance_param6: list[VsBalanceParam]
vs_balance_param7: list[VsBalanceParam]
menu_info: list[MenuInfo]
parts_list: list[PartsList]
class ResponseSeason2BootGetConfigData(ResponseGeneric):
class BattleGiftEventMaster(DtoModel):
class BattleGiftData(DtoModel):
gift_id: int
battle_gift_event_id: int
reward_category: RewardCategoryEnum
reward_type: int
reward_name: str
rarity: int
cash_rate: int
customize_point_rate: int
avatar_point_rate: int
first_distribution_rate: int
battle_gift_event_id: int
event_nm: str
# UNIX timestamp
start_dt: datetime = datetime(1970, 1, 1)
# UNIX timestamp
end_dt: datetime = datetime(1970, 1, 1)
mode_id: int
delivery_type: int
gift_data: list[BattleGiftData]
class RoundEvent_2(DtoModel):
class VsCount(DtoModel):
reward_upper_limit: int
reward_lower_limit: int
reward: list[Reward]
round_event_id: int
round_event_nm: str
# UNIX timestamp
start_dt: datetime = datetime(1970, 1, 1)
# UNIX timestamp
end_dt: datetime = datetime(1970, 1, 1)
round_start_rank: int = 11
random_hidden_flag: int = 0
save_filename: str
vscount: list[VsCount] = []
rank: list[VsCount] = []
point: list[VsCount] = []
playable_course_list: list[Course] = []
class LastRoundEventRanking(DtoModel):
round_rank: int
round_point: int
round_play_count: int
username: str
country: int
store: str
online_battle_rank: int
mytitle_id: int
mytitle_effect_id: int
car_data: list[CarData]
user_avatar: AvatarData
class RoundEventExp(DtoModel):
online_rank_id: int
winbase: int
losebase: int
winnum: int
class StampInfo(DtoModel):
class AddBonus(DtoModel):
bonus_category: int
bonus_play_num: int
bonus_stamp_num: int
bonus_daily_flag: int
class SheetSet(DtoModel):
class StampSheet(DtoModel):
reward_setting_masu: int
reward_category_a: RewardCategoryEnum
reward_type_a: int
reward_category_b: RewardCategoryEnum
reward_type_b: int
reward_is_pickup: int
reward_pickup_display_name: str
sheet_no: int
loop_flag: int
stamp_sheet: list[StampSheet]
m_stamp_event_id: int
stamp_event_nm: str
# UNIX timestamp
start_dt: datetime = datetime(1970, 1, 1)
# UNIX timestamp
end_dt: datetime = datetime(1970, 1, 1)
play_bonus: int
daily_bonus: int
weekly_bonus: int
add_bonus: list[AddBonus]
sheet_design: int
sheet_stamp: int
sheet_set: list[SheetSet]
weekday_bonus: int
weekend_bonus: int
class TakeoverReward(DtoModel):
class TakeoverRewardData(DtoModel):
reward_no: int
reward_category: RewardCategoryEnum
reward_type: int
takeover_reward_type: int
takeover_reward_data: list[TakeoverRewardData]
class SubcardJudge(DtoModel):
condition_id: int
lower_rank: int
higher_rank: int
condition_start: int
condition_end: int
calc: int
class SpecialPromote(DtoModel):
counter: int
online_rank_id: int
class MatchingGroup(DtoModel):
group_id: int
group_percent: int
class SpecialModeData(DtoModel):
start_dt: datetime
end_dt: datetime
story_type: StoryTypeEnum = StoryTypeEnum.NONE
class TimetrialEventData(DtoModel):
class TTEventReward(DtoModel):
point: int
reward_category_a: RewardCategoryEnum
reward_type_a: int
reward_category_b: RewardCategoryEnum
reward_type_b: int
timetrial_event_id: int
name: str
start_dt: datetime = datetime(1970, 1, 1)
end_dt: datetime = datetime(1970, 1, 1)
course_id: int
point: list[int]
reward: list[TTEventReward]
class NumberLotteryData(DtoModel):
m_number_lottery_win_number_no: int
m_number_lottery_schedule_no: int
win_number: int
reward_category: int
reward_type: int
end_dt: int
free_continue_enable: int = 1
free_continue_new: int = 1
free_continue_play: int = 1
difference_time_to_jp: int = 0
asset_version: str = "1"
optional_version: str = "1"
disconnect_offset: int = 0
boost_balance_version: str = "0"
time_release_number: str = "0"
play_stamp_enable: int = 1
play_stamp_bonus_coin: int = 1
gacha_chara_needs: int = 1
both_win_system_control: int = 0
subcard_system_congrol: int = 0
server_maintenance_start_hour: int = 0
server_maintenance_start_minutes: int = 0
server_maintenance_end_hour: int = 0
server_maintenance_end_minutes: int = 0
domain_api_game: str
domain_matching: str
domain_echo1: str
domain_echo2: str
domain_ping: str
battle_gift_event_master: list[BattleGiftEventMaster] = []
round_event: list[RoundEvent_2] = []
last_round_event: list[RoundEvent_2] = []
last_round_event_ranking: list[LastRoundEventRanking] = []
round_event_exp: list[RoundEventExp] = [
RoundEventExp(online_rank_id=0, winbase=0, losebase=0, winnum=0),
RoundEventExp(online_rank_id=1, winbase=0, losebase=0, winnum=10),
RoundEventExp(online_rank_id=2, winbase=0, losebase=0, winnum=10),
RoundEventExp(online_rank_id=3, winbase=0, losebase=0, winnum=10),
RoundEventExp(online_rank_id=4, winbase=0, losebase=0, winnum=10),
RoundEventExp(online_rank_id=5, winbase=0, losebase=0, winnum=20),
RoundEventExp(online_rank_id=6, winbase=0, losebase=0, winnum=20),
RoundEventExp(online_rank_id=7, winbase=0, losebase=0, winnum=20),
RoundEventExp(online_rank_id=8, winbase=0, losebase=0, winnum=30),
RoundEventExp(online_rank_id=9, winbase=0, losebase=0, winnum=30),
RoundEventExp(online_rank_id=10, winbase=0, losebase=0, winnum=30),
RoundEventExp(online_rank_id=11, winbase=110, losebase=-60, winnum=50),
RoundEventExp(online_rank_id=12, winbase=110, losebase=-60, winnum=50),
RoundEventExp(online_rank_id=13, winbase=110, losebase=-60, winnum=50),
RoundEventExp(online_rank_id=14, winbase=110, losebase=-60, winnum=50),
RoundEventExp(online_rank_id=15, winbase=110, losebase=-60, winnum=50),
RoundEventExp(online_rank_id=16, winbase=110, losebase=-60, winnum=70),
RoundEventExp(online_rank_id=17, winbase=110, losebase=-60, winnum=70),
RoundEventExp(online_rank_id=18, winbase=110, losebase=-60, winnum=70),
RoundEventExp(online_rank_id=19, winbase=110, losebase=-60, winnum=70),
RoundEventExp(online_rank_id=20, winbase=110, losebase=-60, winnum=70),
RoundEventExp(online_rank_id=21, winbase=110, losebase=-60, winnum=90),
RoundEventExp(online_rank_id=22, winbase=110, losebase=-60, winnum=90),
RoundEventExp(online_rank_id=23, winbase=110, losebase=-60, winnum=90),
RoundEventExp(online_rank_id=24, winbase=110, losebase=-60, winnum=90),
RoundEventExp(online_rank_id=25, winbase=110, losebase=-60, winnum=90),
RoundEventExp(online_rank_id=26, winbase=110, losebase=-60, winnum=110),
RoundEventExp(online_rank_id=27, winbase=110, losebase=-60, winnum=110),
RoundEventExp(online_rank_id=28, winbase=110, losebase=-60, winnum=110),
RoundEventExp(online_rank_id=29, winbase=110, losebase=-60, winnum=110),
RoundEventExp(online_rank_id=30, winbase=110, losebase=-60, winnum=110),
RoundEventExp(online_rank_id=31, winbase=110, losebase=-60, winnum=110),
RoundEventExp(online_rank_id=32, winbase=110, losebase=-60, winnum=130),
RoundEventExp(online_rank_id=33, winbase=110, losebase=-60, winnum=130),
RoundEventExp(online_rank_id=34, winbase=110, losebase=-60, winnum=130),
RoundEventExp(online_rank_id=35, winbase=110, losebase=-60, winnum=130),
RoundEventExp(online_rank_id=36, winbase=110, losebase=-60, winnum=130),
RoundEventExp(online_rank_id=37, winbase=110, losebase=-60, winnum=130),
RoundEventExp(online_rank_id=38, winbase=110, losebase=-60, winnum=130),
RoundEventExp(online_rank_id=39, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=40, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=41, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=42, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=43, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=44, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=45, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=46, winbase=110, losebase=-60, winnum=150),
RoundEventExp(online_rank_id=47, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=48, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=49, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=50, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=51, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=52, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=53, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=54, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=55, winbase=110, losebase=-60, winnum=170),
RoundEventExp(online_rank_id=56, winbase=110, losebase=-60, winnum=190),
RoundEventExp(online_rank_id=57, winbase=110, losebase=-60, winnum=190),
RoundEventExp(online_rank_id=58, winbase=110, losebase=-60, winnum=190),
RoundEventExp(online_rank_id=59, winbase=110, losebase=-60, winnum=190),
RoundEventExp(online_rank_id=60, winbase=110, losebase=-60, winnum=190),
RoundEventExp(online_rank_id=61, winbase=110, losebase=-60, winnum=190),
RoundEventExp(online_rank_id=62, winbase=110, losebase=-60, winnum=190),
RoundEventExp(online_rank_id=63, winbase=110, losebase=-60, winnum=190),
]
stamp_info: list[StampInfo] = []
timerelease_no: int
timerelease_avatar_gacha_no: int
takeover_reward: list[TakeoverReward] = [
TakeoverReward(
takeover_reward_type=0,
takeover_reward_data=[
TakeoverReward.TakeoverRewardData(
reward_no=1,
reward_category=RewardCategoryEnum.D_COIN,
reward_type=5000,
),
TakeoverReward.TakeoverRewardData(
reward_no=2,
reward_category=RewardCategoryEnum.FULLTUNE_TICKET,
reward_type=1,
),
],
),
# ID Zero mileage rewards
TakeoverReward(
takeover_reward_type=1,
takeover_reward_data=[
TakeoverReward.TakeoverRewardData(
reward_no=1,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=82,
),
TakeoverReward.TakeoverRewardData(
reward_no=1,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=190,
),
TakeoverReward.TakeoverRewardData(
reward_no=2,
reward_category=RewardCategoryEnum.CAR_DRESSUP_TOKEN,
reward_type=2,
),
TakeoverReward.TakeoverRewardData(
reward_no=2,
reward_category=RewardCategoryEnum.AVATAR_TOKEN,
reward_type=2,
),
TakeoverReward.TakeoverRewardData(
reward_no=3,
reward_category=RewardCategoryEnum.CAR_DRESSUP_TOKEN,
reward_type=3,
),
TakeoverReward.TakeoverRewardData(
reward_no=3,
reward_category=RewardCategoryEnum.AVATAR_TOKEN,
reward_type=3,
),
TakeoverReward.TakeoverRewardData(
reward_no=4,
reward_category=RewardCategoryEnum.CAR_DRESSUP_TOKEN,
reward_type=5,
),
TakeoverReward.TakeoverRewardData(
reward_no=4,
reward_category=RewardCategoryEnum.AVATAR_TOKEN,
reward_type=5,
),
TakeoverReward.TakeoverRewardData(
reward_no=5,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=104,
),
TakeoverReward.TakeoverRewardData(
reward_no=5,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=212,
),
],
),
# ID Zero Charimsa level and fame benefits
TakeoverReward(
takeover_reward_type=2,
takeover_reward_data=[
TakeoverReward.TakeoverRewardData(
reward_no=1,
reward_category=RewardCategoryEnum.TITLE,
reward_type=4052,
),
TakeoverReward.TakeoverRewardData(
reward_no=2,
reward_category=RewardCategoryEnum.TITLE,
reward_type=4053,
),
TakeoverReward.TakeoverRewardData(
reward_no=3,
reward_category=RewardCategoryEnum.TITLE,
reward_type=4054,
),
TakeoverReward.TakeoverRewardData(
reward_no=4,
reward_category=RewardCategoryEnum.TITLE,
reward_type=4055,
),
TakeoverReward.TakeoverRewardData(
reward_no=5,
reward_category=RewardCategoryEnum.TITLE,
reward_type=4056,
),
TakeoverReward.TakeoverRewardData(
reward_no=6,
reward_category=RewardCategoryEnum.TITLE,
reward_type=4057,
),
TakeoverReward.TakeoverRewardData(
reward_no=7,
reward_category=RewardCategoryEnum.TITLE,
reward_type=4058,
),
],
),
# SWDC
TakeoverReward(
takeover_reward_type=3,
takeover_reward_data=[
TakeoverReward.TakeoverRewardData(
reward_no=1,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=81,
),
TakeoverReward.TakeoverRewardData(
reward_no=1,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=189,
),
TakeoverReward.TakeoverRewardData(
reward_no=2,
reward_category=RewardCategoryEnum.FULLTUNE_TICKET,
reward_type=1,
),
TakeoverReward.TakeoverRewardData(
reward_no=2,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=103,
),
TakeoverReward.TakeoverRewardData(
reward_no=2,
reward_category=RewardCategoryEnum.AVATAR_BODY,
reward_type=211,
),
],
),
]
subcard_judge: list[SubcardJudge] = [
SubcardJudge(
condition_id=1,
lower_rank=1,
higher_rank=7,
condition_start=17,
condition_end=20,
calc=3,
),
SubcardJudge(
condition_id=2,
lower_rank=1,
higher_rank=15,
condition_start=10,
condition_end=10,
calc=20,
),
SubcardJudge(
condition_id=3,
lower_rank=11,
higher_rank=15,
condition_start=80,
condition_end=89,
calc=7,
),
SubcardJudge(
condition_id=3,
lower_rank=11,
higher_rank=15,
condition_start=90,
condition_end=100,
calc=10,
),
SubcardJudge(
condition_id=3,
lower_rank=1,
higher_rank=15,
condition_start=0,
condition_end=40,
calc=-100,
),
SubcardJudge(
condition_id=1,
lower_rank=1,
higher_rank=15,
condition_start=21,
condition_end=24,
calc=7,
),
SubcardJudge(
condition_id=1,
lower_rank=1,
higher_rank=15,
condition_start=25,
condition_end=25,
calc=10,
),
SubcardJudge(
condition_id=3,
lower_rank=16,
higher_rank=20,
condition_start=70,
condition_end=89,
calc=6,
),
SubcardJudge(
condition_id=3,
lower_rank=16,
higher_rank=20,
condition_start=90,
condition_end=100,
calc=12,
),
SubcardJudge(
condition_id=3,
lower_rank=16,
higher_rank=20,
condition_start=0,
condition_end=50,
calc=-100,
),
]
special_promote: list[SpecialPromote] = [
SpecialPromote(counter=50, online_rank_id=8),
SpecialPromote(counter=200, online_rank_id=16),
SpecialPromote(counter=255, online_rank_id=21),
]
matching_id: int = 0
matching_group: list[MatchingGroup] = [
MatchingGroup(group_id=1, group_percent=30),
MatchingGroup(group_id=2, group_percent=50),
MatchingGroup(group_id=3, group_percent=100),
]
timetrial_disp_date: datetime = datetime(2021, 10, 1)
# price for every car
buy_car_need_cash: int = 5000
# number of buyable shop/customization time limits
time_extension_limit: int = 5
# TODO: set based on special mode/event
collabo_id: CollaboEnum = CollaboEnum.NONE
# UNIX timestamp
driver_debut_end_date: datetime = datetime(2039, 1, 1)
online_battle_param1: int = 0
online_battle_param2: int = 0
online_battle_param3: int = 0
online_battle_param4: int = 0
online_battle_param5: int = 0
online_battle_param6: int = 2
online_battle_param7: int = 0
online_battle_param8: int = 3900
theory_open_version: str = "1.00.00"
theory_close_version: str = "9.99.99"
special_mode_data: Optional[SpecialModeData] = None
timetrial_event_data: Optional[TimetrialEventData] = None
number_lottery_data: list[NumberLotteryData] = []
@@ -0,0 +1,34 @@
from titles.idac.const import StoryTypeEnum
from titles.idac.model.common_model import DtoModel
class ChallengeDto(DtoModel):
vs_type: int
play_difficulty: int
cleared_difficulty: int
story_type: StoryTypeEnum
play_count: int
weak_difficulty: int = 0 # only used by special mode?
eval_id: int
advantage: int
sec1_advantage_avg: int
sec2_advantage_avg: int
sec3_advantage_avg: int
sec4_advantage_avg: int
nearby_advantage_rate: int
win_flag: int
result: int
record: int
course_id: int
last_play_course_id: int
style_car_id: int
course_day: int
class BestChallengeByVsTypeDto(DtoModel):
story_type: StoryTypeEnum
vs_type: int
max_clear_lv: int
last_play_lv: int
last_play_course_id: int
play_count: int
+103
View File
@@ -0,0 +1,103 @@
from enum import Enum
from typing import Optional
from pydantic import AliasChoices, BaseModel, Field
from titles.idac.const import StoryPlayStatusEnum, StoryTypeEnum
from titles.idac.model.common_model import DtoModel, RequestGeneric
class RequestSeason2Headers(RequestGeneric):
timestamp: int
a_serial: str
a_store: int
a_country: int
a_region: int
device_version: str
asset_version: int
class RequestSeason2UserHeaders(RequestSeason2Headers):
session: int
session_procseq: int
class TipsGameModeEnum(str, Enum):
"""Game mode types for tips tracking."""
TIMETRIAL = "timetrial"
STORY = "story"
STORE_BATTLE = "store_battle"
ONLINE_BATTLE = "online_battle"
SPECIAL = "special"
CHALLENGE = "challenge"
THEORY = "theory"
@property
def field_name(self) -> str:
"""Get the corresponding ProfileTipsDto field name."""
return f"{self.value}_play_count"
class Season2ConfigData(DtoModel):
id: int = Field(..., validation_alias=AliasChoices("id", "config_id"))
steering_intensity: Optional[int] = None
transmission_type: Optional[int] = None
default_viewpoint: Optional[int] = None
favorite_bgm: Optional[int] = None
bgm_volume: Optional[int] = None
se_volume: Optional[int] = None
master_volume: Optional[int] = None
store_battle_policy: Optional[int] = None
battle_onomatope_display: Optional[int] = None
cornering_guide: Optional[int] = None
minimap: Optional[int] = None
line_guide: Optional[int] = None
ghost: Optional[int] = None
race_exit: Optional[int] = None
result_skip: Optional[int] = None
stamp_select_skip: Optional[int] = None
class Season2TipsInfo(DtoModel):
tips_list: str
timetrial_play_count: int
story_play_count: int
store_battle_play_count: int
online_battle_play_count: int
special_play_count: int
challenge_play_count: int
theory_play_count: int
class Season2StoryDataUpdate(DtoModel):
user_id: int
story_type: StoryTypeEnum = StoryTypeEnum.STORY
chapter: int
episode: int
difficulty: int
play_status: StoryPlayStatusEnum
play_score: int
chapter_clear: int
chapter_loop_max: int
class Season2StampEvent(DtoModel):
m_stamp_event_id: int
select_flag: int = 0
stamp_masu: int = 0
daily_bonus: int = 0
weekly_bonus: int = 0
weekday_bonus: int = 0
weekend_bonus: int = 0
total_bonus: int = 0
day_total_bonus: int = 0
store_battle_bonus: int = 0
story_bonus: int = 0
online_battle_bonus: int = 0
timetrial_bonus: int = 0
fasteststreetlegaltheory_bonus: int = 0
collaboration_bonus: int = 0
add_bonus_daily_flag_1: int = 0
add_bonus_daily_flag_2: int = 0
add_bonus_daily_flag_3: int = 0
new_flag: bool = False
+24
View File
@@ -0,0 +1,24 @@
from titles.idac.model.season2.boot_model import ResponseSeason2BootGetConfigData
from titles.idac.model.season3.boot_model import ResponseSeason3BootGetConfigData
class StampInfoConfig(ResponseSeason2BootGetConfigData.StampInfo):
pass
class TimetrialConfig(ResponseSeason2BootGetConfigData.TimetrialEventData):
pass
class BattleGiftConfig(ResponseSeason2BootGetConfigData.BattleGiftEventMaster):
pass
class RoundConfig(ResponseSeason2BootGetConfigData.RoundEvent_2):
pass
class LeagueRewardConfig(
ResponseSeason3BootGetConfigData.LeagueRewards,
):
pass
+252
View File
@@ -0,0 +1,252 @@
from datetime import datetime
from titles.idac.model.common_model import *
class LotteryDto(DtoModel):
saved_value: int
lottery_count: int
create_date: datetime
class CarDto(DtoModel):
car_id: int
style_car_id: int
color: int
bureau: int
kana: int
s_no: int
l_no: int
car_flag: int
tune_point: int
tune_level: int = 0
tune_parts: int
infinity_tune: int = 0
online_vs_win: int = 0
pickup_seq: int = 1
purchase_seq: int = 1
color_stock_list: str
color_stock_new_list: str
parts_stock_list: str
parts_stock_new_list: str
parts_set_equip_list: str
parts_list: list[Part]
equip_parts_count: int
use_count: int = 0
story_use_count: int = 0
timetrial_use_count: int = 0
vs_use_count: int = 0
net_vs_use_count: int = 0
theory_use_count: int = 0
car_mileage: int = 0
class PickupOnCar(DtoModel):
style_car_id: int
pickup_seq: int
class PickupOffCar(DtoModel):
style_car_id: int
class RequestSeason2FactoryAvatarGachaResult(RequestGeneric):
class Reward(DtoModel):
reward_id: int
avatar_gacha_id: int
reward_data: list[Reward]
use_ticket_cnt: int
stock_obj: StockData
class ResponseSeason2FactoryAvatarGachaResult(ResponseGeneric):
ticket_data: list[Ticket]
class RequestSeason2FactoryBuyCarResult(RequestGeneric):
use_ticket: int
cash: int
total_cash: int
car_id: int
style_car_id: int
color: int
bureau: int
kana: int
s_no: int
l_no: int
car_flag: int
tune_point: int
tune_parts: int
color_stock_list: str
color_stock_new_list: str
parts_stock_list: str
parts_stock_new_list: str
parts_set_equip_list: str
purchase_seq: int
parts_list: list[Part]
equip_parts_count: int
pickup_on_car_ids: list[PickupOnCar]
pickup_off_car_ids: list[PickupOffCar]
class ResponseSeason2FactoryBuyCarResult(ResponseGeneric):
ticket_data: list[Ticket]
fulltune_count: int
total_car_parts_count: int
car_layout_count: list[CarLayoutCount]
car_style_count: list[CarStyleCount]
class RequestSeason2FactorySaveFavoriteCar(RequestGeneric):
pickup_on_car_ids: list[PickupOnCar]
pickup_off_car_ids: list[PickupOffCar]
ResponseSeason2FactorySaveFavoriteCar = ResponseGeneric
class RequestSeason2FactoryUpdateCustomizeResult(RequestGeneric):
style_car_id: int
color: int
color_stock_list: str
color_stock_new_list: str
parts_stock_list: str
parts_stock_new_list: str
parts_set_equip_list: str
parts_list: list[Part]
equip_parts_count: int
total_car_parts_count: int
ticket_data: list[Ticket]
ResponseSeason2FactoryUpdateCustomizeResult = ResponseGeneric
class RequestSeason2FactoryUpdateMultipleCustomizeResult(RequestGeneric):
class FactoryCar(DtoModel):
style_car_id: int
color: int
color_stock_list: str
color_stock_new_list: str
parts_stock_list: str
parts_stock_new_list: str
parts_set_equip_list: str
parts_list: list[Part]
equip_parts_count: int
car_list: list[FactoryCar]
total_car_parts_count: int
ticket_data: list[Ticket]
ResponseSeason2FactoryUpdateMultipleCustomizeResult = ResponseGeneric
class RequestSeason2FactoryUpdateCustomizeUser(RequestGeneric):
mytitle_id: int
mytitle_efffect_id: int
papercup_id: int
tachometer_id: int
aura_id: int
aura_color_id: int
aura_line_id: int
bgm_id: int
keyholder_id: int
start_menu_bg_id: int
stamp_key_assign_0: int
stamp_key_assign_1: int
stamp_key_assign_2: int
stamp_key_assign_3: int
stock_obj: StockData
ResponseSeason2FactoryUpdateCustomizeUser = ResponseGeneric
class RequestSeason2FactoryUpdateCustomizeAvatar(RequestGeneric):
stock_obj: StockData
avatar_obj: AvatarData
ResponseSeason2FactoryUpdateCustomizeAvatar = ResponseGeneric
RequestSeason2FactoryRenameBefore = RequestGeneric
ResponseSeason2FactoryRenameBefore = ResponseGeneric
class RequestSeason2FactoryRenameResult(RequestGeneric):
username: str
ResponseSeason2FactoryRenameResult = ResponseGeneric
RequestSeason2FactoryGetCarData = RequestGeneric
class ResponseSeason2FactoryGetCarData(ResponseGeneric):
class FactoryCarData(DtoModel):
id: int
car_id: int
style_car_id: int
color: int
bureau: int
kana: int
s_no: int
l_no: int
car_flag: int
tune_point: int
tune_parts: int
infinity_tune: int
online_vs_win: int
color_stock_list: str
color_stock_new_list: str
parts_stock_list: str
parts_stock_new_list: str
parts_set_equip_list: str
use_count: int
story_use_count: int
timetrial_use_count: int
vs_use_count: int
net_vs_use_count: int
theory_use_count: int
car_mileage: int
pickup_seq: int
purchase_seq: int
parts_list: list[Part]
equip_parts_count: int
car_data: list[FactoryCarData]
RequestSeason2FactoryNumberLotteryBefore = RequestGeneric
class ResponseSeason2FactoryNumberLotteryBefore(ResponseGeneric):
class LotteryInfo(DtoModel):
class LotteryWin(DtoModel):
m_number_lottery_schedule_no: int
win_number: int
lottery_count: int
win_list: list[LotteryWin]
lottery_info: LotteryInfo
class RequestSeason2FactoryNumberLotteryResult(RequestGeneric):
style_car_id: int
l_no: int
win_number: int
lottery_count: int
isEnd: int
cash: int
total_cash: int
dressup_point: int
avatar_point: int
ticket_data: list[Ticket]
ResponseSeason2FactoryNumberLotteryResult = ResponseGeneric
+44
View File
@@ -0,0 +1,44 @@
from datetime import datetime
from typing import Optional
from titles.idac.const import BattleGiftStatusEnum, RewardCategoryEnum
from titles.idac.model.common_model import DtoModel
class TicketDto(DtoModel):
ticket_id: RewardCategoryEnum
ticket_cnt: int
class StampDto(DtoModel):
m_stamp_event_id: int
select_flag: int
stamp_masu: int
daily_bonus: int
weekly_bonus: int
weekday_bonus: int
weekend_bonus: int
total_bonus: int
day_total_bonus: int
store_battle_bonus: int
story_bonus: int
online_battle_bonus: int
timetrial_bonus: int
fasteststreetlegaltheory_bonus: int
collaboration_bonus: int
add_bonus_daily_flag_1: int
add_bonus_daily_flag_2: int
add_bonus_daily_flag_3: int
create_date_daily: Optional[datetime] = None
create_date_weekly: Optional[datetime] = None
class TimeTrialEventDto(DtoModel):
timetrial_event_id: int
point: int
class BattleGiftDto(DtoModel):
battle_gift_event_id: int
gift_id: int
gift_get_status: BattleGiftStatusEnum = BattleGiftStatusEnum.AQUIRED
received_date: Optional[datetime] = None
+112
View File
@@ -0,0 +1,112 @@
from datetime import datetime
from titles.idac.model.common_model import DtoModel, RequestGeneric, ResponseGeneric
class RequestSeason2LogSavePlayLog(RequestGeneric):
class PickupCarData(DtoModel):
style_car_id: int
point: int
class GeneralLog(DtoModel):
param: int
class MatchErrorInfo(DtoModel):
group_key: str
error_sequence_id: int
communication_type_jadge_flag: int
opponent_store: int
board_id: str
seat_no: int
mode_id: int
course_id: int
course_day: int
style_car_id: int
pickup_car_data: list[PickupCarData]
story_type: int
chapter: int
episode: int
difficulty: int
play_score: int
record: int
mode_exp: int
mode_rank: int
continue_flag: int
continue_ticket_use_flag: int
grade: int
use_zenkoku_intro: int
start_dt: datetime
end_dt: datetime
mode_select_start_date: datetime
result_start_date: datetime
name_entry_sec: int
game_start_sec: int
mode_select_sec: int
total_course_select_sec: int
total_car_select_sec: int
play_course_select_sec: int
play_car_select_sec: int
shop_stay_sec: int
matching_success_count: int
result_sec: int
transmission: int
start_ui_select: int
start_ui_decision: int
mode_ui_select: int
mode_ui_decision: int
course_ui_select: int
course_ui_decision: int
inshop_standby_status: int
new_player: int
standby_play_flag: int
use_credit: int
viewpoint: int
retire_type: int
timetrial_eval: int
bgm_id: int
invade_invite_flag: int
invade_invite_select_flag: int
first_free_flag: int
coin_to_credit: int
ind_run_general_log: list[GeneralLog]
app_general_log: list[GeneralLog]
group_key: str
rematch_count: int
race_end_flag: int
communication_type: int
match_error_info_arr: list[MatchErrorInfo]
ResponseSeason2LogSavePlayLog = ResponseGeneric
class RequestSeason2LogSaveEndLog(RequestGeneric):
board_id: str
seat_no: int
story_play_cnt: int
expedition_play_cnt: int
timetrial_play_cnt: int
vs_play_cnt: int
net_vs_play_cnt: int
theory_play_cnt: int
buy_car_cnt: int
start_credit: int
continue_credit: int
buy_car_credit: int
extend_time_credit: int
rename_credit: int
total_play_credit_cnt: int
total_buy_car_credit_cnt: int
continue_ticket_use_cnt: int
buy_car_ticket_use_cnt: int
car_id: int
style_car_id: int
replay_mode: int
cash_use_cnt: int
avatar_ticket_use_cnt: int
custom_ticket_use_cnt: int
extend_time_cnt: int
rename_cnt: int
ResponseSeason2LogSaveEndLog = ResponseGeneric
+65
View File
@@ -0,0 +1,65 @@
from datetime import datetime
from pydantic import BaseModel
from titles.idac.model.common_model import (
LockResultEnum,
RequestGeneric,
ResponseGeneric,
)
class RequestSeason2LoginCheckLock(RequestGeneric):
id: int
accesscode: str
device_name: str
class ResponseSeason2LoginCheckLock(ResponseGeneric):
class TakeoverData(BaseModel):
takeover_user_name: str
takeover_extract_day: str
drv_level_class: int
drv_level: int
pride_level: int
swdc_drv_level_class: int
swdc_drv_level: int
full_spec_car_num_class: int
full_spec_car_num: int
mileage_class: int
mileage: int
gamever: int
lock_result: LockResultEnum
lock_date: datetime
daily_play: int # double check this
session: str
shared_security_key: str
session_procseq: int
new_player: int
server_status: int
takeover_data: list[TakeoverData]
RequestSeason2LoginUnLock = RequestGeneric
class ResponseSeason2LoginUnLock(ResponseGeneric):
lock_result: LockResultEnum
RequestSeason2LoginReLock = RequestGeneric
class ResponseSeason2LoginReLock(ResponseGeneric):
lock_result: LockResultEnum
lock_date: int
class RequestSeason2LoginGuestPlay(RequestGeneric):
device_name: str
class ResponseSeason2LoginGuestPlay(ResponseGeneric):
session: str
shared_security_key: str
session_procseq: int
+38
View File
@@ -0,0 +1,38 @@
from titles.idac.model.common_model import *
class RequestSeason2UserUpdateTimeTrialResult(RequestGeneric):
goal_time: int
section_time_1: int
section_time_2: int
section_time_3: int
section_time_4: int
style_car_id: int
car_mileage: int
course_id: int
skill_level_exp: int
mission: int
mileage: int
eval_id: int
event_point: int
stock_obj: StockData
aura_id: int
aura_color_id: int
aura_line_id: int
cash: int
total_cash: int
dressup_point: int
avatar_point: int
ticket_data: list[Ticket]
reward_dist_obj: RewardDist
driver_debut_obj: DriverDebut
mode_rank_obj: ModeRank
class ResponseSeason2UserUpdateTimeTrialResult(ResponseGeneric):
course_rank: int
course_car_rank: int
location_course_store_rank: int
car_use_count: list[CarUseCount]
maker_use_count: list[MakerUseCount]
timetrial_event_data: TimetrialEventData_2
+22
View File
@@ -0,0 +1,22 @@
from datetime import datetime
from titles.idac.model.common_model import DtoModel
class RoundInfoDto(DtoModel):
round_id_in_json: int
name: str
start_dt: datetime
end_dt: datetime
class UserRoundInfoDto(DtoModel):
round_id: int
count: int = 0
win: int = 0
point: int = 0
play_dt: datetime
class UserRoundInfoWithRankDto(UserRoundInfoDto):
rank: int
+23
View File
@@ -0,0 +1,23 @@
from titles.idac.const import StoryTypeEnum
from titles.idac.model.common_model import DtoModel
class StoryDto(DtoModel):
story_type: StoryTypeEnum = StoryTypeEnum.STORY
chapter: int
loop_count: int
class StoryEpisodeDto(DtoModel):
chapter: int
episode: int
play_status: int
class StoryEpisodeDifficultyDto(DtoModel):
episode: int
difficulty: int
play_count: int = 0
clear_count: int
play_status: int
play_score: int
+111
View File
@@ -0,0 +1,111 @@
from datetime import datetime
from titles.idac.model.common_model import DtoModel, RequestGeneric, ResponseGeneric
from titles.idac.model.season2.factory_model import CarDto
from titles.idac.model.season2.user_model import (
ProfileAvatarDto,
ProfileDto,
ProfileRankDto,
)
class TheoryRivalDto(DtoModel):
profile: ProfileDto
avatar: ProfileAvatarDto
car: CarDto
grade: int
class TheoryCourseDto(DtoModel):
user: int
course_id: int
max_victory_grade: int
run_count: int
powerhouse_lv: int
powerhouse_exp: int
played_powerhouse_lv: int
update_dt: datetime
class TheoryPartnerDto(DtoModel):
partner_id: int
fellowship_lv: int
fellowship_exp: int
class TheoryRunningDto(DtoModel):
course_id: int
attack: int
defense: int
safety: int
runaway: int
trick_flag: int
class TheoryProfileDto(DtoModel):
user: int
grade: int = 0
class RequestSeason2TheoryMatching(RequestGeneric):
course_id: int
grade: int
powerhouse_lv: int
class Season2PowerMatch(DtoModel):
id: int
name: str
grade: int
powerhouseLv: int
mytitleId: int
country: int
auraId: int
auraColor: int
auraLine: int
roundRanking: int
storeName: str
sex: int
face: int
eye: int
mouth: int
hair: int
glasses: int
faceAccessory: int
body: int
bodyAccessory: int
behind: int
bg: int
effect: int
special: int
styleCarId: int
color: int
bureau: int
kana: int
sNo: int
lNo: int
tuneLv: int
carFlag: int
tunePoint: int
infinityTune: int
tuneParts: int
partsList: list[int]
partsCount: int
stamp0: int
stamp1: int
stamp2: int
stamp3: int
attack: int = 128
defense: int = 128
safety: int = 128
runaway: int = 128
trickFlg: int = 0
taEval: int
class ResponseSeason2TheoryMatching(ResponseGeneric):
class RivalData(DtoModel):
auto_match: list[Season2PowerMatch] = []
power_match: list[Season2PowerMatch] = []
rival_data: RivalData
@@ -0,0 +1,95 @@
from datetime import datetime
from typing import Optional
from pydantic import Field
from titles.idac.model.common_model import DtoModel, RequestGeneric, ResponseGeneric
class CourseDto(DtoModel):
course_id: int
run_counts: int
skill_level_exp: int
class TrialDto(DtoModel):
user: int
style_car_id: int
course_id: int
eval_id: int
goal_time: int
section_time_1: int
section_time_2: int
section_time_3: int
section_time_4: int
mission: int
play_dt: datetime
rank: Optional[int] = None
total: Optional[int] = None
class CourseBestData(DtoModel):
course_id: int
rank: int
member: int = Field(alias="user")
value: int = Field(alias="goal_time")
total: Optional[int] = None
store: str = ""
style_car_id: int
play_dt: datetime
section_time_1: int
section_time_2: int
section_time_3: int
section_time_4: int
mission: int = 0
class CourseBestCarData(DtoModel):
class CarList(DtoModel):
rank: int
member: int = Field(alias="user")
value: int = Field(alias="goal_time")
total: Optional[int] = None
store: str
style_car_id: int
play_dt: datetime
section_time_1: int
section_time_2: int
section_time_3: int
section_time_4: int
mission: int
course_id: int
car_list: list[CarList]
class CarId(DtoModel):
car_id: int
style_car_id: int
class RequestSeason2TimeTrialGetBestRecordPreRace(RequestGeneric):
car_ids: list[CarId]
course_id: int
class ResponseSeason2TimeTrialGetBestRecordPreRace(ResponseGeneric):
course_car_best_data: list[CourseBestCarData]
course_best_data: list[CourseBestData]
class RequestSeason2TimeTrialGetBestRecordPreBattle(RequestGeneric):
car_ids: list[CarId]
class ResponseSeason2TimeTrialGetBestRecordPreBattle(ResponseGeneric):
course_pickup_car_best_data: list[CourseBestCarData]
RequestSeason2TimeTrialGetBestRecordPreTA = (
RequestSeason2TimeTrialGetBestRecordPreBattle
)
class ResponseSeason2TimeTrialGetBestRecordPreTA(ResponseGeneric):
course_mybest_data: list[CourseBestData]
course_pickup_car_best_data: list[CourseBestCarData]
+881
View File
@@ -0,0 +1,881 @@
from typing import Optional
from datetime import datetime, timezone
from pydantic import BaseModel, field_validator
from titles.idac.const import NameChangeCategoryEnum, StoryTypeEnum
from titles.idac.model.common_model import *
from titles.idac.model.season2.common_model import (
Season2ConfigData,
Season2StampEvent,
Season2TipsInfo,
)
from titles.idac.model.season2.item_model import TicketDto
class ProfileDto(DtoModel):
version: Optional[int] = None
username: Optional[str] = None
country: Optional[int] = None
store: Optional[int] = None
total_play: Optional[int] = None
daily_play: Optional[int] = None
day_play: Optional[int] = None
mileage: Optional[int] = None
device_version: Optional[str] = None
last_play_date: Optional[datetime] = None
mytitle_id: Optional[int] = None
mytitle_efffect_id: Optional[int] = None
sticker_id: Optional[int] = None
sticker_effect_id: Optional[int] = None
papercup_id: Optional[int] = None
tachometer_id: Optional[int] = None
aura_id: Optional[int] = None
aura_color_id: Optional[int] = None
aura_line_id: Optional[int] = None
bgm_id: Optional[int] = None
keyholder_id: Optional[int] = None
start_menu_bg_id: Optional[int] = None
use_car_id: Optional[int] = None
use_style_car_id: Optional[int] = None
bothwin_count: Optional[int] = None
bothwin_score: Optional[int] = None
subcard_count: Optional[int] = None
vs_history: Optional[int] = None
stamp_key_assign_0: Optional[int] = None
stamp_key_assign_1: Optional[int] = None
stamp_key_assign_2: Optional[int] = None
stamp_key_assign_3: Optional[int] = None
name_change_category: Optional[NameChangeCategoryEnum] = None
factory_disp: Optional[int] = None
create_date: Optional[datetime] = None
cash: Optional[int] = None
dressup_point: Optional[int] = None
avatar_point: Optional[int] = None
total_cash: Optional[int] = None
total_car_parts_count: Optional[int] = None
class ProfileConfigDto(DtoModel):
steering_intensity: Optional[int] = None
transmission_type: Optional[int] = None
default_viewpoint: Optional[int] = None
favorite_bgm: Optional[int] = None
favorite_bgm_list: Optional[str] = None
bgm_volume: Optional[int] = None
se_volume: Optional[int] = None
master_volume: Optional[int] = None
store_battle_policy: Optional[int] = None
battle_onomatope_display: Optional[int] = None
cornering_guide: Optional[int] = None
minimap: Optional[int] = None
line_guide: Optional[int] = None
ghost: Optional[int] = None
race_exit: Optional[int] = None
result_skip: Optional[int] = None
stamp_select_skip: Optional[int] = None
break_flag: Optional[int] = None
visible_my_stats: Optional[int] = None
visible_rival_stats: Optional[int] = None
ProfileAvatarDto = AvatarData
class ProfileRankDto(DtoModel):
story_rank_exp: int = 0
story_rank: int = 1
time_trial_rank_exp: int = 0
time_trial_rank: int = 1
online_battle_rank_exp: int = 0
online_battle_rank: int = 1
store_battle_rank_exp: int = 0
store_battle_rank: int = 1
theory_exp: int = 0
theory_rank: int = 1
pride_group_id: int = 0
pride_point: int = 0
grade_exp: int = 0
grade: int = 1
grade_reward_dist: int = 0
story_rank_reward_dist: int = 0
time_trial_rank_reward_dist: int = 0
online_battle_rank_reward_dist: int = 0
store_battle_rank_reward_dist: int = 0
theory_rank_reward_dist: int = 0
max_attained_online_battle_rank: int = 0
max_attained_pride_point: int = 0
is_last_max: int = 0
class ProfileStockDto(DtoModel):
@field_validator("*", mode="before")
def empty_string_to_none(cls, v):
"""Converts all empty strings to NOne so they do not get updated in the db"""
if v == "":
return None
return v
mytitle_list: Optional[str] = None
mytitle_new_list: Optional[str] = None
avatar_face_list: Optional[str] = None
avatar_face_new_list: Optional[str] = None
avatar_eye_list: Optional[str] = None
avatar_eye_new_list: Optional[str] = None
avatar_hair_list: Optional[str] = None
avatar_hair_new_list: Optional[str] = None
avatar_body_list: Optional[str] = None
avatar_body_new_list: Optional[str] = None
avatar_mouth_list: Optional[str] = None
avatar_mouth_new_list: Optional[str] = None
avatar_glasses_list: Optional[str] = None
avatar_glasses_new_list: Optional[str] = None
avatar_face_accessory_list: Optional[str] = None
avatar_face_accessory_new_list: Optional[str] = None
avatar_body_accessory_list: Optional[str] = None
avatar_body_accessory_new_list: Optional[str] = None
avatar_behind_list: Optional[str] = None
avatar_behind_new_list: Optional[str] = None
avatar_bg_list: Optional[str] = None
avatar_bg_new_list: Optional[str] = None
avatar_effect_list: Optional[str] = None
avatar_effect_new_list: Optional[str] = None
avatar_special_list: Optional[str] = None
avatar_special_new_list: Optional[str] = None
stamp_list: Optional[str] = None
stamp_new_list: Optional[str] = None
keyholder_list: Optional[str] = None
keyholder_new_list: Optional[str] = None
papercup_list: Optional[str] = None
papercup_new_list: Optional[str] = None
tachometer_list: Optional[str] = None
tachometer_new_list: Optional[str] = None
aura_list: Optional[str] = None
aura_new_list: Optional[str] = None
aura_color_list: Optional[str] = None
aura_color_new_list: Optional[str] = None
aura_line_list: Optional[str] = None
aura_line_new_list: Optional[str] = None
bgm_list: Optional[str] = None
bgm_new_list: Optional[str] = None
dx_color_list: Optional[str] = None
dx_color_new_list: Optional[str] = None
start_menu_bg_list: Optional[str] = None
start_menu_bg_new_list: Optional[str] = None
under_neon_list: Optional[str] = None
class ProfileTheoryDto(DtoModel):
play_count: int = 0
play_count_multi: int = 0
partner_id: int
partner_progress: int
partner_progress_score: int
practice_start_rank: int = 0
general_flag: int = 0
vs_history: int = 0
vs_history_multi: int = 0
win_count: int = 0
win_count_multi: int = 0
class ProfileTipsDto(DtoModel):
tips_list: Optional[str] = None
timetrial_play_count: Optional[int] = None
story_play_count: Optional[int] = None
store_battle_play_count: Optional[int] = None
online_battle_play_count: Optional[int] = None
special_play_count: Optional[int] = None
challenge_play_count: Optional[int] = None
theory_play_count: Optional[int] = None
# added in season 3
last_team_join_dt: Optional[datetime] = None
class SeasonTimetrialData(DtoModel):
style_car_id: int = 0
course_id: int
skill_level_exp: int
goal_time: int = 0
rank: int = 0
rank_dt: datetime = datetime.fromtimestamp(0, tz=timezone.utc)
class SeasonTimetrialDto(SeasonTimetrialData):
eval_id: int = 0
class StoryDataDto(DtoModel):
class EpisodeDto(DtoModel):
class DifficultyDto(DtoModel):
difficulty: int
play_count: int = 0
clear_count: int = 0
play_status: StoryPlayStatusEnum = StoryPlayStatusEnum.NEW
play_score: int = 0
episode: int
play_status: StoryPlayStatusEnum = StoryPlayStatusEnum.NEW
difficulty_data: list[DifficultyDto] = []
needs_update: bool = False
story_type: StoryTypeEnum = StoryTypeEnum.STORY
chapter: int
loop_count: int = 1
episode_data: list[EpisodeDto] = []
needs_update: bool = False
class RewardDistDto(DtoModel):
grade_reward_dist: int
story_rank_reward_dist: int
time_trial_rank_reward_dist: int
online_battle_rank_reward_dist: int
store_battle_rank_reward_dist: int
theory_rank_reward_dist: int
class UpdateStoryResultRequest(DtoModel):
story_type: StoryTypeEnum = StoryTypeEnum.STORY
chapter: int
episode: int
difficulty: int
play_status: int
play_score: int
chapter_clear: int
chapter_loop_max: int
course_id: int
mileage: int
stock_obj: ProfileStockDto
aura_id: int
aura_color_id: int
aura_line_id: int
cash: int
total_cash: int
dressup_point: int
avatar_point: int
ticket_data: list[TicketDto]
mode_rank_obj: ProfileRankDto
reward_dist_obj: RewardDistDto
driver_debut_obj: DriverDebut
class RequestSeason2UserCreateAccount(RequestGeneric):
class CarObj(DtoModel):
car_id: int
style_car_id: int
color: int
bureau: int
kana: int
s_no: int
l_no: int
car_flag: int
tune_point: int
tune_parts: int
color_stock_list: str
color_stock_new_list: str
parts_stock_list: str
parts_stock_new_list: str
parts_set_equip_list: str
parts_list: list[Part]
equip_parts_count: int
username: str
car_obj: CarObj
avatar_obj: AvatarData
config_obj: Season2ConfigData
stamp_key_assign_0: int
stamp_key_assign_1: int
stamp_key_assign_2: int
stamp_key_assign_3: int
mode_rank_data: ModeRankData
takeover_stock_obj: StockData
takeover_ticket: list[Ticket]
cash: int
use_ticket: int
class ResponseSeason2UserCreateAccount(ResponseGeneric):
round_event: list[RoundEvent] = []
past_round_event: list[PastRoundEvent] = []
stamp_event_data: list[StampEventData] = []
fulltune_count: int = 0
total_car_parts_count: int = 0
car_layout_count: list[CarLayoutCount]
car_style_count: list[CarStyleCount]
battle_gift_data: list[BattleGift] = []
class RequestSeason2UserGetData(RequestGeneric):
battle_gift_event_id: int
class ResponseSeason2UserGetData(ResponseGeneric):
class UserBaseData(DtoModel):
id: int
username: str
access_code: str
country: int
store: int
total_play: int
daily_play: int
day_play: int
mileage: int
device_version: str
asset_version: int
last_play_dt: datetime = Field(alias="last_play_date")
mytitle_id: int
mytitle_efffect_id: int
sticker_id: int
sticker_effect_id: int
papercup_id: int
tachometer_id: int
aura_id: int
aura_color_id: int
aura_line_id: int
bgm_id: int
keyholder_id: int
start_menu_bg_id: int
use_car_id: int
use_style_car_id: int
bothwin_count: int
bothwin_score: int
subcard_count: int
vs_history: int
stamp_key_assign_0: int
stamp_key_assign_1: int
stamp_key_assign_2: int
stamp_key_assign_3: int
name_change_category: int
factory_disp: int
create_dt: datetime = Field(alias="create_date")
store_name: str
mode_rank_data: ModeRankData
have_car_cnt: int
cash: int
dressup_point: int
avatar_point: int
total_cash: int
class MissionData(DtoModel):
id: int
achieve_flag: int
received_flag: int
update_dt: datetime
class WeeklyMission(DtoModel):
id: int
internal_index: int
mission_no: int
reward_no: int
progress: int
class CourseData(DtoModel):
id: int
course_id: int
run_counts: int
skill_level_exp: int
class ToppatuEventData(DtoModel):
id: int
event_id: int
count1: int
count2: int
count3: int
accept_flag: int
class EventData(DtoModel):
id: int
active_event_id: int
dialog_show_date: datetime
show_start_dialog_flag: int
show_progress_dialog_flag: int
show_end_dialog_flag: int
end_event_id: int
class RewardsData(DtoModel):
rewards_id: int
rewards_reason: str
rewards: Reward
class LoginBonusData(DtoModel):
gacha_id: int
gacha_item_id: int
category: int
type: int
class FrozenData(DtoModel):
frozen_status: int = 0
class PenaltyData(DtoModel):
penalty_flag: int
penalty_2_level: int
class AvatarGachaLottery(DtoModel):
avatar_gacha_id: int
class StoryCourse(DtoModel):
course_id: int
count: int
class DriverDebut(DtoModel):
play_count: int
daily_play: int
last_play_dt: datetime
use_start_date: datetime
use_end_date: datetime
use_dt: datetime
ticket_cnt: int
ticket_get_bit: int
class TheoryData(DtoModel):
play_count: int
play_count_multi: int
partner_id: int
partner_progress: int
partner_progress_score: int
practice_start_rank: int
general_flag: int
vs_history: int
vs_history_multi: int
win_count: int
win_count_multi: int
class TheoryCourse(DtoModel):
course_id: int
max_victory_grade: int
run_count: int
powerhouse_lv: int
powerhouse_exp: int
played_powerhouse_lv: int
update_dt: datetime
class TheoryPartner(DtoModel):
partner_id: int
fellowship_lv: int
fellowship_exp: int
class TheoryRunningParm(DtoModel):
course_id: int
attack: int
defense: int
safety: int
runaway: int
trick_flag: int
class SpecialModeHint(DtoModel):
story_type: int
hint_display_flag: int
user_base_data: Optional[UserBaseData] = None
avatar_data: Optional[AvatarData] = None
pick_up_car_data: Optional[list[CarData]] = []
story_data: Optional[list[Story]] = []
vsinfo_data: Optional[list[VsInfo]] = []
stock_data: Optional[StockData] = None
mission_data: Optional[MissionData] = None
weekly_mission_data: Optional[list[WeeklyMission]] = None
course_data: Optional[list[CourseData]] = []
toppatu_event_data: Optional[ToppatuEventData] = None
event_data: Optional[EventData] = None
rewards_data: Optional[list[RewardsData]] = []
login_bonus_data: Optional[LoginBonusData] = None
frozen_data: Optional[FrozenData] = None
penalty_data: Optional[PenaltyData] = None
config_data: Optional[Season2ConfigData] = None
battle_gift_data: Optional[list[BattleGift]] = []
ticket_data: Optional[list[Ticket]] = []
round_event: Optional[list[RoundEvent]] = []
last_round_event: Optional[list[RoundEvent]] = []
past_round_event: Optional[list[PastRoundEvent]] = []
total_round_point: Optional[int] = None
stamp_event_data: Optional[list[StampEventData]] = []
avatar_gacha_lottery_data: Optional[list[AvatarGachaLottery]] = []
fulltune_count: Optional[int] = None
total_car_parts_count: Optional[int] = None
car_layout_count: Optional[list[CarLayoutCount]] = []
car_style_count: Optional[list[CarStyleCount]] = []
car_use_count: Optional[list[CarUseCount]] = []
maker_use_count: Optional[list[MakerUseCount]] = []
story_course: Optional[list[StoryCourse]] = []
driver_debut: Optional[DriverDebut] = None
theory_data: Optional[TheoryData] = None
theory_course_data: Optional[list[TheoryCourse]] = []
theory_partner_data: Optional[list[TheoryPartner]] = []
theory_running_pram_data: Optional[list[TheoryRunningParm]] = []
special_mode_data: Optional[list[SpecialMode]] = []
challenge_mode_data: Optional[list[ChallengeModeData]] = []
season_rewards_data: Optional[list[RewardsData]] = []
timetrial_event_data: Optional[TimetrialEventData2] = None
special_mode_hint_data: Optional[SpecialModeHint] = None
tips_info: Optional[Season2TipsInfo] = None
class RequestSeason2UserUpdateLogin(RequestGeneric):
class RewardsId(BaseModel):
rewards_id: int
mytitle_id: int
mytitle_efffect_id: int
sticker_id: int
sticker_effect_id: int
keyholder_id: int
papercup_id: int
tachometer_id: int
aura_id: int
aura_color_id: int
aura_line_id: int
bgm_id: int
start_menu_bg_id: int
cash: int
total_cash: int
dressup_point: int
avatar_point: int
stock_obj: StockData
avatar_obj: AvatarData
rewards_id_arr: list[RewardsId]
ticket_data: list[Ticket]
battle_gift_event_id: int
gift_id: int
season_rewards_id_arr: list[RewardsId]
ResponseSeason2UserUpdateLogin = ResponseGeneric
class ProfileResult(DtoModel):
mileage: int
stock_obj: StockData
cash: int
total_cash: int
dressup_point: int
avatar_point: int
ticket_data: list[Ticket]
reward_dist_obj: RewardDist
aura_id: int
aura_color_id: int
aura_line_id: int
driver_debut_obj: Optional[DriverDebut] = None
mode_rank_obj: Optional[ModeRank] = None
factory_disp: Optional[int] = None
class RequestSeason2UserUpdateTimeTrialResult(ProfileResult, RequestGeneric):
goal_time: int
section_time_1: int
section_time_2: int
section_time_3: int
section_time_4: int
style_car_id: int
car_mileage: int
course_id: int
skill_level_exp: int
mission: int
mileage: int
eval_id: int
event_point: int
class ResponseSeason2UserUpdateTimeTrialResult(ResponseGeneric):
course_rank: int = 0
course_car_rank: int = 0
location_course_store_rank: int = 0
car_use_count: list[CarUseCount] = []
maker_use_count: list[MakerUseCount] = []
timetrial_event_data: Optional[TimetrialEventData_2] = None
class RequestSeason2UserUpdateModeResult(RequestGeneric):
mileage: int
mytitle_id: int
mytitle_efffect_id: int
standby_play_flag: int
mode_id: int
tips_list: str
config_obj: Season2ConfigData
stock_obj: StockData
aura_id: int
aura_color_id: int
aura_line_id: int
cash: int
total_cash: int
dressup_point: int
avatar_point: int
ticket_data: list[Ticket]
reward_dist_obj: RewardDist
factory_disp: int
class ResponseSeason2UserUpdateModeResult(ResponseGeneric):
# 1 Success, 0 = Failed
server_status: int = 1
class CarTuneResult(DtoModel):
style_car_id: int
car_flag: int
tune_point: int
tune_level: int
tune_parts: int
class RequestSeason2UserUpdateCarTune(RequestGeneric):
style_car_id: int
car_flag: int
tune_point: int
tune_level: int
tune_parts: int
use_ticket: int
class ResponseSeason2UserUpdateCarTune(ResponseGeneric):
story_data: list[Story]
car_use_count: list[CarUseCount] = []
maker_use_count: list[MakerUseCount] = []
class RequestSeason2UserUpdateStoryResult(ProfileResult, RequestGeneric):
style_car_id: int
car_mileage: int
tune_point: int
tune_level: int
story_type: StoryTypeEnum
chapter: int
episode: int
difficulty: int
play_status: StoryPlayStatusEnum
play_score: int
chapter_clear: int
chapter_loop_max: int
course_id: int
ResponseSeason2UserUpdateStoryResult = ResponseSeason2UserUpdateCarTune
class RequestSeason2UserUpdateSpecialResult(ProfileResult, RequestGeneric):
style_car_id: int
car_mileage: int
story_type: StoryTypeEnum
vs_type: int
play_difficulty: int
cleared_difficulty: int
weak_difficulty: int
eval_id: int
advantage: int
sec1_advantage_avg: int
sec2_advantage_avg: int
sec3_advantage_avg: int
sec4_advantage_avg: int
nearby_advantage_rate: int
win_flag: int
result: int
record: int
course_id: int
last_play_course_id: int
course_day: int
hint_display_flag: int
rental_flag: int = 0 # does not exist in season 2, but that makes it easer to code
class ResponseSeason2UserUpdateSpecialResult(ResponseGeneric):
special_mode_data: list[SpecialMode]
car_use_count: list[CarUseCount]
maker_use_count: list[MakerUseCount]
class RequestSeason2UserGetPastSeasonRoundData(RequestGeneric):
season_id: int
class ResponseSeason2UserGetPastSeasonRoundData(ResponseGeneric):
class PastSeasonRoundEventData(BaseModel):
count: int
win: int
rank: int
point: int
total_round_point: int
round_name: str
round_id: int
season_id: int
past_season_round_event_data: list[PastSeasonRoundEventData]
class RequestSeason2UserGetTAData(RequestGeneric):
pass
class ResponseSeason2UserGetTAData(ResponseGeneric):
timetrial_data: list[SeasonTimetrialData]
RequestSeason2UserGetPastSeasonTAData = RequestSeason2UserGetPastSeasonRoundData
class ResponseSeason2UserGetPastSeasonTAData(ResponseGeneric):
season_id: int
past_season_timetrial_data: list[SeasonTimetrialData]
class RequestSeason2UserUpdateTheoryResult(ProfileResult, RequestGeneric):
result: int
style_car_id: int
car_mileage: int
course_id: int
course_day: int
battle_mode: int
vs_history: int
vs_count: int
advantage_1: int
advantage_2: int
advantage_3: int
advantage_4: int
rival_id_1: int
rival_grade_1: int
rival_style_car_id_1: int
rival_powerhouse_lv_1: int
rival_id_2: int
rival_grade_2: int
rival_style_car_id_2: int
rival_powerhouse_lv_2: int
rival_id_3: int
rival_grade_3: int
rival_style_car_id_3: int
rival_powerhouse_lv_3: int
powerhouse_lv: int
powerhouse_exp: int
partner_id: int
partner_progress: int
partner_progress_score: int
practice_start_rank: int
fellowship_lv: int
fellowship_exp: int
max_victory_grade: int
general_flag: int
tune_lv: int
attack: int
defense: int
safety: int
runaway: int
trick_flag: int
class ResponseSeason2UserUpdateTheoryResult(ResponseGeneric):
played_powerhouse_lv: int
car_use_count: list[CarUseCount]
maker_use_count: list[MakerUseCount]
play_count: int
play_count_multi: int
win_count: int
win_count_multi: int
class RequestSeason2UserUpdateChallengeModeResult(ProfileResult, RequestGeneric):
style_car_id: int
car_mileage: int
story_type: StoryTypeEnum
vs_type: int
play_difficulty: int
cleared_difficulty: int
eval_id: int
advantage: int
sec1_advantage_avg: int
sec2_advantage_avg: int
sec3_advantage_avg: int
sec4_advantage_avg: int
nearby_advantage_rate: int
win_flag: int
result: int
record: int
course_id: int
last_play_course_id: int
course_day: int
class ResponseSeason2UserUpdateChallengeModeResult(ResponseGeneric):
challenge_mode_data: list[ChallengeModeData]
car_use_count: list[CarUseCount]
maker_use_count: list[MakerUseCount]
class BattleResult(DtoModel):
result: int
win_flg: int
course_id: int
vs_history: int
break_count: int
break_penalty_flag: bool
class RequestSeason2UserUpdateStoreBattleResult(
BattleResult, ProfileResult, RequestGeneric
):
group_key: str
style_car_id: int
car_mileage: int
course_day: int
players_num: int
winning: int
advantage_1: int
advantage_2: int
advantage_3: int
advantage_4: int
select_course_id: int
select_course_day: int
select_course_random: int
matching_success_sec: int
boost_flag: int
battle_gift_event_id: int
gift_id: int
class ResponseSeason2UserUpdateStoreBattleResult(ResponseGeneric):
vsinfo_data: VsInfo
car_use_count: list[CarUseCount]
maker_use_count: list[MakerUseCount]
class RequestSeason2UserUpdateOnlineBattleResult(
BattleResult, ProfileResult, RequestGeneric
):
group_key: str
style_car_id: int
car_mileage: int
course_day: int
players_num: int
winning: int
advantage_1: int
advantage_2: int
select_course_id: int
select_course_day: int
select_course_random: int
matching_success_sec: int
boost_flag: int
battle_gift_event_id: int
gift_id: int
infinity_tune: int
cpu_flag: int
over_victory_flag: int
round_point: int
subcard_count: int
course_select_priority: int
class ResponseSeason2UserUpdateOnlineBattleResult(ResponseGeneric):
vsinfo_data: VsInfo
round_event: list[RoundEvent]
car_use_count: list[CarUseCount]
maker_use_count: list[MakerUseCount]
RequestSeason2UserUpdateOnlineBattle = RequestGeneric
class ResponseSeason2UserUpdateOnlineBattle(ResponseGeneric):
bothwin_penalty: int
class RequestSeason2UserUpdateStampInfo(BaseModel):
stamp_event_data: list[Season2StampEvent]
ResponseSeason2UserUpdateStampInfo = ResponseGeneric
@@ -0,0 +1,19 @@
from titles.idac.model.common_model import DtoModel
class VsInfoDto(DtoModel):
battle_mode: int
invalid: int
str: int
str_now: int
lose_now: int
vs_history: int
break_count: int
break_penalty_flag: bool
class VsCourseInfoDto(DtoModel):
battle_mode: int
course_id: int
vs_cnt: int
vs_win: int
+17
View File
@@ -0,0 +1,17 @@
from titles.idac.model.common_model import *
from titles.idac.model.season2.boot_model import ResponseSeason2BootGetConfigData
class ResponseSeason3BootGetConfigData(ResponseSeason2BootGetConfigData):
class LeagueRewards(BaseModel):
league_rank_id: int
target_type: int
rank_start: int
rank_end: int
team_point: int
reward_category: RewardCategoryEnum
reward_type: int
reward_reason: str
league_rewards: list[LeagueRewards] = []
league_flag: int = 0 # 0 = open league?
+127
View File
@@ -0,0 +1,127 @@
from datetime import datetime
from enum import IntEnum
from typing import Optional
from titles.idac.const import LeagueRankEnum
from titles.idac.model.common_model import AvatarData, DtoModel
from titles.idac.model.season2.common_model import (
RequestSeason2Headers,
Season2ConfigData,
Season2StampEvent,
Season2TipsInfo,
)
class RequestSeason3Headers(RequestSeason2Headers):
a_als: str = "1" # original ALLS check flag
class RequestSeason3UserHeaders(RequestSeason3Headers):
session: int
session_procseq: int
class Season3ConfigData(Season2ConfigData):
favorite_bgm_list: Optional[str] = None
break_flag: Optional[int] = None
visible_my_stats: Optional[int] = None
visible_rival_stats: Optional[int] = None
class Season3TipsInfo(Season2TipsInfo):
last_team_join_dt: Optional[datetime] = None
class TeamRankingEnum(IntEnum):
NONE = 0
ACE = 1
SECOND = 2
THIRD = 3
class TeamRoleEnum(IntEnum):
MEMBER = 0
LEADER = 1
class TeamMemberActionEnum(IntEnum):
NEW_TEAM_LEADER = 1
JOIN_TEAM = 2
LEAVE_TEAM = 3
TEAM_POINTS_1000 = 4
TEAM_POINTS_5000 = 5
TEAM_POINTS_10000 = 6
TEAM_POINTS_20000 = 7
TEAM_POINTS_30000 = 8
TEAM_POINTS_40000 = 9
TEAM_POINTS_50000 = 10
TEAM_POINTS_60000 = 11
TEAM_POINTS_70000 = 12
TEAM_POINTS_80000 = 13
TEAM_POINTS_90000 = 14
TEAM_POINTS_100000 = 15
ACE = 16
SECOND = 17
THIRD = 18
class PromoteEffectParam(DtoModel):
next_league_rank_id: LeagueRankEnum = LeagueRankEnum.OPEN
point: int = 0
rank: int = 0
demote_league_rank_id: LeagueRankEnum = LeagueRankEnum.OPEN
demote_rank: int = 0
class TeamInfo(DtoModel):
class TeamInfoData(DtoModel):
username: str
action_id: TeamMemberActionEnum = TeamMemberActionEnum.JOIN_TEAM
team_id: int
team_name: str
role: TeamRoleEnum = TeamRoleEnum.MEMBER
league_rank_id: LeagueRankEnum = LeagueRankEnum.OPEN
team_sticker_id: int = 1
daily_bonus: int = 0
weekly_bonus: int = 0
order: TeamRankingEnum = TeamRankingEnum.NONE
country: int
store_name: str
team_info_list: list[TeamInfoData]
class Top(DtoModel):
id: int
username: str
avatar: AvatarData
point: int
order: TeamRankingEnum = TeamRankingEnum.NONE
grade: int
class LeagueInfo(DtoModel):
league_rank_id: LeagueRankEnum = LeagueRankEnum.OPEN
rank: int = 0
team_point: int = 0
point: int = 0
top_list: list[Top] = []
# Shared properties for Season 3
class Season3UpdateResultTeam(DtoModel):
league_info: Optional[LeagueInfo] = None
promote_effect_param: Optional[PromoteEffectParam] = None
team_daily_bonus: int = 0
team_weekly_bonus: int = 0
# Shared properties for Season 3
class Season3UpdateRequestTeam(DtoModel):
league_point: int = 0
team_mileage: int = 0
team_daily_bonus: int = 0
team_weekly_bonus: int = 0
class Season3StampEvent(Season2StampEvent):
convert_flag: bool = False
+15
View File
@@ -0,0 +1,15 @@
from titles.idac.model.common_model import *
from titles.idac.model.season2.result_model import *
from titles.idac.model.season3.common_model import *
class RequestSeason3UserUpdateTimeTrialResult(
RequestSeason2UserUpdateTimeTrialResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateTimeTrialResult(
ResponseSeason2UserUpdateTimeTrialResult, Season3UpdateResultTeam
):
pass
+242
View File
@@ -0,0 +1,242 @@
from datetime import datetime
from enum import IntEnum
from typing import Optional
from pydantic import BaseModel, Field
from titles.idac.model.common_model import *
from titles.idac.model.season3.common_model import *
class JoinStatusEnum(IntEnum):
SUCCESS = 0
INVALID_PASSWORD = 1
TEAM_FULL = 2
UNEXPECTED_ERROR = 3
class TeamMemberCar(DtoModel):
id: int
styleCarId: int
color: int
bureau: int
kana: int
sNo: int
lNo: int
tuneLv: int
carFlag: int
tunePoint: int
infinityTune: int
tuneParts: int
partsList: list[int]
partsCount: int
point: int
class TeamMember(DtoModel):
id: int
username: str
avatar: AvatarData
grade: int
role: TeamRoleEnum = TeamRoleEnum.MEMBER
point: int = 0
order: TeamRankingEnum = TeamRankingEnum.NONE
last_play_dt: int
class TeamInfoDto(DtoModel):
id: Optional[int] = None
version: int = 2
name: str
mileage: int = 0
sticker_id: int = 1
sticker_stock: str = "0111111111111".ljust(256, "0")
country: int
store_id: int
password: Optional[str] = None
auto_invite_flag: int
class TeamMemberDto(DtoModel):
team_id: int
user: int
version: int = 2
role: TeamRoleEnum = TeamRoleEnum.MEMBER
daily_bonus: int = 0
weekly_bonus: int = 0
create_date_daily: Optional[datetime] = Field(default_factory=datetime.now)
create_date_weekly: Optional[datetime] = Field(default_factory=datetime.now)
join_dt: Optional[datetime] = Field(default_factory=datetime.now)
class TeamMemberWithRankingDto(TeamMemberDto):
ranking: int = 0
point: int = 0
class TeamMemberInfoDto(DtoModel):
team_id: int
version: int = 2
user: int
action_id: TeamMemberActionEnum
action_dt: datetime = Field(default_factory=datetime.now)
class UserLeagueInfoDto(DtoModel):
version: int = 2
user: int
team_id: Optional[int] = None
round_id: int
point: Optional[int] = None
rewards_claimed: bool = False
class LeagueInfoDto(DtoModel):
version: int = 2
round_id: int
team_id: int
league_rank_id: LeagueRankEnum = LeagueRankEnum.OPEN
league_point: int = 0
class LeagueInfoWithRankingDto(LeagueInfoDto):
ranking: int = 0
class RecommendTeamDto(DtoModel):
team_id: int
password: str
team_name: str
team_sticker_id: int
country: int
store_id: int
league_rank_id: int
team_point: int
class RecommendTeam(DtoModel):
class RecommendTeamMember(DtoModel):
id: int
username: str
order: TeamRankingEnum
role: TeamRoleEnum
avatar: AvatarData
team_id: int
search_id: int
password: str
team_name: str
team_sticker_id: int
country: int
store_name: str
league_rank_id: int
team_point: int
rank: int
member: list[RecommendTeamMember]
class RequestSeason3CreateTeam(RequestGeneric):
team_name: str
auto_invite_flag: int
class ResponseSeason3CreateTeam(ResponseGeneric):
team_info: TeamInfo
league_info: LeagueInfo = LeagueInfo()
auto_invite_flag: int = 0
class ResponseSeason3LeftTeam(ResponseGeneric):
left_status: int
class RequestSeason3PreJoinTeam(RequestGeneric):
password: str
RequestSeason3JoinTeam = RequestSeason3PreJoinTeam
class ResponseSeason3PreJoinTeam(ResponseGeneric):
join_status: JoinStatusEnum = JoinStatusEnum.INVALID_PASSWORD
team_info: Optional[TeamInfo] = None
class ResponseSeason3JoinTeam(ResponseSeason3PreJoinTeam):
league_info: LeagueInfo = LeagueInfo()
team_member_cars: list[TeamMemberCar] = []
class ResponseSeason3GetTeamManageData(ResponseGeneric):
member_data: list[TeamMember]
password: str
auto_invite_flag: int
class RequestSeason3TeamKickMember(RequestGeneric):
id: int
class ResponseSeason3TeamKickMember(ResponseGeneric):
control_result: int
team_point: int
class RequestSeason3TeamChangeLeader(RequestGeneric):
id: int
class ResponsSeason3TeamChangeLeader(ResponseGeneric):
control_result: int
class ResponseSeason3TeamChangePassword(ResponseGeneric):
control_result: int
password: str
class ResponseSeason3GetTeamInfoData(ResponseGeneric):
class PastLeague(DtoModel):
season_id: int
round_event_id: int
round_event_nm: str
league_rank_id: int
rank: int
point: int
top_list: list[Top]
# not sure? does nothing
reward_category: RewardCategoryEnum = RewardCategoryEnum.FT_TOKEN_PIECES
reward_type: int = 1
member_data: list[TeamMember]
past_league_data: list[PastLeague]
class ResponseSeason3GetTeamHomeData(ResponseGeneric):
team_member_cars: list[TeamMemberCar]
class ResponseSeason3TeamGetTeamLogoStock(ResponseGeneric):
sticker_stock: str
class RequestSeason3TeamUpdateTeamLogoData(RequestGeneric):
sticker_id: int
class ResponseSeason3TeamUpdateTeamLogoData(ResponseGeneric):
control_result: int
sticker_id: int
class ResponseSeason3TeamGetRecommendTeam(ResponseGeneric):
join_status: int
recommend_team_list: list[RecommendTeam]
class RequestSeason3TeamUpdateTeamOption(RequestGeneric):
auto_invite_flag: int
class ResponseSeason3TeamUpdateTeamOption(ResponseGeneric):
control_result: int
auto_invite_flag: int
+24
View File
@@ -0,0 +1,24 @@
from typing import Optional
from titles.idac.model.common_model import DtoModel
from titles.idac.model.season2.theory_model import (
RequestSeason2TheoryMatching,
ResponseSeason2TheoryMatching,
Season2PowerMatch,
)
RequestSeason3TheoryMatching = RequestSeason2TheoryMatching
class Season3PowerMatch(Season2PowerMatch):
stickerid: Optional[int] = None
teamName: Optional[str] = None
league_rank_id: Optional[int] = None
class ResponseSeason3TheoryMatching(ResponseSeason2TheoryMatching):
class RivalData(DtoModel):
auto_match: list[Season3PowerMatch]
power_match: list[Season3PowerMatch]
rival_data: RivalData
+126
View File
@@ -0,0 +1,126 @@
from typing import Optional
from titles.idac.model.common_model import Reward
from titles.idac.model.season2.user_model import *
from titles.idac.model.season3.common_model import *
class RequestSeason3UserCreateAccount(RequestSeason2UserCreateAccount):
config_obj: Season3ConfigData
ResponseSeason3UserCreateAccount = ResponseSeason2UserCreateAccount
RequestSeason3UserGetData = RequestSeason2UserGetData
class ResponseSeason3UserGetData(ResponseSeason2UserGetData):
class RewardsData(BaseModel):
rewards_id: int
rewards_reason: str
rewards: Reward
config_data: Optional[Season3ConfigData] = None
tips_info: Optional[Season3TipsInfo] = None
team_info: Optional[TeamInfo] = None
league_info: Optional[LeagueInfo] = None
last_league_info: list[LeagueInfo] = []
league_rewards: list[RewardsData] = []
team_invite_flag: int = 1
class RequestSeason3UserUpdateLogin(RequestSeason2UserUpdateLogin):
league_rewards_id_arr: list[RequestSeason2UserUpdateLogin.RewardsId]
ResponseSeason3UserUpdateLogin = ResponseSeason2UserUpdateLogin
class RequestSeason3UserUpdateTimeTrialResult(
RequestSeason2UserUpdateTimeTrialResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateTimeTrialResult(
ResponseSeason2UserUpdateTimeTrialResult, Season3UpdateResultTeam
):
pass
class RequestSeason3UserUpdateStoryResult(
RequestSeason2UserUpdateStoryResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateStoryResult(
ResponseSeason2UserUpdateStoryResult, Season3UpdateResultTeam
):
pass
class RequestSeason3UserUpdateSpecialResult(
RequestSeason2UserUpdateSpecialResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateSpecialResult(
ResponseSeason2UserUpdateSpecialResult, Season3UpdateResultTeam
):
pass
class RequestSeason3UserUpdateTheoryResult(
RequestSeason2UserUpdateTheoryResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateTheoryResult(
ResponseSeason2UserUpdateTheoryResult, Season3UpdateResultTeam
):
pass
class RequestSeason3UserUpdateChallengeModeResult(
RequestSeason2UserUpdateChallengeModeResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateChallengeModeResult(
ResponseSeason2UserUpdateChallengeModeResult, Season3UpdateResultTeam
):
pass
class RequestSeason3UserUpdateStoreBattleResult(
RequestSeason2UserUpdateStoreBattleResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateStoreBattleResult(
ResponseSeason2UserUpdateStoreBattleResult, Season3UpdateResultTeam
):
pass
class RequestSeason3UserUpdateOnlineBattleResult(
RequestSeason2UserUpdateOnlineBattleResult, Season3UpdateRequestTeam
):
pass
class ResponseSeason3UserUpdateOnlineBattleResult(
ResponseSeason2UserUpdateOnlineBattleResult, Season3UpdateResultTeam
):
pass
class RequestSeason2UserUpdateStampInfo(RequestSeason2UserUpdateStampInfo):
stamp_event_data: list[Season3StampEvent]
ResponseSeason3UserUpdateStampInfo = RequestSeason2UserUpdateStampInfo
+2 -2
View File
@@ -8,8 +8,8 @@ from core.data import Data
from core.config import CoreConfig
from titles.idac.const import IDACConstants
from titles.idac.database import IDACData
from titles.idac.schema.profile import *
from titles.idac.schema.item import *
from titles.idac.schema.profile_db import *
from titles.idac.schema.item_db import *
class IDACReader(BaseReader):
+112
View File
@@ -0,0 +1,112 @@
from typing import Optional, List, Dict
from sqlalchemy import (
and_,
func,
select,
)
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
from core.config import CoreConfig
from core.data.schema import BaseData
from titles.idac.const import StoryTypeEnum
from titles.idac.mapper.challenge_mapper import IDACChallengeMapper
from titles.idac.model.season2.challenge_model import *
from titles.idac.schema.model.challenge import *
class IDACChallengeDb(BaseData):
def __init__(self, cfg: CoreConfig, conn: "sessionmaker[AsyncSession]"):
super().__init__(cfg, conn)
self.mapper = IDACChallengeMapper()
async def get_challenge(
self, aime_id: int, vs_type: int, play_difficulty: int
) -> Optional[ChallengeDto]:
sql = select(ChallengeDb).where(
and_(
ChallengeDb.user == aime_id,
ChallengeDb.vs_type == vs_type,
ChallengeDb.play_difficulty == play_difficulty,
)
)
result = await self.execute(sql)
challenge_db = result.scalars().first()
if challenge_db is None:
return None
return self.mapper.challenge_to_dto(challenge_db)
async def get_challenges(self, aime_id: int) -> Optional[List[ChallengeDto]]:
sql = select(ChallengeDb).where(ChallengeDb.user == aime_id)
result = await self.execute(sql)
challenges_db = result.scalars().all()
if not challenges_db:
return None
return self.mapper.challenges_to_dto(challenges_db)
async def get_best_challenges_by_vs_type(
self, aime_id: int, story_type: StoryTypeEnum = StoryTypeEnum.BUNTA
) -> List[BestChallengeByVsTypeDto]:
subquery = (
select(
ChallengeDb.story_type,
ChallengeDb.user,
ChallengeDb.vs_type,
func.max(ChallengeDb.play_difficulty).label("last_play_lv"),
)
.where(
and_(
ChallengeDb.user == aime_id,
ChallengeDb.story_type == story_type.value,
)
)
.group_by(ChallengeDb.vs_type)
.subquery()
)
sql = (
select(
ChallengeDb.story_type,
ChallengeDb.vs_type,
ChallengeDb.cleared_difficulty.label("max_clear_lv"),
ChallengeDb.play_difficulty.label("last_play_lv"),
ChallengeDb.last_play_course_id,
ChallengeDb.play_count,
)
.where(
and_(
ChallengeDb.user == subquery.c.user,
ChallengeDb.vs_type == subquery.c.vs_type,
ChallengeDb.play_difficulty == subquery.c.last_play_lv,
),
)
.order_by(ChallengeDb.vs_type)
)
result = await self.execute(sql)
if result is None:
return []
rows = result.fetchall()
if not rows:
return []
return self.mapper.rows_to_best_challenge_dtos(rows)
async def put_challenge(
self, aime_id: int, challenge_data: ChallengeDto
) -> Optional[int]:
challenge_data_db = challenge_data.model_dump_db()
challenge_data_db["user"] = aime_id
sql = insert(ChallengeDb).values(**challenge_data_db)
conflict = sql.on_duplicate_key_update(**challenge_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_challenge: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
-63
View File
@@ -1,63 +0,0 @@
from datetime import datetime
from typing import Dict, Optional
from sqlalchemy import (
Table,
Column,
UniqueConstraint,
and_,
)
from sqlalchemy.types import Integer, TIMESTAMP
from sqlalchemy.schema import ForeignKey
from sqlalchemy.engine import Row
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert
from core.data.schema import BaseData, metadata
lottery = Table(
"idac_user_lottery",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("version", Integer, nullable=False),
Column("saved_value", Integer, nullable=False),
Column("lottery_count", Integer, nullable=False),
Column("create_date", TIMESTAMP, server_default=func.now()),
UniqueConstraint("user", "version", name="idac_user_lottery_uk"),
mysql_charset="utf8mb4",
)
class IDACFactoryData(BaseData):
async def get_lottery(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(lottery).where(
and_(
lottery.c.user == aime_id,
lottery.c.version == version
)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def put_lottery(
self, aime_id: int, version: int, saved_value: int, lottery_count: int, create_date: datetime
) -> Optional[int]:
lottery_data = {
"user": aime_id,
"version": version,
"saved_value": saved_value,
"lottery_count": lottery_count,
"create_date": create_date
}
sql = insert(lottery).values(**lottery_data)
conflict = sql.on_duplicate_key_update(**lottery_data)
result = await self.execute(conflict)
if result is None:
self.logger.warn(f"put_lottery: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
+155
View File
@@ -0,0 +1,155 @@
from typing import List, Optional
from sqlalchemy import (
and_,
)
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
from core.config import CoreConfig
from core.data.schema import BaseData, metadata
from titles.idac.mapper.factory_mapper import IDACFactoryMapper
from titles.idac.model.season2.factory_model import CarDto, LotteryDto
from titles.idac.schema.model.factory import *
class IDACFactoryDb(BaseData):
def __init__(self, cfg: CoreConfig, conn: "sessionmaker[AsyncSession]"):
super().__init__(cfg, conn)
self.mapper = IDACFactoryMapper()
async def get_lottery(self, aime_id: int, version: int) -> Optional[LotteryDto]:
sql = select(LotteryDb).where(
and_(LotteryDb.user == aime_id, LotteryDb.version == version)
)
result = await self.execute(sql)
lottery_db = result.scalars().first()
if lottery_db is None:
return None
return self.mapper.lottery_to_dto(lottery_db)
async def put_lottery(
self,
aime_id: int,
version: int,
lottery_data: LotteryDto,
) -> Optional[int]:
lottery_data_db = lottery_data.model_dump_db()
lottery_data_db["user"] = aime_id
lottery_data_db["version"] = version
sql = insert(LotteryDb).values(**lottery_data_db)
conflict = sql.on_duplicate_key_update(**lottery_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_lottery: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_random_user_car(self, aime_id: int, version: int) -> Optional[CarDto]:
sql = (
select(CarDb)
.where(and_(CarDb.user == aime_id, CarDb.version <= version))
.order_by(func.rand())
.limit(1)
)
result = await self.execute(sql)
car_db = result.scalars().first()
if car_db is None:
return None
return self.mapper.car_to_dto(car_db)
async def get_random_car(self, version: int) -> Optional[CarDto]:
sql = (
select(CarDb).where(CarDb.version <= version).order_by(func.rand()).limit(1)
)
result = await self.execute(sql)
car_db = result.scalars().first()
if car_db is None:
return None
return self.mapper.car_to_dto(car_db)
async def get_car(
self, aime_id: int, version: int, style_car_id: int
) -> Optional[CarDto]:
sql = (
select(CarDb)
.where(
and_(
CarDb.user == aime_id,
CarDb.version <= version,
CarDb.style_car_id == style_car_id,
)
)
.order_by(CarDb.version.desc())
)
result = await self.execute(sql)
car_db = result.scalars().first()
if car_db is None:
return None
return self.mapper.car_to_dto(car_db)
async def get_cars(
self, version: int, aime_id: int, only_pickup: bool = False
) -> Optional[List[CarDto]]:
# subquery to get the max version for each style_car_id
max_version_subquery = (
select(CarDb.style_car_id, func.max(CarDb.version).label("max_version"))
.where(
and_(
CarDb.user == aime_id,
CarDb.version <= version,
)
)
.group_by(CarDb.style_car_id)
.subquery()
)
# main query joining with the subquery
sql = (
select(CarDb)
.join(
max_version_subquery,
and_(
CarDb.style_car_id == max_version_subquery.c.style_car_id,
CarDb.version == max_version_subquery.c.max_version,
),
)
.where(CarDb.user == aime_id)
)
if only_pickup:
sql = sql.where(CarDb.pickup_seq != 0)
sql = sql.order_by(CarDb.style_car_id)
result = await self.execute(sql)
cars_db = result.scalars().all()
if not cars_db:
return None
return self.mapper.cars_to_dto_list(cars_db)
async def put_car(
self, aime_id: int, version: int, car_data: CarDto
) -> Optional[int]:
car_data_db = car_data.model_dump_db()
car_data_db["user"] = aime_id
car_data_db["version"] = version
sql = insert(CarDb).values(**car_data_db)
conflict = sql.on_duplicate_key_update(**car_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_car: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
from typing import Optional
from sqlalchemy.sql import select
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
from core.config import CoreConfig
from core.data.schema.base import BaseData
from titles.idac.mapper.item_mapper import IDACItemMapper
from titles.idac.model.season2.item_model import *
from titles.idac.schema.model.item import *
class IDACItemDb(BaseData):
def __init__(self, cfg: CoreConfig, conn: "sessionmaker[AsyncSession]"):
super().__init__(cfg, conn)
self.mapper = IDACItemMapper()
async def get_ticket(self, aime_id: int, ticket_id: int) -> Optional[TicketDto]:
sql = select(TicketDb).where(
TicketDb.user == aime_id, TicketDb.ticket_id == ticket_id
)
result = await self.execute(sql)
ticket_db = result.scalars().first()
if ticket_db is None:
return None
return self.mapper.ticket_to_dto(ticket_db)
async def get_tickets(self, aime_id: int) -> Optional[list[TicketDto]]:
sql = select(TicketDb).where(TicketDb.user == aime_id)
result = await self.execute(sql)
tickets_db = result.scalars().all()
if not tickets_db:
return None
return self.mapper.tickets_to_dto(tickets_db)
async def get_stamps(self, aime_id: int) -> Optional[list[StampDto]]:
sql = select(StampDb).where(StampDb.user == aime_id)
result = await self.execute(sql)
stamps_db = result.scalars().all()
if not stamps_db:
return None
return self.mapper.stamps_to_dto(stamps_db)
async def get_stamp(
self, aime_id: int, m_stamp_event_id: int
) -> Optional[StampDto]:
sql = select(StampDb).where(
StampDb.user == aime_id, StampDb.m_stamp_event_id == m_stamp_event_id
)
result = await self.execute(sql)
stamp_db = result.scalars().first()
if stamp_db is None:
return None
return self.mapper.stamp_to_dto(stamp_db)
async def get_timetrial_event(
self, aime_id: int, timetrial_event_id: int
) -> Optional[TimeTrialEventDto]:
sql = select(TimeTrialEventDb).where(
TimeTrialEventDb.user == aime_id,
TimeTrialEventDb.timetrial_event_id == timetrial_event_id,
)
result = await self.execute(sql)
event_db = result.scalars().first()
if event_db is None:
return None
return self.mapper.timetrial_event_to_dto(event_db)
async def get_battle_gifts(
self, aime_id: int, battle_gift_event_id: int
) -> Optional[list[BattleGiftDto]]:
sql = select(BattleGiftDb).where(
BattleGiftDb.user == aime_id,
BattleGiftDb.battle_gift_event_id == battle_gift_event_id,
)
result = await self.execute(sql)
gifts_db = result.scalars().all() if result else None
if not gifts_db:
return None
return self.mapper.battle_gifts_to_dto(gifts_db)
async def put_ticket(self, aime_id: int, ticket_data: TicketDto) -> Optional[int]:
ticket_data_db = ticket_data.model_dump_db()
ticket_data_db["user"] = aime_id
sql = insert(TicketDb).values(**ticket_data_db)
conflict = sql.on_duplicate_key_update(**ticket_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_ticket: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_stamp(self, aime_id: int, stamp_data: StampDto) -> Optional[int]:
stamp_data_db = stamp_data.model_dump_db()
stamp_data_db["user"] = aime_id
sql = insert(StampDb).values(**stamp_data_db)
conflict = sql.on_duplicate_key_update(**stamp_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_stamp: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_timetrial_event(
self, aime_id: int, timetrial_event_data: TimeTrialEventDto
) -> Optional[int]:
timetrial_event_data_db = timetrial_event_data.model_dump_db()
timetrial_event_data_db["user"] = aime_id
sql = insert(TimeTrialEventDb).values(**timetrial_event_data_db)
conflict = sql.on_duplicate_key_update(**timetrial_event_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_timetrial_event: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_battle_gift(
self, aime_id: int, battle_gift_data: BattleGiftDto
) -> Optional[int]:
battle_gift_data_db = battle_gift_data.model_dump_db()
battle_gift_data_db["user"] = aime_id
sql = insert(BattleGiftDb).values(**battle_gift_data_db)
conflict = sql.on_duplicate_key_update(**battle_gift_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_battle_gift: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
+41
View File
@@ -0,0 +1,41 @@
from sqlalchemy import (
Column,
Integer,
ForeignKey,
UniqueConstraint,
)
from sqlalchemy.orm import declarative_base
from core.data.schema import metadata
Base = declarative_base(metadata=metadata)
class ChallengeDb(Base):
__tablename__ = "idac_user_challenge"
__table_args__ = (
UniqueConstraint("user", "vs_type", "play_difficulty", name="idac_user_challenge_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"))
vs_type = Column(Integer)
play_difficulty = Column(Integer)
cleared_difficulty = Column(Integer)
story_type = Column(Integer)
play_count = Column(Integer, server_default="1")
weak_difficulty = Column(Integer, server_default="0")
eval_id = Column(Integer)
advantage = Column(Integer)
sec1_advantage_avg = Column(Integer)
sec2_advantage_avg = Column(Integer)
sec3_advantage_avg = Column(Integer)
sec4_advantage_avg = Column(Integer)
nearby_advantage_rate = Column(Integer)
win_flag = Column(Integer)
result = Column(Integer)
record = Column(Integer)
course_id = Column(Integer)
last_play_course_id = Column(Integer)
style_car_id = Column(Integer)
course_day = Column(Integer)
+67
View File
@@ -0,0 +1,67 @@
from sqlalchemy import (
Column,
Integer,
TIMESTAMP,
ForeignKey,
UniqueConstraint,
String,
JSON,
func,
)
from core.data.schema.base import metadata
from sqlalchemy.orm import declarative_base
Base = declarative_base(metadata=metadata)
class LotteryDb(Base):
__tablename__ = "idac_user_lottery"
__table_args__ = (
UniqueConstraint("user", "version", name="idac_user_lottery_uk"),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"))
version = Column(Integer, nullable=False)
saved_value = Column(Integer, nullable=False)
lottery_count = Column(Integer, nullable=False)
create_date = Column(TIMESTAMP, server_default=func.now())
class CarDb(Base):
__tablename__ = "idac_user_car"
__table_args__ = (
UniqueConstraint("user", "version", "style_car_id", name="idac_user_car_uk"),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"))
version = Column(Integer, nullable=False)
car_id = Column(Integer)
style_car_id = Column(Integer)
color = Column(Integer)
bureau = Column(Integer)
kana = Column(Integer)
s_no = Column(Integer)
l_no = Column(Integer)
car_flag = Column(Integer)
tune_point = Column(Integer)
tune_level = Column(Integer, server_default="1")
tune_parts = Column(Integer)
infinity_tune = Column(Integer, server_default="0")
online_vs_win = Column(Integer, server_default="0")
pickup_seq = Column(Integer, server_default="1")
purchase_seq = Column(Integer, server_default="1")
color_stock_list = Column(String(32))
color_stock_new_list = Column(String(32))
parts_stock_list = Column(String(48))
parts_stock_new_list = Column(String(48))
parts_set_equip_list = Column(String(48))
parts_list = Column(JSON)
equip_parts_count = Column(Integer, server_default="0")
use_count = Column(Integer, server_default="0")
story_use_count = Column(Integer, server_default="0")
timetrial_use_count = Column(Integer, server_default="0")
vs_use_count = Column(Integer, server_default="0")
net_vs_use_count = Column(Integer, server_default="0")
theory_use_count = Column(Integer, server_default="0")
car_mileage = Column(Integer, server_default="0")
+99
View File
@@ -0,0 +1,99 @@
from sqlalchemy import (
Column,
Integer,
ForeignKey,
UniqueConstraint,
func,
TIMESTAMP,
)
from sqlalchemy.orm import declarative_base
from core.data.schema.base import metadata
Base = declarative_base(metadata=metadata)
class TicketDb(Base):
__tablename__ = "idac_user_ticket"
__table_args__ = (
UniqueConstraint("user", "ticket_id", name="idac_user_ticket_uk"),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
ticket_id = Column(Integer)
ticket_cnt = Column(Integer)
class StampDb(Base):
__tablename__ = "idac_user_stamp"
__table_args__ = (
UniqueConstraint("user", "m_stamp_event_id", name="idac_user_stamp_uk"),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
m_stamp_event_id = Column(Integer)
select_flag = Column(Integer)
stamp_masu = Column(Integer)
daily_bonus = Column(Integer)
weekly_bonus = Column(Integer)
weekday_bonus = Column(Integer)
weekend_bonus = Column(Integer)
total_bonus = Column(Integer)
day_total_bonus = Column(Integer)
store_battle_bonus = Column(Integer)
story_bonus = Column(Integer)
online_battle_bonus = Column(Integer)
timetrial_bonus = Column(Integer)
fasteststreetlegaltheory_bonus = Column(Integer)
collaboration_bonus = Column(Integer)
add_bonus_daily_flag_1 = Column(Integer)
add_bonus_daily_flag_2 = Column(Integer)
add_bonus_daily_flag_3 = Column(Integer)
create_date_daily = Column(TIMESTAMP, server_default=func.now())
create_date_weekly = Column(TIMESTAMP, server_default=func.now())
class TimeTrialEventDb(Base):
__tablename__ = "idac_user_timetrial_event"
__table_args__ = (
UniqueConstraint(
"user", "timetrial_event_id", name="idac_user_timetrial_event_uk"
),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
timetrial_event_id = Column(Integer)
point = Column(Integer)
class BattleGiftDb(Base):
__tablename__ = "idac_user_battle_gift"
__table_args__ = (
UniqueConstraint(
"user", "battle_gift_event_id", "gift_id", name="idac_user_battle_gift_uk"
),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
battle_gift_event_id = Column(Integer)
gift_id = Column(Integer)
gift_get_status = Column(Integer)
received_date = Column(TIMESTAMP, server_default=func.now())
+270
View File
@@ -0,0 +1,270 @@
from sqlalchemy import (
Column,
Integer,
String,
ForeignKey,
UniqueConstraint,
func,
TEXT,
TIMESTAMP,
)
from sqlalchemy.orm import declarative_base
from core.data.schema.base import metadata
Base = declarative_base(metadata=metadata)
class ProfileDb(Base):
__tablename__ = "idac_profile"
__table_args__ = (UniqueConstraint("user", "version", name="idac_profile_uk"),)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
version = Column(Integer, nullable=False)
username = Column(String(8))
country = Column(Integer)
store = Column(Integer)
total_play = Column(Integer, server_default="0")
daily_play = Column(Integer, server_default="0")
day_play = Column(Integer, server_default="0")
mileage = Column(Integer, server_default="0")
device_version = Column(String(7), server_default="1.50.00")
last_play_date = Column(TIMESTAMP, server_default=func.now())
mytitle_id = Column(Integer, server_default="0")
mytitle_efffect_id = Column(Integer, server_default="0")
sticker_id = Column(Integer, server_default="0")
sticker_effect_id = Column(Integer, server_default="0")
papercup_id = Column(Integer, server_default="0")
tachometer_id = Column(Integer, server_default="0")
aura_id = Column(Integer, server_default="0")
aura_color_id = Column(Integer, server_default="0")
aura_line_id = Column(Integer, server_default="0")
bgm_id = Column(Integer, server_default="0")
keyholder_id = Column(Integer, server_default="0")
start_menu_bg_id = Column(Integer, server_default="0")
use_car_id = Column(Integer, server_default="1")
use_style_car_id = Column(Integer, server_default="1")
bothwin_count = Column(Integer, server_default="0")
bothwin_score = Column(Integer, server_default="0")
subcard_count = Column(Integer, server_default="0")
vs_history = Column(Integer, server_default="0")
stamp_key_assign_0 = Column(Integer)
stamp_key_assign_1 = Column(Integer)
stamp_key_assign_2 = Column(Integer)
stamp_key_assign_3 = Column(Integer)
name_change_category = Column(Integer, server_default="0")
factory_disp = Column(Integer, server_default="0")
create_date = Column(TIMESTAMP, server_default=func.now())
cash = Column(Integer, server_default="0")
dressup_point = Column(Integer, server_default="0")
avatar_point = Column(Integer, server_default="0")
total_cash = Column(Integer, server_default="0")
total_car_parts_count = Column(Integer, server_default="0")
class ProfileConfigDb(Base):
__tablename__ = "idac_profile_config"
__table_args__ = (UniqueConstraint("user", name="idac_profile_config_uk"),)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
steering_intensity = Column(Integer)
transmission_type = Column(Integer)
default_viewpoint = Column(Integer)
favorite_bgm = Column(Integer)
favorite_bgm_list = Column(TEXT)
bgm_volume = Column(Integer)
se_volume = Column(Integer)
master_volume = Column(Integer)
store_battle_policy = Column(Integer)
battle_onomatope_display = Column(Integer)
cornering_guide = Column(Integer)
minimap = Column(Integer)
line_guide = Column(Integer)
ghost = Column(Integer)
race_exit = Column(Integer)
result_skip = Column(Integer)
stamp_select_skip = Column(Integer)
break_flag = Column(Integer)
visible_my_stats = Column(Integer)
visible_rival_stats = Column(Integer)
class ProfileAvatarDb(Base):
__tablename__ = "idac_profile_avatar"
__table_args__ = (UniqueConstraint("user", name="idac_profile_avatar_uk"),)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
sex = Column(Integer)
face = Column(Integer)
eye = Column(Integer)
mouth = Column(Integer)
hair = Column(Integer)
glasses = Column(Integer)
face_accessory = Column(Integer)
body = Column(Integer)
body_accessory = Column(Integer)
behind = Column(Integer)
bg = Column(Integer)
effect = Column(Integer)
special = Column(Integer)
class ProfileRankDb(Base):
__tablename__ = "idac_profile_rank"
__table_args__ = (UniqueConstraint("user", "version", name="idac_profile_rank_uk"),)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
version = Column(Integer, nullable=False)
story_rank_exp = Column(Integer, server_default="0")
story_rank = Column(Integer, server_default="1")
time_trial_rank_exp = Column(Integer, server_default="0")
time_trial_rank = Column(Integer, server_default="1")
online_battle_rank_exp = Column(Integer, server_default="0")
online_battle_rank = Column(Integer, server_default="1")
store_battle_rank_exp = Column(Integer, server_default="0")
store_battle_rank = Column(Integer, server_default="1")
theory_exp = Column(Integer, server_default="0")
theory_rank = Column(Integer, server_default="1")
pride_group_id = Column(Integer, server_default="0")
pride_point = Column(Integer, server_default="0")
grade_exp = Column(Integer, server_default="0")
grade = Column(Integer, server_default="1")
grade_reward_dist = Column(Integer, server_default="0")
story_rank_reward_dist = Column(Integer, server_default="0")
time_trial_rank_reward_dist = Column(Integer, server_default="0")
online_battle_rank_reward_dist = Column(Integer, server_default="0")
store_battle_rank_reward_dist = Column(Integer, server_default="0")
theory_rank_reward_dist = Column(Integer, server_default="0")
max_attained_online_battle_rank = Column(Integer, server_default="0")
max_attained_pride_point = Column(Integer, server_default="0")
is_last_max = Column(Integer, server_default="0")
class ProfileStockDb(Base):
__tablename__ = "idac_profile_stock"
__table_args__ = (
UniqueConstraint("user", "version", name="idac_profile_stock_uk"),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
version = Column(Integer, nullable=False)
mytitle_list = Column(TEXT)
mytitle_new_list = Column(TEXT)
avatar_face_list = Column(TEXT)
avatar_face_new_list = Column(TEXT)
avatar_eye_list = Column(TEXT)
avatar_eye_new_list = Column(TEXT)
avatar_hair_list = Column(TEXT)
avatar_hair_new_list = Column(TEXT)
avatar_body_list = Column(TEXT)
avatar_body_new_list = Column(TEXT)
avatar_mouth_list = Column(TEXT)
avatar_mouth_new_list = Column(TEXT)
avatar_glasses_list = Column(TEXT)
avatar_glasses_new_list = Column(TEXT)
avatar_face_accessory_list = Column(TEXT)
avatar_face_accessory_new_list = Column(TEXT)
avatar_body_accessory_list = Column(TEXT)
avatar_body_accessory_new_list = Column(TEXT)
avatar_behind_list = Column(TEXT)
avatar_behind_new_list = Column(TEXT)
avatar_bg_list = Column(TEXT)
avatar_bg_new_list = Column(TEXT)
avatar_effect_list = Column(TEXT)
avatar_effect_new_list = Column(TEXT)
avatar_special_list = Column(TEXT)
avatar_special_new_list = Column(TEXT)
stamp_list = Column(TEXT)
stamp_new_list = Column(TEXT)
keyholder_list = Column(String(256))
keyholder_new_list = Column(String(256))
papercup_list = Column(TEXT)
papercup_new_list = Column(TEXT)
tachometer_list = Column(TEXT)
tachometer_new_list = Column(TEXT)
aura_list = Column(TEXT)
aura_new_list = Column(TEXT)
aura_color_list = Column(TEXT)
aura_color_new_list = Column(TEXT)
aura_line_list = Column(TEXT)
aura_line_new_list = Column(TEXT)
bgm_list = Column(TEXT)
bgm_new_list = Column(TEXT)
dx_color_list = Column(TEXT)
dx_color_new_list = Column(TEXT)
start_menu_bg_list = Column(TEXT)
start_menu_bg_new_list = Column(TEXT)
under_neon_list = Column(TEXT)
class ProfileTheoryDb(Base):
__tablename__ = "idac_profile_theory"
__table_args__ = (
UniqueConstraint("user", "version", name="idac_profile_theory_uk"),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
version = Column(Integer, nullable=False)
play_count = Column(Integer, server_default="0")
play_count_multi = Column(Integer, server_default="0")
partner_id = Column(Integer)
partner_progress = Column(Integer)
partner_progress_score = Column(Integer)
practice_start_rank = Column(Integer, server_default="0")
general_flag = Column(Integer, server_default="0")
vs_history = Column(Integer, server_default="0")
vs_history_multi = Column(Integer, server_default="0")
win_count = Column(Integer, server_default="0")
win_count_multi = Column(Integer, server_default="0")
class ProfileTipsDb(Base):
__tablename__ = "idac_profile_tips"
__table_args__ = (UniqueConstraint("user", "version", name="idac_profile_tips_uk"),)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
)
version = Column(Integer, nullable=False)
tips_list = Column(String(16), server_default="QAAAAAAAAAAAAAAA")
timetrial_play_count = Column(Integer, server_default="0")
story_play_count = Column(Integer, server_default="0")
store_battle_play_count = Column(Integer, server_default="0")
online_battle_play_count = Column(Integer, server_default="0")
special_play_count = Column(Integer, server_default="0")
challenge_play_count = Column(Integer, server_default="0")
theory_play_count = Column(Integer, server_default="0")
last_team_join_dt = Column(TIMESTAMP)
+50
View File
@@ -0,0 +1,50 @@
from sqlalchemy import (
Column,
Integer,
String,
TIMESTAMP,
ForeignKey,
UniqueConstraint,
func,
)
from sqlalchemy.orm import declarative_base
from core.data.schema.base import metadata
Base = declarative_base(metadata=metadata)
class RoundInfoDb(Base):
__tablename__ = "idac_round_info"
__table_args__ = (
UniqueConstraint("round_id_in_json", "season", name="idac_round_info_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
round_id_in_json = Column(Integer, nullable=False)
name = Column(String(64))
season = Column(Integer, nullable=False)
start_dt = Column(TIMESTAMP, server_default=func.now())
end_dt = Column(TIMESTAMP, server_default=func.now())
class UserRoundInfoDb(Base):
__tablename__ = "idac_user_round_info"
__table_args__ = (
UniqueConstraint("user", "round_id", name="idac_user_round_info_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
round_id = Column(
Integer,
ForeignKey("idac_round_info.id", ondelete="cascade", onupdate="cascade"),
)
count = Column(Integer)
win = Column(Integer)
point = Column(Integer)
play_dt = Column(TIMESTAMP, server_default=func.now())
+60
View File
@@ -0,0 +1,60 @@
from sqlalchemy import Column, Integer, UniqueConstraint, ForeignKey
from sqlalchemy.orm import declarative_base
from core.data.schema import metadata
Base = declarative_base(metadata=metadata)
class StoryDb(Base):
__tablename__ = "idac_user_story"
__table_args__ = (UniqueConstraint("user", "chapter", name="idac_user_story_uk"),)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
story_type = Column(Integer)
chapter = Column(Integer)
loop_count = Column(Integer, server_default="1")
class StoryEpisodeDb(Base):
__tablename__ = "idac_user_story_episode"
__table_args__ = (
UniqueConstraint(
"user", "chapter", "episode", name="idac_user_story_episode_uk"
),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
chapter = Column(Integer)
episode = Column(Integer)
play_status = Column(Integer)
class StoryEpisodeDifficultyDb(Base):
__tablename__ = "idac_user_story_episode_difficulty"
__table_args__ = (
UniqueConstraint(
"user",
"episode",
"difficulty",
name="idac_user_story_episode_difficulty_uk",
),
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
episode = Column(Integer)
difficulty = Column(Integer)
play_count = Column(Integer)
clear_count = Column(Integer)
play_status = Column(Integer)
play_score = Column(Integer)
+118
View File
@@ -0,0 +1,118 @@
from sqlalchemy import (
Column,
Integer,
Boolean,
String,
ForeignKey,
UniqueConstraint,
func,
TIMESTAMP,
)
from sqlalchemy.orm import declarative_base
from core.data.schema.base import metadata
Base = declarative_base(metadata=metadata)
class TeamInfoDb(Base):
__tablename__ = "idac_team_info"
__table_args__ = (
UniqueConstraint("id", "version", name="idac_team_info_uk"),
UniqueConstraint("password", name="idac_team_info_password_uk"),
)
id = Column(Integer, primary_key=True)
version = Column(Integer, nullable=False, server_default="2")
name = Column(String(15), nullable=False)
mileage = Column(Integer, nullable=False, server_default="0")
sticker_id = Column(Integer, nullable=False, server_default="1")
sticker_stock = Column(String(256), server_default="0111111111111".ljust(256, "0"))
country = Column(Integer, nullable=False)
store_id = Column(Integer, nullable=False)
password = Column(String(10), nullable=False)
auto_invite_flag = Column(Integer, nullable=False, server_default="0")
class TeamMemberDb(Base):
__tablename__ = "idac_team_member"
__table_args__ = (
UniqueConstraint("version", "team_id", "user", name="idac_team_member_uk"),
)
id = Column(Integer, primary_key=True)
version = Column(Integer, nullable=False, server_default="2")
team_id = Column(
Integer,
ForeignKey("idac_team_info.id", ondelete="cascade", onupdate="cascade"),
)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
role = Column(Integer, nullable=False, server_default="0")
daily_bonus = Column(Integer, nullable=False, server_default="0")
weekly_bonus = Column(Integer, nullable=False, server_default="0")
create_date_daily = Column(TIMESTAMP, nullable=False, server_default=func.now())
create_date_weekly = Column(TIMESTAMP, nullable=False, server_default=func.now())
join_dt = Column(TIMESTAMP, nullable=False, server_default=func.now())
class TeamMemberInfoDb(Base):
__tablename__ = "idac_team_member_info"
id = Column(Integer, primary_key=True)
version = Column(Integer, nullable=False, server_default="2")
team_id = Column(
Integer,
ForeignKey("idac_team_info.id", ondelete="cascade", onupdate="cascade"),
)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
action_id = Column(
Integer,
nullable=False,
server_default="2",
)
action_dt = Column(TIMESTAMP, nullable=False, server_default=func.now())
class LeagueInfoDb(Base):
__tablename__ = "idac_league_info"
__table_args__ = (
UniqueConstraint("version", "round_id", "team_id", name="idac_league_info_pk"),
)
id = Column(Integer, primary_key=True)
version = Column(Integer, nullable=False, server_default="2")
round_id = Column(
Integer,
nullable=False,
)
team_id = Column(
Integer,
ForeignKey("idac_team_info.id", ondelete="cascade", onupdate="cascade"),
)
league_rank_id = Column(Integer, nullable=False, server_default="0")
league_point = Column(Integer, nullable=False, server_default="0")
class UserLeagueInfoDb(Base):
__tablename__ = "idac_user_league_info"
__table_args__ = (
UniqueConstraint(
"version", "user", "round_id", name="idac_user_league_info_pk"
),
)
id = Column(Integer, primary_key=True)
version = Column(Integer, nullable=False, server_default="2")
user = Column(
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
)
team_id = Column(Integer, nullable=False)
round_id = Column(Integer, nullable=False)
point = Column(Integer, nullable=False, server_default="0")
rewards_claimed = Column(Boolean, nullable=False, server_default="0")
+69
View File
@@ -0,0 +1,69 @@
from sqlalchemy import (
Column,
Integer,
TIMESTAMP,
ForeignKey,
UniqueConstraint,
func,
)
from sqlalchemy.orm import declarative_base
from core.data.schema import metadata
Base = declarative_base(metadata=metadata)
class TheoryCourseDb(Base):
__tablename__ = "idac_user_theory_course"
__table_args__ = (
UniqueConstraint("user", "course_id", name="idac_user_theory_course_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
course_id = Column(Integer)
max_victory_grade = Column(Integer, server_default="0")
run_count = Column(Integer, server_default="1")
powerhouse_lv = Column(Integer)
powerhouse_exp = Column(Integer)
played_powerhouse_lv = Column(Integer)
update_dt = Column(TIMESTAMP, server_default=func.now())
class TheoryPartnerDb(Base):
__tablename__ = "idac_user_theory_partner"
__table_args__ = (
UniqueConstraint("user", "partner_id", name="idac_user_theory_partner_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
partner_id = Column(Integer)
fellowship_lv = Column(Integer)
fellowship_exp = Column(Integer)
class TheoryRunningDb(Base):
__tablename__ = "idac_user_theory_running"
__table_args__ = (
UniqueConstraint("user", "course_id", name="idac_user_theory_running_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
course_id = Column(Integer)
attack = Column(Integer)
defense = Column(Integer)
safety = Column(Integer)
runaway = Column(Integer)
trick_flag = Column(Integer)
+67
View File
@@ -0,0 +1,67 @@
from sqlalchemy import (
Column,
Integer,
TIMESTAMP,
UniqueConstraint,
ForeignKey,
Index,
func,
)
from sqlalchemy.orm import declarative_base
from core.data.schema import metadata
Base = declarative_base(metadata=metadata)
class CourseDb(Base):
__tablename__ = "idac_user_course"
__table_args__ = (
UniqueConstraint("user", "course_id", name="idac_user_course_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
course_id = Column(Integer)
run_counts = Column(Integer, server_default="1")
skill_level_exp = Column(Integer, server_default="0")
class TrialDb(Base):
__tablename__ = "idac_user_time_trial"
__table_args__ = (
UniqueConstraint(
"user",
"version",
"course_id",
"style_car_id",
name="idac_user_time_trial_uk",
),
Index(
"idx_trial_ranking",
"version",
"course_id",
"style_car_id",
"user",
"goal_time",
),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
version = Column(Integer, nullable=False)
style_car_id = Column(Integer)
course_id = Column(Integer)
eval_id = Column(Integer, server_default="0")
goal_time = Column(Integer)
section_time_1 = Column(Integer)
section_time_2 = Column(Integer)
section_time_3 = Column(Integer)
section_time_4 = Column(Integer)
mission = Column(Integer)
play_dt = Column(TIMESTAMP, server_default=func.now())
+47
View File
@@ -0,0 +1,47 @@
from sqlalchemy import Column, UniqueConstraint, Integer, Boolean
from sqlalchemy.schema import ForeignKey
from sqlalchemy.orm import declarative_base
from core.data.schema import metadata
Base = declarative_base(metadata=metadata)
class VsInfoDb(Base):
__tablename__ = "idac_user_vs_info"
__table_args__ = (
UniqueConstraint("user", "battle_mode", name="idac_user_vs_info_uk"),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
battle_mode = Column(Integer)
invalid = Column(Integer)
str = Column(Integer)
str_now = Column(Integer)
lose_now = Column(Integer)
vs_history = Column(Integer)
break_count = Column(Integer)
break_penalty_flag = Column(Boolean)
class VsCourseInfoDb(Base):
__tablename__ = "idac_user_vs_course_info"
__table_args__ = (
UniqueConstraint(
"user", "battle_mode", "course_id", name="idac_user_vs_course_info_uk"
),
{"mysql_charset": "utf8mb4"},
)
id = Column(Integer, primary_key=True, nullable=False)
user = Column(
Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")
)
battle_mode = Column(Integer)
course_id = Column(Integer)
vs_cnt = Column(Integer)
vs_win = Column(Integer)
-492
View File
@@ -1,492 +0,0 @@
from typing import Dict, List, Optional
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
from sqlalchemy.engine.base import Connection
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
from core.config import CoreConfig
profile = Table(
"idac_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, nullable=False),
Column("username", String(8)),
Column("country", Integer),
Column("store", Integer),
Column("team_id", Integer, server_default="0"),
Column("total_play", Integer, server_default="0"),
Column("daily_play", Integer, server_default="0"),
Column("day_play", Integer, server_default="0"),
Column("mileage", Integer, server_default="0"),
Column("device_version", String(7), server_default="1.50.00"),
Column("last_play_date", TIMESTAMP, server_default=func.now()),
Column("mytitle_id", Integer, server_default="0"),
Column("mytitle_efffect_id", Integer, server_default="0"),
Column("sticker_id", Integer, server_default="0"),
Column("sticker_effect_id", Integer, server_default="0"),
Column("papercup_id", Integer, server_default="0"),
Column("tachometer_id", Integer, server_default="0"),
Column("aura_id", Integer, server_default="0"),
Column("aura_color_id", Integer, server_default="0"),
Column("aura_line_id", Integer, server_default="0"),
Column("bgm_id", Integer, server_default="0"),
Column("keyholder_id", Integer, server_default="0"),
Column("start_menu_bg_id", Integer, server_default="0"),
Column("use_car_id", Integer, server_default="1"),
Column("use_style_car_id", Integer, server_default="1"),
Column("bothwin_count", Integer, server_default="0"),
Column("bothwin_score", Integer, server_default="0"),
Column("subcard_count", Integer, server_default="0"),
Column("vs_history", Integer, server_default="0"),
Column("stamp_key_assign_0", Integer),
Column("stamp_key_assign_1", Integer),
Column("stamp_key_assign_2", Integer),
Column("stamp_key_assign_3", Integer),
Column("name_change_category", Integer, server_default="0"),
Column("factory_disp", Integer, server_default="0"),
Column("create_date", TIMESTAMP, server_default=func.now()),
Column("cash", Integer, server_default="0"),
Column("dressup_point", Integer, server_default="0"),
Column("avatar_point", Integer, server_default="0"),
Column("total_cash", Integer, server_default="0"),
UniqueConstraint("user", "version", name="idac_profile_uk"),
mysql_charset="utf8mb4",
)
# No point setting defaults since the game sends everything on profile creation anyway
config = Table(
"idac_profile_config",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("config_id", Integer),
Column("steering_intensity", Integer),
Column("transmission_type", Integer),
Column("default_viewpoint", Integer),
Column("favorite_bgm", Integer),
Column("bgm_volume", Integer),
Column("se_volume", Integer),
Column("master_volume", Integer),
Column("store_battle_policy", Integer),
Column("battle_onomatope_display", Integer),
Column("cornering_guide", Integer),
Column("minimap", Integer),
Column("line_guide", Integer),
Column("ghost", Integer),
Column("race_exit", Integer),
Column("result_skip", Integer),
Column("stamp_select_skip", Integer),
UniqueConstraint("user", name="idac_profile_config_uk"),
mysql_charset="utf8mb4",
)
# No point setting defaults since the game sends everything on profile creation anyway
avatar = Table(
"idac_profile_avatar",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("sex", Integer),
Column("face", Integer),
Column("eye", Integer),
Column("mouth", Integer),
Column("hair", Integer),
Column("glasses", Integer),
Column("face_accessory", Integer),
Column("body", Integer),
Column("body_accessory", Integer),
Column("behind", Integer),
Column("bg", Integer),
Column("effect", Integer),
Column("special", Integer),
UniqueConstraint("user", name="idac_profile_avatar_uk"),
mysql_charset="utf8mb4",
)
rank = Table(
"idac_profile_rank",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False),
Column("story_rank_exp", Integer, server_default="0"),
Column("story_rank", Integer, server_default="1"),
Column("time_trial_rank_exp", Integer, server_default="0"),
Column("time_trial_rank", Integer, server_default="1"),
Column("online_battle_rank_exp", Integer, server_default="0"),
Column("online_battle_rank", Integer, server_default="1"),
Column("store_battle_rank_exp", Integer, server_default="0"),
Column("store_battle_rank", Integer, server_default="1"),
Column("theory_exp", Integer, server_default="0"),
Column("theory_rank", Integer, server_default="1"),
Column("pride_group_id", Integer, server_default="0"),
Column("pride_point", Integer, server_default="0"),
Column("grade_exp", Integer, server_default="0"),
Column("grade", Integer, server_default="1"),
Column("grade_reward_dist", Integer, server_default="0"),
Column("story_rank_reward_dist", Integer, server_default="0"),
Column("time_trial_rank_reward_dist", Integer, server_default="0"),
Column("online_battle_rank_reward_dist", Integer, server_default="0"),
Column("store_battle_rank_reward_dist", Integer, server_default="0"),
Column("theory_rank_reward_dist", Integer, server_default="0"),
Column("max_attained_online_battle_rank", Integer, server_default="1"),
Column("max_attained_pride_point", Integer, server_default="0"),
Column("is_last_max", Integer, server_default="0"),
UniqueConstraint("user", "version", name="idac_profile_rank_uk"),
mysql_charset="utf8mb4",
)
stock = Table(
"idac_profile_stock",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False),
Column("mytitle_list", String(1024), server_default=""),
Column("mytitle_new_list", String(1024), server_default=""),
Column("avatar_face_list", String(255), server_default=""),
Column("avatar_face_new_list", String(255), server_default=""),
Column("avatar_eye_list", String(255), server_default=""),
Column("avatar_eye_new_list", String(255), server_default=""),
Column("avatar_hair_list", String(255), server_default=""),
Column("avatar_hair_new_list", String(255), server_default=""),
Column("avatar_body_list", String(255), server_default=""),
Column("avatar_body_new_list", String(255), server_default=""),
Column("avatar_mouth_list", String(255), server_default=""),
Column("avatar_mouth_new_list", String(255), server_default=""),
Column("avatar_glasses_list", String(255), server_default=""),
Column("avatar_glasses_new_list", String(255), server_default=""),
Column("avatar_face_accessory_list", String(255), server_default=""),
Column("avatar_face_accessory_new_list", String(255), server_default=""),
Column("avatar_body_accessory_list", String(255), server_default=""),
Column("avatar_body_accessory_new_list", String(255), server_default=""),
Column("avatar_behind_list", String(255), server_default=""),
Column("avatar_behind_new_list", String(255), server_default=""),
Column("avatar_bg_list", String(255), server_default=""),
Column("avatar_bg_new_list", String(255), server_default=""),
Column("avatar_effect_list", String(255), server_default=""),
Column("avatar_effect_new_list", String(255), server_default=""),
Column("avatar_special_list", String(255), server_default=""),
Column("avatar_special_new_list", String(255), server_default=""),
Column("stamp_list", String(255), server_default=""),
Column("stamp_new_list", String(255), server_default=""),
Column("keyholder_list", String(256), server_default=""),
Column("keyholder_new_list", String(256), server_default=""),
Column("papercup_list", String(255), server_default=""),
Column("papercup_new_list", String(255), server_default=""),
Column("tachometer_list", String(255), server_default=""),
Column("tachometer_new_list", String(255), server_default=""),
Column("aura_list", String(255), server_default=""),
Column("aura_new_list", String(255), server_default=""),
Column("aura_color_list", String(255), server_default=""),
Column("aura_color_new_list", String(255), server_default=""),
Column("aura_line_list", String(255), server_default=""),
Column("aura_line_new_list", String(255), server_default=""),
Column("bgm_list", String(255), server_default=""),
Column("bgm_new_list", String(255), server_default=""),
Column("dx_color_list", String(255), server_default=""),
Column("dx_color_new_list", String(255), server_default=""),
Column("start_menu_bg_list", String(255), server_default=""),
Column("start_menu_bg_new_list", String(255), server_default=""),
Column("under_neon_list", String(255), server_default=""),
UniqueConstraint("user", "version", name="idac_profile_stock_uk"),
mysql_charset="utf8mb4",
)
theory = Table(
"idac_profile_theory",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False),
Column("play_count", Integer, server_default="0"),
Column("play_count_multi", Integer, server_default="0"),
Column("partner_id", Integer),
Column("partner_progress", Integer),
Column("partner_progress_score", Integer),
Column("practice_start_rank", Integer, server_default="0"),
Column("general_flag", Integer, server_default="0"),
Column("vs_history", Integer, server_default="0"),
Column("vs_history_multi", Integer, server_default="0"),
Column("win_count", Integer, server_default="0"),
Column("win_count_multi", Integer, server_default="0"),
UniqueConstraint("user", "version", name="idac_profile_theory_uk"),
mysql_charset="utf8mb4",
)
tips = Table(
"idac_profile_tips",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False),
Column("tips_list", String(16), server_default="QAAAAAAAAAAAAAAA"),
Column("timetrial_play_count", Integer, server_default="0"),
Column("story_play_count", Integer, server_default="0"),
Column("store_battle_play_count", Integer, server_default="0"),
Column("online_battle_play_count", Integer, server_default="0"),
Column("special_play_count", Integer, server_default="0"),
Column("challenge_play_count", Integer, server_default="0"),
Column("theory_play_count", Integer, server_default="0"),
UniqueConstraint("user", "version", name="idac_profile_tips_uk"),
mysql_charset="utf8mb4",
)
class IDACProfileData(BaseData):
def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
super().__init__(cfg, conn)
self.date_time_format_ext = (
"%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5]
)
self.date_time_format_short = "%Y-%m-%d"
async def get_profile(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(profile).where(
and_(
profile.c.user == aime_id,
profile.c.version <= version,
)
).order_by(profile.c.version.desc())
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_different_random_profiles(
self, aime_id: int, version: int, count: int = 9
) -> Optional[Row]:
sql = (
select(profile)
.where(
and_(
profile.c.user != aime_id,
profile.c.version == version,
)
)
.order_by(func.rand())
.limit(count)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def get_profile_config(self, aime_id: int) -> Optional[Row]:
sql = select(config).where(
and_(
config.c.user == aime_id,
)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_profile_avatar(self, aime_id: int) -> Optional[Row]:
sql = select(avatar).where(
and_(
avatar.c.user == aime_id,
)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_profile_rank(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(rank).where(
and_(
rank.c.user == aime_id,
rank.c.version <= version,
)
).order_by(rank.c.version.desc())
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_profile_stock(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(stock).where(
and_(
stock.c.user == aime_id,
stock.c.version <= version,
)
).order_by(stock.c.version.desc())
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_profile_theory(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(theory).where(
and_(
theory.c.user == aime_id,
theory.c.version <= version,
)
).order_by(theory.c.version.desc())
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_profile_tips(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(tips).where(
and_(
tips.c.user == aime_id,
tips.c.version <= version,
)
).order_by(tips.c.version.desc())
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def put_profile(
self, aime_id: int, version: int, profile_data: Dict
) -> Optional[int]:
profile_data["user"] = aime_id
profile_data["version"] = version
sql = insert(profile).values(**profile_data)
conflict = sql.on_duplicate_key_update(**profile_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_profile: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_profile_config(self, aime_id: int, config_data: Dict) -> Optional[int]:
config_data["user"] = aime_id
sql = insert(config).values(**config_data)
conflict = sql.on_duplicate_key_update(**config_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_config: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_avatar(self, aime_id: int, avatar_data: Dict) -> Optional[int]:
avatar_data["user"] = aime_id
sql = insert(avatar).values(**avatar_data)
conflict = sql.on_duplicate_key_update(**avatar_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_avatar: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_rank(
self, aime_id: int, version: int, rank_data: Dict
) -> Optional[int]:
rank_data["user"] = aime_id
rank_data["version"] = version
sql = insert(rank).values(**rank_data)
conflict = sql.on_duplicate_key_update(**rank_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_profile_rank: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_profile_stock(
self, aime_id: int, version: int, stock_data: Dict
) -> Optional[int]:
stock_data["user"] = aime_id
stock_data["version"] = version
sql = insert(stock).values(**stock_data)
conflict = sql.on_duplicate_key_update(**stock_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_profile_stock: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_profile_theory(
self, aime_id: int, version: int, theory_data: Dict
) -> Optional[int]:
theory_data["user"] = aime_id
theory_data["version"] = version
sql = insert(theory).values(**theory_data)
conflict = sql.on_duplicate_key_update(**theory_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_theory: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_tips(
self, aime_id: int, version: int, tips_data: Dict
) -> Optional[int]:
tips_data["user"] = aime_id
tips_data["version"] = version
sql = insert(tips).values(**tips_data)
conflict = sql.on_duplicate_key_update(**tips_data)
result = await self.execute(conflict)
if result is None:
self.logger.warn(
f"put_profile_tips: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
+286
View File
@@ -0,0 +1,286 @@
from typing import Optional
from sqlalchemy import and_
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.orm import sessionmaker, aliased
from sqlalchemy.ext.asyncio import AsyncSession
from core.data.schema import BaseData
from core.config import CoreConfig
from titles.idac.model.season2.user_model import *
from titles.idac.schema.model.profile import *
from titles.idac.mapper.profile_mapper import IDACProfileMapper
class IDACProfileDb(BaseData):
def __init__(self, cfg: CoreConfig, conn: "sessionmaker[AsyncSession]") -> None:
super().__init__(cfg, conn)
self.mapper = IDACProfileMapper()
self.date_time_format_ext = (
"%Y-%m-%d %H:%M:%S.%f" # Needs to be lopped off at [:-5]
)
self.date_time_format_short = "%Y-%m-%d"
async def get_profile(self, aime_id: int, version: int) -> Optional[ProfileDto]:
sql = (
select([ProfileDb])
.where(
and_(
ProfileDb.user == aime_id,
ProfileDb.version <= version,
)
)
.order_by(ProfileDb.version.desc())
)
result = await self.execute(sql)
profile_db = result.scalars().first()
if profile_db is None:
return None
return self.mapper.profile_to_dto(profile_db)
async def get_different_random_profiles(
self, aime_id: int, version: int, count: int = 9
) -> Optional[list[ProfileDto]]:
sql = (
select([ProfileDb])
.where(
and_(
ProfileDb.user != aime_id,
ProfileDb.version == version,
)
)
.order_by(func.random())
.limit(count)
)
result = await self.execute(sql)
profiles_db = result.scalars().all()
if not profiles_db:
return None
return [self.mapper.profile_to_dto(profile) for profile in profiles_db]
async def get_profile_config(self, aime_id: int) -> Optional[ProfileConfigDto]:
sql = select(ProfileConfigDb).where(ProfileConfigDb.user == aime_id)
result = await self.execute(sql)
config_db = result.scalars().first()
if config_db is None:
return None
return self.mapper.profile_config_to_dto(config_db)
async def get_profile_avatar(self, aime_id: int) -> Optional[ProfileAvatarDto]:
sql = select(ProfileAvatarDb).where(ProfileAvatarDb.user == aime_id)
result = await self.execute(sql)
avatar_db = result.scalars().first()
if avatar_db is None:
return None
return self.mapper.profile_avatar_to_dto(avatar_db)
async def get_profile_rank(
self, aime_id: int, version: int
) -> Optional[ProfileRankDto]:
sql = (
select(ProfileRankDb)
.where(
and_(
ProfileRankDb.user == aime_id,
ProfileRankDb.version <= version,
)
)
.order_by(ProfileRankDb.version.desc())
)
result = await self.execute(sql)
rank_db = result.scalars().first()
if rank_db is None:
return None
return self.mapper.profile_rank_to_dto(rank_db)
async def get_profile_stock(
self, aime_id: int, version: int
) -> Optional[ProfileStockDto]:
sql = (
select(ProfileStockDb)
.where(
and_(
ProfileStockDb.user == aime_id,
ProfileStockDb.version <= version,
)
)
.order_by(ProfileStockDb.version.desc())
)
result = await self.execute(sql)
stock_db = result.scalars().first()
if stock_db is None:
return None
return self.mapper.profile_stock_to_dto(stock_db)
async def get_profile_theory(
self, aime_id: int, version: int
) -> Optional[ProfileTheoryDto]:
sql = (
select(ProfileTheoryDb)
.where(
and_(
ProfileTheoryDb.user == aime_id,
ProfileTheoryDb.version <= version,
)
)
.order_by(ProfileTheoryDb.version.desc())
)
result = await self.execute(sql)
theory_db = result.scalars().first()
if theory_db is None:
return None
return self.mapper.profile_theory_to_dto(theory_db)
async def get_profile_tips(
self, aime_id: int, version: int
) -> Optional[ProfileTipsDto]:
sql = (
select(ProfileTipsDb)
.where(
and_(
ProfileTipsDb.user == aime_id,
ProfileTipsDb.version <= version,
)
)
.order_by(ProfileTipsDb.version.desc())
)
result = await self.execute(sql)
tips_db = result.scalars().first()
if tips_db is None:
return None
return self.mapper.profile_tips_to_dto(tips_db)
async def put_profile(
self, aime_id: int, version: int, profile_data: ProfileDto
) -> Optional[int]:
profile_data_db = profile_data.model_dump_db()
profile_data_db["user"] = aime_id
profile_data_db["version"] = version
sql = insert(ProfileDb).values(profile_data_db)
conflict = sql.on_duplicate_key_update(profile_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_profile: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_profile_config(
self, aime_id: int, config_data: ProfileConfigDto
) -> Optional[int]:
profile_config_data = config_data.model_dump_db(exclude={"id"})
profile_config_data["user"] = aime_id
sql = insert(ProfileConfigDb).values(profile_config_data)
conflict = sql.on_duplicate_key_update(profile_config_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_config: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_avatar(
self, aime_id: int, avatar_data: ProfileAvatarDto
) -> Optional[int]:
avatar_data_db = avatar_data.model_dump_db()
avatar_data_db["user"] = aime_id
sql = insert(ProfileAvatarDb).values(avatar_data_db)
conflict = sql.on_duplicate_key_update(avatar_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_avatar: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_rank(
self, aime_id: int, version: int, rank_data: ProfileRankDto
) -> Optional[int]:
rank_data_db = rank_data.model_dump_db()
rank_data_db["user"] = aime_id
rank_data_db["version"] = version
sql = insert(ProfileRankDb).values(rank_data_db)
conflict = sql.on_duplicate_key_update(rank_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_rank: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_stock(
self, aime_id: int, version: int, stock_data: ProfileStockDto
) -> Optional[int]:
stock_data_db = stock_data.model_dump_db()
# Also filter empty strings
stock_data_db = {k: v for k, v in stock_data_db.items() if v != ""}
stock_data_db["user"] = aime_id
stock_data_db["version"] = version
sql = insert(ProfileStockDb).values(stock_data_db)
conflict = sql.on_duplicate_key_update(stock_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_stock: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_theory(
self, aime_id: int, version: int, theory_data: ProfileTheoryDto
) -> Optional[int]:
theory_data_db = theory_data.model_dump_db()
theory_data_db["user"] = aime_id
theory_data_db["version"] = version
sql = insert(ProfileTheoryDb).values(theory_data_db)
conflict = sql.on_duplicate_key_update(theory_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_theory: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_profile_tips(
self, aime_id: int, version: int, tips_data: ProfileTipsDto
) -> Optional[int]:
tips_data_db = tips_data.model_dump_db()
tips_data_db["user"] = aime_id
tips_data_db["version"] = version
sql = insert(ProfileTipsDb).values(tips_data_db)
conflict = sql.on_duplicate_key_update(tips_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_profile_tips: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
+160
View File
@@ -0,0 +1,160 @@
from typing import Optional
from sqlalchemy import and_
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
from core.data.schema import BaseData
from core.config import CoreConfig
from titles.idac.mapper.round_mapper import IDACRoundMapper
from titles.idac.model.season2.round_model import (
RoundInfoDto,
UserRoundInfoDto,
UserRoundInfoWithRankDto,
)
from titles.idac.schema.model.round import RoundInfoDb, UserRoundInfoDb
class IDACRoundDb(BaseData):
def __init__(self, cfg: CoreConfig, conn: "sessionmaker[AsyncSession]"):
super().__init__(cfg, conn)
self.mapper = IDACRoundMapper()
async def get_round_info(
self, version: int, round_id: int
) -> Optional[RoundInfoDto]:
sql = select(RoundInfoDb).where(
and_(
RoundInfoDb.season == version, RoundInfoDb.round_id_in_json == round_id
)
)
result = await self.execute(sql)
round_info_db = result.scalars().one_or_none()
if round_info_db is None:
return None
return self.mapper.round_info_to_dto(round_info_db)
async def get_all_previous_round_info(
self, season: int, current_round_id: int
) -> list[RoundInfoDto]:
sql = (
select(RoundInfoDb)
.where(
and_(
RoundInfoDb.season == season,
RoundInfoDb.round_id_in_json < current_round_id,
)
)
.order_by(RoundInfoDb.round_id_in_json.asc())
)
result = await self.execute(sql)
round_info_db = result.scalars().all()
return self.mapper.round_info_list_to_dto_list(round_info_db)
async def put_round_info(self, version: int, round: RoundInfoDto) -> Optional[int]:
round_info_db = round.model_dump_db()
round_info_db["season"] = version
sql = insert(RoundInfoDb).values(round_info_db)
conflict = sql.on_duplicate_key_update(round_info_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_round_info: Failed to update! round_id: {round.round_id_in_json}"
)
return None
return result.lastrowid
async def get_round_rank_by_id(
self, aime_id: int, round_event_id: int
) -> Optional[UserRoundInfoWithRankDto]:
ranking_stmt = (
select(
UserRoundInfoDb,
func.rank()
.over(order_by=[UserRoundInfoDb.point.desc(), UserRoundInfoDb.play_dt])
.label("rank"),
)
.where(UserRoundInfoDb.round_id == round_event_id)
.subquery()
)
sql = select(ranking_stmt).where(ranking_stmt.c.user == aime_id)
result = await self.execute(sql)
user_round_with_rank = result.fetchone()
if not user_round_with_rank:
return None
return self.mapper.user_round_info_with_rank_to_dto(user_round_with_rank)
async def get_round_top_five(
self, round_event_id: int
) -> list[UserRoundInfoWithRankDto]:
sql = (
select(
UserRoundInfoDb,
func.rank()
.over(order_by=[UserRoundInfoDb.point.desc(), UserRoundInfoDb.play_dt])
.label("rank"),
)
.where(UserRoundInfoDb.round_id == round_event_id)
.order_by(UserRoundInfoDb.point.desc(), UserRoundInfoDb.play_dt)
.limit(5)
)
result = await self.execute(sql)
user_round_with_rank_list = result.fetchall()
return self.mapper.user_round_info_with_rank_list_to_dto_list(
user_round_with_rank_list
)
async def get_user_round_info(
self, aime_id: int, round_id: int
) -> Optional[UserRoundInfoDto]:
sql = select(UserRoundInfoDb).where(
and_(
UserRoundInfoDb.user == aime_id,
UserRoundInfoDb.round_id == round_id,
)
)
result = await self.execute(sql)
user_round_info = result.scalars().one_or_none()
if user_round_info is None:
return None
return self.mapper.user_round_info_to_dto(user_round_info)
async def put_user_round_info(
self, aime_id: int, round_id: int, round_data: UserRoundInfoDto
) -> Optional[int]:
round_user_db = round_data.model_dump_db()
round_user_db["user"] = aime_id
round_user_db["round_id"] = round_id
sql = insert(UserRoundInfoDb).values(round_data)
conflict = sql.on_duplicate_key_update(round_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_round_user_info: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
# TODO: get top five players of last round event for Boot/GetConfigData
-145
View File
@@ -1,145 +0,0 @@
from typing import Dict, List, Optional
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, update
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
from sqlalchemy.engine.base import Connection
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.engine import Row
from sqlalchemy.dialects.mysql import insert
from datetime import datetime
from core.data.schema import BaseData, metadata
from core.config import CoreConfig
round_details = Table(
"idac_round_info",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("round_id_in_json", Integer),
Column("name", String(64)),
Column("season", Integer),
Column("start_dt", TIMESTAMP, server_default=func.now()),
Column("end_dt", TIMESTAMP, server_default=func.now()),
mysql_charset="utf8mb4",
)
round_info = Table(
"idac_user_round_info",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("round_id", ForeignKey("idac_round_info.id", ondelete="cascade", onupdate="cascade")),
Column("count", Integer),
Column("win", Integer),
Column("point", Integer),
Column("play_dt", TIMESTAMP, server_default=func.now()),
UniqueConstraint("user", "round_id", name="idac_user_round_info_uk"),
mysql_charset="utf8mb4",
)
class IDACOnlineRounds(BaseData):
# get player's ranking from a specified round event
async def get_round_rank_by_id(self, aime_id: int, round_event_id: int) -> Optional[Row]:
subquery = (
select([func.group_concat(func.concat(round_info.c.user, '|', round_info.c.point),order_by=[round_info.c.point.desc(), round_info.c.play_dt])])
.select_from(round_info)
.where(round_info.c.round_id == round_event_id)
.as_scalar()
)
sql = (
select([func.find_in_set(func.concat(round_info.c.user, '|', round_info.c.point), subquery.label('rank'))])
.select_from(round_info)
.where(
and_(
round_info.c.user == aime_id,
round_info.c.round_id == round_event_id
)
)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
# get player's info from a specified round event
async def get_round_info_by_id(self, aime_id: int, round_event_id: int) -> Optional[Row]:
sql = select(round_info).where(
and_(
round_info.c.user == aime_id,
round_info.c.round_id == round_event_id
)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
# get top 5 of a specified round event
async def get_round_top_five(self, round_event_id: int) -> Optional[Row]:
subquery = (
select([func.group_concat(func.concat(round_info.c.user, '|', round_info.c.point),order_by=[round_info.c.point.desc(), round_info.c.play_dt])])
.select_from(round_info)
.where(round_info.c.round_id == round_event_id)
.as_scalar()
)
sql = (
select([func.find_in_set(func.concat(round_info.c.user, '|', round_info.c.point), subquery.label('rank'))], round_info.c.user)
.select_from(round_info)
.where(round_info.c.round_id == round_event_id)
.limit(5)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
# save players info to a specified round event
async def put_round_event(
self, aime_id: int, round_id: int, round_data: Dict
) -> Optional[int]:
round_data["user"] = aime_id
round_data["round_id"] = round_id
sql = insert(round_info).values(**round_data)
conflict = sql.on_duplicate_key_update(**round_data)
result = await self.execute(conflict)
if result is None:
self.logger.warn(f"putround: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
# insert if the event does not exist in database
async def _try_load_round_event(
self, round_id: int, version: int, round_data: Dict
) -> Optional[int]:
sql = select(round_details).where(round_details.c.round_id_in_json == round_id)
result = await self.execute(sql)
# check if the round already exists
existing_round = result.fetchone() if result else None
if existing_round is None:
tmp = {
"round_id_in_json": round_id,
"name": round_data["round_event_nm"],
"season": version,
"start_dt": datetime.fromtimestamp(round_data["start_dt"]),
"end_dt": datetime.fromtimestamp(round_data["end_dt"]),
}
sql = insert(round_details).values(**tmp)
result = await self.execute(sql)
return result.lastrowid
return existing_round["id"]
# TODO: get top five players of last round event for Boot/GetConfigData
+153
View File
@@ -0,0 +1,153 @@
from typing import Optional, List
from sqlalchemy import and_, update, select
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
from core.config import CoreConfig
from core.data.schema import BaseData
from titles.idac.mapper.story_mapper import IDACStoryMapper
from titles.idac.model.season2.story_model import *
from .model.story import *
class IDACStoryDb(BaseData):
def __init__(self, cfg: CoreConfig, conn: "sessionmaker[AsyncSession]"):
super().__init__(cfg, conn)
self.mapper = IDACStoryMapper()
async def get_story(self, aime_id: int, chapter_id: int) -> Optional[StoryDto]:
sql = select(StoryDb).where(
and_(StoryDb.user == aime_id, StoryDb.chapter == chapter_id)
)
result = await self.execute(sql)
story_db = result.scalars().first()
if story_db is None:
return None
return self.mapper.story_to_dto(story_db)
async def get_stories(self, aime_id: int) -> Optional[List[StoryDto]]:
sql = select(StoryDb).where(StoryDb.user == aime_id)
result = await self.execute(sql)
stories_db = result.scalars().all()
if not stories_db:
return None
return self.mapper.stories_to_dto(stories_db)
async def get_story_episodes(
self, aime_id: int, chapter_id: int
) -> Optional[List[StoryEpisodeDto]]:
sql = select(StoryEpisodeDb).where(
and_(StoryEpisodeDb.user == aime_id, StoryEpisodeDb.chapter == chapter_id)
)
result = await self.execute(sql)
episodes_db = result.scalars().all()
if not episodes_db:
return None
return self.mapper.story_episodes_to_dto(episodes_db)
async def get_story_episode(
self, aime_id: int, episode_id: int
) -> Optional[StoryEpisodeDto]:
sql = select(StoryEpisodeDb).where(
and_(StoryEpisodeDb.user == aime_id, StoryEpisodeDb.episode == episode_id)
)
result = await self.execute(sql)
episode_db = result.scalars().first()
if episode_db is None:
return None
return self.mapper.story_episode_to_dto(episode_db)
async def get_story_episode_difficulties(
self, aime_id: int, episode_id: int
) -> Optional[List[StoryEpisodeDifficultyDto]]:
sql = select(StoryEpisodeDifficultyDb).where(
and_(
StoryEpisodeDifficultyDb.user == aime_id,
StoryEpisodeDifficultyDb.episode == episode_id,
)
)
result = await self.execute(sql)
difficulties_db = result.scalars().all()
if not difficulties_db:
return None
return self.mapper.story_episode_difficulties_to_dto(difficulties_db)
async def put_story(self, aime_id: int, story_data: StoryDto) -> Optional[int]:
story_data_db = story_data.model_dump_db()
story_data_db["user"] = aime_id
sql = insert(StoryDb).values(story_data_db)
conflict = sql.on_duplicate_key_update(story_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_story: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_story_episode_play_status(
self, aime_id: int, chapter_id: int, play_status: int = 1
) -> Optional[int]:
sql = (
update(StoryEpisodeDb)
.where(
and_(
StoryEpisodeDb.user == aime_id, StoryEpisodeDb.chapter == chapter_id
)
)
.values(play_status=play_status)
)
result = await self.execute(sql)
if result is None:
self.logger.warning(
f"put_story_episode_play_status: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_story_episode(
self, aime_id: int, chapter_id: int, episode_data: StoryEpisodeDto
) -> Optional[int]:
episode_data_db = episode_data.model_dump_db()
episode_data_db["user"] = aime_id
episode_data_db["chapter"] = chapter_id
sql = insert(StoryEpisodeDb).values(episode_data_db)
conflict = sql.on_duplicate_key_update(episode_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_story_episode: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def put_story_episode_difficulty(
self, aime_id: int, episode_id: int, difficulty_data: StoryEpisodeDifficultyDto
) -> Optional[int]:
difficulty_data_db = difficulty_data.model_dump_db()
difficulty_data_db["user"] = aime_id
difficulty_data_db["episode"] = episode_id
sql = insert(StoryEpisodeDifficultyDb).values(difficulty_data_db)
conflict = sql.on_duplicate_key_update(difficulty_data_db)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_story_episode_difficulty: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid

Some files were not shown because too many files have changed in this diff Show More