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()
Esempio n. 2
0
    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()
Esempio n. 3
0
    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()
Esempio n. 6
0
    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}"}))
Esempio n. 7
0
    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()
Esempio n. 11
0
    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()
Esempio n. 12
0
    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()
Esempio n. 14
0
    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()
Esempio n. 15
0
    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()
Esempio n. 16
0
    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()
Esempio n. 17
0
    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()
Esempio n. 18
0
    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()
Esempio n. 19
0
    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()
Esempio n. 20
0
    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
Esempio n. 21
0
    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()