idac: battle gift event, tips, QoL improvements added

This commit is contained in:
Dniel97 2024-04-01 20:19:37 +02:00
parent c741c052e9
commit 9379172791
Signed by: Dniel97
GPG Key ID: 6180B3C768FB2E08
14 changed files with 669 additions and 53 deletions

View File

@ -0,0 +1,83 @@
"""IDAC Battle Gift and Tips added
Revision ID: e4e8d89c9b02
Revises: 81e44dd6047a
Create Date: 2024-04-01 17:49:50.009718
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = "e4e8d89c9b02"
down_revision = "81e44dd6047a"
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"idac_user_battle_gift",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("battle_gift_event_id", sa.Integer(), nullable=True),
sa.Column("gift_id", sa.Integer(), nullable=True),
sa.Column("gift_status", sa.Integer(), nullable=True),
sa.Column(
"received_date",
sa.TIMESTAMP(),
server_default=sa.text("now()"),
nullable=True,
),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user", "battle_gift_event_id", "gift_id", name="idac_user_battle_gift_uk"
),
mysql_charset="utf8mb4",
)
op.create_table(
"idac_profile_tips",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column(
"tips_list",
sa.String(length=16),
server_default="QAAAAAAAAAAAAAAA",
nullable=True,
),
sa.Column(
"timetrial_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column("story_play_count", sa.Integer(), server_default="0", nullable=True),
sa.Column(
"store_battle_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column(
"online_battle_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column(
"special_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column(
"challenge_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column("theory_play_count", sa.Integer(), server_default="0", nullable=True),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("user", "version", name="idac_profile_tips_uk"),
mysql_charset="utf8mb4",
)
def downgrade():
op.drop_table("idac_user_battle_gift")
op.drop_table("idac_profile_tips")

View File

@ -775,7 +775,7 @@ python dbutils.py --game SDGT upgrade
| Display ID | Description | | Display ID | Description |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------- | | ---------------------------------------- | ------------------------------------------------------------------------------------------------- |
| 1 | ADV image in the size 1280x720, shown during attract | | 1 | ADV image in the size 1920x1080, shown during attract |
| 2 | Start image in the size 1280x720, shown in the Main Menu after selection the corresponding banner | | 2 | Start image in the size 1280x720, shown in the Main Menu after selection the corresponding banner |
| 3 | Banner image in the size 640×120, shown in the Main Menu | | 3 | Banner image in the size 640×120, shown in the Main Menu |
| 5 | Stamp Background image in the size 1780x608 | | 5 | Stamp Background image in the size 1780x608 |
@ -809,6 +809,19 @@ python dbutils.py --game SDGT upgrade
}, },
``` ```
### Battle Gift
- `gift_id`: unique gift index (starts from 0 f.e.)
- `battle_gift_event_id`: unique event id
- `reward_category`: item category (f.e. 21 = Chat Stamp)
- `reward_type`: item id (f.e. 483 = Remilia Scarlet)
- `reward_name`: name of the reward
- `rarity`: 2 Golden, 1 Silver, 3 Bronze
- `cash_rate`: ?
- `customize_point_rate`: ?
- `avatar_point_rate`: ?
- `first_distribution_rate`: only used server side for the probability of the first distribution
### Credits: ### Credits:
- Bottersnike: For the HUGE Reverse Engineering help - Bottersnike: For the HUGE Reverse Engineering help

View File

@ -10,6 +10,10 @@ server:
port_echo2: 20002 port_echo2: 20002
port_matching_p2p: 20003 port_matching_p2p: 20003
timerelease:
timerelease_no: 3
timerelease_avatar_gacha_no: 3
stamp: stamp:
enable: True enable: True
enabled_stamps: # max 3 play stamps enabled_stamps: # max 3 play stamps
@ -20,3 +24,7 @@ stamp:
timetrial: timetrial:
enable: True enable: True
enabled_timetrial: "touhou_remilia_scarlet" enabled_timetrial: "touhou_remilia_scarlet"
battle_event:
enabled: True
enabled_battle_event: "touhou_1st"

View File

@ -66,6 +66,22 @@ class IDACServerConfig:
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
self.__config, "idac", "server", "port_matching_p2p", default=20003 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: class IDACStampConfig:
@ -112,10 +128,32 @@ class IDACTimetrialConfig:
"enabled_timetrial", "enabled_timetrial",
default="touhou_remilia_scarlet", default="touhou_remilia_scarlet",
) )
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_gift", "enable", default=True
)
@property
def enabled_battle_gift(self) -> str:
return CoreConfig.get_config_field(
self.__config,
"idac",
"battle_gift",
"enabled_battle_gift",
default="touhou_1st",
)
class IDACConfig(dict): class IDACConfig(dict):
def __init__(self) -> None: def __init__(self) -> None:
self.server = IDACServerConfig(self) self.server = IDACServerConfig(self)
self.timerelease = IDACTimereleaseConfig(self)
self.stamp = IDACStampConfig(self) self.stamp = IDACStampConfig(self)
self.timetrial = IDACTimetrialConfig(self) self.timetrial = IDACTimetrialConfig(self)
self.battle_gift = IDACTBattleGiftConfig(self)

View File

@ -0,0 +1,82 @@
{
"battle_gift_event_id": 2,
"event_nm": "東方Projectコラボ",
"start_dt": "2024-04-01",
"end_dt": "2024-06-03",
"mode_id": 1,
"delivery_type": 1,
"gift_data": [
{
"gift_id": 0,
"battle_gift_event_id": 2,
"reward_category": 21,
"reward_type": 418,
"reward_name": "素敵なお賽銭箱はそこよ",
"rarity": 3,
"cash_rate": 0,
"customize_point_rate": 0,
"avatar_point_rate": 0,
"first_distribution_rate": 60
},
{
"gift_id": 1,
"battle_gift_event_id": 2,
"reward_category": 21,
"reward_type": 419,
"reward_name": "面倒な種族ね",
"rarity": 2,
"cash_rate": 0,
"customize_point_rate": 0,
"avatar_point_rate": 0,
"first_distribution_rate": 20
},
{
"gift_id": 2,
"battle_gift_event_id": 2,
"reward_category": 21,
"reward_type": 423,
"reward_name": "調子がそこそこだぜ",
"rarity": 3,
"cash_rate": 0,
"customize_point_rate": 0,
"avatar_point_rate": 0,
"first_distribution_rate": 60
},
{
"gift_id": 3,
"battle_gift_event_id": 2,
"reward_category": 21,
"reward_type": 422,
"reward_name": "あー?",
"rarity": 2,
"cash_rate": 0,
"customize_point_rate": 0,
"avatar_point_rate": 0,
"first_distribution_rate": 20
},
{
"gift_id": 4,
"battle_gift_event_id": 2,
"reward_category": 21,
"reward_type": 427,
"reward_name": "さ、記事にするわよー",
"rarity": 3,
"cash_rate": 0,
"customize_point_rate": 0,
"avatar_point_rate": 0,
"first_distribution_rate": 60
},
{
"gift_id": 5,
"battle_gift_event_id": 2,
"reward_category": 21,
"reward_type": 426,
"reward_name": "号外~、号外だよ~",
"rarity": 2,
"cash_rate": 0,
"customize_point_rate": 0,
"avatar_point_rate": 0,
"first_distribution_rate": 20
}
]
}

View File

@ -30,7 +30,6 @@
180, 180,
180, 180,
180, 180,
200,
200 200
], ],
"reward": [ "reward": [

View File

@ -30,7 +30,6 @@
180, 180,
180, 180,
180, 180,
200,
200 200
], ],
"reward": [ "reward": [

View File

@ -30,7 +30,6 @@
180, 180,
180, 180,
180, 180,
200,
200 200
], ],
"reward": [ "reward": [

View File

@ -321,6 +321,23 @@ timetrial_event = Table(
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
battle_gift = Table(
"idac_user_battle_gift",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("battle_gift_event_id", Integer),
Column("gift_id", Integer),
Column("gift_status", Integer),
Column("received_date", TIMESTAMP, server_default=func.now()),
UniqueConstraint("user", "battle_gift_event_id", "gift_id", name="idac_user_battle_gift_uk"),
mysql_charset="utf8mb4",
)
class IDACItemData(BaseData): class IDACItemData(BaseData):
async def get_random_user_car(self, aime_id: int, version: int) -> Optional[List[Row]]: async def get_random_user_car(self, aime_id: int, version: int) -> Optional[List[Row]]:
@ -843,6 +860,19 @@ class IDACItemData(BaseData):
if result is None: if result is None:
return None return None
return result.fetchone() return result.fetchone()
async def get_battle_gifts(self, aime_id: int, battle_gift_event_id: int) -> Optional[Row]:
sql = select(battle_gift).where(
and_(
battle_gift.c.user == aime_id,
battle_gift.c.battle_gift_event_id == battle_gift_event_id,
)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_car(self, aime_id: int, version: int, car_data: Dict) -> Optional[int]: async def put_car(self, aime_id: int, version: int, car_data: Dict) -> Optional[int]:
car_data["user"] = aime_id car_data["user"] = aime_id
@ -1074,3 +1104,19 @@ class IDACItemData(BaseData):
) )
return None return None
return result.lastrowid return result.lastrowid
async def put_battle_gift(
self, aime_id: int, battle_gift_data: Dict
) -> Optional[int]:
battle_gift_data["user"] = aime_id
sql = insert(battle_gift).values(**battle_gift_data)
conflict = sql.on_duplicate_key_update(**battle_gift_data)
result = await self.execute(conflict)
if result is None:
self.logger.warn(
f"put_battle_gift: Failed to update! aime_id: {aime_id}"
)
return None
return result.lastrowid

View File

@ -244,6 +244,28 @@ theory = Table(
mysql_charset="utf8mb4", 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): class IDACProfileData(BaseData):
def __init__(self, cfg: CoreConfig, conn: Connection) -> None: def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
@ -348,6 +370,19 @@ class IDACProfileData(BaseData):
if result is None: if result is None:
return None return None
return result.fetchone() 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,
)
)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def put_profile( async def put_profile(
self, aime_id: int, version: int, profile_data: Dict self, aime_id: int, version: int, profile_data: Dict
@ -438,3 +473,20 @@ class IDACProfileData(BaseData):
) )
return None return None
return result.lastrowid 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

View File

@ -1,6 +1,6 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
import os import os
from random import choice from random import choice, randint
from typing import Any, Dict, List from typing import Any, Dict, List
import json import json
import logging import logging
@ -17,20 +17,27 @@ class IDACSeason2(IDACBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = IDACConstants.VER_IDAC_SEASON_2 self.version = IDACConstants.VER_IDAC_SEASON_2
# load the play stamps and timetrial events into memory # load timerelease numbers from config
self.timerelease_no = self.game_config.timerelease.timerelease_no
self.timerelease_avatar_gacha_no = (
self.game_config.timerelease.timerelease_avatar_gacha_no
)
# load the user configured play stamps (up to 3)
self.stamp_info = [] self.stamp_info = []
if self.game_config.stamp.enable: if self.game_config.stamp.enable:
for stamp in self.game_config.stamp.enabled_stamps: for stamp in self.game_config.stamp.enabled_stamps:
if not os.path.exists(f"./titles/idac/data/stamps/{stamp}.json"): if not os.path.exists(f"./titles/idac/data/stamp/{stamp}.json"):
self.logger.warning(f"Stamp {stamp} is enabled but does not exist!") self.logger.warning(f"Stamp {stamp} is enabled but does not exist!")
continue continue
with open( with open(
f"./titles/idac/data/stamps/{stamp}.json", encoding="UTF-8" f"./titles/idac/data/stamp/{stamp}.json", encoding="UTF-8"
) as f: ) as f:
self.logger.debug(f"Loading stamp {stamp}") self.logger.debug(f"Loading stamp {stamp}")
self.stamp_info.append(self._fix_dates(json.load(f))) self.stamp_info.append(self._fix_dates(json.load(f)))
# load the user configured time trials (only one)
self.timetrial_event = {} self.timetrial_event = {}
self.timetrial_event_id = None self.timetrial_event_id = None
if self.game_config.timetrial.enable: if self.game_config.timetrial.enable:
@ -53,6 +60,25 @@ class IDACSeason2(IDACBase):
"timetrial_event_id" "timetrial_event_id"
) )
# load the user configured battle gifts (only one)
self.battle_gift_event = None
if self.game_config.battle_gift.enable:
battle_gift = self.game_config.battle_gift.enabled_battle_gift
if battle_gift is not None:
if not os.path.exists(
f"./titles/idac/data/battle_gift/{battle_gift}.json"
):
self.logger.warning(
f"Battle gift {battle_gift} is enabled but does not exist!"
)
else:
self.logger.debug(f"Loading battle gift {battle_gift}")
with open(
f"./titles/idac/data/battle_gift/{battle_gift}.json",
encoding="UTF-8",
) as f:
self.battle_gift_event = self._fix_dates(json.load(f))
async def handle_alive_get_request(self, data: Dict, headers: Dict): async def handle_alive_get_request(self, data: Dict, headers: Dict):
return { return {
"status_code": "0", "status_code": "0",
@ -122,7 +148,6 @@ class IDACSeason2(IDACBase):
"difference_time_to_jp": 0, "difference_time_to_jp": 0,
# has to match the game asset version to show theory of street # has to match the game asset version to show theory of street
"asset_version": "1", "asset_version": "1",
# option version? MV01?
"optional_version": "1", "optional_version": "1",
"disconnect_offset": 0, "disconnect_offset": 0,
"boost_balance_version": "0", "boost_balance_version": "0",
@ -130,8 +155,8 @@ class IDACSeason2(IDACBase):
"play_stamp_enable": 1, "play_stamp_enable": 1,
"play_stamp_bonus_coin": 1, "play_stamp_bonus_coin": 1,
"gacha_chara_needs": 1, "gacha_chara_needs": 1,
"both_win_system_control": 1, "both_win_system_control": 0,
"subcard_system_congrol": 1, "subcard_system_congrol": 0,
"server_maintenance_start_hour": 0, "server_maintenance_start_hour": 0,
"server_maintenance_start_minutes": 0, "server_maintenance_start_minutes": 0,
"server_maintenance_end_hour": 0, "server_maintenance_end_hour": 0,
@ -141,7 +166,10 @@ class IDACSeason2(IDACBase):
"domain_echo1": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}", "domain_echo1": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}",
"domain_echo2": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}", "domain_echo2": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}",
"domain_ping": f"{self.core_cfg.server.hostname}", "domain_ping": f"{self.core_cfg.server.hostname}",
"battle_gift_event_master": [], "battle_gift_event_master": (
[self.battle_gift_event] if self.battle_gift_event else []
),
# online battle round event
"round_event": [ "round_event": [
{ {
"round_event_id": 30, "round_event_id": 30,
@ -180,7 +208,7 @@ class IDACSeason2(IDACBase):
"reward_upper_limit": 180, "reward_upper_limit": 180,
"reward_lower_limit": 180, "reward_lower_limit": 180,
"reward": [{"reward_category": 21, "reward_type": 462}], "reward": [{"reward_category": 21, "reward_type": 462}],
} },
], ],
"rank": [], "rank": [],
"point": [], "point": [],
@ -245,26 +273,148 @@ class IDACSeason2(IDACBase):
"round_event_exp": [], "round_event_exp": [],
"stamp_info": self.stamp_info, "stamp_info": self.stamp_info,
# 0 = use default data, 1+ = server version of timereleasedata response # 0 = use default data, 1+ = server version of timereleasedata response
"timerelease_no": 3, "timerelease_no": self.timerelease_no,
# 0 = use default data, 1+ = server version of gachadata response # 0 = use default data, 1+ = server version of gachadata response
"timerelease_avatar_gacha_no": 3, "timerelease_avatar_gacha_no": self.timerelease_avatar_gacha_no,
"takeover_reward": [], # takover reward from other games such as 1=IDZ and 3=SWDC
"takeover_reward": [
{
"takeover_reward_type": 0,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 1, "reward_type": 5000},
{"reward_no": 2, "reward_category": 25, "reward_type": 1},
],
},
{
"takeover_reward_type": 1,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 15, "reward_type": 82},
{"reward_no": 1, "reward_category": 15, "reward_type": 190},
{"reward_no": 2, "reward_category": 3, "reward_type": 2},
{"reward_no": 2, "reward_category": 5, "reward_type": 2},
{"reward_no": 3, "reward_category": 3, "reward_type": 3},
{"reward_no": 3, "reward_category": 5, "reward_type": 3},
{"reward_no": 4, "reward_category": 3, "reward_type": 5},
{"reward_no": 4, "reward_category": 5, "reward_type": 5},
{"reward_no": 5, "reward_category": 15, "reward_type": 104},
{"reward_no": 5, "reward_category": 15, "reward_type": 212},
],
},
{
"takeover_reward_type": 2,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 24, "reward_type": 4052},
{"reward_no": 2, "reward_category": 24, "reward_type": 4053},
{"reward_no": 3, "reward_category": 24, "reward_type": 4054},
{"reward_no": 4, "reward_category": 24, "reward_type": 4055},
{"reward_no": 5, "reward_category": 24, "reward_type": 4056},
{"reward_no": 6, "reward_category": 24, "reward_type": 4057},
{"reward_no": 7, "reward_category": 24, "reward_type": 4058},
],
},
{
"takeover_reward_type": 3,
"takeover_reward_data": [
{"reward_no": 1, "reward_category": 15, "reward_type": 81},
{"reward_no": 1, "reward_category": 15, "reward_type": 189},
{"reward_no": 2, "reward_category": 25, "reward_type": 1},
{"reward_no": 2, "reward_category": 15, "reward_type": 103},
{"reward_no": 2, "reward_category": 15, "reward_type": 211},
],
},
],
"subcard_judge": [ "subcard_judge": [
{ {
"condition_id": 1, "condition_id": 1,
"lower_rank": 0, "lower_rank": 1,
"higher_rank": 10, "higher_rank": 7,
"condition_start": 2, "condition_start": 17,
"condition_end": 3, "condition_end": 20,
} "calc": 3,
], },
"special_promote": [{"counter": 1, "online_rank_id": 1}],
"matching_id": 1,
"matching_group": [
{ {
"group_id": 1, "condition_id": 2,
"group_percent": 1, "lower_rank": 1,
} "higher_rank": 15,
"condition_start": 10,
"condition_end": 10,
"calc": 20,
},
{
"condition_id": 3,
"lower_rank": 11,
"higher_rank": 15,
"condition_start": 80,
"condition_end": 89,
"calc": 7,
},
{
"condition_id": 3,
"lower_rank": 11,
"higher_rank": 15,
"condition_start": 90,
"condition_end": 100,
"calc": 10,
},
{
"condition_id": 3,
"lower_rank": 1,
"higher_rank": 15,
"condition_start": 0,
"condition_end": 40,
"calc": -100,
},
{
"condition_id": 1,
"lower_rank": 1,
"higher_rank": 15,
"condition_start": 21,
"condition_end": 24,
"calc": 7,
},
{
"condition_id": 1,
"lower_rank": 1,
"higher_rank": 15,
"condition_start": 25,
"condition_end": 25,
"calc": 10,
},
{
"condition_id": 3,
"lower_rank": 16,
"higher_rank": 20,
"condition_start": 70,
"condition_end": 89,
"calc": 6,
},
{
"condition_id": 3,
"lower_rank": 16,
"higher_rank": 20,
"condition_start": 90,
"condition_end": 100,
"calc": 12,
},
{
"condition_id": 3,
"lower_rank": 16,
"higher_rank": 20,
"condition_start": 0,
"condition_end": 50,
"calc": -100,
},
],
"special_promote": [
{"counter": 50, "online_rank_id": 8},
{"counter": 200, "online_rank_id": 16},
{"counter": 255, "online_rank_id": 21},
],
"matching_id": 0,
"matching_group": [
{"group_id": 1, "group_percent": 30},
{"group_id": 2, "group_percent": 50},
{"group_id": 3, "group_percent": 100},
], ],
"timetrial_disp_date": int( "timetrial_disp_date": int(
datetime.strptime("2023-10-01", "%Y-%m-%d").timestamp() datetime.strptime("2023-10-01", "%Y-%m-%d").timestamp()
@ -272,21 +422,22 @@ class IDACSeason2(IDACBase):
# price for every car # price for every car
"buy_car_need_cash": 5000, "buy_car_need_cash": 5000,
# number of buyable shop/customization time limits # number of buyable shop/customization time limits
"time_extension_limit": 1, "time_extension_limit": 5,
"collabo_id": 0, "collabo_id": 0,
"driver_debut_end_date": int( "driver_debut_end_date": int(
datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp() datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp()
), ),
"online_battle_param1": 1, "online_battle_param1": 0,
"online_battle_param2": 1, "online_battle_param2": 0,
"online_battle_param3": 1, "online_battle_param3": 0,
"online_battle_param4": 1, "online_battle_param4": 0,
"online_battle_param5": 1, "online_battle_param5": 0,
"online_battle_param6": 1, "online_battle_param6": 2,
"online_battle_param7": 1, "online_battle_param7": 0,
"online_battle_param8": 1, "online_battle_param8": 3900,
"theory_open_version": "1.30", "theory_open_version": "1.30.00",
"theory_close_version": "1.50", "theory_close_version": "9.99.99",
# unlocks teh version specific special mode
"special_mode_data": { "special_mode_data": {
"start_dt": int( "start_dt": int(
datetime.strptime("2023-01-01", "%Y-%m-%d").timestamp() datetime.strptime("2023-01-01", "%Y-%m-%d").timestamp()
@ -692,6 +843,20 @@ class IDACSeason2(IDACBase):
vs_info["course_select_priority"] = data.get("course_select_priority") vs_info["course_select_priority"] = data.get("course_select_priority")
return vs_info return vs_info
def _choose_gift_id(self) -> Dict:
gift_data = self.battle_gift_event["gift_data"]
# calculate the total_rate based on the first_distribution_rate
total_rate = sum(gift["first_distribution_rate"] for gift in gift_data)
# randomly choose a number between 1 and the total rate
rand_num = randint(1, total_rate)
cumulative_rate = 0
for gift in gift_data:
# if the random number is less than the cumulative rate, return the gift
cumulative_rate += gift["first_distribution_rate"]
if rand_num <= cumulative_rate:
return gift
async def handle_user_getdata_request(self, data: Dict, headers: Dict): async def handle_user_getdata_request(self, data: Dict, headers: Dict):
user_id = int(headers["session"]) user_id = int(headers["session"])
@ -926,6 +1091,74 @@ class IDACSeason2(IDACBase):
"point": timetrial["point"], "point": timetrial["point"],
} }
# check if the battle gift event is active
if self.battle_gift_event:
# get the users battle gifts, for the current active battle gift event
battle_gifts = await self.data.item.get_battle_gifts(
user_id, self.battle_gift_event.get("battle_gift_event_id")
)
if battle_gifts:
# just return all aquired gifts
battle_gift_data = [
{
"first_distribution_flag": 0,
"gift_data": [
{
"gift_id": gift["gift_id"],
# 1 = acquired
"gift_get_status": gift["gift_status"],
}
for gift in battle_gifts
],
}
]
else:
# get a random gift from the active battle gift event
gift_id = self._choose_gift_id()["gift_id"]
# save the battle_gift inside the database
await self.data.item.put_battle_gift(
user_id,
{
"battle_gift_event_id": self.battle_gift_event.get(
"battle_gift_event_id"
),
"gift_id": gift_id,
"gift_status": 1, # aquired
},
)
battle_gift_data = [
{
# trigger the first gift animation
"first_distribution_flag": 1,
"gift_data": [
{
# return a random selected gift_id
"gift_id": gift_id,
# set the status to 2 = new gift
"gift_get_status": 2,
}
],
}
]
# get the tips from the database
tips = await self.data.profile.get_profile_tips(user_id, self.version)
# if there are no tips, create a new one
if tips is None:
await self.data.profile.put_profile_tips(user_id, self.version, {})
# get the tips from the database
tips = await self.data.profile.get_profile_tips(user_id, self.version)
tips_info = tips._asdict()
del tips_info["id"]
del tips_info["user"]
del tips_info["version"]
return { return {
"status_code": "0", "status_code": "0",
"user_base_data": user_data, "user_base_data": user_data,
@ -969,7 +1202,9 @@ class IDACSeason2(IDACBase):
"frozen_data": {"frozen_status": 2}, "frozen_data": {"frozen_status": 2},
"penalty_data": {"penalty_flag": 0, "penalty_2_level": 0}, "penalty_data": {"penalty_flag": 0, "penalty_2_level": 0},
"config_data": config_data, "config_data": config_data,
"battle_gift_data": [], # gift_get_status 2 = new gift, 1 = acquired
# first_distribution related are useless since this is all handled by server
"battle_gift_data": battle_gift_data,
"ticket_data": ticket_data, "ticket_data": ticket_data,
"round_event": [], "round_event": [],
"last_round_event": [], "last_round_event": [],
@ -984,17 +1219,16 @@ class IDACSeason2(IDACBase):
"car_use_count": [], "car_use_count": [],
"maker_use_count": [], "maker_use_count": [],
"story_course": [{"course_id": 0, "count": 1}], "story_course": [{"course_id": 0, "count": 1}],
# TODO! "driver_debut": {
# "driver_debut": { # "play_count": 5,
# "play_count": 137, # "daily_play": 5,
# "daily_play": 5, # "last_play_dt": datetime.now().timestamp() - 86400,
# "last_play_dt": 0, # "use_start_date": datetime.now().timestamp() - 86400,
# "use_start_date": 0, # "use_end_date": datetime.now().timestamp() + 86400,
# "use_end_date": 0, # "use_dt": datetime.now().timestamp(),
# "use_dt": 0, # "ticket_cnt": 1,
# "ticket_cnt": 0, # "ticket_get_bit": (2 ** 5) - 1,
# "ticket_get_bit": 0, },
# },
"theory_data": theory_data, "theory_data": theory_data,
"theory_course_data": theory_course_data, "theory_course_data": theory_course_data,
"theory_partner_data": theory_partner_data, "theory_partner_data": theory_partner_data,
@ -1004,6 +1238,7 @@ class IDACSeason2(IDACBase):
"season_rewards_data": [], "season_rewards_data": [],
"timetrial_event_data": timetrial_event_data, "timetrial_event_data": timetrial_event_data,
"special_mode_hint_data": {"story_type": 0, "hint_display_flag": 0}, "special_mode_hint_data": {"story_type": 0, "hint_display_flag": 0},
"tips_info": tips_info,
} }
async def handle_timetrial_getbestrecordpreta_request( async def handle_timetrial_getbestrecordpreta_request(
@ -1596,6 +1831,12 @@ class IDACSeason2(IDACBase):
user_id, self.timetrial_event_id, event_point user_id, self.timetrial_event_id, event_point
) )
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"timetrial_play_count": tips["timetrial_play_count"] + 1}
)
return { return {
"status_code": "0", "status_code": "0",
"course_rank": course_rank, "course_rank": course_rank,
@ -1733,6 +1974,12 @@ class IDACSeason2(IDACBase):
}, },
) )
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"story_play_count": tips["story_play_count"] + 1}
)
return { return {
"status_code": "0", "status_code": "0",
"story_data": await self._generate_story_data(user_id), "story_data": await self._generate_story_data(user_id),
@ -1806,6 +2053,12 @@ class IDACSeason2(IDACBase):
# finally save the special mode with story_type=4 in database # finally save the special mode with story_type=4 in database
await self.data.item.put_challenge(user_id, data) await self.data.item.put_challenge(user_id, data)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"special_play_count": tips["special_play_count"] + 1}
)
return { return {
"status_code": "0", "status_code": "0",
"special_mode_data": await self._generate_special_data(user_id), "special_mode_data": await self._generate_special_data(user_id),
@ -1886,6 +2139,12 @@ class IDACSeason2(IDACBase):
# finally save the challenge mode with story_type=3 in database # finally save the challenge mode with story_type=3 in database
await self.data.item.put_challenge(user_id, data) await self.data.item.put_challenge(user_id, data)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"challenge_play_count": tips["challenge_play_count"] + 1}
)
return { return {
"status_code": "0", "status_code": "0",
"challenge_mode_data": await self._generate_challenge_data(user_id), "challenge_mode_data": await self._generate_challenge_data(user_id),
@ -2118,7 +2377,12 @@ class IDACSeason2(IDACBase):
# not required? # not required?
mode_id = data.pop("mode_id") mode_id = data.pop("mode_id")
standby_play_flag = data.pop("standby_play_flag") standby_play_flag = data.pop("standby_play_flag")
# save the tips list in database
tips_list = data.pop("tips_list") tips_list = data.pop("tips_list")
await self.data.profile.put_profile_tips(
user_id, self.version, {"tips_list": tips_list}
)
# save stock data in database # save stock data in database
await self._save_stock_data(user_id, stock_data) await self._save_stock_data(user_id, stock_data)
@ -2522,6 +2786,12 @@ class IDACSeason2(IDACBase):
}, },
) )
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"theory_play_count": tips["theory_play_count"] + 1}
)
return { return {
"status_code": "0", "status_code": "0",
"played_powerhouse_lv": data.get("powerhouse_lv"), "played_powerhouse_lv": data.get("powerhouse_lv"),
@ -2584,7 +2854,9 @@ class IDACSeason2(IDACBase):
"bothwin_penalty": 1, "bothwin_penalty": 1,
} }
async def handle_user_updateonlinebattleresult_request(self, data: Dict, headers: Dict): async def handle_user_updateonlinebattleresult_request(
self, data: Dict, headers: Dict
):
user_id = headers["session"] user_id = headers["session"]
stock_data: Dict = data.pop("stock_obj") stock_data: Dict = data.pop("stock_obj")
@ -2640,7 +2912,15 @@ class IDACSeason2(IDACBase):
}, },
) )
vs_info = self._update_vs_info(user_id, IDACConstants.BATTLE_MODE_ONLINE, data) vs_info = await self._update_vs_info(
user_id, IDACConstants.BATTLE_MODE_ONLINE, data
)
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"online_battle_play_count": tips["online_battle_play_count"] + 1}
)
return { return {
"status_code": "0", "status_code": "0",
@ -2671,9 +2951,20 @@ class IDACSeason2(IDACBase):
# no idea? # no idea?
result = data.pop("result") result = data.pop("result")
# save the received battle gift in database, hopefully that works
battle_gift_event_id = data.pop("battle_gift_event_id") battle_gift_event_id = data.pop("battle_gift_event_id")
gift_id = data.pop("gift_id") gift_id = data.pop("gift_id")
await self.data.item.put_battle_gift(
user_id,
{
"battle_gift_event_id": battle_gift_event_id,
"gift_id": gift_id,
"gift_status": 1, # aquired
},
)
# save stock data in database # save stock data in database
await self._save_stock_data(user_id, stock_data) await self._save_stock_data(user_id, stock_data)
@ -2726,6 +3017,12 @@ class IDACSeason2(IDACBase):
user_id, IDACConstants.BATTLE_MODE_OFFLINE, data user_id, IDACConstants.BATTLE_MODE_OFFLINE, data
) )
# update the tips play count
tips = await self.data.profile.get_profile_tips(user_id, self.version)
await self.data.profile.put_profile_tips(
user_id, self.version, {"store_battle_play_count": tips["store_battle_play_count"] + 1}
)
return { return {
"status_code": "0", "status_code": "0",
"vsinfo_data": vs_info, "vsinfo_data": vs_info,