From 1d28ad6ce06daa07fdab5c8c6952d60186c3b73a Mon Sep 17 00:00:00 2001 From: terry3041 Date: Sat, 23 Mar 2024 14:16:20 +0800 Subject: [PATCH] lumi support --- .../ead361541998_chunithm_luminous.py | 88 +++++++++++++++++++ docs/game_specific_info.md | 1 + readme.md | 2 + titles/chuni/base.py | 27 ++++++ titles/chuni/const.py | 6 +- titles/chuni/index.py | 14 ++- titles/chuni/luminous.py | 72 +++++++++++++++ titles/chuni/schema/item.py | 88 +++++++++++++++++++ titles/chuni/schema/profile.py | 34 +++++++ 9 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 core/data/alembic/versions/ead361541998_chunithm_luminous.py create mode 100644 titles/chuni/luminous.py diff --git a/core/data/alembic/versions/ead361541998_chunithm_luminous.py b/core/data/alembic/versions/ead361541998_chunithm_luminous.py new file mode 100644 index 0000000..33b949c --- /dev/null +++ b/core/data/alembic/versions/ead361541998_chunithm_luminous.py @@ -0,0 +1,88 @@ +"""CHUNITHM Luminous + +Revision ID: ead361541998 +Revises: d8950c7ce2fc +Create Date: 2024-03-05 08:39:14.630558 + +""" + +from alembic import op +from sqlalchemy import Column, UniqueConstraint +from sqlalchemy.types import Boolean, Integer + +# revision identifiers, used by Alembic. +revision = "ead361541998" +down_revision = "d8950c7ce2fc" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "chuni_profile_net_battle", + Column("id", Integer, primary_key=True, nullable=False), + Column("user", Integer, nullable=False), + Column("isRankUpChallengeFailed", Boolean), + Column("highestBattleRankId", Integer), + Column("battleIconId", Integer), + Column("battleIconNum", Integer), + Column("avatarEffectPoint", Integer), + mysql_charset="utf8mb4", + ) + op.create_foreign_key( + None, + "chuni_profile_net_battle", + "aime_user", + ["user"], + ["id"], + ondelete="cascade", + onupdate="cascade", + ) + + op.create_table( + "chuni_item_cmission", + Column("id", Integer, primary_key=True, nullable=False), + Column("user", Integer, nullable=False), + Column("missionId", Integer, nullable=False), + Column("point", Integer), + UniqueConstraint("user", "missionId", name="chuni_item_cmission_uk"), + mysql_charset="utf8mb4", + ) + op.create_foreign_key( + None, + "chuni_item_cmission", + "aime_user", + ["user"], + ["id"], + ondelete="cascade", + onupdate="cascade", + ) + + op.create_table( + "chuni_item_cmission_progress", + Column("id", Integer, primary_key=True, nullable=False), + Column("user", Integer, nullable=False), + Column("missionId", Integer, nullable=False), + Column("order", Integer), + Column("stage", Integer), + Column("progress", Integer), + UniqueConstraint( + "user", "missionId", "order", name="chuni_item_cmission_progress_uk" + ), + mysql_charset="utf8mb4", + ) + op.create_foreign_key( + None, + "chuni_item_cmission_progress", + "aime_user", + ["user"], + ["id"], + ondelete="cascade", + onupdate="cascade", + ) + + +def downgrade(): + op.drop_table("chuni_profile_net_battle") + op.drop_table("chuni_item_cmission") + op.drop_table("chuni_item_cmission_progress") diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md index 8b04697..d61ef7a 100644 --- a/docs/game_specific_info.md +++ b/docs/game_specific_info.md @@ -55,6 +55,7 @@ Games listed below have been tested and confirmed working. | 12 | CHUNITHM NEW PLUS!! | | 13 | CHUNITHM SUN | | 14 | CHUNITHM SUN PLUS | +| 15 | CHUNITHM LUMINOUS | ### Importer diff --git a/readme.md b/readme.md index 114fef5..2a6883d 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,7 @@ Games listed below have been tested and confirmed working. Only game versions ol + NEW PLUS + SUN + SUN PLUS + + LUMINOUS + CHUNITHM JP + AIR @@ -28,6 +29,7 @@ Games listed below have been tested and confirmed working. Only game versions ol + NEW PLUS + SUN + SUN PLUS + + LUMINOUS + crossbeats REV. + Crossbeats REV. diff --git a/titles/chuni/base.py b/titles/chuni/base.py index c3b9ca2..949b925 100644 --- a/titles/chuni/base.py +++ b/titles/chuni/base.py @@ -936,6 +936,33 @@ class ChuniBase: upsert[rating_type], ) + # added in LUMINOUS + if "userCMissionList" in upsert: + for cmission in upsert["userCMissionList"]: + mission_id = cmission["missionId"] + + await self.data.item.put_cmission( + user_id, + { + "missionId": mission_id, + "point": cmission["point"], + }, + ) + + for progress in cmission["userCMissionProgressList"]: + await self.data.item.put_cmission_progress( + user_id, mission_id, progress + ) + + if "userNetBattleData" in upsert: + net_battle = upsert["userNetBattleData"][0] + + # fix the boolean + net_battle["isRankUpChallengeFailed"] = ( + False if net_battle["isRankUpChallengeFailed"] == "false" else True + ) + await self.data.profile.put_net_battle(user_id, net_battle) + return {"returnCode": "1"} async def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict: diff --git a/titles/chuni/const.py b/titles/chuni/const.py index 3e83378..99c7057 100644 --- a/titles/chuni/const.py +++ b/titles/chuni/const.py @@ -20,6 +20,7 @@ class ChuniConstants: VER_CHUNITHM_NEW_PLUS = 12 VER_CHUNITHM_SUN = 13 VER_CHUNITHM_SUN_PLUS = 14 + VER_CHUNITHM_LUMINOUS = 15 VERSION_NAMES = [ "CHUNITHM", "CHUNITHM PLUS", @@ -35,9 +36,10 @@ class ChuniConstants: "CHUNITHM NEW!!", "CHUNITHM NEW PLUS!!", "CHUNITHM SUN", - "CHUNITHM SUN PLUS" + "CHUNITHM SUN PLUS", + "CHUNITHM LUMINOUS", ] @classmethod def game_ver_to_string(cls, ver: int): - return cls.VERSION_NAMES[ver] \ No newline at end of file + return cls.VERSION_NAMES[ver] diff --git a/titles/chuni/index.py b/titles/chuni/index.py index b102d5e..6997e82 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -34,6 +34,7 @@ from .new import ChuniNew from .newplus import ChuniNewPlus from .sun import ChuniSun from .sunplus import ChuniSunPlus +from .luminous import ChuniLuminous class ChuniServlet(BaseServlet): def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: @@ -61,6 +62,7 @@ class ChuniServlet(BaseServlet): ChuniNewPlus, ChuniSun, ChuniSunPlus, + ChuniLuminous, ] self.logger = logging.getLogger("chuni") @@ -103,7 +105,9 @@ class ChuniServlet(BaseServlet): for method in method_list: method_fixed = inflection.camelize(method)[6:-7] # number of iterations was changed to 70 in SUN and then to 36 - if version == ChuniConstants.VER_CHUNITHM_SUN_PLUS: + if version == ChuniConstants.VER_CHUNITHM_LUMINOUS: + iter_count = 8 + elif version == ChuniConstants.VER_CHUNITHM_SUN_PLUS: iter_count = 36 elif version == ChuniConstants.VER_CHUNITHM_SUN: iter_count = 70 @@ -195,8 +199,10 @@ class ChuniServlet(BaseServlet): internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS elif version >= 210 and version < 215: # SUN internal_ver = ChuniConstants.VER_CHUNITHM_SUN - elif version >= 215: # SUN + elif version >= 215 and version < 220: # SUN PLUS internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS + elif version >= 220: # LUMINOUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS elif game_code == "SDGS": # Int if version < 110: # SUPERSTAR internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE # FIXME: Not sure what was intended to go here? was just "PARADISE" @@ -206,8 +212,10 @@ class ChuniServlet(BaseServlet): internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS elif version >= 120 and version < 125: # SUN internal_ver = ChuniConstants.VER_CHUNITHM_SUN - elif version >= 125: # SUN PLUS + elif version >= 125 and version < 130: # SUN PLUS internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS + elif version >= 130: # LUMINOUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: # If we get a 32 character long hex string, it's a hash and we're diff --git a/titles/chuni/luminous.py b/titles/chuni/luminous.py new file mode 100644 index 0000000..5929bcb --- /dev/null +++ b/titles/chuni/luminous.py @@ -0,0 +1,72 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.config import ChuniConfig +from titles.chuni.const import ChuniConstants +from titles.chuni.sunplus import ChuniSunPlus + + +class ChuniLuminous(ChuniSunPlus): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_LUMINOUS + + async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: + user_data = await super().handle_cm_get_user_preview_api_request(data) + + user_data["lastDataVersion"] = "2.20.00" + return user_data + + async def handle_get_game_map_area_condition_api_request(self, data: Dict) -> Dict: + return {"length": 0, "gameMapAreaConditionList": []} + + async def handle_get_user_c_mission_api_request(self, data: Dict) -> Dict: + user_id = data["userId"] + mission_id = data["missionId"] + + progress_list = [] + point = 0 + + mission_data = await self.data.item.get_cmission(user_id, mission_id) + progress_data = await self.data.item.get_cmission_progress(user_id, mission_id) + + if mission_data and progress_data: + point = mission_data["point"] + + for progress in progress_data: + progress_list.append( + { + "order": progress["order"], + "stage": progress["stage"], + "progress": progress["progress"], + } + ) + + return { + "userId": user_id, + "missionId": mission_id, + "point": point, + "userCMissionProgressList": progress_list, + } + + async def handle_get_user_net_battle_ranking_info_api_request( + self, data: Dict + ) -> Dict: + user_id = data["userId"] + + net_battle = {} + net_battle_data = await self.data.profile.get_net_battle(user_id) + + if net_battle_data: + net_battle = { + "isRankUpChallengeFailed": net_battle_data["isRankUpChallengeFailed"], + "highestBattleRankId": net_battle_data["highestBattleRankId"], + "battleIconId": net_battle_data["battleIconId"], + "battleIconNum": net_battle_data["battleIconNum"], + "avatarEffectPoint": net_battle_data["avatarEffectPoint"], + } + + return { + "userId": user_id, + "userNetBattleData": net_battle, + } diff --git a/titles/chuni/schema/item.py b/titles/chuni/schema/item.py index 5077e14..db91623 100644 --- a/titles/chuni/schema/item.py +++ b/titles/chuni/schema/item.py @@ -243,6 +243,36 @@ matching = Table( mysql_charset="utf8mb4", ) +cmission = Table( + "chuni_item_cmission", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column( + "user", + ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), + nullable=False, + ), + Column("missionId", Integer, nullable=False), + Column("point", Integer), + UniqueConstraint("user", "missionId", name="chuni_item_cmission_uk"), + mysql_charset="utf8mb4", +) + +cmission_progress = Table( + "chuni_item_cmission_progress", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("missionId", Integer, nullable=False), + Column("order", Integer), + Column("stage", Integer), + Column("progress", Integer), + UniqueConstraint( + "user", "missionId", "order", name="chuni_item_cmission_progress_uk" + ), + mysql_charset="utf8mb4", +) + class ChuniItemData(BaseData): async def get_oldest_free_matching(self, version: int) -> Optional[Row]: @@ -594,3 +624,61 @@ class ChuniItemData(BaseData): ) return None return result.lastrowid + + async def put_cmission_progress( + self, user_id: int, mission_id: int, progress_data: Dict + ) -> Optional[int]: + progress_data["user"] = user_id + progress_data["missionId"] = mission_id + + sql = insert(cmission_progress).values(**progress_data) + conflict = sql.on_duplicate_key_update(**progress_data) + + result = await self.execute(conflict) + if result is None: + return None + return result.lastrowid + + async def get_cmission_progress( + self, user_id: int, mission_id: int + ) -> Optional[List[Row]]: + sql = cmission_progress.select( + and_( + cmission_progress.c.user == user_id, + cmission_progress.c.missionId == mission_id, + ) + ).order_by(cmission_progress.c.order.asc()) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchall() + + async def put_cmission(self, user_id: int, mission_data: Dict) -> Optional[int]: + mission_data["user"] = user_id + + sql = insert(cmission).values(**mission_data) + conflict = sql.on_duplicate_key_update(**mission_data) + + result = await self.execute(conflict) + if result is None: + return None + return result.lastrowid + + async def get_cmissions(self, user_id: int) -> Optional[List[Row]]: + sql = cmission.select(cmission.c.user == user_id) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchall() + + async def get_cmission(self, user_id: int, mission_id: int) -> Optional[Row]: + sql = cmission.select( + and_(cmission.c.user == user_id, cmission.c.missionId == mission_id) + ) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchone() diff --git a/titles/chuni/schema/profile.py b/titles/chuni/schema/profile.py index 9864928..13d93e6 100644 --- a/titles/chuni/schema/profile.py +++ b/titles/chuni/schema/profile.py @@ -412,6 +412,19 @@ rating = Table( mysql_charset="utf8mb4", ) +net_battle = Table( + "chuni_profile_net_battle", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("isRankUpChallengeFailed", Boolean), + Column("highestBattleRankId", Integer), + Column("battleIconId", Integer), + Column("battleIconNum", Integer), + Column("avatarEffectPoint", Integer), + mysql_charset="utf8mb4", +) + class ChuniProfileData(BaseData): async def update_name(self, user_id: int, new_name: str) -> bool: @@ -733,6 +746,27 @@ class ChuniProfileData(BaseData): return { "total_play_count": total_play_count } + + async def get_net_battle(self, aime_id: int) -> Optional[Row]: + sql = select(net_battle).where(net_battle.c.user == aime_id) + result = await self.execute(sql) + + if result is None: + return None + return result.fetchone() + + async def put_net_battle( + self, aime_id: int, net_battle_data: Dict + ) -> Optional[int]: + net_battle_data["user"] = aime_id + + sql = insert(net_battle).values(**net_battle_data) + conflict = sql.on_duplicate_key_update(**net_battle_data) + + result = await self.execute(conflict) + if result is None: + return None + return result.lastrowid async def put_profile_rating( self,