round database implenments

round database implenments

idac: database upgrade script

idac: unused upgrade script

idac: even more round event implements

idac: some bugfixes

idac: convert round event file to UTF-8

idac: async round info loading

idac: last round ranking impl

idac: bugfixes

idac: bugfixes

idac: bugfixes

idac: 160~240 number plate implemented

idac: remove SQL comment

idac: sort things out and make multi-season compatibility
This commit is contained in:
Dniel97 2024-06-12 21:11:25 +02:00
parent 1bb6891411
commit e50fedad49
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
11 changed files with 1034 additions and 111 deletions

5
.gitignore vendored
View File

@ -160,4 +160,7 @@ config/*
deliver/*
*.gz
dbdump-*.json
dbdump-*.json
/.vs
/titles/id8
/titles/idac/battle.py

View File

@ -0,0 +1,72 @@
"""idac rounds event info added
Revision ID: 202d1ada1b39
Revises: e4e8d89c9b02
Create Date: 2024-05-03 15:51:02.384863
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '202d1ada1b39'
down_revision = 'e4e8d89c9b02'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"idac_round_info",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("round_id_in_json", sa.Integer(), nullable=True),
sa.Column("name", String(64), nullable=True),
sa.Column("season", sa.Integer(), nullable=True),
sa.Column(
"start_dt",
sa.TIMESTAMP(),
server_default=sa.text("now()"),
nullable=True,
),
sa.Column(
"end_dt",
sa.TIMESTAMP(),
server_default=sa.text("now()"),
nullable=True,
),
sa.PrimaryKeyConstraint("id"),
mysql_charset="utf8mb4",
)
op.create_table(
"idac_user_round_info",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("round_id", sa.Integer(), nullable=True),
sa.Column("count", sa.Integer(), nullable=True),
sa.Column("win", sa.Integer(), nullable=True),
sa.Column("point", sa.Integer(), nullable=True),
sa.Column(
"play_dt",
sa.TIMESTAMP(),
server_default=sa.text("now()"),
nullable=True,
),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.ForeignKeyConstraint(
["round_id"], ["idac_round_info.id"], onupdate="cascade", ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user", "round_id", name="idac_user_round_info_uk"
),
mysql_charset="utf8mb4",
)
def downgrade():
op.drop_table("idac_round_info")
op.drop_table("idac_user_round_info")

View File

@ -0,0 +1,39 @@
"""idac plate number lottery added
Revision ID: 7e98c2c328b1
Revises: 202d1ada1b39
Create Date: 2024-05-07 12:25:27.606480
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7e98c2c328b1'
down_revision = '202d1ada1b39'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"idac_user_lottery",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("saved_value", sa.Integer(), nullable=False),
sa.Column("lottery_count", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user", "version", name="idac_user_lottery_uk"
),
mysql_charset="utf8mb4",
)
def downgrade():
op.drop_table("idac_user_lottery")

View File

@ -28,3 +28,8 @@ timetrial:
battle_event:
enabled: True
enabled_battle_event: "touhou_1st"
round_event:
enable: True
enabled_round: "S2R2"
last_round: "S2R1"

View File

@ -149,6 +149,36 @@ class IDACTBattleGiftConfig:
default="touhou_1st",
)
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="S1R1",
)
@property
def last_round(self) -> str:
return CoreConfig.get_config_field(
self.__config,
"idac",
"round_event",
"last_round",
default="S1R1",
)
class IDACConfig(dict):
def __init__(self) -> None:
@ -157,3 +187,4 @@ class IDACConfig(dict):
self.stamp = IDACStampConfig(self)
self.timetrial = IDACTimetrialConfig(self)
self.battle_gift = IDACTBattleGiftConfig(self)
self.round_event = IDACRoundConfig(self)

View File

@ -0,0 +1,183 @@
{
"round_event_id": 9,
"round_event_nm": "シーズン2 特別ラウンド",
"start_dt": 1647468000,
"end_dt": 1648062000,
"round_start_rank": 0,
"save_filename": "",
"vscount": [
{
"reward_upper_limit": 30,
"reward_lower_limit": 30,
"reward": [
{
"reward_category": 21,
"reward_type": 379
}
]
},
{
"reward_upper_limit": 10,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 21,
"reward_type": 367
}
]
}
],
"rank": [
{
"reward_upper_limit": 1,
"reward_lower_limit": 1,
"reward": [
{
"reward_category": 24,
"reward_type": 4328
}
]
},
{
"reward_upper_limit": 2,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 24,
"reward_type": 4329
}
]
},
{
"reward_upper_limit": 11,
"reward_lower_limit": 50,
"reward": [
{
"reward_category": 24,
"reward_type": 4330
}
]
},
{
"reward_upper_limit": 51,
"reward_lower_limit": 100,
"reward": [
{
"reward_category": 24,
"reward_type": 4331
}
]
},
{
"reward_upper_limit": 101,
"reward_lower_limit": 1000,
"reward": [
{
"reward_category": 24,
"reward_type": 4332
}
]
}
],
"point": [
],
"playable_course_list": [
{
"course_id": 0,
"course_day": 0
},
{
"course_id": 0,
"course_day": 1
},
{
"course_id": 2,
"course_day": 0
},
{
"course_id": 2,
"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": 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": 8,
"course_day": 0
},
{
"course_id": 8,
"course_day": 1
},
{
"course_id": 10,
"course_day": 0
},
{
"course_id": 10,
"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
}
]
}

View File

@ -0,0 +1,229 @@
{
"round_event_id": 10,
"round_event_nm": "シーズン2 2ndラウンド",
"start_dt": 1648072800,
"end_dt": 1651086000,
"round_start_rank": 0,
"save_filename": "",
"vscount": [
{
"reward_upper_limit": 180,
"reward_lower_limit": 180,
"reward": [
{
"reward_category": 21,
"reward_type": 462
}
]
},
{
"reward_upper_limit": 120,
"reward_lower_limit": 120,
"reward": [
{
"reward_category": 21,
"reward_type": 461
}
]
},
{
"reward_upper_limit": 80,
"reward_lower_limit": 80,
"reward": [
{
"reward_category": 22,
"reward_type": 516
}
]
},
{
"reward_upper_limit": 40,
"reward_lower_limit": 40,
"reward": [
{
"reward_category": 21,
"reward_type": 484
}
]
},
{
"reward_upper_limit": 10,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 21,
"reward_type": 483
}
]
}
],
"rank": [
{
"reward_upper_limit": 1,
"reward_lower_limit": 1,
"reward": [
{
"reward_category": 24,
"reward_type": 4333
}
]
},
{
"reward_upper_limit": 2,
"reward_lower_limit": 10,
"reward": [
{
"reward_category": 24,
"reward_type": 4334
}
]
},
{
"reward_upper_limit": 11,
"reward_lower_limit": 50,
"reward": [
{
"reward_category": 24,
"reward_type": 4335
}
]
},
{
"reward_upper_limit": 51,
"reward_lower_limit": 100,
"reward": [
{
"reward_category": 24,
"reward_type": 4336
}
]
},
{
"reward_upper_limit": 101,
"reward_lower_limit": 1000,
"reward": [
{
"reward_category": 24,
"reward_type": 4337
}
]
}
],
"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": 20,
"course_day": 0
},
{
"course_id": 20,
"course_day": 1
},
{
"course_id": 22,
"course_day": 0
},
{
"course_id": 22,
"course_day": 1
},
{
"course_id": 24,
"course_day": 0
},
{
"course_id": 24,
"course_day": 1
},
{
"course_id": 26,
"course_day": 0
},
{
"course_id": 26,
"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": 48,
"course_day": 0
},
{
"course_id": 48,
"course_day": 1
},
{
"course_id": 50,
"course_day": 0
},
{
"course_id": 50,
"course_day": 1
}
]
}

View File

@ -2,6 +2,8 @@ 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
class IDACData(Data):
@ -10,3 +12,5 @@ class IDACData(Data):
self.profile = IDACProfileData(cfg, self.session)
self.item = IDACItemData(cfg, self.session)
self.rounds = IDACOnlineRounds(cfg, self.session)
self.factory = IDACFactoryData(cfg, self.session)

View File

@ -0,0 +1,60 @@
from typing import Dict, Optional, List
from sqlalchemy import (
Table,
Column,
UniqueConstraint,
PrimaryKeyConstraint,
and_,
update,
)
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
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),
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
) -> Optional[int]:
lottery_data = {}
lottery_data["user"] = aime_id
lottery_data["version"] = version
lottery_data["saved_value"] = saved_value
lottery_data["lottery_count"] = lottery_count
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

View File

@ -0,0 +1,138 @@
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 core.data.schema import BaseData, metadata
from core.config import CoreConfig
import datetime
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)
rid = result.fetchone()
if rid is None:
tmp = {}
tmp["round_id_in_json"] = round_id
tmp["name"] = round_data["round_event_nm"]
tmp["season"] = version
tmp["start_dt"] = datetime.datetime.fromtimestamp(round_data["start_dt"])
tmp["end_dt"] = datetime.datetime.fromtimestamp(round_data["end_dt"])
sql = insert(round_details).values(**tmp)
result = await self.execute(sql)
return result.lastrowid
return rid["id"]
#TODO: get top five players of last round event for Boot/GetConfigData

View File

@ -4,6 +4,7 @@ from random import choice, randint
from typing import Any, Dict, List
import json
import logging
import asyncio
from core.config import CoreConfig
from core.utils import Utils
@ -60,6 +61,9 @@ class IDACSeason2(IDACBase):
"timetrial_event_id"
)
# load the user configured round event (only one)
asyncio.create_task(self._load_round_event())
# load the user configured battle gifts (only one)
self.battle_gift_event = None
if self.game_config.battle_gift.enable:
@ -79,6 +83,117 @@ class IDACSeason2(IDACBase):
) as f:
self.battle_gift_event = self._fix_dates(json.load(f))
async def _load_round_event(self):
self.round_event_id = 0
self.round_event = []
self.last_round_event_id = 0
self.last_round_event = []
self.last_round_event_ranking = []
if self.game_config.round_event.enable:
# Load current round event
round = self.game_config.round_event.enabled_round
if round is not None:
if not os.path.exists(f"./titles/idac/data/rounds/season{self.version+1}/{round}.json"):
self.logger.warning(f"Round info {round} is enabled but json file does not exist!")
else:
with open(
f"./titles/idac/data/rounds/season{self.version+1}/{round}.json", encoding="UTF-8"
) as f:
self.logger.debug(f"Loading round info {round}...")
tmp = json.load(f)
self.round_event_id = await self.data.rounds._try_load_round_event(tmp["round_event_id"], self.version, tmp)
self.round_event.append(self._fix_dates(tmp))
self.logger.debug(f"Loaded round id for database: {self.round_event_id}...")
# Load last round event
round = self.game_config.round_event.last_round
if round is not None:
if not os.path.exists(f"./titles/idac/data/rounds/season{self.version+1}/{round}.json"):
self.logger.warning(f"Round info {round} is enabled but json file does not exist!")
else:
with open(
f"./titles/idac/data/rounds/season{self.version+1}/{round}.json", encoding="UTF-8"
) as f:
self.logger.debug(f"Loading round info {round}...")
tmp = json.load(f)
self.last_round_event_id = await self.data.rounds._try_load_round_event(tmp["round_event_id"], self.version, tmp)
self.last_round_event.append(tmp)
self.logger.debug(f"Loaded round id for database: {self.last_round_event_id}...")
# Load top five of last round event
# class LastRoundEventRanking(BaseModel):
# round_rank: int
# round_point: int
# round_play_count: int
ranking = await self.data.rounds.get_round_top_five(self.last_round_event_id)
if ranking is not None:
rank_profile = {
"round_rank": 0,
"round_point": 0,
"round_play_count": 0,
"username": "DUMMY",
"country": 9,
"store": self.core_cfg.server.name,
"online_battle_rank": 0,
"mytitle_id": 0,
"mytitle_effect_id": 0,
"car_data": [],
"user_avatar": []
}
for user in ranking:
# get the user's profile
p = await self.data.profile.get_profile(user["user"], self.version)
user_data = p._asdict()
arcade = await self.data.arcade.get_arcade(user_data["store"])
user_data["store_name"] = (
self.core_cfg.server.name if arcade is None else arcade["name"]
)
rank_profile["username"] = user_data["username"]
rank_profile["country"] = user_data["country"]
rank_profile["store"] = user_data["store_name"]
rank_profile["mytitle_id"] = user_data["mytitle_id"]
rank_profile["mytitle_effect_id"] = user_data["mytitle_effect_id"]
# get the user's avatar
a = await self.data.profile.get_profile_avatar(user["user"])
avatar_data = a._asdict()
del avatar_data["id"]
del avatar_data["user"]
rank_profile["user_avatar"] = avatar_data
# get the user's rank
r = await self.data.profile.get_profile_rank(user_id, self.version)
rank_data = r._asdict()
del rank_data["id"]
del rank_data["user"]
del rank_data["version"]
rank_profile["online_battle_rank"] = rank_data["online_battle_rank"]
# get the user's car
cars = await self.data.item.get_cars(self.version, user_id, only_pickup=True)
fulltune_count = 0
total_car_parts_count = 0
car_data = []
for car in cars:
tmp = car._asdict()
del tmp["id"]
del tmp["user"]
del tmp["version"]
car_data.append(tmp)
rank_profile["car_data"] = car_data
# get round info
ri = await self.data.rounds.get_round_info_by_id(user["user"], self.last_round_event_id)
if ri is not None:
ri = ri._asdict()
rank_profile["round_rank"] = user["find_in_set_1"]
rank_profile["round_point"] = ri["point"]
rank_profile["round_play_count"] = ri["count"]
self.last_round_event_ranking.append(rank_profile)
async def handle_alive_get_request(self, data: Dict, headers: Dict):
return {
"status_code": "0",
@ -170,106 +285,9 @@ class IDACSeason2(IDACBase):
[self.battle_gift_event] if self.battle_gift_event else []
),
# online battle round event
"round_event": [
{
"round_event_id": 30,
"round_event_nm": f"{self.core_cfg.server.name} Event",
"start_dt": int(
datetime.strptime("2023-01-01", "%Y-%m-%d").timestamp()
),
"end_dt": int(
datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp()
),
"round_start_rank": 0,
"save_filename": "0",
# https://info-initialdac.sega.jp/1898/
"vscount": [
{
"reward_upper_limit": 10,
"reward_lower_limit": 10,
"reward": [{"reward_category": 21, "reward_type": 483}],
},
{
"reward_upper_limit": 40,
"reward_lower_limit": 40,
"reward": [{"reward_category": 21, "reward_type": 484}],
},
{
"reward_upper_limit": 80,
"reward_lower_limit": 80,
"reward": [{"reward_category": 22, "reward_type": 516}],
},
{
"reward_upper_limit": 120,
"reward_lower_limit": 120,
"reward": [{"reward_category": 21, "reward_type": 461}],
},
{
"reward_upper_limit": 180,
"reward_lower_limit": 180,
"reward": [{"reward_category": 21, "reward_type": 462}],
},
],
"rank": [],
"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": 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": 24, "course_day": 0},
{"course_id": 24, "course_day": 1},
{"course_id": 26, "course_day": 0},
{"course_id": 26, "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": 48, "course_day": 0},
{"course_id": 48, "course_day": 1},
{"course_id": 50, "course_day": 0},
{"course_id": 50, "course_day": 1},
{"course_id": 52, "course_day": 0},
{"course_id": 52, "course_day": 1},
{"course_id": 54, "course_day": 0},
{"course_id": 54, "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": 68, "course_day": 0},
{"course_id": 68, "course_day": 1},
{"course_id": 70, "course_day": 0},
{"course_id": 70, "course_day": 1},
],
}
],
"last_round_event": [],
"last_round_event_ranking": [],
"round_event": self.round_event,
"last_round_event": self.last_round_event,
"last_round_event_ranking": self.last_round_event_ranking,
"round_event_exp": [],
"stamp_info": self.stamp_info,
# 0 = use default data, 1+ = server version of timereleasedata response
@ -843,6 +861,57 @@ class IDACSeason2(IDACBase):
vs_info["course_select_priority"] = data.get("course_select_priority")
return vs_info
async def _update_round_info(self, user_id: int, round_event_id: int, point: int, win: int) -> Dict:
# get the round info data from database
round_info = []
ri = await self.data.rounds.get_round_info_by_id(user_id, round_event_id)
if ri is not None:
tmp = ri._asdict()
del tmp["id"]
# calculate new round points and info
tmp["point"] = 0 if tmp["point"] + point < 1 else tmp["point"] + point
tmp["count"] += 1
tmp["win"] += win
tmp["play_dt"] = datetime.now()
# update players round info
await self.data.rounds.put_round_event(user_id, round_event_id, tmp)
del tmp["play_dt"]
# get players new round ranking
r = await self.data.rounds.get_round_rank_by_id(user_id, round_event_id)
round_ranking = r._asdict()
tmp["rank"] = round_ranking["find_in_set_1"] if tmp["point"] > 0 else 0
# TODO: get players historical earned points
tmp["total_round_point"] = 0
round_info.append(tmp)
else:
# new player of now-going round event
tmp = {}
tmp["point"] = 0 if point < 1 else point
tmp["count"] = 1
tmp["win"] = win
tmp["play_dt"] = datetime.now()
await self.data.rounds.put_round_event(user_id, round_event_id, tmp)
del tmp["play_dt"]
r = await self.data.rounds.get_round_rank_by_id(user_id, round_event_id)
round_ranking = r._asdict()
tmp["rank"] = round_ranking["find_in_set_1"] if tmp["point"] > 0 else 0
# TODO: get players historical earned points
tmp["total_round_point"] = 0
round_info.append(tmp)
return round_info
def _choose_gift_id(self) -> Dict:
gift_data = self.battle_gift_event["gift_data"]
# calculate the total_rate based on the first_distribution_rate
@ -925,6 +994,25 @@ class IDACSeason2(IDACBase):
}
)
# get the users'd round data
round_info = []
ri = await self.data.rounds.get_round_info_by_id(user_id, self.round_event_id)
if ri is not None:
r = await self.data.rounds.get_round_rank_by_id(user_id, self.round_event_id)
round_ranking = r._asdict()
tmp = ri._asdict()
del tmp["id"]
del tmp["user"]
del tmp["round_id"]
del tmp["play_dt"]
tmp["rank"] = round_ranking["find_in_set_1"]
# TODO: calculate this
tmp["total_round_point"] = 0
round_info.append(tmp)
# get the user's course, required for the "course proeficiency"
courses = await self.data.item.get_courses(user_id)
course_data = []
@ -1206,7 +1294,7 @@ class IDACSeason2(IDACBase):
# first_distribution related are useless since this is all handled by server
"battle_gift_data": battle_gift_data,
"ticket_data": ticket_data,
"round_event": [],
"round_event": round_info,
"last_round_event": [],
"past_round_event": [],
"total_round_point": 0,
@ -2849,6 +2937,7 @@ class IDACSeason2(IDACBase):
}
async def handle_user_updateonlinebattle_request(self, data: Dict, headers: Dict):
#TODO: voiding cheaters' result. which will need to analysis historical battle results returned by the game.
return {
"status_code": "0",
"bothwin_penalty": 1,
@ -2922,18 +3011,12 @@ class IDACSeason2(IDACBase):
user_id, self.version, {"online_battle_play_count": tips["online_battle_play_count"] + 1}
)
round_info = await self._update_round_info(user_id, self.round_event_id, data.pop("round_point"), data.pop("win_flg"))
return {
"status_code": "0",
"vsinfo_data": vs_info,
"round_event": [
{
"count": 1,
"win": 1,
"rank": 1,
"point": 1,
"total_round_point": 1,
}
],
"round_event": round_info,
"car_use_count": [],
"maker_use_count": [],
}
@ -3026,3 +3109,79 @@ class IDACSeason2(IDACBase):
"car_use_count": [],
"maker_use_count": [],
}
async def handle_factory_numberlotterybefore_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
win_list = []
lottery_count = 0
p = await self.data.factory.get_lottery(user_id, self.version)
if p is not None:
lottery_data = p._asdict()
lottery_count = lottery_data["lottery_count"]
number = 0
while number < 10:
if int(lottery_data["saved_value"]) & 1:
win_list_data = {
"m_number_lottery_schedule_no": 1,
"win_number": 0
}
win_list_data["win_number"] = number*1111
win_list.append(win_list_data)
lottery_data["saved_value"] = lottery_data["saved_value"] / 2
number = number + 1
return {
"status_code": "0",
"lottery_info": {
"lottery_count": lottery_count,
"win_list": win_list
}
}
async def handle_factory_numberlotteryresult_request(self, data: Dict, headers: Dict):
user_id = headers["session"]
win_number = data.pop("win_number")
lottery_count = data.pop("lottery_count")
# save count if not a lucky number otherwise save all
if win_number != 10000:
shifted = win_number / 1111
number = 1 << int(shifted)
p = await self.data.factory.get_lottery(user_id, self.version)
if p is not None:
lottery_data = p._asdict()
saved_value = lottery_data["saved_value"] + number
else:
saved_value = number
await self.data.factory.put_lottery(user_id, self.version, saved_value, lottery_count)
else:
p = await self.data.factory.get_lottery(user_id, self.version)
if p is not None:
lottery_data = p._asdict()
saved_value = lottery_data["saved_value"]
else:
saved_value = 0
await self.data.factory.put_lottery(user_id, self.version, saved_value, lottery_count)
# save car data if lottery ended
car = {}
car["style_car_id"] = data.pop("style_car_id")
car["l_no"] = data.pop("l_no")
if data.pop("isEnd") == 1:
await self.data.item.put_car(user_id, self.version, car)
# save ticket data
for ticket in data.pop("ticket_data"):
await self.data.item.put_ticket(user_id, ticket)
# save cash
await self.data.profile.put_profile(user_id, self.version, data)
return {
"status_code": "0"
}