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()
    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()
Example #3
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):
        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()
Example #5
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()
    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()
Example #7
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()