From e50fedad49ff955cf043638ef6b45ce5bacb5b6b Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Wed, 12 Jun 2024 21:11:25 +0200 Subject: [PATCH] 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 --- .gitignore | 5 +- ...2d1ada1b39_idac_rounds_event_info_added.py | 72 ++++ ...2c328b1_idac_plate_number_lottery_added.py | 39 ++ example_config/idac.yaml | 5 + titles/idac/config.py | 31 ++ titles/idac/data/rounds/season2/S2R1.json | 183 +++++++++ titles/idac/data/rounds/season2/S2R2.json | 229 +++++++++++ titles/idac/database.py | 4 + titles/idac/schema/factory.py | 60 +++ titles/idac/schema/rounds.py | 138 +++++++ titles/idac/season2.py | 379 +++++++++++++----- 11 files changed, 1034 insertions(+), 111 deletions(-) create mode 100644 core/data/alembic/versions/202d1ada1b39_idac_rounds_event_info_added.py create mode 100644 core/data/alembic/versions/7e98c2c328b1_idac_plate_number_lottery_added.py create mode 100644 titles/idac/data/rounds/season2/S2R1.json create mode 100644 titles/idac/data/rounds/season2/S2R2.json create mode 100644 titles/idac/schema/factory.py create mode 100644 titles/idac/schema/rounds.py diff --git a/.gitignore b/.gitignore index b5a0e6e..dc74225 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,7 @@ config/* deliver/* *.gz -dbdump-*.json \ No newline at end of file +dbdump-*.json +/.vs +/titles/id8 +/titles/idac/battle.py diff --git a/core/data/alembic/versions/202d1ada1b39_idac_rounds_event_info_added.py b/core/data/alembic/versions/202d1ada1b39_idac_rounds_event_info_added.py new file mode 100644 index 0000000..8079836 --- /dev/null +++ b/core/data/alembic/versions/202d1ada1b39_idac_rounds_event_info_added.py @@ -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") diff --git a/core/data/alembic/versions/7e98c2c328b1_idac_plate_number_lottery_added.py b/core/data/alembic/versions/7e98c2c328b1_idac_plate_number_lottery_added.py new file mode 100644 index 0000000..439805c --- /dev/null +++ b/core/data/alembic/versions/7e98c2c328b1_idac_plate_number_lottery_added.py @@ -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") diff --git a/example_config/idac.yaml b/example_config/idac.yaml index 7d3bd93..d5c0761 100644 --- a/example_config/idac.yaml +++ b/example_config/idac.yaml @@ -28,3 +28,8 @@ timetrial: battle_event: enabled: True enabled_battle_event: "touhou_1st" + +round_event: + enable: True + enabled_round: "S2R2" + last_round: "S2R1" \ No newline at end of file diff --git a/titles/idac/config.py b/titles/idac/config.py index c411d47..ea5cfa7 100644 --- a/titles/idac/config.py +++ b/titles/idac/config.py @@ -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) diff --git a/titles/idac/data/rounds/season2/S2R1.json b/titles/idac/data/rounds/season2/S2R1.json new file mode 100644 index 0000000..defe7dc --- /dev/null +++ b/titles/idac/data/rounds/season2/S2R1.json @@ -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 + } + ] +} \ No newline at end of file diff --git a/titles/idac/data/rounds/season2/S2R2.json b/titles/idac/data/rounds/season2/S2R2.json new file mode 100644 index 0000000..aa89aa4 --- /dev/null +++ b/titles/idac/data/rounds/season2/S2R2.json @@ -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 + } + ] +} \ No newline at end of file diff --git a/titles/idac/database.py b/titles/idac/database.py index dac4556..e38f90d 100644 --- a/titles/idac/database.py +++ b/titles/idac/database.py @@ -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) diff --git a/titles/idac/schema/factory.py b/titles/idac/schema/factory.py new file mode 100644 index 0000000..6b44de2 --- /dev/null +++ b/titles/idac/schema/factory.py @@ -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 \ No newline at end of file diff --git a/titles/idac/schema/rounds.py b/titles/idac/schema/rounds.py new file mode 100644 index 0000000..9958e19 --- /dev/null +++ b/titles/idac/schema/rounds.py @@ -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 \ No newline at end of file diff --git a/titles/idac/season2.py b/titles/idac/season2.py index b312950..cf413fa 100644 --- a/titles/idac/season2.py +++ b/titles/idac/season2.py @@ -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" + } +