def run(self, status: Status): patcher = Patcher(self.rom, self.static_data) status.step("Apply base patches by psy_commando, irdkwia and End45...") sleep(5) # gotta give some spotlight to them. if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') if not patcher.is_applied('ProvideATUPXSupport'): patcher.apply('ProvideATUPXSupport') if not patcher.is_applied('ExtraSpace'): patcher.apply('ExtraSpace') if self.config['improvements']['patch_moveshortcuts']: status.step("Apply 'MoveShortcuts' patch...") if not patcher.is_applied('MoveShortcuts'): patcher.apply('MoveShortcuts') if self.config['improvements']['patch_unuseddungeonchance']: status.step("Apply 'UnusedDungeonChancePatch' patch...") if not patcher.is_applied('UnusedDungeonChancePatch'): patcher.apply('UnusedDungeonChancePatch') if self.config['improvements']['patch_totalteamcontrol']: status.step("Apply 'Complete Team Control' patches...") if not patcher.is_applied('CompleteTeamControl'): patcher.apply('CompleteTeamControl') if not patcher.is_applied('FarOffPalOverdrive'): patcher.apply('FarOffPalOverdrive') if not patcher.is_applied('PartnersTriggerHiddenTraps'): patcher.apply('PartnersTriggerHiddenTraps') if not patcher.is_applied('ReduceJumpcutPauseTime'): patcher.apply('ReduceJumpcutPauseTime') status.done()
def run(self, status: Status): if not self.config['dungeons']['fixed_rooms']: return status.done() mappa: MappaBin = FileType.MAPPA_BIN.deserialize( self.rom.getFileByName('BALANCE/mappa_s.bin')) fixed: FixedBin = FileType.FIXED_BIN.deserialize( self.rom.getFileByName('BALANCE/fixed.bin'), static_data=self.static_data) status.step('Randomizing Boss Floor Layouts...') for i in BOSS_ROOMS: fixed_room = fixed.fixed_floors[i] for floor_list, floor_id in self._get_dungeon_floors_for_fixed_room( mappa.floor_lists, i): self._assign_dungeon_floor_regular_tileset( floor_list, floor_id) w, h, new_layout = self._get_random_room( self._get_special_in_floor(fixed_room)) fixed_room.width = w fixed_room.height = h fixed_room.actions = new_layout self.rom.setFileByName('BALANCE/fixed.bin', FileType.FIXED_BIN.serialize(fixed)) self.rom.setFileByName('BALANCE/mappa_s.bin', FileType.MAPPA_BIN.serialize(mappa)) mappag_after = FileType.MAPPA_G_BIN.serialize( convert_mappa_to_mappag(mappa)) self.rom.setFileByName('BALANCE/mappa_gs.bin', mappag_after) status.done()
def run(self, status: Status): if self.config['pokemon']['movesets'] == MovesetConfig.NO: return status.done() status.step("Randomizing movesets...") md: Md = FileType.MD.deserialize(self.rom.getFileByName('BALANCE/monster.md')) waza_p: WazaP = FileType.WAZA_P.deserialize(self.rom.getFileByName('BALANCE/waza_p.bin')) for md_entry, waza_p_entry in zip(md.entries, waza_p.learnsets): waza_p_entry.egg_moves = [choice(VALID_MOVE_IDS) for _ in waza_p_entry.egg_moves] # Don't randomize, since not all have TM/HSs #waza_p_entry.tm_hm_moves = [choice(VALID_MOVE_IDS) for _ in waza_p_entry.tm_hm_moves] for idx, e in enumerate(waza_p_entry.level_up_moves): if idx > 0 or self.config['pokemon']['movesets'] == MovesetConfig.FULLY_RANDOM: e.move_id = choice(VALID_MOVE_IDS) elif self.config['pokemon']['movesets'] == MovesetConfig.FIRST_DAMAGE: e.move_id = choice(DAMAGING_MOVES) elif self.config['pokemon']['movesets'] == MovesetConfig.FIRST_STAB: if md_entry.type_primary not in STAB_DICT: e.move_id = choice(VALID_MOVE_IDS) else: e.move_id = choice(STAB_DICT[md_entry.type_primary]) self.rom.setFileByName('BALANCE/waza_p.bin', FileType.WAZA_P.serialize(waza_p)) status.done()
def run(self): try: for randomizer in self.randomizers: local_status_steps_left = randomizer.step_count() local_status = Status() def local_status_fn(_, descr): nonlocal local_status_steps_left if descr != Status.DONE_SPECIAL_STR: if local_status_steps_left > 0: local_status_steps_left -= 1 self.status.step(descr) else: for i in range(local_status_steps_left): self.status.step('Randomizing...') local_status.subscribe(local_status_fn) randomizer.run(local_status) self.status.step('Saving scripts...') save_scripts(self.rom, self.static_data) except BaseException as error: logger.error("Exception during randomization.", exc_info=error) self.error = sys.exc_info() with self.lock: self.done = True self.status.done()
def run(self, status: Status): if not self.config['starters_npcs']['starters']: return status.done() status.step("Randomizing Partner Starters...") overlay13 = get_binary_from_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0013.bin']) pokemon_string_data = self.static_data.string_index_data.string_blocks[ "Pokemon Names"] langs = list(get_all_string_files(self.rom, self.static_data)) orig_partner_ids = HardcodedPersonalityTestStarters.get_partner_md_ids( overlay13, self.static_data) new_partner_ids = [ self._random_gender(choice(get_allowed_md_ids(self.config))) for _ in range(0, len(orig_partner_ids)) ] HardcodedPersonalityTestStarters.set_partner_md_ids( new_partner_ids, overlay13, self.static_data) status.step("Randomizing Player Starters...") # The player options are put into two-pairs for each nature, first male then female. orig_player_ids = HardcodedPersonalityTestStarters.get_player_md_ids( overlay13, self.static_data) new_player_ids = [] k = 0 # Index of text for "Will be..." for i in range(0, len(orig_player_ids)): new_id = choice(get_allowed_md_ids(self.config)) if k % 3 == 0: k += 1 # todo: refactor, this isn't really efficient. for lang, string_file in langs: string_file.strings[0x67C + k] = replace_strings( string_file.strings[0x67C + k], { self._get_name(string_file, orig_player_ids[i], pokemon_string_data): self._get_name(string_file, new_id, pokemon_string_data) }) if i % 2 == 1 and new_id + NUM_ENTITIES <= 1154: new_id += NUM_ENTITIES new_player_ids.append(new_id) k += 1 HardcodedPersonalityTestStarters.set_player_md_ids( new_player_ids, overlay13, self.static_data) status.step("Cloning missing starter portraits...") kao = FileType.KAO.deserialize( self.rom.getFileByName('FONT/kaomado.kao')) for new in new_player_ids + new_partner_ids: new_base = new % 600 clone_missing_portraits(kao, new_base - 1) set_binary_in_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0013.bin'], overlay13) for lang, string_file in langs: self.rom.setFileByName(f'MESSAGE/{lang.filename}', FileType.STR.serialize(string_file)) self.rom.setFileByName('FONT/kaomado.kao', FileType.KAO.serialize(kao)) status.done()
async def on_message(self, message): decoded_message = json.loads(message) logger.debug(f'{self.request.remote_ip}: Sent msg.') try: if not self.randomization_is_running: if decoded_message['action'] == 'start': logger.info(f'{self.request.remote_ip}: Sent start request.') self.randomization_is_running = True config = ConfigFileLoader.load_from_dict(decoded_message['config']) rom = RomStorage.roms[self.request.remote_ip] seed = get_effective_seed(config['seed']) random.seed(seed) status = Status() def update_fn(progress, desc): logger.debug(f'{self.request.remote_ip}: {round((progress - 1 / randomizer.total_steps * 100))}: {desc}') if desc == Status.DONE_SPECIAL_STR: self.write_message(json.dumps( {'status': 'progress', 'step': progress - 1, 'totalSteps': randomizer.total_steps, 'message': "Randomizing complete!", 'seed': seed})) else: self.write_message(json.dumps( {'status': 'progress', 'step': progress - 1, 'totalSteps': randomizer.total_steps, 'message': desc, 'seed': seed})) def check_done(): if not randomizer.is_done(): return True logger.debug(f'{self.request.remote_ip}: Check done!') if randomizer.error: traceback_str = ''.join(traceback.format_exception(*randomizer.error)) logger.error(f'{self.request.remote_ip}: ERROR: {traceback_str}') self.write_message(json.dumps( {'status': 'error', 'message': f"Error: {traceback_str}", 'seed': seed})) else: logger.info(f'{self.request.remote_ip}: DONE!') self.write_message(json.dumps( {'status': 'done', 'step': randomizer.total_steps, 'totalSteps': randomizer.total_steps, 'message': "Randomization complete!", 'seed': seed})) return False status.subscribe(lambda a, b: WebserverFrontend.run_on_main_thread(partial(update_fn, a, b))) randomizer = RandomizerThread(status, rom, config, seed, WebserverFrontend()) WebserverFrontend.run_timeout(0.1, check_done) randomizer.start() except BaseException as ex: self.randomization_is_running = False tb = traceback.format_exc() print(tb) await self.write_message(json.dumps({'status': 'error', 'message': f"Fatal error: {ex}\n{tb}"}))
def run(self, status: Status): if not self.config['text']['main']: return status.done() status.step('Randomizing all main text...') for lang, strings in get_all_string_files(self.rom, self.static_data): for string_block in self._collect_categories(self.static_data.string_index_data.string_blocks): part = strings.strings[string_block.begin:string_block.end] shuffle(part) strings.strings[string_block.begin:string_block.end] = part self.rom.setFileByName(f'MESSAGE/{lang.filename}', FileType.STR.serialize(strings)) status.done()
def run(self, status: Status): mappa = FileType.MAPPA_BIN.deserialize( self.rom.getFileByName('BALANCE/mappa_s.bin')) item_lists = None trap_lists = None if self.config['dungeons']['items']: status.step("Randomizing dungeon items...") item_lists = [] for _ in range(0, MAX_ITEM_LISTS): item_lists.append(self._randomize_items()) if self.config['dungeons']['traps']: status.step("Randomizing dungeon traps...") trap_lists = [] for _ in range(0, MAX_TRAP_LISTS): trap_lists.append(self._randomize_traps()) status.step("Randomizing dungeons...") self._randomize(mappa, trap_lists, item_lists) mappa_after = FileType.MAPPA_BIN.serialize(mappa) self.rom.setFileByName('BALANCE/mappa_s.bin', mappa_after) mappag_after = FileType.MAPPA_G_BIN.serialize( convert_mappa_to_mappag(mappa)) self.rom.setFileByName('BALANCE/mappa_gs.bin', mappag_after) status.done()
def run(self, status: Status): if not self.config['starters_npcs']['npcs']: return status.done() status.step("Apply 'ActorAndLevelLoader' patch...") patcher = Patcher(self.rom, self.static_data) if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') status.step("Updating special recruitment table...") actor_list: ActorListBin = FileType.SIR0.unwrap_obj( FileType.SIR0.deserialize( self.rom.getFileByName('BALANCE/actor_list.bin')), ActorListBin) binary = get_binary_from_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0011.bin']) sp_list = HardcodedRecruitmentTables.get_monster_species_list( binary, self.static_data) for i, actor in enumerate(actor_list.list): if i in ACTOR_TO_RECRUIT_MAPPING: for bi in ACTOR_TO_RECRUIT_MAPPING[i]: sp_list[bi] = actor.entid HardcodedRecruitmentTables.set_monster_species_list( sp_list, binary, self.static_data) set_binary_in_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0011.bin'], binary) status.done()
def run(self, status: Status): if not self.config['text']['story']: return status.done() status.step('Randomizing all script text: Reading strings...') all_strings_langs = {} for lang, _ in get_all_string_files(self.rom, self.static_data): all_strings = [] ssb_map: Dict[str, Ssb] = {} all_strings_langs[lang] = all_strings, ssb_map for file_path in get_files_from_rom_with_extension( self.rom, 'ssb'): script = get_script(file_path, self.rom, self.static_data) all_strings += script.strings[lang.name.lower()] ssb_map[file_path] = script status.step('Randomizing all script text: Writing strings...') for lang, (all_strings, ssb_map) in all_strings_langs.items(): shuffle(all_strings) for file_path, script in ssb_map.items(): samples = [] for _ in range(0, len(script.strings[lang.name.lower()])): samples.append(all_strings.pop()) script.strings[lang.name.lower()] = samples status.done()
def run(self, status: Status): if not self._has_something_to_randomize(): return status.done() status.step("Randomizing Pokémon data...") md: Md = FileType.MD.deserialize( self.rom.getFileByName('BALANCE/monster.md')) for midx in range(0, NUM_ENTITIES): if len(md.entries) <= midx + NUM_ENTITIES: continue base_entry = md.entries[midx] secn_entry = md.entries[midx + NUM_ENTITIES] if self.config['pokemon']['iq_groups']: group = choice(VALID_IQ_GROUPS) base_entry.iq_group = group secn_entry.iq_group = group if self.config['pokemon']['typings']: type1 = choice(VALID_FIRST_TYPE) type2 = choice(VALID_SECOND_TYPE) while type1 == type2: type2 = choice(VALID_SECOND_TYPE) base_entry.type_primary = type1 secn_entry.type_primary = type1 base_entry.type_secondary = type2 secn_entry.type_secondary = type2 if self.config['pokemon']['abilities']: ability_ids = self.config['pokemon']['abilities_enabled'] + [ Ability.NONE.value ] if len(ability_ids) > 0: ability1 = Ability(choice(ability_ids)) ability2 = Ability(choice(ability_ids)) while ability2 == ability1: ability2 = Ability(choice(ability_ids)) base_entry.ability_primary = ability1 base_entry.ability_secondary = ability2 secn_entry.ability_primary = ability1 secn_entry.ability_secondary = ability2 self.rom.setFileByName('BALANCE/monster.md', FileType.MD.serialize(md)) status.done()
def run(self, status: Status): if not self.config['chapters']['randomize']: return status.step("Randomizing Chapter Names...") for script_name in SCRIPTS_WITH_CHAPTER_NAMES: ssb: Ssb = get_script(script_name, self.rom, self.static_data) for rtn in ssb.routine_ops: for op in rtn: if op.op_code.name == 'back_SetBanner2': chapter_name = random_txt_line( self.config['chapters']['text']) string_index = op.params[5] - len(ssb.constants) for lang, _ in get_all_string_files( self.rom, self.static_data): ssb.strings[ lang.name.lower()][string_index] = chapter_name status.done()
def run(self, status: Status): if not self.config['starters_npcs']['overworld_music']: return status.step("Randomizing Overworld Music...") for script_name in get_files_from_rom_with_extension(self.rom, 'ssb'): ssb: Ssb = get_script(script_name, self.rom, self.static_data) for rtn in ssb.routine_ops: for op in rtn: op_c = self.static_data.script_data.op_codes__by_name[ op.op_code.name][0] for i, param_spec in enumerate(op_c.arguments): if param_spec.type == 'Bgm': # Only randomize real music (looping tracks) if any((b.id == op.params[i] for b in self.bgs)): op.params[i] = self._get_random_music_id() # We don't really support those, so replace them with 1 sec wait. if op_c.name == 'WaitBgmSignal' or op_c.name == 'WaitBgm' or op_c.name == 'WaitBgm2': op.op_code = self.static_data.script_data.op_codes__by_name[ 'Wait'][0] op.params = [60] status.done()
def run(self, status: Status): if not self.config['locations']['randomize']: return status.step("Randomizing Location Names...") ground_map_names = self.static_data.string_index_data.string_blocks[ "Ground Map Names"] dunge_names_main = self.static_data.string_index_data.string_blocks[ "Dungeon Names (Main)"] dunge_names_sele = self.static_data.string_index_data.string_blocks[ "Dungeon Names (Selection)"] dunge_names_sdba = self.static_data.string_index_data.string_blocks[ "Dungeon Names (SetDungeonBanner)"] dunge_names_bann = self.static_data.string_index_data.string_blocks[ "Dungeon Names (Banner)"] rename_dungeon_map_all = {} for lang, strings in get_all_string_files(self.rom, self.static_data): rename_dungeon_map = {} rename_dungeon_map_all[lang] = rename_dungeon_map for main, sele, sdba, bann in zip( range(dunge_names_main.begin, dunge_names_main.end), range(dunge_names_sele.begin, dunge_names_sele.end), range(dunge_names_sdba.begin, dunge_names_sdba.end), range(dunge_names_bann.begin, dunge_names_bann.end), ): orig_name = strings.strings[main] new_name = self._generate_name() rename_dungeon_map[orig_name] = new_name strings.strings[main] = new_name strings.strings[sele] = new_name strings.strings[sdba] = new_name strings.strings[bann] = new_name for i in range(ground_map_names.begin, ground_map_names.end): orig_name = strings.strings[i] if orig_name in rename_dungeon_map: new_name = rename_dungeon_map[orig_name] else: new_name = self._generate_name() rename_dungeon_map[orig_name] = new_name strings.strings[i] = new_name self.rom.setFileByName(f'MESSAGE/{lang.filename}', FileType.STR.serialize(strings)) status.step("Replacing script text that mentions locations...") replace_text_script(self.rom, self.static_data, rename_dungeon_map_all) status.done()
def run(self, status: Status): if not self.config['starters_npcs']['global_items']: return status.step("Apply patches...") patcher = Patcher(self.rom, self.static_data) if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') if not patcher.is_applied('ExtractHardcodedItemLists'): patcher.apply('ExtractHardcodedItemLists') status.step("Randomizing global item lists...") for i in range(0, ITEM_LIST_COUNT): self.rom.setFileByName( f'TABLEDAT/list_{i:02}.bin', ItemListHandler.serialize(self._randomize_items())) status.done()
def run(self, status: Status): if not self.config['starters_npcs']['npcs']: return status.done() status.step("Apply 'ActorAndLevelLoader' patch...") patcher = Patcher(self.rom, self.static_data) if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') status.step("Updating bosses...") actor_list: ActorListBin = FileType.SIR0.unwrap_obj( FileType.SIR0.deserialize( self.rom.getFileByName('BALANCE/actor_list.bin')), ActorListBin) binary = get_binary_from_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0029.bin']) boss_list = HardcodedFixedFloorTables.get_monster_spawn_list( binary, self.static_data) for i, actor in enumerate(actor_list.list): if i in ACTOR_TO_BOSS_MAPPING: for bi in ACTOR_TO_BOSS_MAPPING[i]: boss_list[bi].md_idx = actor.entid for extra_id in EXTRA_FF_MONSTER_RANDOMIZE: boss_list[extra_id].md_idx = choice( get_allowed_md_ids(self.config, False)) HardcodedFixedFloorTables.set_monster_spawn_list( binary, boss_list, self.static_data) set_binary_in_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0029.bin'], binary) status.done()
def run(self, status: Status): if not self.config['improvements']['download_portraits']: return status.done() status.step("Apply 'ActorAndLevelLoader' patch...") patcher = Patcher(self.rom, self.static_data) if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') overlay13 = get_binary_from_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0013.bin']) actor_list: ActorListBin = FileType.SIR0.unwrap_obj( FileType.SIR0.deserialize( self.rom.getFileByName('BALANCE/actor_list.bin')), ActorListBin) starters = HardcodedPersonalityTestStarters.get_partner_md_ids( overlay13, self.static_data) partners = HardcodedPersonalityTestStarters.get_player_md_ids( overlay13, self.static_data) md = FileType.MD.deserialize( self.rom.getFileByName('BALANCE/monster.md')) with urllib.request.urlopen( "http://sprites.pmdcollab.org/resources/pokemons.json") as url: config = json.loads(url.read().decode()) kao = FileType.KAO.deserialize( self.rom.getFileByName('FONT/kaomado.kao')) if fun.is_fun_allowed(): status.step("Downloading portraits...") fun.replace_portraits(self.rom, self.static_data) return status.done() status.step("Downloading portraits for NPCs...") for actor in actor_list.list: if actor.entid > 0: self._import_portrait(kao, config, actor.entid, md.entries[actor.entid]) status.step("Downloading portraits for starters...") for starter in starters: self._import_portrait(kao, config, starter, md.entries[starter]) status.step("Downloading portraits for partners...") for partner in partners: self._import_portrait(kao, config, partner, md.entries[partner]) self.rom.setFileByName('FONT/kaomado.kao', FileType.KAO.serialize(kao)) def add_rows(): if Global.main_builder: o = Global.main_builder.get_object('store_debug_portraits') o.clear() for row in self._debugs: o.append(row) self.frontend.idle_add(add_rows) status.done()
def run(self, status: Status): status.step("Loading Seed Info...") langs = list(get_all_string_files(self.rom, self.static_data)) str_offset = STR_EU if self.static_data.game_region == GAME_REGION_US: str_offset = STR_US for lang, string_file in langs: string_file.strings[str_offset] = f"""Randomized with SkyTemple Randomizer. Version:[CS:Z]{version()}[CR] Seed: [CS:C]{self.seed}[CR] [CS:H]PLEASE NOTE:[CR] This seed will only produce the same output when used with the exact same version and configuration of the randomizer that was used. You can see the configuration of the last randomization applied by talking to the NPC on Crossroads.""" for lang, string_file in langs: self.rom.setFileByName(f'MESSAGE/{lang.filename}', FileType.STR.serialize(string_file)) status.step("Placing Info NPC...") # Place NPC in scene scene: Ssa = FileType.SSA.deserialize(self.rom.getFileByName(f'SCRIPT/{MAP}/{SCENE}')) layer = scene.layer_list[0] already_exists = any(a.script_id == TALK_SCRIPT for a in layer.actors) if not already_exists: layer.actors.append(SsaActor( scriptdata=self.static_data.script_data, actor_id=ACTOR_TO_USE, pos=SsaPosition( scriptdata=self.static_data.script_data, direction=self.static_data.script_data.directions__by_name['Down'].ssa_id, x_pos=NPC_X, y_pos=NPC_Y, x_offset=0, y_offset=0 ), script_id=TALK_SCRIPT, unkE=-1, )) already_exists = any(a.script_id == TWO_TALK_SCRIPT for a in layer.actors) if not already_exists: layer.actors.append(SsaActor( scriptdata=self.static_data.script_data, actor_id=TWO_ACTOR_TO_USE, pos=SsaPosition( scriptdata=self.static_data.script_data, direction=self.static_data.script_data.directions__by_name['Down'].ssa_id, x_pos=TWO_NPC_X, y_pos=TWO_NPC_Y, x_offset=0, y_offset=0 ), script_id=TWO_TALK_SCRIPT, unkE=-1, )) self.rom.setFileByName(f'SCRIPT/{MAP}/{SCENE}', FileType.SSA.serialize(scene)) # Fill talk script 1 exps = f""" def 0 {{ with (actor ACTOR_TALK_MAIN) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_TALK_SUB) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_ATTENDANT1) {{ SetAnimation(2); }} message_SetFace(ACTOR_NPC_TEST010, FACE_HAPPY, FACE_POS_TOP_L_FACEINW); message_Talk(" This ROM has been randomized\\nwith the SkyTemple Randomizer!"); message_ResetActor(); message_Notice("SkyTemple Randomizer by [CS:A]Parakoopa[CR].\\nVersion:[CS:Z]{escape(version())}[CR]\\nSeed: [CS:C]{escape(str(self.seed))}[CR]"); §l_menu; switch ( message_SwitchMenu(0, 1) ) {{ case menu("Show Settings"): ~settings(); jump @l_menu; case menu("Patch Credits"): ~patches(); jump @l_menu; case menu("Goodbye!"): default: break; }} JumpCommon(CORO_END_TALK); }} macro settings() {{ §l_settings; switch ( message_SwitchMenu(0, 1) ) {{ case menu("Starters & More"): message_Mail("Randomize Starters?: {self._bool(self.config['starters_npcs']['starters'])}\\nRandomize NPCs and Bosses?: {self._bool(self.config['starters_npcs']['npcs'])}\\nRandomize Shops?: {self._bool(self.config['starters_npcs']['global_items'])}\\nRandomize OW Music?: {self._bool(self.config['starters_npcs']['overworld_music'])}"); jump @l_settings; case menu("Dungeons: General"): message_Mail("Mode: {self._dungeon_mode(self.config['dungeons']['mode'])}\\nLayouts and Tilesets?: {self._bool(self.config['dungeons']['layouts'])}\\nRandomize Weather?: {self._weather(self.config['dungeons']['weather'])}\\nRandomize Items?: {self._bool(self.config['dungeons']['items'])}\\nRandomize Pokémon?: {self._bool(self.config['dungeons']['pokemon'])}\\nRandomize Traps?: {self._bool(self.config['dungeons']['traps'])}\\nRandomize Boss Rooms?: {self._bool(self.config['dungeons']['fixed_rooms'])}"); jump @l_settings; case menu("Improvements"): message_Mail("Download portraits?: {self._bool(self.config['improvements']['download_portraits'])}\\nApply 'MoveShortcuts'?: {self._bool(self.config['improvements']['patch_moveshortcuts'])}\\nApply 'UnusedDungeonChance'?: {self._bool(self.config['improvements']['patch_unuseddungeonchance'])}\\nApply 'CTC'?: {self._bool(self.config['improvements']['patch_totalteamcontrol'])}"); jump @l_settings; case menu("Pokémon: General"): message_Mail("Randomize IQ Groups?: {self._bool(self.config['pokemon']['iq_groups'])}\\nRandomize Abilities?: {self._bool(self.config['pokemon']['abilities'])}\\nRandomize Typings?: {self._bool(self.config['pokemon']['typings'])}\\nRandomize Movesets?: {self._movesets(self.config['pokemon']['movesets'])}\\nBan Unowns?: {self._bool(self.config['pokemon']['ban_unowns'])}"); jump @l_settings; case menu("Pokémon: Abilities"): {self._abilities(self.config['pokemon']['abilities_enabled'])} jump @l_settings; case menu("Locations (First)"): message_Mail("Randomize?: {self._bool(self.config['locations']['randomize'])}"); {self._locs_chaps(self.config['locations']['first'])} jump @l_settings; case menu("Locations (Second)"): message_Mail("Randomize?: {self._bool(self.config['locations']['randomize'])}"); {self._locs_chaps(self.config['locations']['second'])} jump @l_settings; case menu("Chapters"): message_Mail("Randomize?: {self._bool(self.config['chapters']['randomize'])}"); {self._locs_chaps(self.config['chapters']['text'])} jump @l_settings; case menu("Text"): message_Mail("Randomize Main Texts?: {self._bool(self.config['text']['main'])}\\nRandomize Story Dialogue: {self._bool(self.config['text']['story'])}"); jump @l_settings; {self._dungeon_cases()} case menu("Goodbye!"): default: break; }} }} macro patches() {{ §l_patches; switch ( message_SwitchMenu(0, 1) ) {{ {self._patch_credits()} case menu("Goodbye!"): default: break; }} }} """ script, _ = ScriptCompiler(self.static_data).compile_explorerscript( exps, 'script.exps', lookup_paths=[] ) script_fn = f'SCRIPT/{MAP}/{TALK_SCRIPT_NAME}' script_sera = FileType.SSB.serialize(script, static_data=self.static_data) try: create_file_in_rom(self.rom, script_fn, script_sera) except FileExistsError: self.rom.setFileByName(script_fn, script_sera) exps = f""" def 0 {{ with (actor ACTOR_TALK_MAIN) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_TALK_SUB) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_ATTENDANT1) {{ SetAnimation(2); }} message_SetFace(ACTOR_NPC_TEST009, FACE_HAPPY, FACE_POS_TOP_L_FACEINW); message_Talk(" This ROM has been randomized\\nwith the SkyTemple Randomizer!"); message_ResetActor(); message_Notice("SkyTemple Randomizer by [CS:A]Parakoopa[CR].\\nVersion:[CS:Z]{escape(version())}[CR]\\nSeed: [CS:C]{escape(str(self.seed))}[CR]"); §l_menu; switch ( message_SwitchMenu(0, 1) ) {{ case menu("Artist Credits"): ~artists(); jump @l_menu; case menu("Goodbye!"): default: break; }} JumpCommon(CORO_END_TALK); }} macro artists() {{ §l_artists; switch ( message_SwitchMenu(0, 1) ) {{ {self._artist_credits()} case menu("Goodbye!"): default: break; }} message_ResetActor(); }} """ script, _ = ScriptCompiler(self.static_data).compile_explorerscript( exps, 'script.exps', lookup_paths=[] ) script_fn = f'SCRIPT/{MAP}/{TWO_TALK_SCRIPT_NAME}' script_sera = FileType.SSB.serialize(script, static_data=self.static_data) try: create_file_in_rom(self.rom, script_fn, script_sera) except FileExistsError: self.rom.setFileByName(script_fn, script_sera) status.done()
def run(self, status: Status): status.step('Unlocking dungeons...') new_ops = [] coro_id = self.static_data.script_data.common_routine_info__by_name['EVENT_DIVIDE'].id ops = self.static_data.script_data.op_codes__by_name # DECOMPILE ssb: Ssb = get_script( 'SCRIPT/COMMON/unionall.ssb', self.rom, static_data=self.static_data ) routine_ops = list(OpsLabelJumpToResolver(ssb.get_filled_routine_ops())) # CREATE NEW OPS off = Counter() off.count = -10000 for dungeon_id, dungeon in self.config['dungeons']['settings'].items(): if dungeon['unlock']: if len(new_ops) < 1: new_ops.append(SkyTempleSsbOperation( off(), ops['debug_Print'][0], [SsbOpParamConstString('SkyTemple Randomizer: Dungeon Unlock...')] )) label_closed = SsbLabel(9000 + dungeon_id, coro_id) label_request = SsbLabel(9200 + dungeon_id, coro_id) label_else = SsbLabel(9400 + dungeon_id, coro_id) new_ops.append(SkyTempleSsbOperation( off(), ops['SwitchDungeonMode'][0], [dungeon_id] )) new_ops.append(SsbLabelJump(SkyTempleSsbOperation( off(), ops['Case'][0], [0] ), label_closed)) new_ops.append(SsbLabelJump(SkyTempleSsbOperation( off(), ops['Case'][0], [2] ), label_request)) new_ops.append(SsbLabelJump(SkyTempleSsbOperation( off(), ops['Jump'][0], [] ), label_else)) new_ops.append(label_closed) new_ops.append(SkyTempleSsbOperation( off(), ops['flag_SetDungeonMode'][0], [dungeon_id, 1] )) new_ops.append(SsbLabelJump(SkyTempleSsbOperation( off(), ops['Jump'][0], [] ), label_else)) new_ops.append(label_request) new_ops.append(SkyTempleSsbOperation( off(), ops['flag_SetDungeonMode'][0], [dungeon_id, 3] )) new_ops.append(label_else) routine_ops[coro_id] = new_ops + routine_ops[coro_id] # COMPILE label_finalizer = LabelFinalizer(strip_last_label(routine_ops)) routine_ops = OpsLabelJumpToRemover(routine_ops, label_finalizer.label_offsets).routines new_ssb, _ = ScriptCompiler(self.static_data).compile_structured( [b for a, b in ssb.routine_info], routine_ops, [x.name for x in self.static_data.script_data.common_routine_info__by_id], SourceMap.create_empty() ) self.rom.setFileByName('SCRIPT/COMMON/unionall.ssb', FileType.SSB.serialize( new_ssb, static_data=self.static_data )) clear_script_cache_for('SCRIPT/COMMON/unionall.ssb') status.done()
def on_randomize_clicked(self, *args): if self.chosen_file is None: self.display_error("Please choose an input file.") return dialog = Gtk.FileChooserNative.new( "Output ROM filename...", self.window, Gtk.FileChooserAction.SAVE, None, None ) filter_nds = Gtk.FileFilter() filter_nds.set_name("Nintendo DS ROMs (*.nds)") filter_nds.add_mime_type("application/x-nintendo-ds-rom") filter_nds.add_pattern("*.nds") dialog.add_filter(filter_nds) response = dialog.run() out_fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: clear_script_cache() if '.' not in out_fn: out_fn += '.nds' try: self.builder.get_object('progress_close').set_sensitive(False) progress_bar: Gtk.ProgressBar = self.builder.get_object('progress_bar') progress_label: Gtk.Label = self.builder.get_object('progress_label') progress_diag: Gtk.Dialog = self.builder.get_object('progress') progress_diag.set_title('Randomizing...') def update_fn(progress, desc): progress_bar.set_fraction((progress - 1) / randomizer.total_steps) if desc == Status.DONE_SPECIAL_STR: progress_label.set_text("Randomizing complete!") else: progress_label.set_text(f"{floor((progress - 1) / randomizer.total_steps * 100)}%: {desc}") def check_done(): if not randomizer.is_done(): return True self.builder.get_object('progress_close').set_sensitive(True) if randomizer.error: img: Gtk.Image = self.builder.get_object('img_portrait_duskako') img.set_from_file(os.path.join(data_dir(), 'duskako_sad.png')) traceback_str = ''.join(traceback.format_exception(*randomizer.error)) progress_label.set_text(f"Error: {traceback_str}") progress_diag.set_title('Randomizing failed!') else: rom.saveToFile(out_fn) img: Gtk.Image = self.builder.get_object('img_portrait_duskako') img.set_from_file(os.path.join(data_dir(), 'duskako_happy.png')) progress_label.set_text("Randomizing complete!") progress_diag.set_title('Randomizing complete!') return False rom = NintendoDSRom.fromFile(self.chosen_file) status = Status() status.subscribe(lambda a, b: GLib.idle_add(partial(update_fn, a, b))) config = self.ui_reader.read() # Set the seed seed = get_effective_seed(config['seed']) random.seed(seed) self.builder.get_object('seed_label').set_text('Your Seed: ' + str(seed)) randomizer = RandomizerThread(status, rom, config, seed) randomizer.start() # SHOW DIALOG img: Gtk.Image = self.builder.get_object('img_portrait_duskako') img.set_from_file(os.path.join(data_dir(), 'duskako_neutral.png')) GLib.timeout_add(100, check_done) progress_diag.run() except BaseException as ex: tb = traceback.format_exc() print(tb) self.display_error(f"Error: {ex}\n{tb}") return
def run(self, status: Status): if not self.config['starters_npcs']['npcs']: return status.done() lang, string_file = get_main_string_file(self.rom, self.static_data) pokemon_string_data = self.static_data.string_index_data.string_blocks[ "Pokemon Names"] status.step("Apply 'ActorAndLevelLoader' patch...") patcher = Patcher(self.rom, self.static_data) if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') status.step("Randomizing NPC actor list...") mapped_actors = self._randomize_actors(string_file, pokemon_string_data) status.step("Replacing main text that mentions NPCs...") names_mapped_all = {} for lang, string_file in get_all_string_files(self.rom, self.static_data): names_mapped = {} names_mapped_all[lang] = names_mapped for old, new in mapped_actors.items(): old_base = old % 600 new_base = new % 600 old_name = self._get_name(string_file, old_base, pokemon_string_data) new_name = self._get_name(string_file, new_base, pokemon_string_data) names_mapped[old_name] = new_name replace_text_main(string_file, names_mapped, pokemon_string_data.begin, pokemon_string_data.end) self.rom.setFileByName(f'MESSAGE/{lang.filename}', FileType.STR.serialize(string_file)) status.step("Replacing script text that mentions NPCs...") replace_text_script(self.rom, self.static_data, names_mapped_all) status.step("Cloning missing NPC portraits...") kao = FileType.KAO.deserialize( self.rom.getFileByName('FONT/kaomado.kao')) for new in mapped_actors.values(): new_base = new % 600 clone_missing_portraits(kao, new_base - 1) self.rom.setFileByName('FONT/kaomado.kao', FileType.KAO.serialize(kao)) status.done()