# vim: set fileencoding=utf-8 import copy from typing import Any, Dict, List, Optional, Tuple from bemani.backend.ess import EventLogHandler from bemani.backend.sdvx.base import SoundVoltexBase from bemani.backend.sdvx.gravitywars import SoundVoltexGravityWars from bemani.common import ID, Profile, VersionConstants from bemani.data import Score, UserID from bemani.protocol import Node class SoundVoltexHeavenlyHaven( EventLogHandler, SoundVoltexBase, ): name = 'SOUND VOLTEX IV HEAVENLY HAVEN' version = VersionConstants.SDVX_HEAVENLY_HAVEN GAME_LIMITED_LOCKED = 1 GAME_LIMITED_UNLOCKABLE = 2 GAME_LIMITED_UNLOCKED = 3 GAME_CURRENCY_PACKETS = 0 GAME_CURRENCY_BLOCKS = 1 GAME_CATALOG_TYPE_SONG = 0 GAME_CATALOG_TYPE_APPEAL_CARD = 1 GAME_CATALOG_TYPE_CREW = 4 GAME_CLEAR_TYPE_NO_PLAY = 0 GAME_CLEAR_TYPE_FAILED = 1 GAME_CLEAR_TYPE_CLEAR = 2 GAME_CLEAR_TYPE_HARD_CLEAR = 3 GAME_CLEAR_TYPE_ULTIMATE_CHAIN = 4 GAME_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN = 5 GAME_GRADE_NO_PLAY = 0 GAME_GRADE_D = 1 GAME_GRADE_C = 2 GAME_GRADE_B = 3 GAME_GRADE_A = 4 GAME_GRADE_A_PLUS = 5 GAME_GRADE_AA = 6 GAME_GRADE_AA_PLUS = 7 GAME_GRADE_AAA = 8 GAME_GRADE_AAA_PLUS = 9 GAME_GRADE_S = 10 GAME_SKILL_NAME_ID_LV_01 = 1 GAME_SKILL_NAME_ID_LV_02 = 2 GAME_SKILL_NAME_ID_LV_03 = 3 GAME_SKILL_NAME_ID_LV_04 = 4 GAME_SKILL_NAME_ID_LV_05 = 5 GAME_SKILL_NAME_ID_LV_06 = 6 GAME_SKILL_NAME_ID_LV_07 = 7 GAME_SKILL_NAME_ID_LV_08 = 8 GAME_SKILL_NAME_ID_LV_09 = 9 GAME_SKILL_NAME_ID_LV_10 = 10 GAME_SKILL_NAME_ID_LV_11 = 11 GAME_SKILL_NAME_ID_LV_INF = 12 GAME_SKILL_NAME_ID_KAC_6TH_BODY = 13 GAME_SKILL_NAME_ID_KAC_6TH_TECHNOLOGY = 14 GAME_SKILL_NAME_ID_KAC_6TH_HEART = 15 GAME_SKILL_NAME_ID_TENKAICHI = 16 GAME_SKILL_NAME_ID_MUSIC_FESTIVAL = 17 GAME_SKILL_NAME_ID_YELLOWTAIL = 18 # For the April Fool's day courses. GAME_SKILL_NAME_ID_BMK2017 = 19 GAME_SKILL_NAME_ID_KAC_7TH_TIGER = 20 GAME_SKILL_NAME_ID_KAC_7TH_WOLF = 21 GAME_SKILL_NAME_ID_RIKKA = 22 # For the course that ran from 1/18/2018-2/18/2018 GAME_SKILL_NAME_ID_KAC_8TH = 23 # Return the local2 service so that SDVX 4 and above will send certain packets. extra_services: List[str] = [ 'local2', ] @classmethod def get_settings(cls) -> Dict[str, Any]: """ Return all of our front-end modifiably settings. """ return { 'bools': [ { 'name': 'Disable Online Matching', 'tip': 'Disable online matching between games.', 'category': 'game_config', 'setting': 'disable_matching', }, { 'name': 'Force Song Unlock', 'tip': 'Force unlock all songs.', 'category': 'game_config', 'setting': 'force_unlock_songs', }, { 'name': 'Force Appeal Card Unlock', 'tip': 'Force unlock all appeal cards.', 'category': 'game_config', 'setting': 'force_unlock_cards', }, { 'name': 'Force Crew Card Unlock', 'tip': 'Force unlock all crew and subcrew cards.', 'category': 'game_config', 'setting': 'force_unlock_crew', }, ], } def previous_version(self) -> Optional[SoundVoltexBase]: return SoundVoltexGravityWars(self.data, self.config, self.model) def __game_to_db_clear_type(self, clear_type: int) -> int: return { self.GAME_CLEAR_TYPE_NO_PLAY: self.CLEAR_TYPE_NO_PLAY, self.GAME_CLEAR_TYPE_FAILED: self.CLEAR_TYPE_FAILED, self.GAME_CLEAR_TYPE_CLEAR: self.CLEAR_TYPE_CLEAR, self.GAME_CLEAR_TYPE_HARD_CLEAR: self.CLEAR_TYPE_HARD_CLEAR, self.GAME_CLEAR_TYPE_ULTIMATE_CHAIN: self.CLEAR_TYPE_ULTIMATE_CHAIN, self.GAME_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: self.CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN, }[clear_type] def __db_to_game_clear_type(self, clear_type: int) -> int: return { self.CLEAR_TYPE_NO_PLAY: self.GAME_CLEAR_TYPE_NO_PLAY, self.CLEAR_TYPE_FAILED: self.GAME_CLEAR_TYPE_FAILED, self.CLEAR_TYPE_CLEAR: self.GAME_CLEAR_TYPE_CLEAR, self.CLEAR_TYPE_HARD_CLEAR: self.GAME_CLEAR_TYPE_HARD_CLEAR, self.CLEAR_TYPE_ULTIMATE_CHAIN: self.GAME_CLEAR_TYPE_ULTIMATE_CHAIN, self.CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: self.GAME_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN, }[clear_type] def __game_to_db_grade(self, grade: int) -> int: return { self.GAME_GRADE_NO_PLAY: self.GRADE_NO_PLAY, self.GAME_GRADE_D: self.GRADE_D, self.GAME_GRADE_C: self.GRADE_C, self.GAME_GRADE_B: self.GRADE_B, self.GAME_GRADE_A: self.GRADE_A, self.GAME_GRADE_A_PLUS: self.GRADE_A_PLUS, self.GAME_GRADE_AA: self.GRADE_AA, self.GAME_GRADE_AA_PLUS: self.GRADE_AA_PLUS, self.GAME_GRADE_AAA: self.GRADE_AAA, self.GAME_GRADE_AAA_PLUS: self.GRADE_AAA_PLUS, self.GAME_GRADE_S: self.GRADE_S, }[grade] def __db_to_game_grade(self, grade: int) -> int: return { self.GRADE_NO_PLAY: self.GAME_GRADE_NO_PLAY, self.GRADE_D: self.GAME_GRADE_D, self.GRADE_C: self.GAME_GRADE_C, self.GRADE_B: self.GAME_GRADE_B, self.GRADE_A: self.GAME_GRADE_A, self.GRADE_A_PLUS: self.GAME_GRADE_A_PLUS, self.GRADE_AA: self.GAME_GRADE_AA, self.GRADE_AA_PLUS: self.GAME_GRADE_AA_PLUS, self.GRADE_AAA: self.GAME_GRADE_AAA, self.GRADE_AAA_PLUS: self.GAME_GRADE_AAA_PLUS, self.GRADE_S: self.GAME_GRADE_S, }[grade] def handle_game_sv4_exception_request(self, request: Node) -> Node: return Node.void('game') def handle_game_sv4_lounge_request(self, request: Node) -> Node: game = Node.void('game') # Refresh interval in seconds. game.add_child(Node.u32('interval', 10)) return game def handle_game_sv4_entry_s_request(self, request: Node) -> Node: game = Node.void('game') # This should be created on the fly for a lobby that we're in. game.add_child(Node.u32('entry_id', 1)) return game def handle_game_sv4_entry_e_request(self, request: Node) -> Node: # Lobby destroy method, eid node (u32) should be used # to destroy any open lobbies. return Node.void('game') def __get_skill_analyzer_seasons(self) -> Dict[int, str]: return { 0: "第1回 Aコース", 1: "第1回 Bコース", 2: "第1回 Cコース", 3: "第2回 Aコース", 4: "第2回 Bコース", 5: "第3回", 6: "第4回 Aコース", 7: "第4回 Bコース", 8: "第4回 Cコース", 9: "第5回", 10: "第6回 Aコース", 11: "第6回 Bコース", 12: "第6回 Cコース", 13: "The 6th KAC挑戦コース【体】", 14: "The 6th KAC挑戦コース【技】", 15: "The 6th KAC挑戦コース【心】", 16: "天下一 (梅)", 17: "天下一 (竹)", 18: "天下一 (松)", 19: "BEMANI MASTER KOREA 2017", 20: "The 7th KACチャレンジコース【猛虎】", 21: "The 7th KACチャレンジコース【餓狼】", 22: "The 8th KACチャレンジコース【阿修羅】", 23: "The 8th KACエンジョイコース【阿修羅】", 24: "第5回 Bコース", } def __get_skill_analyzer_skill_levels(self) -> Dict[int, str]: return { 1: "SKILL ANALYZER Level.01", 2: "SKILL ANALYZER Level.02", 3: "SKILL ANALYZER Level.03", 4: "SKILL ANALYZER Level.04", 5: "SKILL ANALYZER Level.05", 6: "SKILL ANALYZER Level.06", 7: "SKILL ANALYZER Level.07", 8: "SKILL ANALYZER Level.08", 9: "SKILL ANALYZER Level.09", 10: "SKILL ANALYZER Level.10", 11: "SKILL ANALYZER Level.11", 12: "SKILL ANALYZER Level.∞", } def __get_skill_analyzer_skill_name_ids(self) -> Dict[int, int]: return { 1: self.GAME_SKILL_NAME_ID_LV_01, 2: self.GAME_SKILL_NAME_ID_LV_02, 3: self.GAME_SKILL_NAME_ID_LV_03, 4: self.GAME_SKILL_NAME_ID_LV_04, 5: self.GAME_SKILL_NAME_ID_LV_05, 6: self.GAME_SKILL_NAME_ID_LV_06, 7: self.GAME_SKILL_NAME_ID_LV_07, 8: self.GAME_SKILL_NAME_ID_LV_08, 9: self.GAME_SKILL_NAME_ID_LV_09, 10: self.GAME_SKILL_NAME_ID_LV_10, 11: self.GAME_SKILL_NAME_ID_LV_11, 12: self.GAME_SKILL_NAME_ID_LV_INF, } def __get_skill_analyzer_courses(self) -> List[Dict[str, Any]]: return [ # Skill LV.01 { 'season_id': 0, 'skill_level': 1, 'tracks': [ { 'id': 653, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 846, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 23, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 1, 'skill_level': 1, 'tracks': [ { 'id': 60, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 770, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 16, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 2, 'skill_level': 1, 'tracks': [ { 'id': 17, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 922, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 76, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 3, 'skill_level': 1, 'tracks': [ { 'id': 201, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 182, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 766, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 4, 'skill_level': 1, 'tracks': [ { 'id': 106, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 568, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 768, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 5, 'skill_level': 1, 'tracks': [ { 'id': 795, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 110, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 51, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 6, 'skill_level': 1, 'tracks': [ { 'id': 258, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 913, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 189, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 7, 'skill_level': 1, 'tracks': [ { 'id': 1025, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 914, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 186, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 8, 'skill_level': 1, 'tracks': [ { 'id': 600, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 915, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 671, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 9, 'skill_level': 1, 'tracks': [ { 'id': 1035, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 1014, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1033, 'type': self.CHART_TYPE_NOVICE, }, ], }, { 'season_id': 10, 'skill_level': 1, 'tracks': [ { 'id': 1044, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 1176, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 1083, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 11, 'skill_level': 1, 'tracks': [ { 'id': 1049, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 367, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 1005, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 12, 'skill_level': 1, 'tracks': [ { 'id': 1190, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 636, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 1054, 'type': self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.02 { 'season_id': 0, 'skill_level': 2, 'tracks': [ { 'id': 6, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 222, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 48, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 1, 'skill_level': 2, 'tracks': [ { 'id': 566, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 748, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 19, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 2, 'skill_level': 2, 'tracks': [ { 'id': 22, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 40, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 275, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 3, 'skill_level': 2, 'tracks': [ { 'id': 171, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 950, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 513, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 4, 'skill_level': 2, 'tracks': [ { 'id': 185, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 700, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 923, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 5, 'skill_level': 2, 'tracks': [ { 'id': 219, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 528, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 996, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 6, 'skill_level': 2, 'tracks': [ { 'id': 87, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 486, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 66, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 7, 'skill_level': 2, 'tracks': [ { 'id': 93, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 664, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 3, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 8, 'skill_level': 2, 'tracks': [ { 'id': 191, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 771, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 8, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 9, 'skill_level': 2, 'tracks': [ { 'id': 405, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 451, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 173, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 10, 'skill_level': 2, 'tracks': [ { 'id': 1074, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1095, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 930, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 11, 'skill_level': 2, 'tracks': [ { 'id': 1057, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1081, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 868, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 12, 'skill_level': 2, 'tracks': [ { 'id': 1076, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1002, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 916, 'type': self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.03 { 'season_id': 0, 'skill_level': 3, 'tracks': [ { 'id': 775, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 684, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 778, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 1, 'skill_level': 3, 'tracks': [ { 'id': 523, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 921, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 218, 'type': self.CHART_TYPE_ADVANCED, }, ], }, # Skill course for Season ID 2 was removed due to removed songs. { 'season_id': 3, 'skill_level': 3, 'tracks': [ { 'id': 90, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 557, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 843, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 4, 'skill_level': 3, 'tracks': [ { 'id': 317, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 882, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 531, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 5, 'skill_level': 3, 'tracks': [ { 'id': 161, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 291, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 970, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 6, 'skill_level': 3, 'tracks': [ { 'id': 674, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 216, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 434, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 7, 'skill_level': 3, 'tracks': [ { 'id': 590, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 898, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 152, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 8, 'skill_level': 3, 'tracks': [ { 'id': 353, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 896, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 39, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 9, 'skill_level': 3, 'tracks': [ { 'id': 1008, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 608, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 815, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 10, 'skill_level': 3, 'tracks': [ { 'id': 1086, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1122, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1026, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 11, 'skill_level': 3, 'tracks': [ { 'id': 1001, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1092, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1113, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 12, 'skill_level': 3, 'tracks': [ { 'id': 1004, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1111, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1090, 'type': self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.04 { 'season_id': 0, 'skill_level': 4, 'tracks': [ { 'id': 757, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 480, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 758, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 1, 'skill_level': 4, 'tracks': [ { 'id': 467, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 456, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 107, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 2, 'skill_level': 4, 'tracks': [ { 'id': 67, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 544, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 9, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 3, 'skill_level': 4, 'tracks': [ { 'id': 449, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 506, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 962, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 4, 'skill_level': 4, 'tracks': [ { 'id': 136, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 534, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 640, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 5, 'skill_level': 4, 'tracks': [ { 'id': 630, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 647, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 785, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 6, 'skill_level': 4, 'tracks': [ { 'id': 781, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 623, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 540, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 7, 'skill_level': 4, 'tracks': [ { 'id': 104, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 521, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 342, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 8, 'skill_level': 4, 'tracks': [ { 'id': 485, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 359, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 834, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 9, 'skill_level': 4, 'tracks': [ { 'id': 966, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 983, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 967, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 10, 'skill_level': 4, 'tracks': [ { 'id': 1070, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1073, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1022, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 11, 'skill_level': 4, 'tracks': [ { 'id': 1075, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1123, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1029, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 12, 'skill_level': 4, 'tracks': [ { 'id': 1094, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1128, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1027, 'type': self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.05 { 'season_id': 0, 'skill_level': 5, 'tracks': [ { 'id': 871, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 327, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 66, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 1, 'skill_level': 5, 'tracks': [ { 'id': 435, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 750, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 700, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 2, 'skill_level': 5, 'tracks': [ { 'id': 318, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 157, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 567, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 3, 'skill_level': 5, 'tracks': [ { 'id': 760, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1020, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 923, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 4, 'skill_level': 5, 'tracks': [ { 'id': 65, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 966, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 874, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 5, 'skill_level': 5, 'tracks': [ { 'id': 645, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 335, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 961, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 6, 'skill_level': 5, 'tracks': [ { 'id': 695, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 276, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 870, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 7, 'skill_level': 5, 'tracks': [ { 'id': 743, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 958, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 441, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 8, 'skill_level': 5, 'tracks': [ { 'id': 790, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 277, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 944, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 9, 'skill_level': 5, 'tracks': [ { 'id': 964, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 58, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1025, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 10, 'skill_level': 5, 'tracks': [ { 'id': 1040, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1200, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 895, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 11, 'skill_level': 5, 'tracks': [ { 'id': 1024, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1201, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1124, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 12, 'skill_level': 5, 'tracks': [ { 'id': 1007, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1220, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1067, 'type': self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.06 { 'season_id': 0, 'skill_level': 6, 'tracks': [ { 'id': 713, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 40, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 33, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 1, 'skill_level': 6, 'tracks': [ { 'id': 230, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 827, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 146, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 2, 'skill_level': 6, 'tracks': [ { 'id': 239, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 375, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 94, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 3, 'skill_level': 6, 'tracks': [ { 'id': 80, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 678, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 928, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 4, 'skill_level': 6, 'tracks': [ { 'id': 856, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 488, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 968, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 5, 'skill_level': 6, 'tracks': [ { 'id': 172, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 262, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 781, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 6, 'skill_level': 6, 'tracks': [ { 'id': 998, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 885, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 400, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 7, 'skill_level': 6, 'tracks': [ { 'id': 301, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 879, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 62, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 8, 'skill_level': 6, 'tracks': [ { 'id': 897, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 2, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 986, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 9, 'skill_level': 6, 'tracks': [ { 'id': 898, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 962, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1032, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 10, 'skill_level': 6, 'tracks': [ { 'id': 1115, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1184, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1230, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 11, 'skill_level': 6, 'tracks': [ { 'id': 1154, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1114, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 891, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 12, 'skill_level': 6, 'tracks': [ { 'id': 1139, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 864, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1010, 'type': self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.07 { 'season_id': 0, 'skill_level': 7, 'tracks': [ { 'id': 349, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 896, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 246, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 1, 'skill_level': 7, 'tracks': [ { 'id': 210, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 558, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 368, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 2, 'skill_level': 7, 'tracks': [ { 'id': 769, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 710, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 609, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 3, 'skill_level': 7, 'tracks': [ { 'id': 967, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 711, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 594, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 4, 'skill_level': 7, 'tracks': [ { 'id': 738, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 264, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 834, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 5, 'skill_level': 7, 'tracks': [ { 'id': 762, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 544, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 898, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 6, 'skill_level': 7, 'tracks': [ { 'id': 211, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 14, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 183, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 7, 'skill_level': 7, 'tracks': [ { 'id': 666, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 54, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 763, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 8, 'skill_level': 7, 'tracks': [ { 'id': 145, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 99, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 90, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 9, 'skill_level': 7, 'tracks': [ { 'id': 490, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 889, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1042, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 10, 'skill_level': 7, 'tracks': [ { 'id': 1156, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1138, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1091, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 11, 'skill_level': 7, 'tracks': [ { 'id': 1012, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1248, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 926, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 12, 'skill_level': 7, 'tracks': [ { 'id': 1134, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 919, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1250, 'type': self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.08 { 'season_id': 0, 'skill_level': 8, 'tracks': [ { 'id': 690, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 380, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 492, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 1, 'skill_level': 8, 'tracks': [ { 'id': 603, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 278, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 557, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 2, 'skill_level': 8, 'tracks': [ { 'id': 357, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 562, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 612, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 3, 'skill_level': 8, 'tracks': [ { 'id': 26, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 22, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 503, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 4, 'skill_level': 8, 'tracks': [ { 'id': 945, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 639, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 644, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 5, 'skill_level': 8, 'tracks': [ { 'id': 521, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 572, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 173, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 6, 'skill_level': 8, 'tracks': [ { 'id': 659, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 749, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 251, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 7, 'skill_level': 8, 'tracks': [ { 'id': 361, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 744, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 831, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 8, 'skill_level': 8, 'tracks': [ { 'id': 372, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 747, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 872, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 9, 'skill_level': 8, 'tracks': [ { 'id': 971, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 752, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1062, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 10, 'skill_level': 8, 'tracks': [ { 'id': 336, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1199, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1197, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 11, 'skill_level': 8, 'tracks': [ { 'id': 955, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1037, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 812, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 12, 'skill_level': 8, 'tracks': [ { 'id': 596, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 902, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 844, 'type': self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.09 # Skill course for Season ID 0 was removed due to removed songs. { 'season_id': 1, 'skill_level': 9, 'tracks': [ { 'id': 295, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 742, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 302, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 2, 'skill_level': 9, 'tracks': [ { 'id': 322, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 759, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 607, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 3, 'skill_level': 9, 'tracks': [ { 'id': 599, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 122, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 946, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 4, 'skill_level': 9, 'tracks': [ { 'id': 394, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 228, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 124, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 5, 'skill_level': 9, 'tracks': [ { 'id': 456, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 852, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 252, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 6, 'skill_level': 9, 'tracks': [ { 'id': 918, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 63, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 47, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 7, 'skill_level': 9, 'tracks': [ { 'id': 917, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 959, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 912, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 8, 'skill_level': 9, 'tracks': [ { 'id': 576, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 943, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 359, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 9, 'skill_level': 9, 'tracks': [ { 'id': 497, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 948, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 954, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 10, 'skill_level': 9, 'tracks': [ { 'id': 841, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1087, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1112, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 11, 'skill_level': 9, 'tracks': [ { 'id': 761, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 765, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1006, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 12, 'skill_level': 9, 'tracks': [ { 'id': 737, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 887, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 933, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, # Skill LV.10 { 'season_id': 0, 'skill_level': 10, 'tracks': [ { 'id': 833, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 858, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 229, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 1, 'skill_level': 10, 'tracks': [ { 'id': 333, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 871, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 259, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 2, 'skill_level': 10, 'tracks': [ { 'id': 779, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 817, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 362, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 3, 'skill_level': 10, 'tracks': [ { 'id': 961, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 967, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 993, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 4, 'skill_level': 10, 'tracks': [ { 'id': 625, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 214, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 365, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 5, 'skill_level': 10, 'tracks': [ { 'id': 966, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 876, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 506, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 6, 'skill_level': 10, 'tracks': [ { 'id': 641, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 463, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 712, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 7, 'skill_level': 10, 'tracks': [ { 'id': 390, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 655, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 707, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 8, 'skill_level': 10, 'tracks': [ { 'id': 922, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 166, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 670, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 9, 'skill_level': 10, 'tracks': [ { 'id': 1126, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1034, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 834, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 10, 'skill_level': 10, 'tracks': [ { 'id': 1217, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1041, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1078, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 11, 'skill_level': 10, 'tracks': [ { 'id': 1237, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1157, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 907, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 12, 'skill_level': 10, 'tracks': [ { 'id': 1228, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 881, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1135, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, # Skill LV.11 { 'season_id': 3, 'skill_level': 11, 'tracks': [ { 'id': 941, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 718, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 816, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 4, 'skill_level': 11, 'tracks': [ { 'id': 30, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 2, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 540, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 5, 'skill_level': 11, 'tracks': [ { 'id': 931, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 818, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 810, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 6, 'skill_level': 11, 'tracks': [ { 'id': 789, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 634, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 532, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 7, 'skill_level': 11, 'tracks': [ { 'id': 808, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 965, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 909, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 9, 'skill_level': 11, 'tracks': [ { 'id': 1013, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1035, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1107, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 10, 'skill_level': 11, 'tracks': [ { 'id': 173, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 151, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 362, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 11, 'skill_level': 11, 'tracks': [ { 'id': 1060, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1062, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1222, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, # Skill LV.Inf { 'season_id': 3, 'skill_level': 12, 'tracks': [ { 'id': 654, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 360, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 1028, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 5, 'skill_level': 12, 'tracks': [ { 'id': 709, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 374, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 1036, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 6, 'skill_level': 12, 'tracks': [ { 'id': 551, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1032, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1099, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 7, 'skill_level': 12, 'tracks': [ { 'id': 927, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 525, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 1100, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 9, 'skill_level': 12, 'tracks': [ { 'id': 1102, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1148, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1185, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 24, 'skill_level': 12, 'tracks': [ { 'id': 661, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 258, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 791, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 10, 'skill_level': 12, 'tracks': [ { 'id': 679, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1178, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 1270, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, # 6th KAC { 'season_id': 13, 'course_id': 1, 'course_name': 'The 6th KAC挑戦コース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_BODY, 'course_type': 2, 'tracks': [ { 'id': 806, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 971, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 913, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 14, 'course_id': 1, 'course_name': 'The 6th KAC挑戦コース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_TECHNOLOGY, 'course_type': 2, 'tracks': [ { 'id': 758, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 965, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 914, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 15, 'course_id': 1, 'course_name': 'The 6th KAC挑戦コース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_HEART, 'course_type': 2, 'tracks': [ { 'id': 814, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 964, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 915, 'type': self.CHART_TYPE_INFINITE, }, ], }, { 'season_id': 13, 'course_id': 2, 'course_name': 'The 6th KAC挑戦コース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_BODY, 'course_type': 2, 'tracks': [ { 'id': 806, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 971, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 913, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 14, 'course_id': 2, 'course_name': 'The 6th KAC挑戦コース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_TECHNOLOGY, 'course_type': 2, 'tracks': [ { 'id': 758, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 965, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 914, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 15, 'course_id': 2, 'course_name': 'The 6th KAC挑戦コース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_HEART, 'course_type': 2, 'tracks': [ { 'id': 814, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 964, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 915, 'type': self.CHART_TYPE_ADVANCED, }, ], }, # Tenkaichi courses. { 'season_id': 16, 'course_id': 1, 'course_name': '天下一', 'skill_name_id': self.GAME_SKILL_NAME_ID_TENKAICHI, 'course_type': 3, 'tracks': [ { 'id': 625, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 697, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 708, 'type': self.CHART_TYPE_NOVICE, }, ], }, { 'season_id': 17, 'course_id': 1, 'course_name': '天下一', 'skill_name_id': self.GAME_SKILL_NAME_ID_TENKAICHI, 'course_type': 3, 'tracks': [ { 'id': 625, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 697, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 708, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 18, 'course_id': 1, 'course_name': '天下一', 'skill_name_id': self.GAME_SKILL_NAME_ID_TENKAICHI, 'course_type': 3, 'tracks': [ { 'id': 625, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 697, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 708, 'type': self.CHART_TYPE_EXHAUST, }, ], }, { 'season_id': 16, 'course_id': 2, 'course_name': '天下一', 'skill_name_id': self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, 'course_type': 3, 'tracks': [ { 'id': 362, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 360, 'type': self.CHART_TYPE_NOVICE, }, { 'id': 927, 'type': self.CHART_TYPE_NOVICE, }, ], }, { 'season_id': 17, 'course_id': 2, 'course_name': '天下一', 'skill_name_id': self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, 'course_type': 3, 'tracks': [ { 'id': 362, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 360, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 927, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 18, 'course_id': 2, 'course_name': '天下一', 'skill_name_id': self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, 'course_type': 3, 'tracks': [ { 'id': 362, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 360, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 927, 'type': self.CHART_TYPE_EXHAUST, }, ], }, # BMK2017 courses { 'season_id': 19, 'course_id': 1, 'course_name': 'BEMANI MASTER KOREA', 'skill_name_id': self.GAME_SKILL_NAME_ID_BMK2017, 'tracks': [ { 'id': 954, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 960, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 961, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, # 7th KAC { 'season_id': 20, 'course_id': 1, 'course_name': 'The 7th KACチャレンジコース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_TIGER, 'course_type': 4, 'tracks': [ { 'id': 1149, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 367, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1102, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 21, 'course_id': 1, 'course_name': 'The 7th KACチャレンジコース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_WOLF, 'course_type': 4, 'tracks': [ { 'id': 1042, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 126, 'type': self.CHART_TYPE_INFINITE, }, { 'id': 1101, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 20, 'course_id': 2, 'course_name': 'The 7th KACチャレンジコース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_TIGER, 'course_type': 4, 'tracks': [ { 'id': 1149, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 367, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1102, 'type': self.CHART_TYPE_ADVANCED, }, ], }, { 'season_id': 21, 'course_id': 2, 'course_name': 'The 7th KACチャレンジコース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_WOLF, 'course_type': 4, 'tracks': [ { 'id': 1042, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 126, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1101, 'type': self.CHART_TYPE_ADVANCED, }, ], }, # 8th KAC { 'season_id': 22, 'course_id': 1, 'course_name': 'The 8th KACチャレンジコース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_8TH, 'course_type': 5, 'tracks': [ { 'id': 1334, 'type': self.CHART_TYPE_MAXIMUM, }, { 'id': 610, 'type': self.CHART_TYPE_EXHAUST, }, { 'id': 1033, 'type': self.CHART_TYPE_MAXIMUM, }, ], }, { 'season_id': 23, 'course_id': 1, 'course_name': 'The 8th KACエンジョイコース', 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_8TH, 'course_type': 5, 'tracks': [ { 'id': 1334, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 610, 'type': self.CHART_TYPE_ADVANCED, }, { 'id': 1033, 'type': self.CHART_TYPE_ADVANCED, }, ], }, ] def handle_game_sv4_common_request(self, request: Node) -> Node: game = Node.void('game') limited = Node.void('music_limited') game.add_child(limited) # Song unlock config game_config = self.get_game_config() if game_config.get_bool('force_unlock_songs'): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): ids.add((song.id, song.chart)) for (songid, chart) in ids: info = Node.void('info') limited.add_child(info) info.add_child(Node.s32('music_id', songid)) info.add_child(Node.u8('music_type', chart)) info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) # Catalog, maybe this is for the online store? catalog = Node.void('catalog') game.add_child(catalog) for _item in []: # type: ignore info = Node.void('info') catalog.add_child(info) info.add_child(Node.u8('catalog_type', 0)) info.add_child(Node.u32('catalog_id', 0)) info.add_child(Node.u32('discount_rate', 0)) # Event config event = Node.void('event') game.add_child(event) def enable_event(eid: str) -> None: evt = Node.void('info') event.add_child(evt) evt.add_child(Node.string('event_id', eid)) if not game_config.get_bool('disable_matching'): # Matching enabled events enable_event("MATCHING_MODE") enable_event("MATCHING_MODE_FREE_IP") enable_event("ICON_FLOOR_INFECTION") enable_event("ICON_POLICY_BREAK") enable_event("ACHIEVEMENT_ENABLE") enable_event("VOLFORCE_ENABLE") enable_event("CONTINUATION") enable_event("TENKAICHI_MODE") enable_event("SERIALCODE_JAPAN") enable_event("DEMOGAME_PLAY") enable_event("TOTAL_MEMORIAL_ENABLE") enable_event("EVENT_IDS_SERIALCODE_TOHO_02") enable_event("KONAMI_50TH_LOGO") enable_event("KAC6TH_FINISH") enable_event("KAC7TH_FINISH") enable_event("KAC8TH_FINISH") # An old collaboration event we don't support. reitaisai = Node.void('reitaisai2018') game.add_child(reitaisai) # Volte factory, an older event we don't support. volte_factory = Node.void('volte_factory') game.add_child(volte_factory) goods = Node.void('goods') volte_factory.add_child(goods) stock = Node.void('stock') volte_factory.add_child(stock) # I think this is a list of purchaseable appeal cards. appealcard = Node.void('appealcard') game.add_child(appealcard) # Event parameters (we don't support story mode). extend = Node.void('extend') game.add_child(extend) # Available skill courses skill_course = Node.void('skill_course') game.add_child(skill_course) achievements = self.data.local.user.get_all_achievements(self.game, self.version) courserates: Dict[Tuple[int, int], Dict[str, int]] = {} def getrates(season_id: int, course_id: int) -> Dict[str, int]: if (course_id, season_id) in courserates: return courserates[(course_id, season_id)] else: return { 'attempts': 0, 'clears': 0, 'total_score': 0, } for _, achievement in achievements: if achievement.type != 'course': continue course_id = achievement.id % 100 season_id = int(achievement.id / 100) rate = getrates(season_id, course_id) rate['attempts'] += 1 if achievement.data.get_int('clear_type') >= 2: rate['clears'] += 1 rate['total_score'] = achievement.data.get_int('score') courserates[(course_id, season_id)] = rate seasons = self.__get_skill_analyzer_seasons() skill_levels = self.__get_skill_analyzer_skill_levels() courses = self.__get_skill_analyzer_courses() skill_name_ids = self.__get_skill_analyzer_skill_name_ids() for course in courses: info = Node.void('info') skill_course.add_child(info) info.add_child(Node.s32('season_id', course['season_id'])) info.add_child(Node.string('season_name', seasons[course['season_id']])) info.add_child(Node.bool('season_new_flg', course['season_id'] in {10, 11, 12, 22, 23})) info.add_child(Node.s16('course_id', course.get('course_id', course.get('skill_level', -1)))) info.add_child(Node.string('course_name', course.get('course_name', skill_levels.get(course.get('skill_level', -1), '')))) # Course type 0 is skill level courses. The course type is the same as the skill level (01-12). # If skill level is specified as '0', then the course type shows up as 'OTHER' instead of Skill Lv.01-12. # Course type 2 = KAC 6th. # Course type 3 = TENKAICHI mode. # Course type 4 = KAC 7th. # Course type 5 = KAC 8th. info.add_child(Node.s16('course_type', course.get('course_type', 0))) info.add_child(Node.s16('skill_level', course.get('skill_level', 0))) info.add_child(Node.s16('skill_name_id', course.get('skill_name_id', skill_name_ids.get(course.get('skill_level', -1), 0)))) info.add_child(Node.bool('matching_assist', course.get('skill_level', -1) >= 1 and course.get('skill_level', -1) <= 7)) # Calculate clear rate and average score rate = getrates(course['season_id'], course.get('course_id', course.get('skill_level', -1))) if rate['attempts'] > 0: info.add_child(Node.s32('clear_rate', int(100.0 * (rate['clears'] / rate['attempts'])))) info.add_child(Node.u32('avg_score', rate['total_score'] // rate['attempts'])) else: info.add_child(Node.s32('clear_rate', 0)) info.add_child(Node.u32('avg_score', 0)) for trackno, trackdata in enumerate(course['tracks']): track = Node.void('track') info.add_child(track) track.add_child(Node.s16('track_no', trackno)) track.add_child(Node.s32('music_id', trackdata['id'])) track.add_child(Node.s8('music_type', trackdata['type'])) # Museca link event that we don't support. museca_link = Node.void('museca_link') game.add_child(museca_link) return game def handle_game_sv4_shop_request(self, request: Node) -> Node: self.update_machine_name(request.child_value('shopname')) # Respond with number of milliseconds until next request game = Node.void('game') game.add_child(Node.u32('nxt_time', 1000 * 5 * 60)) return game def handle_game_sv4_hiscore_request(self, request: Node) -> Node: # Grab location for local scores locid = ID.parse_machine_id(request.child_value('locid')) game = Node.void('game') # Now, grab global and local scores as well as clear rates global_records = self.data.remote.music.get_all_records(self.game, self.version) users = { uid: prof for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) } area_users = [ uid for uid in users if users[uid].get_int('loc', -1) == locid ] area_records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users) clears = self.get_clear_rates() records: Dict[int, Dict[int, Dict[str, Tuple[UserID, Score]]]] = {} missing_users = ( [userid for (userid, _) in global_records if userid not in users] + [userid for (userid, _) in area_records if userid not in users] ) for (userid, profile) in self.get_any_profiles(missing_users): users[userid] = profile for (userid, score) in global_records: if userid not in users: raise Exception('Logic error, missing profile for user!') if score.id not in records: records[score.id] = {} if score.chart not in records[score.id]: records[score.id][score.chart] = {} records[score.id][score.chart]['global'] = (userid, score) for (userid, score) in area_records: if userid not in users: raise Exception('Logic error, missing profile for user!') if score.id not in records: records[score.id] = {} if score.chart not in records[score.id]: records[score.id][score.chart] = {} records[score.id][score.chart]['area'] = (userid, score) # Output it to the game highscores = Node.void('sc') game.add_child(highscores) for musicid in records: for chart in records[musicid]: (globaluserid, globalscore) = records[musicid][chart]['global'] global_profile = users[globaluserid] if clears[musicid][chart]['total'] > 0: clear_rate = float(clears[musicid][chart]['clears']) / float(clears[musicid][chart]['total']) else: clear_rate = 0.0 info = Node.void('d') highscores.add_child(info) info.add_child(Node.u32('id', musicid)) info.add_child(Node.u32('ty', chart)) info.add_child(Node.string('a_sq', ID.format_extid(global_profile.extid))) info.add_child(Node.string('a_nm', global_profile.get_str('name'))) info.add_child(Node.u32('a_sc', globalscore.points)) info.add_child(Node.s32('cr', int(clear_rate * 10000))) info.add_child(Node.s32('avg_sc', clears[musicid][chart]['average'])) if 'area' in records[musicid][chart]: (localuserid, localscore) = records[musicid][chart]['area'] local_profile = users[localuserid] info.add_child(Node.string('l_sq', ID.format_extid(local_profile.extid))) info.add_child(Node.string('l_nm', local_profile.get_str('name'))) info.add_child(Node.u32('l_sc', localscore.points)) return game def handle_game_sv4_load_request(self, request: Node) -> Node: refid = request.child_value('refid') root = self.get_profile_by_refid(refid) if root is not None: return root # Figure out if this user has an older profile or not userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: previous_game = self.previous_version() else: previous_game = None if previous_game is not None: profile = previous_game.get_profile(userid) else: profile = None if profile is not None: root = Node.void('game') root.add_child(Node.u8('result', 2)) root.add_child(Node.string('name', profile.get_str('name'))) return root else: root = Node.void('game') root.add_child(Node.u8('result', 1)) return root def handle_game_sv4_frozen_request(self, request: Node) -> Node: game = Node.void('game') game.add_child(Node.u8('result', 0)) return game def handle_game_sv4_new_request(self, request: Node) -> Node: refid = request.child_value('refid') name = request.child_value('name') loc = ID.parse_machine_id(request.child_value('locid')) self.new_profile_by_refid(refid, name, loc) root = Node.void('game') return root def handle_game_sv4_load_m_request(self, request: Node) -> Node: refid = request.child_value('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: scores = self.data.remote.music.get_scores(self.game, self.version, userid) else: scores = [] # Output to the game game = Node.void('game') music = Node.void('music') game.add_child(music) for score in scores: info = Node.void('info') music.add_child(info) stats = score.data.get_dict('stats') info.add_child( Node.u32_array( 'param', [ score.id, score.chart, score.points, self.__db_to_game_clear_type(score.data.get_int('clear_type')), self.__db_to_game_grade(score.data.get_int('grade')), 0, # 5: Any value 0, # 6: Any value stats.get_int('btn_rate'), stats.get_int('long_rate'), stats.get_int('vol_rate'), 0, # 10: Any value 0, # 11: Another medal, perhaps old score medal? 0, # 12: Another grade, perhaps old score grade? 0, # 13: Any value 0, # 14: Any value 0, # 15: Any value 0, # 16: Another medal, perhaps old score medal? 0, # 17: Another grade, perhaps old score grade? 0, # 18: Any value 0, # 19: Any value ], ), ) return game def handle_game_sv4_load_r_request(self, request: Node) -> Node: refid = request.child_value('refid') game = Node.void('game') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: links = self.data.local.user.get_links(self.game, self.version, userid) index = 0 for link in links: if link.type != 'rival': continue other_profile = self.get_profile(link.other_userid) if other_profile is None: continue # Base information about rival rival = Node.void('rival') game.add_child(rival) rival.add_child(Node.s16('no', index)) rival.add_child(Node.string('seq', ID.format_extid(other_profile.extid))) rival.add_child(Node.string('name', other_profile.get_str('name'))) # Keep track of index index = index + 1 # Return scores for this user on random charts scores = self.data.remote.music.get_scores(self.game, self.version, link.other_userid) for score in scores: music = Node.void('music') rival.add_child(music) music.add_child( Node.u32_array( 'param', [ score.id, score.chart, score.points, self.__db_to_game_clear_type(score.data.get_int('clear_type')), self.__db_to_game_grade(score.data.get_int('grade')), ] ) ) return game def handle_game_sv4_save_request(self, request: Node) -> Node: refid = request.child_value('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: oldprofile = self.get_profile(userid) newprofile = self.unformat_profile(userid, request, oldprofile) else: newprofile = None if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) return Node.void('game') def handle_game_sv4_save_m_request(self, request: Node) -> Node: refid = request.child_value('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None # Doesn't matter if userid is None here, that's an anonymous score musicid = request.child_value('music_id') chart = request.child_value('music_type') points = request.child_value('score') combo = request.child_value('max_chain') clear_type = self.__game_to_db_clear_type(request.child_value('clear_type')) grade = self.__game_to_db_grade(request.child_value('score_grade')) stats = { 'btn_rate': request.child_value('btn_rate'), 'long_rate': request.child_value('long_rate'), 'vol_rate': request.child_value('vol_rate'), 'critical': request.child_value('critical'), 'near': request.child_value('near'), 'error': request.child_value('error'), } # Save the score self.update_score( userid, musicid, chart, points, clear_type, grade, combo, stats, ) # Return a blank response return Node.void('game') def handle_game_sv4_play_e_request(self, request: Node) -> Node: return Node.void('game') def handle_game_sv4_save_e_request(self, request: Node) -> Node: # This has to do with Policy floor infection, but we don't # implement multi-game support so meh. game = Node.void('game') pbc_infection = Node.void('pbc_infection') game.add_child(pbc_infection) for name in ['packet', 'block', 'coloris']: child = Node.void(name) pbc_infection.add_child(child) child.add_child(Node.s32('before', 0)) child.add_child(Node.s32('after', 0)) pb_infection = Node.void('pb_infection') game.add_child(pb_infection) for name in ['packet', 'block']: child = Node.void(name) pb_infection.add_child(child) child.add_child(Node.s32('before', 0)) child.add_child(Node.s32('after', 0)) return game def handle_game_sv4_play_s_request(self, request: Node) -> Node: root = Node.void('game') root.add_child(Node.u32('play_id', 1)) return root def handle_game_sv4_buy_request(self, request: Node) -> Node: refid = request.child_value('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: profile = self.get_profile(userid) else: profile = None if userid is not None and profile is not None: # Look up packets and blocks packet = profile.get_int('packet') block = profile.get_int('block') # Add on any additional we earned this round packet = packet + (request.child_value('earned_gamecoin_packet') or 0) block = block + (request.child_value('earned_gamecoin_block') or 0) currency_type = request.child_value('currency_type') price = request.child_value('item/price') if isinstance(price, list): # Sometimes we end up buying more than one item at once price = sum(price) if currency_type == self.GAME_CURRENCY_PACKETS: # This is a valid purchase newpacket = packet - price if newpacket < 0: result = 1 else: packet = newpacket result = 0 elif currency_type == self.GAME_CURRENCY_BLOCKS: # This is a valid purchase newblock = block - price if newblock < 0: result = 1 else: block = newblock result = 0 else: # Bad currency type result = 1 if result == 0: # Transaction is valid, update the profile with new packets and blocks profile.replace_int('packet', packet) profile.replace_int('block', block) self.put_profile(userid, profile) # If this was a song unlock, we should mark it as unlocked item_type = request.child_value('item/item_type') item_id = request.child_value('item/item_id') param = request.child_value('item/param') if not isinstance(item_type, list): # Sometimes we buy multiple things at once. Make it easier by always assuming this. item_type = [item_type] item_id = [item_id] param = [param] for i in range(len(item_type)): self.data.local.user.put_achievement( self.game, self.version, userid, item_id[i], f'item_{item_type[i]}', { 'param': param[i], }, ) else: # Unclear what to do here, return a bad response packet = 0 block = 0 result = 1 game = Node.void('game') game.add_child(Node.u32('gamecoin_packet', packet)) game.add_child(Node.u32('gamecoin_block', block)) game.add_child(Node.s8('result', result)) return game def handle_game_sv4_save_c_request(self, request: Node) -> Node: refid = request.child_value('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: season_id = request.child_value('ssnid') course_id = request.child_value('crsid') clear_type = request.child_value('ct') achievement_rate = request.child_value('ar') grade = request.child_value('gr') score = request.child_value('sc') self.data.local.user.put_achievement( self.game, self.version, userid, (season_id * 100) + course_id, 'course', { 'clear_type': clear_type, 'achievement_rate': achievement_rate, 'score': score, 'grade': grade, }, ) # Return a blank response return Node.void('game') def format_profile(self, userid: UserID, profile: Profile) -> Node: game = Node.void('game') # Generic profile stuff game.add_child(Node.string('name', profile.get_str('name'))) game.add_child(Node.string('code', ID.format_extid(profile.extid))) game.add_child(Node.string('sdvx_id', ID.format_extid(profile.extid))) game.add_child(Node.u16('appeal_id', profile.get_int('appealid'))) game.add_child(Node.s16('skill_base_id', profile.get_int('skill_base_id'))) game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id'))) game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) # Play statistics statistics = self.get_play_statistics(userid) game.add_child(Node.u32('play_count', statistics.total_plays)) game.add_child(Node.u32('today_count', statistics.today_plays)) game.add_child(Node.u32('play_chain', statistics.consecutive_days)) # Also exists but we don't support: # - day_count: Number of days where this user had at least one play. # - max_play_chain: Max consecutive days in a row where the user had at last one play. # - week_count: Number of weeks here this user had at least one play. # - week_play_count: Number of plays in the last week (I think). # - week_chain: Number of weeks in a row where the user had at least one play in that week. # - max_week_chain: Maximum number of weeks in a row where the user had at least one play in that week. # Player options and last touched song. lastdict = profile.get_dict('last') game.add_child(Node.s32('last_music_id', lastdict.get_int('music_id', -1))) game.add_child(Node.u8('last_music_type', lastdict.get_int('music_type'))) game.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) game.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) game.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) game.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) game.add_child(Node.u8('ars_option', lastdict.get_int('ars_option'))) game.add_child(Node.u8('notes_option', lastdict.get_int('notes_option'))) game.add_child(Node.u8('early_late_disp', lastdict.get_int('early_late_disp'))) game.add_child(Node.u8('eff_c_left', lastdict.get_int('eff_c_left'))) game.add_child(Node.u8('eff_c_right', lastdict.get_int('eff_c_right', 1))) game.add_child(Node.u32('lanespeed', lastdict.get_int('lanespeed'))) game.add_child(Node.s32('hispeed', lastdict.get_int('hispeed'))) game.add_child(Node.s32('draw_adjust', lastdict.get_int('draw_adjust'))) # Item unlocks itemnode = Node.void('item') game.add_child(itemnode) game_config = self.get_game_config() achievements = self.data.local.user.get_achievements(self.game, self.version, userid) for item in achievements: if item.type[:5] != 'item_': continue itemtype = int(item.type[5:]) if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: # Don't echo unlocked songs, we will add all of them later continue if game_config.get_bool('force_unlock_cards') and itemtype == self.GAME_CATALOG_TYPE_APPEAL_CARD: # Don't echo unlocked appeal cards, we will add all of them later continue if game_config.get_bool('force_unlock_crew') and itemtype == self.GAME_CATALOG_TYPE_CREW: # Don't echo unlocked crew, we will add all of them later continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', itemtype)) info.add_child(Node.u32('id', item.id)) info.add_child(Node.u32('param', item.data.get_int('param'))) if game_config.get_bool('force_unlock_songs'): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 if song.data.get_int('difficulty') > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) info.add_child(Node.u32('id', itemid)) info.add_child(Node.u32('param', ids[itemid])) if game_config.get_bool('force_unlock_cards'): catalog = self.data.local.game.get_items(self.game, self.version) for unlock in catalog: if unlock.type != 'appealcard': continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_APPEAL_CARD)) info.add_child(Node.u32('id', unlock.id)) info.add_child(Node.u32('param', 1)) if game_config.get_bool('force_unlock_crew'): for crewid in range(1, 999): info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_CREW)) info.add_child(Node.u32('id', crewid)) info.add_child(Node.u32('param', 1)) # Skill courses skill = Node.void('skill') game.add_child(skill) skill_level = 0 for course in achievements: if course.type != 'course': continue course_id = course.id % 100 season_id = int(course.id / 100) if course.data.get_int('clear_type') >= 2: # The user cleared this, lets take the highest level clear for this courselist = [ c for c in self.__get_skill_analyzer_courses() if c.get('course_id', c.get('skill_level', -1)) == course_id and c['season_id'] == season_id ] if len(courselist) > 0: skill_level = max(skill_level, courselist[0]['skill_level']) course_node = Node.void('course') skill.add_child(course_node) course_node.add_child(Node.s16('ssnid', season_id)) course_node.add_child(Node.s16('crsid', course_id)) course_node.add_child(Node.s32('sc', course.data.get_int('score'))) course_node.add_child(Node.s16('ct', course.data.get_int('clear_type'))) course_node.add_child(Node.s16('gr', course.data.get_int('grade'))) course_node.add_child(Node.s16('ar', course.data.get_int('achievement_rate'))) course_node.add_child(Node.s16('cnt', 1)) # Calculated skill level game.add_child(Node.s16('skill_level', skill_level)) # Game parameters paramnode = Node.void('param') game.add_child(paramnode) for param in achievements: if param.type[:6] != 'param_': continue paramtype = int(param.type[6:]) info = Node.void('info') paramnode.add_child(info) info.add_child(Node.s32('id', param.id)) info.add_child(Node.s32('type', paramtype)) info.add_child(Node.s32_array('param', param.data['param'])) # This looks to be variable, so no validation on length # Infection nodes, we don't support these but it here for posterity. pbc_infection = Node.void('pbc_infection') game.add_child(pbc_infection) for name in ['packet', 'block', 'coloris']: child = Node.void(name) pbc_infection.add_child(child) child.add_child(Node.s32('before', 0)) child.add_child(Node.s32('after', 0)) pb_infection = Node.void('pb_infection') game.add_child(pb_infection) for name in ['packet', 'block']: child = Node.void(name) pb_infection.add_child(child) child.add_child(Node.s32('before', 0)) child.add_child(Node.s32('after', 0)) return game def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: newprofile = copy.deepcopy(oldprofile) # Update blaster energy and in-game currencies earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') if earned_gamecoin_packet is not None: newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) earned_gamecoin_block = request.child_value('earned_gamecoin_block') if earned_gamecoin_block is not None: newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) earned_blaster_energy = request.child_value('earned_blaster_energy') if earned_blaster_energy is not None: newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) # Miscelaneous profile stuff newprofile.replace_int('blaster_count', request.child_value('blaster_count')) newprofile.replace_int('appealid', request.child_value('appeal_id')) newprofile.replace_int('skill_level', request.child_value('skill_level')) newprofile.replace_int('skill_base_id', request.child_value('skill_base_id')) newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() if request.child('item') is not None: for child in request.child('item').children: if child.name != 'info': continue item_id = child.child_value('id') item_type = child.child_value('type') param = child.child_value('param') if game_config.get_bool('force_unlock_cards') and item_type == self.GAME_CATALOG_TYPE_APPEAL_CARD: # Don't save back appeal cards because they were force unlocked continue if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG: # Don't save back songs, because they were force unlocked continue if game_config.get_bool('force_unlock_crew') and item_type == self.GAME_CATALOG_TYPE_CREW: # Don't save back crew, because they were force unlocked continue self.data.local.user.put_achievement( self.game, self.version, userid, item_id, f'item_{item_type}', { 'param': param, }, ) # Update params if request.child('param') is not None: for child in request.child('param').children: if child.name != 'info': continue param_id = child.child_value('id') param_type = child.child_value('type') param_param = child.child_value('param') self.data.local.user.put_achievement( self.game, self.version, userid, param_id, f'param_{param_type}', { 'param': param_param, }, ) # Grab last information and player options. lastdict = newprofile.get_dict('last') lastdict.replace_int('music_id', request.child_value('music_id')) lastdict.replace_int('music_type', request.child_value('music_type')) lastdict.replace_int('sort_type', request.child_value('sort_type')) lastdict.replace_int('narrow_down', request.child_value('narrow_down')) lastdict.replace_int('headphone', request.child_value('headphone')) lastdict.replace_int('gauge_option', request.child_value('gauge_option')) lastdict.replace_int('ars_option', request.child_value('ars_option')) lastdict.replace_int('notes_option', request.child_value('notes_option')) lastdict.replace_int('early_late_disp', request.child_value('early_late_disp')) lastdict.replace_int('eff_c_left', request.child_value('eff_c_left')) lastdict.replace_int('eff_c_right', request.child_value('eff_c_right')) lastdict.replace_int('lanespeed', request.child_value('lanespeed')) lastdict.replace_int('hispeed', request.child_value('hispeed')) lastdict.replace_int('draw_adjust', request.child_value('draw_adjust')) # Save back last information gleaned from results newprofile.replace_dict('last', lastdict) # Keep track of play statistics self.update_play_statistics(userid) return newprofile