import asyncio from datetime import datetime, timedelta, timezone from typing import Dict from core.config import CoreConfig from .config import ChuniConfig from .const import ( ChuniConstants, LinkedVerseUnlockConditionType, MapAreaConditionLogicalOperator, MapAreaConditionType, ) from .luminous import MysticAreaConditions from .verse import ChuniVerse class ChuniXVerse(ChuniVerse): def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: super().__init__(core_cfg, game_cfg) self.version = ChuniConstants.VER_CHUNITHM_X_VERSE async def handle_c_m_get_user_preview_api_request(self, data: Dict) -> Dict: user_data = await super().handle_c_m_get_user_preview_api_request(data) # Does CARD MAKER 1.35 work this far up? user_data["lastDataVersion"] = "2.40.00" return user_data async def handle_get_game_map_area_condition_api_request(self, data: Dict) -> Dict: events = await self.data.static.get_enabled_events(self.version) if events is None: return {"length": 0, "gameMapAreaConditionList": []} events_by_id = {event["eventId"]: event for event in events} mystic_conditions = MysticAreaConditions( events_by_id, 3239201, self.date_time_format ) # Mystic Rainbow of X-VERSE Area 2 unlocks when VERSE ep. ORIGIN is finished. mystic_conditions.add_condition(17021, 3020803, 3239202) # Mystic Rainbow of X-VERSE Area 3 unlocks when VERSE ep. AIR is finished. mystic_conditions.add_condition(17104, 3020804, 3239203) # Mystic Rainbow of X-VERSE Area 4 unlocks when VERSE ep. STAR is finished. mystic_conditions.add_condition(17208, 3020805, 3239204) # Mystic Rainbow of X-VERSE Area 5 unlocks when VERSE ep. AMAZON is finished. mystic_conditions.add_condition(17304, 3020806, 3239205) # Mystic Rainbow of X-VERSE Area 6 unlocks when VERSE ep. CRYSTAL is finished. mystic_conditions.add_condition(17407, 3020807, 3239206) # Mystic Rainbow of X-VERSE Area 7 unlocks when VERSE ep. PARADISE is finished. mystic_conditions.add_condition(17483, 3020808, 3239207) return { "length": len(mystic_conditions.conditions), "gameMapAreaConditionList": mystic_conditions.conditions, } async def handle_get_game_course_level_api_request(self, data: Dict) -> Dict: uc_likes = [] # includes both UCs and LVs, though the former doesn't show up at all in X-VERSE unlock_challenges, linked_verses = await asyncio.gather( self.data.static.get_unlock_challenges(self.version), self.data.static.get_linked_verses(self.version), ) if unlock_challenges: uc_likes.extend(unlock_challenges) if linked_verses: uc_likes.extend(linked_verses) if not uc_likes: return {"length": 0, "gameCourseLevelList": []} course_level_list = [] current_time = datetime.now(timezone.utc).replace(tzinfo=None) for uc_like in uc_likes: course_ids = [ uc_like[f"courseId{i}"] for i in range(1, 6) if uc_like[f"courseId{i}"] is not None ] event_start_date = uc_like["startDate"].replace(hour=0, minute=0, second=0) for i, course_id in enumerate(course_ids): start_date = event_start_date + timedelta(days=7 * i) if i == len(course_ids) - 1: end_date = datetime(2099, 12, 31, 23, 59, 59) else: end_date = ( event_start_date + timedelta(days=7 * (i + 1)) - timedelta(seconds=1) ) if start_date <= current_time <= end_date: course_level_list.append( { "courseId": course_id, "startDate": start_date.strftime(self.date_time_format), "endDate": end_date.strftime(self.date_time_format), } ) return { "length": len(course_level_list), "gameCourseLevelList": course_level_list, } async def handle_get_game_l_v_condition_open_api_request(self, data: Dict) -> Dict: linked_verses = await self.data.static.get_linked_verses(self.version) if not linked_verses: return {"length": 0, "gameLinkedVerseConditionOpenList": []} linked_verse_by_id = {r["linkedVerseId"]: r for r in linked_verses} conditions = [] for lv_id, map_id in [ (10001, 3020803), # ORIGIN (10002, 3020804), # AIR (10003, 3020805), # STAR (10004, 3020806), # AMAZON (10005, 3020807), # CRYSTAL (10006, 3020808), # PARADISE ]: if (lv := linked_verse_by_id.get(lv_id)) is None: continue conditions.append( { "linkedVerseId": lv["linkedVerseId"], "length": 1, "conditionList": [ { "type": MapAreaConditionType.MAP_CLEARED.value, "conditionId": map_id, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, "startDate": lv["startDate"].strftime( self.date_time_format ), "endDate": "2099-12-31 23:59:59", } ], } ) return { "length": len(conditions), "gameLinkedVerseConditionOpenList": conditions, } async def handle_get_game_l_v_condition_unlock_api_request( self, data: Dict ) -> Dict: linked_verses = await self.data.static.get_linked_verses(self.version) if not linked_verses: return { "length": 0, "gameLinkedVerseConditionUnlockList": [], } linked_verse_by_id = {r["linkedVerseId"]: r for r in linked_verses} conditions = [] # For reference on official Linked VERSE conditions: # https://docs.google.com/spreadsheets/d/1j7kmCR0-R5W3uivwkw-6A_eUCXttnJLnkTO0Qf7dya0/edit?usp=sharing # Linked GATE ORIGIN - Play 30 ORIGIN Fables songs if gate_origin := linked_verse_by_id.get(10001): conditions.append( { "linkedVerseId": gate_origin["linkedVerseId"], "length": 1, "conditionList": [ { "type": LinkedVerseUnlockConditionType.PLAY_SONGS.value, "conditionList": "59;79;148;71;75;140;163;80;51;64;65;74;95;67;53;100;108;107;105;82;76;141;63;147;69;151;70;101;152;180", "logicalOpe": MapAreaConditionLogicalOperator.AND.value, "startDate": gate_origin["startDate"].strftime( self.date_time_format ), "endDate": "2099-12-31 00:00:00", } ], } ) # Linked GATE AIR - Obtain class banner if gate_air := linked_verse_by_id.get(10002): conditions.append( { "linkedVerseId": gate_air["linkedVerseId"], "length": 1, "conditionList": [ { "type": LinkedVerseUnlockConditionType.COURSE_CLEAR_AND_CLASS_EMBLEM.value, "conditionList": "1_2_3_4_5_6", "logicalOpe": MapAreaConditionLogicalOperator.AND.value, "startDate": gate_air["startDate"].strftime( self.date_time_format ), "endDate": "2099-12-31 00:00:00", } ], } ) # Linked GATE STAR - Obtain a trophy by leveling a character to level 15 if gate_star := linked_verse_by_id.get(10003): conditions.append( { "linkedVerseId": gate_star["linkedVerseId"], "length": 1, "conditionList": [ { "type": LinkedVerseUnlockConditionType.TROPHY_OBTAINED.value, "conditionList": "9718", "logicalOpe": MapAreaConditionLogicalOperator.AND.value, "startDate": gate_star["startDate"].strftime( self.date_time_format ), "endDate": "2099-12-31 00:00:00", } ], } ) # Linked GATE AMAZON - Play Killing Rhythm and Climax from the favorites folder if gate_amazon := linked_verse_by_id.get(10004): conditions.append( { "linkedVerseId": gate_amazon["linkedVerseId"], "length": 1, "conditionList": [ { "type": LinkedVerseUnlockConditionType.PLAY_SONGS_IN_FAVORITE.value, "conditionList": "712;777", "logicalOpe": MapAreaConditionLogicalOperator.AND.value, "startDate": gate_amazon["startDate"].strftime( self.date_time_format ), "endDate": "2099-12-31 00:00:00", } ], } ) # Linked GATE CRYSTAL - Clear team course while equipping a character of minimum rank 26 if gate_crystal := linked_verse_by_id.get(10005): conditions.append( { "linkedVerseId": gate_crystal["linkedVerseId"], "length": 1, "conditionList": [ { "type": LinkedVerseUnlockConditionType.CLEAR_TEAM_COURSE_WITH_CHARACTER_OF_MINIMUM_RANK.value, "conditionList": "26", "logicalOpe": MapAreaConditionLogicalOperator.AND.value, "startDate": gate_crystal["startDate"].strftime( self.date_time_format ), "endDate": "2099-12-31 00:00:00", } ], } ) # Linked GATE PARADISE - Play one solo song by each of the artists in Inori if gate_paradise := linked_verse_by_id.get(10006): conditions.append( { "linkedVerseId": gate_paradise["linkedVerseId"], "length": 1, "conditionList": [ { "type": LinkedVerseUnlockConditionType.PLAY_SONGS.value, "conditionList": "180_384_2355;407_2353;788_629_600;2704;2050_2354", "logicalOpe": MapAreaConditionLogicalOperator.AND.value, "startDate": gate_paradise["startDate"].strftime( self.date_time_format ), "endDate": "2099-12-31 00:00:00", } ], } ) return { "length": len(conditions), "gameLinkedVerseConditionUnlockList": conditions, } async def handle_get_user_l_v_api_request(self, data: Dict) -> Dict: user_id = int(data["userId"]) rows = await self.data.item.get_linked_verse(user_id) or [] linked_verses = [] for row in rows: data = row._asdict() data.pop("id") data.pop("user") linked_verses.append(data) return { "userId": user_id, "userLinkedVerseList": linked_verses, }