From ba5591cd8116e1fabd0f3fa739f05706b6c04034 Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Wed, 12 Jun 2024 23:43:42 +0200 Subject: [PATCH] idac: added support for basic 1.70 - Added special mode based on device version - Added number plate lottery - Updated online battle rounds handling --- changelog.md | 6 + ...2d1ada1b39_idac_rounds_event_info_added.py | 6 +- ...2c328b1_idac_plate_number_lottery_added.py | 8 +- .../aeb6b1e28354_idac_device_version_added.py | 26 ++ docs/game_specific_info.md | 7 +- titles/idac/config.py | 4 +- titles/idac/schema/factory.py | 25 +- titles/idac/schema/item.py | 30 +- titles/idac/schema/profile.py | 12 +- titles/idac/schema/rounds.py | 7 +- titles/idac/season2.py | 271 +++++++++++++----- titles/idac/templates/const.json | 2 +- 12 files changed, 277 insertions(+), 127 deletions(-) create mode 100644 core/data/alembic/versions/aeb6b1e28354_idac_device_version_added.py diff --git a/changelog.md b/changelog.md index 0e8b39a..e514163 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,12 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240612 ++ Support Initial D THE ARCADE v1.70 + + Added special mode based on device version + + Added number plate lottery + + Updated online battle rounds handling + ## 20240526 + Fixed missing awaits causing coroutine error 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 index 8079836..ac00d65 100644 --- a/core/data/alembic/versions/202d1ada1b39_idac_rounds_event_info_added.py +++ b/core/data/alembic/versions/202d1ada1b39_idac_rounds_event_info_added.py @@ -1,7 +1,7 @@ """idac rounds event info added Revision ID: 202d1ada1b39 -Revises: e4e8d89c9b02 +Revises: 7dc13e364e53 Create Date: 2024-05-03 15:51:02.384863 """ @@ -12,7 +12,7 @@ from sqlalchemy.dialects import mysql # revision identifiers, used by Alembic. revision = '202d1ada1b39' -down_revision = 'e4e8d89c9b02' +down_revision = '7dc13e364e53' branch_labels = None depends_on = None @@ -22,7 +22,7 @@ def upgrade(): "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("name", sa.String(64), nullable=True), sa.Column("season", sa.Integer(), nullable=True), sa.Column( "start_dt", 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 index 439805c..84ff737 100644 --- a/core/data/alembic/versions/7e98c2c328b1_idac_plate_number_lottery_added.py +++ b/core/data/alembic/versions/7e98c2c328b1_idac_plate_number_lottery_added.py @@ -1,4 +1,4 @@ -"""idac plate number lottery added +"""IDAC plate number lottery added Revision ID: 7e98c2c328b1 Revises: 202d1ada1b39 @@ -24,6 +24,12 @@ def upgrade(): sa.Column("version", sa.Integer(), nullable=False), sa.Column("saved_value", sa.Integer(), nullable=False), sa.Column("lottery_count", sa.Integer(), nullable=False), + sa.Column( + "create_date", + sa.TIMESTAMP(), + server_default=sa.text("now()"), + nullable=False, + ), sa.ForeignKeyConstraint( ["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade" ), diff --git a/core/data/alembic/versions/aeb6b1e28354_idac_device_version_added.py b/core/data/alembic/versions/aeb6b1e28354_idac_device_version_added.py new file mode 100644 index 0000000..30ac1ab --- /dev/null +++ b/core/data/alembic/versions/aeb6b1e28354_idac_device_version_added.py @@ -0,0 +1,26 @@ +"""IDAC device_version added + +Revision ID: aeb6b1e28354 +Revises: 7e98c2c328b1 +Create Date: 2024-05-29 17:40:45.123656 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = 'aeb6b1e28354' +down_revision = '7e98c2c328b1' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('idac_profile', sa.Column('device_version', sa.String(length=7), server_default='1.50.00', nullable=True)) + op.drop_column('idac_profile', 'asset_version') + + +def downgrade(): + op.add_column('idac_profile', sa.Column('asset_version', mysql.INTEGER(), server_default="1", nullable=True)) + op.drop_column('idac_profile', 'device_version') diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md index c38224f..035ff57 100644 --- a/docs/game_specific_info.md +++ b/docs/game_specific_info.md @@ -733,10 +733,11 @@ python dbutils.py upgrade ### TimeRelease Chapter: -1. Story: 1, 2, 3, 4, 5, 6, 7, 8, 9, 19 (Chapter 10), (29 Chapter 11?) +1. Story: 1, 2, 3, 4, 5, 6, 7, 8, 9, 19 (Chapter 10), (29 Chapter 11) 2. MF Ghost: 10, 11, 12, 13, 14, 15 3. Bunta: 15, 16, 17, 18, 20, 21, 21, 22 -4. Special Event: 23, 24, 25, 26, 27, 28 (Touhou Project) +4. Touhou Project Special Event: 23, 24, 25, 26, 27, 28 +5. Hatsune Miku Special Event: 36, 37, 38 ### TimeRelease Courses: @@ -762,6 +763,8 @@ python dbutils.py upgrade | 58 | Momiji Line(もみじライン) | Hillclimb(上り) | | 24 | Happogahara(八方ヶ原) | Outbound(往路) | | 26 | Happogahara(八方ヶ原) | Inbound(復路) | +| 28 | Nagao(長尾) | Downhill(下り) | +| 30 | Nagao(長尾) | Hillclimb(上り) | | 40 | Sadamine(定峰) | Downhill(下り) | | 42 | Sadamine(定峰) | Hillclimb(上り) | | 44 | Tsuchisaka(土坂) | Outbound(往路) | diff --git a/titles/idac/config.py b/titles/idac/config.py index ea5cfa7..5a13504 100644 --- a/titles/idac/config.py +++ b/titles/idac/config.py @@ -166,7 +166,7 @@ class IDACRoundConfig: "idac", "round_event", "enabled_round", - default="S1R1", + default="S2R2", ) @property @@ -176,7 +176,7 @@ class IDACRoundConfig: "idac", "round_event", "last_round", - default="S1R1", + default="S2R1", ) diff --git a/titles/idac/schema/factory.py b/titles/idac/schema/factory.py index 6b44de2..24b64ab 100644 --- a/titles/idac/schema/factory.py +++ b/titles/idac/schema/factory.py @@ -1,13 +1,12 @@ -from typing import Dict, Optional, List +from datetime import datetime +from typing import Dict, Optional from sqlalchemy import ( Table, Column, UniqueConstraint, - PrimaryKeyConstraint, and_, - update, ) -from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.types import Integer, TIMESTAMP from sqlalchemy.schema import ForeignKey from sqlalchemy.engine import Row from sqlalchemy.sql import func, select @@ -23,6 +22,7 @@ lottery = Table( Column("version", Integer, nullable=False), Column("saved_value", Integer, nullable=False), Column("lottery_count", Integer, nullable=False), + Column("create_date", TIMESTAMP, server_default=func.now()), UniqueConstraint("user", "version", name="idac_user_lottery_uk"), mysql_charset="utf8mb4", ) @@ -41,14 +41,17 @@ class IDACFactoryData(BaseData): return result.fetchone() async def put_lottery( - self, aime_id: int, version: int, saved_value: int, lottery_count: int + + self, aime_id: int, version: int, saved_value: int, lottery_count: int, create_date: datetime ) -> 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 + lottery_data = { + "user": aime_id, + "version": version, + "saved_value": saved_value, + "lottery_count": lottery_count, + "create_date": create_date + } sql = insert(lottery).values(**lottery_data) conflict = sql.on_duplicate_key_update(**lottery_data) @@ -57,4 +60,4 @@ class IDACFactoryData(BaseData): 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 + return result.lastrowid diff --git a/titles/idac/schema/item.py b/titles/idac/schema/item.py index 0c7e490..8dc4f09 100644 --- a/titles/idac/schema/item.py +++ b/titles/idac/schema/item.py @@ -249,30 +249,6 @@ vs_course_info = Table( mysql_charset="utf8mb4", ) -round_infos = Table( - "idac_round_info", - metadata, - Column("id", Integer, primary_key=True, nullable=False), - 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", Integer), - Column("count", Integer), - Column("win", Integer), - Column("points", Integer), - UniqueConstraint("user", "round_id", name="idac_user_round_info_uk"), - mysql_charset="utf8mb4", -) - stamp = Table( "idac_user_stamp", metadata, @@ -354,7 +330,7 @@ class IDACItemData(BaseData): return result.fetchone() async def get_random_car(self, version: int) -> Optional[List[Row]]: - sql = select(car).where(car.c.version == version).order_by(func.rand()).limit(1) + sql = select(car).where(car.c.version <= version).order_by(func.rand()).limit(1) result = await self.execute(sql) if result is None: @@ -367,7 +343,7 @@ class IDACItemData(BaseData): sql = select(car).where( and_( car.c.user == aime_id, - car.c.version == version, + car.c.version <= version, car.c.style_car_id == style_car_id, ) ) @@ -384,7 +360,7 @@ class IDACItemData(BaseData): sql = select(car).where( and_( car.c.user == aime_id, - car.c.version == version, + car.c.version <= version, car.c.pickup_seq != 0, ) ) diff --git a/titles/idac/schema/profile.py b/titles/idac/schema/profile.py index 32759c3..2f77005 100644 --- a/titles/idac/schema/profile.py +++ b/titles/idac/schema/profile.py @@ -28,7 +28,7 @@ profile = Table( Column("daily_play", Integer, server_default="0"), Column("day_play", Integer, server_default="0"), Column("mileage", Integer, server_default="0"), - Column("asset_version", Integer, server_default="1"), + Column("device_version", String(7), server_default="1.50.00"), Column("last_play_date", TIMESTAMP, server_default=func.now()), Column("mytitle_id", Integer, server_default="0"), Column("mytitle_efffect_id", Integer, server_default="0"), @@ -279,7 +279,7 @@ class IDACProfileData(BaseData): sql = select(profile).where( and_( profile.c.user == aime_id, - profile.c.version == version, + profile.c.version <= version, ) ) @@ -336,7 +336,7 @@ class IDACProfileData(BaseData): sql = select(rank).where( and_( rank.c.user == aime_id, - rank.c.version == version, + rank.c.version <= version, ) ) @@ -349,7 +349,7 @@ class IDACProfileData(BaseData): sql = select(stock).where( and_( stock.c.user == aime_id, - stock.c.version == version, + stock.c.version <= version, ) ) @@ -362,7 +362,7 @@ class IDACProfileData(BaseData): sql = select(theory).where( and_( theory.c.user == aime_id, - theory.c.version == version, + theory.c.version <= version, ) ) @@ -375,7 +375,7 @@ class IDACProfileData(BaseData): sql = select(tips).where( and_( tips.c.user == aime_id, - tips.c.version == version, + tips.c.version <= version, ) ) diff --git a/titles/idac/schema/rounds.py b/titles/idac/schema/rounds.py index 9958e19..46c2e9f 100644 --- a/titles/idac/schema/rounds.py +++ b/titles/idac/schema/rounds.py @@ -119,11 +119,13 @@ class IDACOnlineRounds(BaseData): 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 @@ -131,8 +133,11 @@ class IDACOnlineRounds(BaseData): 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 + + # TODO: get top five players of last round event for Boot/GetConfigData diff --git a/titles/idac/season2.py b/titles/idac/season2.py index 87a8d04..6d4a896 100644 --- a/titles/idac/season2.py +++ b/titles/idac/season2.py @@ -93,12 +93,11 @@ class IDACSeason2(IDACBase): # 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"): + path = f"./titles/idac/data/rounds/season{self.version+1}/{round}.json" + if not os.path.exists(path): self.logger.warning(f"Round info {round} is enabled but json file does not exist!") else: - with open( - f"./titles/idac/data/rounds/season{self.version+1}/{round}.json", encoding="UTF-8" - ) as f: + with open(path, encoding="UTF-8") as f: self.logger.debug(f"Loading round info {round}...") tmp = json.load(f) self.round_event_id = await self.data.rounds._try_load_round_event(tmp["round_event_id"], self.version, tmp) @@ -108,12 +107,11 @@ class IDACSeason2(IDACBase): # 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"): + path = f"./titles/idac/data/rounds/season{self.version+1}/{round}.json" + if not os.path.exists(path): self.logger.warning(f"Round info {round} is enabled but json file does not exist!") else: - with open( - f"./titles/idac/data/rounds/season{self.version+1}/{round}.json", encoding="UTF-8" - ) as f: + with open(path, encoding="UTF-8") as f: self.logger.debug(f"Loading round info {round}...") tmp = json.load(f) self.last_round_event_id = await self.data.rounds._try_load_round_event(tmp["round_event_id"], self.version, tmp) @@ -216,6 +214,16 @@ class IDACSeason2(IDACBase): output[key] = value return output + + def _special_story_type(self, headers: Dict): + version = headers["device_version"] + ver_str = version.replace(".", "")[:3] + + # 4 = touhou project, 5 = hatsune miku + return { + "150": 4, + "170": 5 + }.get(ver_str, 0) async def handle_boot_getconfigdata_request(self, data: Dict, headers: Dict): """ @@ -255,6 +263,18 @@ class IDACSeason2(IDACBase): else: domain_api_game = f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/{ver_str}/" + # sets the special mode data if the version supports the special mode + special_mode_data = {} + special_story_type = self._special_story_type(headers) + if special_story_type != 0: + special_mode_data = { + "start_dt": int( + datetime.strptime("2023-01-01", "%Y-%m-%d").timestamp() + ), + "end_dt": int(datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp()), + "story_type": special_story_type + } + return { "status_code": "0", "free_continue_enable": 1, @@ -456,14 +476,90 @@ class IDACSeason2(IDACBase): "theory_open_version": "1.30.00", "theory_close_version": "9.99.99", # unlocks the version specific special mode - "special_mode_data": { - "start_dt": int( - datetime.strptime("2023-01-01", "%Y-%m-%d").timestamp() - ), - "end_dt": int(datetime.strptime("2029-01-01", "%Y-%m-%d").timestamp()), - "story_type": 4, # touhou special event - }, + "special_mode_data": special_mode_data, "timetrial_event_data": self.timetrial_event, + "number_lottery_data": [ + { + "m_number_lottery_win_number_no": 10, + "m_number_lottery_schedule_no": 1, + "win_number": 0, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 11, + "m_number_lottery_schedule_no": 1, + "win_number": 1111, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 12, + "m_number_lottery_schedule_no": 1, + "win_number": 2222, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 13, + "m_number_lottery_schedule_no": 1, + "win_number": 3333, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 14, + "m_number_lottery_schedule_no": 1, + "win_number": 4444, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 15, + "m_number_lottery_schedule_no": 1, + "win_number": 5555, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 16, + "m_number_lottery_schedule_no": 1, + "win_number": 6666, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 17, + "m_number_lottery_schedule_no": 1, + "win_number": 7777, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 18, + "m_number_lottery_schedule_no": 1, + "win_number": 8888, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + { + "m_number_lottery_win_number_no": 19, + "m_number_lottery_schedule_no": 1, + "win_number": 9999, + "reward_category": 34, + "reward_type": 1, + "end_dt": 0, + }, + ], } async def handle_boot_bookkeep_request(self, data: Dict, headers: Dict): @@ -524,7 +620,7 @@ class IDACSeason2(IDACBase): timerelease chapter: 1 = Story: 1, 2, 3, 4, 5, 6, 7, 8, 9, 19 (Chapter 10), (29 Chapter 11 lol?) 2 = MF Ghost: 10, 11, 12, 13, 14, 15 - 3 = Bunta: 15, 16, 17, 18, 20, (21, 21, 22?) + 3 = Bunta: 15, 16, 17, 18, 20, 21, 21, 22 4 = Special Event: 23, 24, 25, 26, 27, 28 (Touhou Project) """ path = "./titles/idac/data/" @@ -612,6 +708,16 @@ class IDACSeason2(IDACBase): "shop_best_data": best_data, "rank_management_flag": 0, } + + def _is_valid_version(self, db_version: str, cl_version: str) -> bool: + if len(db_version) < 7 or len(cl_version) < 7: + return False + + # convert the trings to int and compare + db_version = int(db_version.replace(".", "")[:7]) + cl_version = int(cl_version.replace(".", "")[:7]) + + return cl_version >= db_version async def handle_login_checklock_request(self, data: Dict, headers: Dict): user_id = data["id"] @@ -625,6 +731,13 @@ class IDACSeason2(IDACBase): # check if an IDAC profile already exists p = await self.data.profile.get_profile(user_id, self.version) is_new_player = 1 if p is None else 0 + + db_version = p["device_version"] if p is not None else "1.00.00" + cl_version = headers["device_version"] + + # check if the client version is valid + if not self._is_valid_version(db_version, cl_version): + lock_result = 2 else: lock_result = 0 user_id = "" @@ -706,10 +819,10 @@ class IDACSeason2(IDACBase): return story_data - async def _generate_special_data(self, user_id: int) -> Dict: - # 4 = special mode + async def _generate_special_data(self, user_id: int, headers: Dict) -> Dict: + # 4 = touhou project, 5 = hatsune miku specials = await self.data.item.get_best_challenges_by_vs_type( - user_id, story_type=4 + user_id, story_type=self._special_story_type(headers) ) special_data = [] @@ -1321,7 +1434,7 @@ class IDACSeason2(IDACBase): "theory_course_data": theory_course_data, "theory_partner_data": theory_partner_data, "theory_running_pram_data": theory_running_pram_data, - "special_mode_data": await self._generate_special_data(user_id), + "special_mode_data": await self._generate_special_data(user_id, headers), "challenge_mode_data": await self._generate_challenge_data(user_id), "season_rewards_data": [], "timetrial_event_data": timetrial_event_data, @@ -1519,7 +1632,7 @@ class IDACSeason2(IDACBase): # save profile in database data["store"] = headers.get("a_store", 0) data["country"] = headers.get("a_country", 0) - data["asset_version"] = headers.get("asset_version", 1) + data["device_version"] = headers.get("device_version", "1.50.00") await self.data.profile.put_profile(user_id, self.version, data) # save rank data in database @@ -1925,7 +2038,9 @@ 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, {"timetrial_play_count": tips["timetrial_play_count"] + 1} + user_id, + self.version, + {"timetrial_play_count": tips["timetrial_play_count"] + 1}, ) return { @@ -2147,12 +2262,14 @@ 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, {"special_play_count": tips["special_play_count"] + 1} + user_id, + self.version, + {"special_play_count": tips["special_play_count"] + 1}, ) return { "status_code": "0", - "special_mode_data": await self._generate_special_data(user_id), + "special_mode_data": await self._generate_special_data(user_id, headers), "car_use_count": [], "maker_use_count": [], } @@ -2233,7 +2350,9 @@ 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, {"challenge_play_count": tips["challenge_play_count"] + 1} + user_id, + self.version, + {"challenge_play_count": tips["challenge_play_count"] + 1}, ) return { @@ -3011,7 +3130,9 @@ 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, {"online_battle_play_count": tips["online_battle_play_count"] + 1} + 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")) @@ -3103,7 +3224,9 @@ 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, {"store_battle_play_count": tips["store_battle_play_count"] + 1} + user_id, + self.version, + {"store_battle_play_count": tips["store_battle_play_count"] + 1}, ) return { @@ -3118,23 +3241,25 @@ class IDACSeason2(IDACBase): 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 - } + # retrieve the lottery data for the user + l = await self.data.factory.get_lottery(user_id, self.version) + if l: + # check if create_data is younger than 24 hours + if datetime.now() - l["create_date"] < timedelta(days=1): + lottery_count = l["lottery_count"] + saved_value = int(l["saved_value"]) - 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 + # check each of the first 10 bits in saved_value + for number in range(10): + if saved_value & 1: + # if the least significant bit is 1, add to the win_list + win_list.append({ + "m_number_lottery_schedule_no": 1, + "win_number": number * 1111 + }) + + # right shift saved_value to check the next bit + saved_value >>= 1 return { "status_code": "0", @@ -3148,43 +3273,43 @@ class IDACSeason2(IDACBase): user_id = headers["session"] win_number = data.pop("win_number") lottery_count = data.pop("lottery_count") + style_car_id = data.pop("style_car_id") + license_no = data.pop("l_no") + is_end = data.pop("isEnd") + + ticket_data = data.pop("ticket_data") + + # retrieve the lottery data for the user + l = await self.data.factory.get_lottery(user_id, self.version) + create_date = l["create_date"] if l else datetime.now() + saved_value = l["saved_value"] if l else 0 - # 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) + # calculate the bit position to set based on the win_number + shifted = win_number // 1111 + saved_value += 1 << shifted - # save ticket data - for ticket in data.pop("ticket_data"): + # update the create_date timestamp when the last create_date is older than 24 hours + if l and datetime.now() - l["create_date"] > timedelta(days=1): + create_date = datetime.now() + + # update the lottery data with the new saved_value and lottery_count + await self.data.factory.put_lottery(user_id, self.version, saved_value, lottery_count, create_date) + + if license_no != 10000 and is_end == 1: + # ithe lottery is ended, save car data + await self.data.item.put_car(user_id, self.version, { + "style_car_id": style_car_id, + "l_no": license_no + }) + + # save each ticket data + for ticket in ticket_data: await self.data.item.put_ticket(user_id, ticket) - # save cash + # save remaining profile data await self.data.profile.put_profile(user_id, self.version, data) return { "status_code": "0" } - diff --git a/titles/idac/templates/const.json b/titles/idac/templates/const.json index 339d382..e6c6ee0 100644 --- a/titles/idac/templates/const.json +++ b/titles/idac/templates/const.json @@ -1 +1 @@ -{"car":[{"style_car_id":0,"style_name":"SPRINTER TRUENO GT-APEX (AE86) [DH]","route_style_name":"DH"},{"style_car_id":131072,"style_name":"SPRINTER TRUENO GT-APEX (AE86) [HC]","route_style_name":"HC"},{"style_car_id":9,"style_name":"SPRINTER TRUENO 2door GT-APEX (AE86) [DH]","route_style_name":"DH"},{"style_car_id":131073,"style_name":"COROLLA LEVIN GT-APEX (AE86) [HC]","route_style_name":"HC"},{"style_car_id":65537,"style_name":"COROLLA LEVIN GT-APEX (AE86) [AR]","route_style_name":"AR"},{"style_car_id":131074,"style_name":"COROLLA LEVIN SR (AE85) [HC]","route_style_name":"HC"},{"style_car_id":65538,"style_name":"COROLLA LEVIN SR (AE85) [AR]","route_style_name":"AR"},{"style_car_id":7,"style_name":"86 GT (ZN6) [DH]","route_style_name":"DH"},{"style_car_id":131079,"style_name":"86 GT (ZN6) [HC]","route_style_name":"HC"},{"style_car_id":65543,"style_name":"86 GT (ZN6) [AR]","route_style_name":"AR"},{"style_car_id":3,"style_name":"MR2 G-Limited (SW20) [DH]","route_style_name":"DH"},{"style_car_id":65539,"style_name":"MR2 G-Limited (SW20) [AR]","route_style_name":"AR"},{"style_car_id":5,"style_name":"MR-S S EDITION (ZZW30) [DH]","route_style_name":"DH"},{"style_car_id":4,"style_name":"ALTEZZA RS200 Z EDITION (SXE10) [DH]","route_style_name":"DH"},{"style_car_id":131082,"style_name":"CELICA GT-FOUR (ST205) [HC]","route_style_name":"HC"},{"style_car_id":65546,"style_name":"CELICA GT-FOUR (ST205) [AR]","route_style_name":"AR"},{"style_car_id":131078,"style_name":"SUPRA RZ (JZA80) [HC]","route_style_name":"HC"},{"style_car_id":65542,"style_name":"SUPRA RZ (JZA80) [AR]","route_style_name":"AR"},{"style_car_id":65547,"style_name":"GR YARIS 1st Edition RZ “High performance” (GXPA16) [AR]","route_style_name":"AR"},{"style_car_id":12,"style_name":"GR SUPRA RZ (DB42) [DH]","route_style_name":"DH"},{"style_car_id":65548,"style_name":"GR SUPRA RZ (DB42) [AR]","route_style_name":"AR"},{"style_car_id":131328,"style_name":"SKYLINE GT-R V・specⅡ (BNR32) [HC]","route_style_name":"HC"},{"style_car_id":65792,"style_name":"SKYLINE GT-R V・specⅡ (BNR32) [AR]","route_style_name":"AR"},{"style_car_id":131329,"style_name":"SKYLINE GT-R V・specⅡ Nür (BNR34) [HC]","route_style_name":"HC"},{"style_car_id":65793,"style_name":"SKYLINE GT-R V・specⅡ Nür (BNR34) [AR]","route_style_name":"AR"},{"style_car_id":131330,"style_name":"SILVIA K's (S13) [HC]","route_style_name":"HC"},{"style_car_id":65794,"style_name":"SILVIA K's (S13) [AR]","route_style_name":"AR"},{"style_car_id":259,"style_name":"Silvia Q's (S14) [DH]","route_style_name":"DH"},{"style_car_id":131331,"style_name":"Silvia Q's (S14) [HC]","route_style_name":"HC"},{"style_car_id":131332,"style_name":"Silvia spec-R (S15) [HC]","route_style_name":"HC"},{"style_car_id":65796,"style_name":"Silvia spec-R (S15) [AR]","route_style_name":"AR"},{"style_car_id":261,"style_name":"180SX TYPE Ⅱ (RPS13) [DH]","route_style_name":"DH"},{"style_car_id":65797,"style_name":"180SX TYPE Ⅱ (RPS13) [AR]","route_style_name":"AR"},{"style_car_id":131334,"style_name":"FAIRLADY Z Version S (Z33) [HC]","route_style_name":"HC"},{"style_car_id":131335,"style_name":"NISSAN GT-R NISMO (R35) [HC]","route_style_name":"HC"},{"style_car_id":264,"style_name":"SKYLINE 25GT TURBO (ER34) [DH]","route_style_name":"DH"},{"style_car_id":131336,"style_name":"SKYLINE 25GT TURBO (ER34) [HC]","route_style_name":"HC"},{"style_car_id":265,"style_name":"Fairlady Z (S30) [DH]","route_style_name":"DH"},{"style_car_id":131337,"style_name":"Fairlady Z (S30) [HC]","route_style_name":"HC"},{"style_car_id":512,"style_name":"Civic SiR・Ⅱ (EG6) [DH]","route_style_name":"DH"},{"style_car_id":131584,"style_name":"Civic SiR・Ⅱ (EG6) [HC]","route_style_name":"HC"},{"style_car_id":513,"style_name":"CIVIC TYPE R (EK9) [DH]","route_style_name":"DH"},{"style_car_id":66049,"style_name":"CIVIC TYPE R (EK9) [AR]","route_style_name":"AR"},{"style_car_id":514,"style_name":"INTEGRA TYPE R (DC2) [DH]","route_style_name":"DH"},{"style_car_id":131586,"style_name":"INTEGRA TYPE R (DC2) [HC]","route_style_name":"HC"},{"style_car_id":515,"style_name":"S2000 (AP1) [DH]","route_style_name":"DH"},{"style_car_id":66051,"style_name":"S2000 (AP1) [AR]","route_style_name":"AR"},{"style_car_id":131588,"style_name":"NSX (NA1) [HC]","route_style_name":"HC"},{"style_car_id":768,"style_name":"SAVANNA RX-7 ∞Ⅲ (FC3S) [DH]","route_style_name":"DH"},{"style_car_id":66304,"style_name":"SAVANNA RX-7 ∞Ⅲ (FC3S) [AR]","route_style_name":"AR"},{"style_car_id":131841,"style_name":"ε~fini RX-7 Type R (FD3S) [HC]","route_style_name":"HC"},{"style_car_id":66305,"style_name":"ε~fini RX-7 Type R (FD3S) [AR]","route_style_name":"AR"},{"style_car_id":770,"style_name":"RX-8 Type S (SE3P) [DH]","route_style_name":"DH"},{"style_car_id":771,"style_name":"EUNOS ROADSTER (NA6CE) [DH]","route_style_name":"DH"},{"style_car_id":772,"style_name":"ROADSTER RS (NB8C) [DH]","route_style_name":"DH"},{"style_car_id":131845,"style_name":"RX-7 Type RS (FD3S) [HC]","route_style_name":"HC"},{"style_car_id":66309,"style_name":"RX-7 Type RS (FD3S) [AR]","route_style_name":"AR"},{"style_car_id":132096,"style_name":"IMPREZA WRX type R STi Version Ⅴ (GC8) [HC]","route_style_name":"HC"},{"style_car_id":66560,"style_name":"IMPREZA WRX type R STi Version Ⅴ (GC8) [AR]","route_style_name":"AR"},{"style_car_id":1027,"style_name":"SUBARU BRZ S (ZC6) [DH]","route_style_name":"DH"},{"style_car_id":66563,"style_name":"SUBARU BRZ S (ZC6) [AR]","route_style_name":"AR"},{"style_car_id":132100,"style_name":"STI S207 NBR CHALLENGE PACKAGE (VAB) [HC]","route_style_name":"HC"},{"style_car_id":1280,"style_name":"LANCER GSR Evolution Ⅲ (CE9A) [DH]","route_style_name":"DH"},{"style_car_id":66816,"style_name":"LANCER GSR Evolution Ⅲ (CE9A) [AR]","route_style_name":"AR"},{"style_car_id":132353,"style_name":"LANCER RS EVOLUTION Ⅳ (CN9A) [HC]","route_style_name":"HC"},{"style_car_id":66817,"style_name":"LANCER RS EVOLUTION Ⅳ (CN9A) [AR]","route_style_name":"AR"},{"style_car_id":132355,"style_name":"LANCER EVOLUTION Ⅶ GSR (CT9A) [HC]","route_style_name":"HC"},{"style_car_id":66821,"style_name":"LANCER RS EVOLUTION Ⅴ (CP9A) [AR]","route_style_name":"AR"},{"style_car_id":66822,"style_name":"LANCER GSR EVOLUTION Ⅵ T.M.EDITION (CP9A) [AR]","route_style_name":"AR"},{"style_car_id":1536,"style_name":"Cappuccino (EA11R) [DH]","route_style_name":"DH"},{"style_car_id":67073,"style_name":"SWIFT Sport (ZC33S) [AR]","route_style_name":"AR"},{"style_car_id":132864,"style_name":"SILEIGHTY [HC]","route_style_name":"HC"},{"style_car_id":67328,"style_name":"SILEIGHTY [AR]","route_style_name":"AR"},{"style_car_id":133376,"style_name":"911Turbo3.6 (964) [HC]","route_style_name":"HC"},{"style_car_id":67840,"style_name":"911Turbo3.6 (964) [AR]","route_style_name":"AR"},{"style_car_id":2305,"style_name":"718Cayman GTS (982) [DH]","route_style_name":"DH"}],"course":[{"course_id":0,"course_name":"Lake Akina/CCW"},{"course_id":2,"course_name":"Lake Akina/CW"},{"course_id":52,"course_name":"Hakone/DH"},{"course_id":54,"course_name":"Hakone/HC"},{"course_id":36,"course_name":"Usui/CCW"},{"course_id":38,"course_name":"Usui/CW"},{"course_id":4,"course_name":"Myogi/DH"},{"course_id":6,"course_name":"Myogi/HC"},{"course_id":8,"course_name":"Akagi/DH"},{"course_id":10,"course_name":"Akagi/HC"},{"course_id":12,"course_name":"Akina/DH"},{"course_id":14,"course_name":"Akina/HC"},{"course_id":16,"course_name":"Irohazaka/DH"},{"course_id":18,"course_name":"Irohazaka/HC"},{"course_id":20,"course_name":"Tsukuba/OB"},{"course_id":22,"course_name":"Tsukuba/IB"},{"course_id":56,"course_name":"Momiji Line/DH"},{"course_id":58,"course_name":"Momiji Line/HC"},{"course_id":24,"course_name":"Happogahara/OB"},{"course_id":26,"course_name":"Happogahara/IB"},{"course_id":40,"course_name":"Sadamine/DH"},{"course_id":42,"course_name":"Sadamine/HC"},{"course_id":44,"course_name":"Tsuchisaka/OB"},{"course_id":46,"course_name":"Tsuchisaka/IB"},{"course_id":48,"course_name":"Akina Snow/DH"},{"course_id":50,"course_name":"Akina Snow/HC"},{"course_id":68,"course_name":"Odawara/F"},{"course_id":70,"course_name":"Odawara/R"}]} \ No newline at end of file +{"car":[{"style_car_id":0,"style_name":"SPRINTER TRUENO GT-APEX (AE86) [DH]","route_style_name":"DH"},{"style_car_id":131072,"style_name":"SPRINTER TRUENO GT-APEX (AE86) [HC]","route_style_name":"HC"},{"style_car_id":9,"style_name":"SPRINTER TRUENO 2door GT-APEX (AE86) [DH]","route_style_name":"DH"},{"style_car_id":131073,"style_name":"COROLLA LEVIN GT-APEX (AE86) [HC]","route_style_name":"HC"},{"style_car_id":65537,"style_name":"COROLLA LEVIN GT-APEX (AE86) [AR]","route_style_name":"AR"},{"style_car_id":131074,"style_name":"COROLLA LEVIN SR (AE85) [HC]","route_style_name":"HC"},{"style_car_id":65538,"style_name":"COROLLA LEVIN SR (AE85) [AR]","route_style_name":"AR"},{"style_car_id":7,"style_name":"86 GT (ZN6) [DH]","route_style_name":"DH"},{"style_car_id":131079,"style_name":"86 GT (ZN6) [HC]","route_style_name":"HC"},{"style_car_id":65543,"style_name":"86 GT (ZN6) [AR]","route_style_name":"AR"},{"style_car_id":3,"style_name":"MR2 G-Limited (SW20) [DH]","route_style_name":"DH"},{"style_car_id":65539,"style_name":"MR2 G-Limited (SW20) [AR]","route_style_name":"AR"},{"style_car_id":5,"style_name":"MR-S S EDITION (ZZW30) [DH]","route_style_name":"DH"},{"style_car_id":131077,"style_name":"MR-S S EDITION (ZZW30) [HC]","route_style_name":"HC"},{"style_car_id":4,"style_name":"ALTEZZA RS200 Z EDITION (SXE10) [DH]","route_style_name":"DH"},{"style_car_id":131082,"style_name":"CELICA GT-FOUR (ST205) [HC]","route_style_name":"HC"},{"style_car_id":65546,"style_name":"CELICA GT-FOUR (ST205) [AR]","route_style_name":"AR"},{"style_car_id":131078,"style_name":"SUPRA RZ (JZA80) [HC]","route_style_name":"HC"},{"style_car_id":65542,"style_name":"SUPRA RZ (JZA80) [AR]","route_style_name":"AR"},{"style_car_id":65547,"style_name":"GR YARIS 1st Edition RZ “High performance” (GXPA16) [AR]","route_style_name":"AR"},{"style_car_id":12,"style_name":"GR SUPRA RZ (DB42) [DH]","route_style_name":"DH"},{"style_car_id":65548,"style_name":"GR SUPRA RZ (DB42) [AR]","route_style_name":"AR"},{"style_car_id":131328,"style_name":"SKYLINE GT-R V・specⅡ (BNR32) [HC]","route_style_name":"HC"},{"style_car_id":65792,"style_name":"SKYLINE GT-R V・specⅡ (BNR32) [AR]","route_style_name":"AR"},{"style_car_id":131329,"style_name":"SKYLINE GT-R V・specⅡ Nür (BNR34) [HC]","route_style_name":"HC"},{"style_car_id":65793,"style_name":"SKYLINE GT-R V・specⅡ Nür (BNR34) [AR]","route_style_name":"AR"},{"style_car_id":258,"style_name":"SILVIA K's (S13) [DH]","route_style_name":"DH"},{"style_car_id":131330,"style_name":"SILVIA K's (S13) [HC]","route_style_name":"HC"},{"style_car_id":65794,"style_name":"SILVIA K's (S13) [AR]","route_style_name":"AR"},{"style_car_id":259,"style_name":"Silvia Q's (S14) [DH]","route_style_name":"DH"},{"style_car_id":131331,"style_name":"Silvia Q's (S14) [HC]","route_style_name":"HC"},{"style_car_id":131332,"style_name":"Silvia spec-R (S15) [HC]","route_style_name":"HC"},{"style_car_id":65796,"style_name":"Silvia spec-R (S15) [AR]","route_style_name":"AR"},{"style_car_id":261,"style_name":"180SX TYPE Ⅱ (RPS13) [DH]","route_style_name":"DH"},{"style_car_id":65797,"style_name":"180SX TYPE Ⅱ (RPS13) [AR]","route_style_name":"AR"},{"style_car_id":131334,"style_name":"FAIRLADY Z Version S (Z33) [HC]","route_style_name":"HC"},{"style_car_id":131335,"style_name":"NISSAN GT-R NISMO (R35) [HC]","route_style_name":"HC"},{"style_car_id":264,"style_name":"SKYLINE 25GT TURBO (ER34) [DH]","route_style_name":"DH"},{"style_car_id":131336,"style_name":"SKYLINE 25GT TURBO (ER34) [HC]","route_style_name":"HC"},{"style_car_id":265,"style_name":"Fairlady Z (S30) [DH]","route_style_name":"DH"},{"style_car_id":131337,"style_name":"Fairlady Z (S30) [HC]","route_style_name":"HC"},{"style_car_id":65802,"style_name":"FAIRLADY Z Version ST (RZ34) [AR]","route_style_name":"AR"},{"style_car_id":512,"style_name":"Civic SiR・Ⅱ (EG6) [DH]","route_style_name":"DH"},{"style_car_id":131584,"style_name":"Civic SiR・Ⅱ (EG6) [HC]","route_style_name":"HC"},{"style_car_id":513,"style_name":"CIVIC TYPE R (EK9) [DH]","route_style_name":"DH"},{"style_car_id":66049,"style_name":"CIVIC TYPE R (EK9) [AR]","route_style_name":"AR"},{"style_car_id":514,"style_name":"INTEGRA TYPE R (DC2) [DH]","route_style_name":"DH"},{"style_car_id":131586,"style_name":"INTEGRA TYPE R (DC2) [HC]","route_style_name":"HC"},{"style_car_id":515,"style_name":"S2000 (AP1) [DH]","route_style_name":"DH"},{"style_car_id":66051,"style_name":"S2000 (AP1) [AR]","route_style_name":"AR"},{"style_car_id":131588,"style_name":"NSX (NA1) [HC]","route_style_name":"HC"},{"style_car_id":768,"style_name":"SAVANNA RX-7 ∞Ⅲ (FC3S) [DH]","route_style_name":"DH"},{"style_car_id":66304,"style_name":"SAVANNA RX-7 ∞Ⅲ (FC3S) [AR]","route_style_name":"AR"},{"style_car_id":131841,"style_name":"ε~fini RX-7 Type R (FD3S) [HC]","route_style_name":"HC"},{"style_car_id":66305,"style_name":"ε~fini RX-7 Type R (FD3S) [AR]","route_style_name":"AR"},{"style_car_id":770,"style_name":"RX-8 Type S (SE3P) [DH]","route_style_name":"DH"},{"style_car_id":771,"style_name":"EUNOS ROADSTER (NA6CE) [DH]","route_style_name":"DH"},{"style_car_id":772,"style_name":"ROADSTER RS (NB8C) [DH]","route_style_name":"DH"},{"style_car_id":131845,"style_name":"RX-7 Type RS (FD3S) [HC]","route_style_name":"HC"},{"style_car_id":66309,"style_name":"RX-7 Type RS (FD3S) [AR]","route_style_name":"AR"},{"style_car_id":132096,"style_name":"IMPREZA WRX type R STi Version Ⅴ (GC8) [HC]","route_style_name":"HC"},{"style_car_id":66560,"style_name":"IMPREZA WRX type R STi Version Ⅴ (GC8) [AR]","route_style_name":"AR"},{"style_car_id":66561,"style_name":"IMPREZA WRX STI (GDBF) [AR]","route_style_name":"AR"},{"style_car_id":66562,"style_name":"IMPREZA WRX STi (GDBA) [AR]","route_style_name":"AR"},{"style_car_id":1027,"style_name":"SUBARU BRZ S (ZC6) [DH]","route_style_name":"DH"},{"style_car_id":66563,"style_name":"SUBARU BRZ S (ZC6) [AR]","route_style_name":"AR"},{"style_car_id":132100,"style_name":"STI S207 NBR CHALLENGE PACKAGE (VAB) [HC]","route_style_name":"HC"},{"style_car_id":66564,"style_name":"STI S207 NBR CHALLENGE PACKAGE (VAB) [AR]","route_style_name":"AR"},{"style_car_id":1280,"style_name":"LANCER GSR Evolution Ⅲ (CE9A) [DH]","route_style_name":"DH"},{"style_car_id":66816,"style_name":"LANCER GSR Evolution Ⅲ (CE9A) [AR]","route_style_name":"AR"},{"style_car_id":132353,"style_name":"LANCER RS EVOLUTION Ⅳ (CN9A) [HC]","route_style_name":"HC"},{"style_car_id":66817,"style_name":"LANCER RS EVOLUTION Ⅳ (CN9A) [AR]","route_style_name":"AR"},{"style_car_id":132354,"style_name":"LANCER Evolution Ⅸ GSR (CT9A) [HC]","route_style_name":"HC"},{"style_car_id":132355,"style_name":"LANCER EVOLUTION Ⅶ GSR (CT9A) [HC]","route_style_name":"HC"},{"style_car_id":66819,"style_name":"LANCER EVOLUTION Ⅶ GSR (CT9A) [AR]","route_style_name":"AR"},{"style_car_id":132356,"style_name":"LANCER EVOLUTION Ⅹ GSR (CZ4A) [HC]","route_style_name":"HC"},{"style_car_id":66821,"style_name":"LANCER RS EVOLUTION Ⅴ (CP9A) [AR]","route_style_name":"AR"},{"style_car_id":132358,"style_name":"LANCER GSR EVOLUTION Ⅵ T.M.EDITION (CP9A) [HC]","route_style_name":"HC"},{"style_car_id":66822,"style_name":"LANCER GSR EVOLUTION Ⅵ T.M.EDITION (CP9A) [AR]","route_style_name":"AR"},{"style_car_id":1536,"style_name":"Cappuccino (EA11R) [DH]","route_style_name":"DH"},{"style_car_id":132608,"style_name":"Cappuccino (EA11R) [HC]","route_style_name":"HC"},{"style_car_id":1537,"style_name":"SWIFT Sport (ZC33S) [DH]","route_style_name":"DH"},{"style_car_id":67073,"style_name":"SWIFT Sport (ZC33S) [AR]","route_style_name":"AR"},{"style_car_id":132864,"style_name":"SILEIGHTY [HC]","route_style_name":"HC"},{"style_car_id":67328,"style_name":"SILEIGHTY [AR]","route_style_name":"AR"},{"style_car_id":133376,"style_name":"911Turbo3.6 (964) [HC]","route_style_name":"HC"},{"style_car_id":67840,"style_name":"911Turbo3.6 (964) [AR]","route_style_name":"AR"},{"style_car_id":2305,"style_name":"718Cayman GTS (982) [DH]","route_style_name":"DH"},{"style_car_id":133377,"style_name":"718Cayman GTS (982) [HC]","route_style_name":"HC"}],"course":[{"course_id":0,"course_name":"Lake Akina/CCW"},{"course_id":2,"course_name":"Lake Akina/CW"},{"course_id":52,"course_name":"Hakone/DH"},{"course_id":54,"course_name":"Hakone/HC"},{"course_id":36,"course_name":"Usui/CCW"},{"course_id":38,"course_name":"Usui/CW"},{"course_id":4,"course_name":"Myogi/DH"},{"course_id":6,"course_name":"Myogi/HC"},{"course_id":8,"course_name":"Akagi/DH"},{"course_id":10,"course_name":"Akagi/HC"},{"course_id":12,"course_name":"Akina/DH"},{"course_id":14,"course_name":"Akina/HC"},{"course_id":16,"course_name":"Irohazaka/DH"},{"course_id":18,"course_name":"Irohazaka/HC"},{"course_id":20,"course_name":"Tsukuba/OB"},{"course_id":22,"course_name":"Tsukuba/IB"},{"course_id":56,"course_name":"Momiji Line/DH"},{"course_id":58,"course_name":"Momiji Line/HC"},{"course_id":24,"course_name":"Happogahara/OB"},{"course_id":26,"course_name":"Happogahara/IB"},{"course_id":28,"course_name":"Nagao/DH"},{"course_id":30,"course_name":"Nagao/HC"},{"course_id":40,"course_name":"Sadamine/DH"},{"course_id":42,"course_name":"Sadamine/HC"},{"course_id":44,"course_name":"Tsuchisaka/OB"},{"course_id":46,"course_name":"Tsuchisaka/IB"},{"course_id":48,"course_name":"Akina Snow/DH"},{"course_id":50,"course_name":"Akina Snow/HC"},{"course_id":68,"course_name":"Odawara/F"},{"course_id":70,"course_name":"Odawara/R"},{"course_id":72,"course_name":"Tsukuba (snow)/OB"},{"course_id":74,"course_name":"Tsukuba (snow)/IB"}]} \ No newline at end of file