2023-11-12 21:39:34 +00:00
|
|
|
"""
|
|
|
|
Classes and functions related to creating a ROM patch
|
|
|
|
"""
|
2024-03-14 11:37:10 +00:00
|
|
|
import copy
|
2023-11-12 21:39:34 +00:00
|
|
|
import os
|
|
|
|
import pkgutil
|
2024-03-14 11:37:10 +00:00
|
|
|
from typing import TYPE_CHECKING, Dict, List, Tuple
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
import bsdiff4
|
|
|
|
|
|
|
|
from worlds.Files import APDeltaPatch
|
|
|
|
from settings import get_settings
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
from .data import TrainerPokemonDataTypeEnum, BASE_OFFSET, data
|
2023-11-12 21:39:34 +00:00
|
|
|
from .items import reverse_offset_item_value
|
2024-03-14 11:37:10 +00:00
|
|
|
from .options import (RandomizeWildPokemon, RandomizeTrainerParties, EliteFourRequirement, NormanRequirement,
|
|
|
|
MatchTrainerLevels)
|
|
|
|
from .pokemon import HM_MOVES, get_random_move
|
|
|
|
from .util import bool_array_to_int, encode_string, get_easter_egg
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from . import PokemonEmeraldWorld
|
|
|
|
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
_LOOPING_MUSIC = [
|
|
|
|
"MUS_GSC_ROUTE38", "MUS_GSC_PEWTER", "MUS_ROUTE101", "MUS_ROUTE110", "MUS_ROUTE120", "MUS_ROUTE122",
|
|
|
|
"MUS_PETALBURG", "MUS_OLDALE", "MUS_GYM", "MUS_SURF", "MUS_PETALBURG_WOODS", "MUS_LILYCOVE_MUSEUM",
|
|
|
|
"MUS_OCEANIC_MUSEUM", "MUS_ENCOUNTER_GIRL", "MUS_ENCOUNTER_MALE", "MUS_ABANDONED_SHIP", "MUS_FORTREE",
|
|
|
|
"MUS_BIRCH_LAB", "MUS_B_TOWER_RS", "MUS_ENCOUNTER_SWIMMER", "MUS_CAVE_OF_ORIGIN", "MUS_ENCOUNTER_RICH",
|
|
|
|
"MUS_VERDANTURF", "MUS_RUSTBORO", "MUS_POKE_CENTER", "MUS_CAUGHT", "MUS_VICTORY_GYM_LEADER", "MUS_VICTORY_LEAGUE",
|
|
|
|
"MUS_VICTORY_WILD", "MUS_C_VS_LEGEND_BEAST", "MUS_ROUTE104", "MUS_ROUTE119", "MUS_CYCLING", "MUS_POKE_MART",
|
|
|
|
"MUS_LITTLEROOT", "MUS_MT_CHIMNEY", "MUS_ENCOUNTER_FEMALE", "MUS_LILYCOVE", "MUS_DESERT", "MUS_HELP",
|
|
|
|
"MUS_UNDERWATER", "MUS_VICTORY_TRAINER", "MUS_ENCOUNTER_MAY", "MUS_ENCOUNTER_INTENSE", "MUS_ENCOUNTER_COOL",
|
|
|
|
"MUS_ROUTE113", "MUS_ENCOUNTER_AQUA", "MUS_FOLLOW_ME", "MUS_ENCOUNTER_BRENDAN", "MUS_EVER_GRANDE",
|
|
|
|
"MUS_ENCOUNTER_SUSPICIOUS", "MUS_VICTORY_AQUA_MAGMA", "MUS_GAME_CORNER", "MUS_DEWFORD", "MUS_SAFARI_ZONE",
|
|
|
|
"MUS_VICTORY_ROAD", "MUS_AQUA_MAGMA_HIDEOUT", "MUS_SAILING", "MUS_MT_PYRE", "MUS_SLATEPORT", "MUS_MT_PYRE_EXTERIOR",
|
|
|
|
"MUS_SCHOOL", "MUS_HALL_OF_FAME", "MUS_FALLARBOR", "MUS_SEALED_CHAMBER", "MUS_CONTEST_WINNER", "MUS_CONTEST",
|
|
|
|
"MUS_ENCOUNTER_MAGMA", "MUS_ABNORMAL_WEATHER", "MUS_WEATHER_GROUDON", "MUS_SOOTOPOLIS", "MUS_HALL_OF_FAME_ROOM",
|
|
|
|
"MUS_TRICK_HOUSE", "MUS_ENCOUNTER_TWINS", "MUS_ENCOUNTER_ELITE_FOUR", "MUS_ENCOUNTER_HIKER", "MUS_CONTEST_LOBBY",
|
|
|
|
"MUS_ENCOUNTER_INTERVIEWER", "MUS_ENCOUNTER_CHAMPION", "MUS_B_FRONTIER", "MUS_B_ARENA", "MUS_B_PYRAMID",
|
|
|
|
"MUS_B_PYRAMID_TOP", "MUS_B_PALACE", "MUS_B_TOWER", "MUS_B_DOME", "MUS_B_PIKE", "MUS_B_FACTORY", "MUS_VS_RAYQUAZA",
|
|
|
|
"MUS_VS_FRONTIER_BRAIN", "MUS_VS_MEW", "MUS_B_DOME_LOBBY", "MUS_VS_WILD", "MUS_VS_AQUA_MAGMA", "MUS_VS_TRAINER",
|
|
|
|
"MUS_VS_GYM_LEADER", "MUS_VS_CHAMPION", "MUS_VS_REGI", "MUS_VS_KYOGRE_GROUDON", "MUS_VS_RIVAL", "MUS_VS_ELITE_FOUR",
|
|
|
|
"MUS_VS_AQUA_MAGMA_LEADER", "MUS_RG_FOLLOW_ME", "MUS_RG_GAME_CORNER", "MUS_RG_ROCKET_HIDEOUT", "MUS_RG_GYM",
|
|
|
|
"MUS_RG_CINNABAR", "MUS_RG_LAVENDER", "MUS_RG_CYCLING", "MUS_RG_ENCOUNTER_ROCKET", "MUS_RG_ENCOUNTER_GIRL",
|
|
|
|
"MUS_RG_ENCOUNTER_BOY", "MUS_RG_HALL_OF_FAME", "MUS_RG_VIRIDIAN_FOREST", "MUS_RG_MT_MOON", "MUS_RG_POKE_MANSION",
|
|
|
|
"MUS_RG_ROUTE1", "MUS_RG_ROUTE24", "MUS_RG_ROUTE3", "MUS_RG_ROUTE11", "MUS_RG_VICTORY_ROAD", "MUS_RG_VS_GYM_LEADER",
|
|
|
|
"MUS_RG_VS_TRAINER", "MUS_RG_VS_WILD", "MUS_RG_VS_CHAMPION", "MUS_RG_PALLET", "MUS_RG_OAK_LAB", "MUS_RG_OAK",
|
|
|
|
"MUS_RG_POKE_CENTER", "MUS_RG_SS_ANNE", "MUS_RG_SURF", "MUS_RG_POKE_TOWER", "MUS_RG_SILPH", "MUS_RG_FUCHSIA",
|
|
|
|
"MUS_RG_CELADON", "MUS_RG_VICTORY_TRAINER", "MUS_RG_VICTORY_WILD", "MUS_RG_VICTORY_GYM_LEADER", "MUS_RG_VERMILLION",
|
|
|
|
"MUS_RG_PEWTER", "MUS_RG_ENCOUNTER_RIVAL", "MUS_RG_RIVAL_EXIT", "MUS_RG_CAUGHT", "MUS_RG_POKE_JUMP",
|
|
|
|
"MUS_RG_UNION_ROOM", "MUS_RG_NET_CENTER", "MUS_RG_MYSTERY_GIFT", "MUS_RG_BERRY_PICK", "MUS_RG_SEVII_CAVE",
|
|
|
|
"MUS_RG_TEACHY_TV_SHOW", "MUS_RG_SEVII_ROUTE", "MUS_RG_SEVII_DUNGEON", "MUS_RG_SEVII_123", "MUS_RG_SEVII_45",
|
|
|
|
"MUS_RG_SEVII_67", "MUS_RG_VS_DEOXYS", "MUS_RG_VS_MEWTWO", "MUS_RG_VS_LEGEND", "MUS_RG_ENCOUNTER_GYM_LEADER",
|
|
|
|
"MUS_RG_ENCOUNTER_DEOXYS", "MUS_RG_TRAINER_TOWER", "MUS_RG_SLOW_PALLET", "MUS_RG_TEACHY_TV_MENU",
|
|
|
|
]
|
|
|
|
|
|
|
|
_FANFARES: Dict[str, int] = {
|
|
|
|
"MUS_LEVEL_UP": 80,
|
|
|
|
"MUS_OBTAIN_ITEM": 160,
|
|
|
|
"MUS_EVOLVED": 220,
|
|
|
|
"MUS_OBTAIN_TMHM": 220,
|
|
|
|
"MUS_HEAL": 160,
|
|
|
|
"MUS_OBTAIN_BADGE": 340,
|
|
|
|
"MUS_MOVE_DELETED": 180,
|
|
|
|
"MUS_OBTAIN_BERRY": 120,
|
|
|
|
"MUS_AWAKEN_LEGEND": 710,
|
|
|
|
"MUS_SLOTS_JACKPOT": 250,
|
|
|
|
"MUS_SLOTS_WIN": 150,
|
|
|
|
"MUS_TOO_BAD": 160,
|
|
|
|
"MUS_RG_POKE_FLUTE": 450,
|
|
|
|
"MUS_RG_OBTAIN_KEY_ITEM": 170,
|
|
|
|
"MUS_RG_DEX_RATING": 196,
|
|
|
|
"MUS_OBTAIN_B_POINTS": 313,
|
|
|
|
"MUS_OBTAIN_SYMBOL": 318,
|
|
|
|
"MUS_REGISTER_MATCH_CALL": 135,
|
|
|
|
}
|
|
|
|
|
|
|
|
CAVE_EVENT_NAME_TO_ID = {
|
|
|
|
"TERRA_CAVE_ROUTE_114_1": 1,
|
|
|
|
"TERRA_CAVE_ROUTE_114_2": 2,
|
|
|
|
"TERRA_CAVE_ROUTE_115_1": 3,
|
|
|
|
"TERRA_CAVE_ROUTE_115_2": 4,
|
|
|
|
"TERRA_CAVE_ROUTE_116_1": 5,
|
|
|
|
"TERRA_CAVE_ROUTE_116_2": 6,
|
|
|
|
"TERRA_CAVE_ROUTE_118_1": 7,
|
|
|
|
"TERRA_CAVE_ROUTE_118_2": 8,
|
|
|
|
"MARINE_CAVE_ROUTE_105_1": 9,
|
|
|
|
"MARINE_CAVE_ROUTE_105_2": 10,
|
|
|
|
"MARINE_CAVE_ROUTE_125_1": 11,
|
|
|
|
"MARINE_CAVE_ROUTE_125_2": 12,
|
|
|
|
"MARINE_CAVE_ROUTE_127_1": 13,
|
|
|
|
"MARINE_CAVE_ROUTE_127_2": 14,
|
|
|
|
"MARINE_CAVE_ROUTE_129_1": 15,
|
|
|
|
"MARINE_CAVE_ROUTE_129_2": 16,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _set_bytes_le(byte_array: bytearray, address: int, size: int, value: int) -> None:
|
|
|
|
offset = 0
|
|
|
|
while size > 0:
|
|
|
|
byte_array[address + offset] = value & 0xFF
|
|
|
|
value = value >> 8
|
|
|
|
offset += 1
|
|
|
|
size -= 1
|
|
|
|
|
|
|
|
|
2023-11-12 21:39:34 +00:00
|
|
|
class PokemonEmeraldDeltaPatch(APDeltaPatch):
|
|
|
|
game = "Pokemon Emerald"
|
|
|
|
hash = "605b89b67018abcea91e693a4dd25be3"
|
|
|
|
patch_file_ending = ".apemerald"
|
|
|
|
result_file_ending = ".gba"
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_source_data(cls) -> bytes:
|
|
|
|
return get_base_rom_as_bytes()
|
|
|
|
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
2023-11-12 21:39:34 +00:00
|
|
|
base_rom = get_base_rom_as_bytes()
|
|
|
|
base_patch = pkgutil.get_data(__name__, "data/base_patch.bsdiff4")
|
|
|
|
patched_rom = bytearray(bsdiff4.patch(base_rom, base_patch))
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set free fly location
|
|
|
|
if world.options.free_fly_location:
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
data.rom_addresses["gArchipelagoOptions"] + 0x20,
|
|
|
|
1,
|
|
|
|
world.free_fly_location_id
|
|
|
|
)
|
|
|
|
|
|
|
|
location_info: List[Tuple[int, int, str]] = []
|
2023-11-12 21:39:34 +00:00
|
|
|
for location in world.multiworld.get_locations(world.player):
|
|
|
|
if location.address is None:
|
|
|
|
continue
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
if location.item is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Set local item values
|
|
|
|
if not world.options.remote_items and location.item.player == world.player:
|
|
|
|
if type(location.item_address) is int:
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
location.item_address,
|
|
|
|
2,
|
|
|
|
reverse_offset_item_value(location.item.code)
|
|
|
|
)
|
|
|
|
elif type(location.item_address) is list:
|
|
|
|
for address in location.item_address:
|
|
|
|
_set_bytes_le(patched_rom, address, 2, reverse_offset_item_value(location.item.code))
|
2023-11-12 21:39:34 +00:00
|
|
|
else:
|
2024-03-14 11:37:10 +00:00
|
|
|
if type(location.item_address) is int:
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
location.item_address,
|
|
|
|
2,
|
|
|
|
data.constants["ITEM_ARCHIPELAGO_PROGRESSION"]
|
|
|
|
)
|
|
|
|
elif type(location.item_address) is list:
|
|
|
|
for address in location.item_address:
|
|
|
|
_set_bytes_le(patched_rom, address, 2, data.constants["ITEM_ARCHIPELAGO_PROGRESSION"])
|
|
|
|
|
|
|
|
# Creates a list of item information to store in tables later. Those tables are used to display the item and
|
|
|
|
# player name in a text box. In the case of not enough space, the game will default to "found an ARCHIPELAGO
|
|
|
|
# ITEM"
|
|
|
|
location_info.append((location.address - BASE_OFFSET, location.item.player, location.item.name))
|
|
|
|
|
|
|
|
if world.options.trainersanity:
|
|
|
|
# Duplicate entries for rival fights
|
|
|
|
# For each of the 5 fights, there are 6 variations that have to be accounted for (for starters * genders)
|
|
|
|
# The Brendan Mudkip is used as a proxy in the rest of the AP code
|
|
|
|
for locale in ["ROUTE_103", "ROUTE_110", "ROUTE_119", "RUSTBORO", "LILYCOVE"]:
|
|
|
|
location = world.multiworld.get_location(data.locations[f"TRAINER_BRENDAN_{locale}_MUDKIP_REWARD"].label, world.player)
|
|
|
|
alternates = [
|
|
|
|
f"TRAINER_BRENDAN_{locale}_TREECKO",
|
|
|
|
f"TRAINER_BRENDAN_{locale}_TORCHIC",
|
|
|
|
f"TRAINER_MAY_{locale}_MUDKIP",
|
|
|
|
f"TRAINER_MAY_{locale}_TREECKO",
|
|
|
|
f"TRAINER_MAY_{locale}_TORCHIC",
|
|
|
|
]
|
|
|
|
location_info.extend((
|
|
|
|
data.constants["TRAINER_FLAGS_START"] + data.constants[trainer],
|
|
|
|
location.item.player,
|
|
|
|
location.item.name
|
|
|
|
) for trainer in alternates)
|
|
|
|
|
|
|
|
player_name_ids: Dict[str, int] = {world.multiworld.player_name[world.player]: 0}
|
|
|
|
item_name_offsets: Dict[str, int] = {}
|
|
|
|
next_item_name_offset = 0
|
|
|
|
for i, (flag, item_player, item_name) in enumerate(sorted(location_info, key=lambda t: t[0])):
|
|
|
|
# The player's own items are still set in the table with the value 0 to indicate the game should not show any
|
|
|
|
# message (the message for receiving an item will pop up when the client eventually gives it to them).
|
|
|
|
# In race mode, no item location data is included, and only recieved (or own) items will show any text box.
|
|
|
|
if item_player == world.player or world.multiworld.is_race:
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0, 2, flag)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2, 2, 0)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4, 1, 0)
|
|
|
|
else:
|
|
|
|
player_name = world.multiworld.player_name[item_player]
|
|
|
|
|
|
|
|
if player_name not in player_name_ids:
|
|
|
|
# Only space for 50 player names
|
|
|
|
if len(player_name_ids) >= 50:
|
|
|
|
continue
|
|
|
|
|
|
|
|
player_name_ids[player_name] = len(player_name_ids)
|
|
|
|
for j, b in enumerate(encode_string(player_name, 17)):
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
data.rom_addresses["gArchipelagoPlayerNames"] + (player_name_ids[player_name] * 17) + j,
|
|
|
|
1,
|
|
|
|
b
|
|
|
|
)
|
|
|
|
|
|
|
|
if item_name not in item_name_offsets:
|
|
|
|
if len(item_name) > 35:
|
|
|
|
item_name = item_name[:34] + "…"
|
|
|
|
|
|
|
|
# Only 36 * 250 bytes for item names
|
|
|
|
if next_item_name_offset + len(item_name) + 1 > 36 * 250:
|
|
|
|
continue
|
|
|
|
|
|
|
|
item_name_offsets[item_name] = next_item_name_offset
|
|
|
|
next_item_name_offset += len(item_name) + 1
|
|
|
|
for j, b in enumerate(encode_string(item_name) + b"\xFF"):
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
data.rom_addresses["gArchipelagoItemNames"] + (item_name_offsets[item_name]) + j,
|
|
|
|
1,
|
|
|
|
b
|
|
|
|
)
|
|
|
|
|
|
|
|
# There should always be enough space for one entry per location
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0, 2, flag)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2, 2, item_name_offsets[item_name])
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4, 1, player_name_ids[player_name])
|
|
|
|
|
|
|
|
easter_egg = get_easter_egg(world.options.easter_egg.value)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set start inventory
|
|
|
|
start_inventory = world.options.start_inventory.value.copy()
|
|
|
|
|
|
|
|
starting_badges = 0
|
|
|
|
if start_inventory.pop("Stone Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 0)
|
|
|
|
if start_inventory.pop("Knuckle Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 1)
|
|
|
|
if start_inventory.pop("Dynamo Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 2)
|
|
|
|
if start_inventory.pop("Heat Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 3)
|
|
|
|
if start_inventory.pop("Balance Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 4)
|
|
|
|
if start_inventory.pop("Feather Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 5)
|
|
|
|
if start_inventory.pop("Mind Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 6)
|
|
|
|
if start_inventory.pop("Rain Badge", 0) > 0:
|
|
|
|
starting_badges |= (1 << 7)
|
|
|
|
|
|
|
|
pc_slots: List[Tuple[str, int]] = []
|
|
|
|
while any(qty > 0 for qty in start_inventory.values()):
|
|
|
|
if len(pc_slots) >= 19:
|
|
|
|
break
|
|
|
|
|
|
|
|
for i, item_name in enumerate(start_inventory.keys()):
|
|
|
|
if len(pc_slots) >= 19:
|
|
|
|
break
|
|
|
|
|
|
|
|
quantity = min(start_inventory[item_name], 999)
|
|
|
|
if quantity == 0:
|
|
|
|
continue
|
|
|
|
|
|
|
|
start_inventory[item_name] -= quantity
|
|
|
|
|
|
|
|
pc_slots.append((item_name, quantity))
|
|
|
|
|
|
|
|
pc_slots.sort(reverse=True)
|
|
|
|
|
|
|
|
for i, slot in enumerate(pc_slots):
|
|
|
|
address = data.rom_addresses["sNewGamePCItems"] + (i * 4)
|
|
|
|
item = reverse_offset_item_value(world.item_name_to_id[slot[0]])
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(patched_rom, address + 0, 2, item)
|
|
|
|
_set_bytes_le(patched_rom, address + 2, 2, slot[1])
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set species data
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_species_info(world, patched_rom, easter_egg)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set encounter tables
|
|
|
|
if world.options.wild_pokemon != RandomizeWildPokemon.option_vanilla:
|
|
|
|
_set_encounter_tables(world, patched_rom)
|
|
|
|
|
|
|
|
# Set opponent data
|
2024-03-14 11:37:10 +00:00
|
|
|
if world.options.trainer_parties != RandomizeTrainerParties.option_vanilla or easter_egg[0] == 2:
|
|
|
|
_set_opponents(world, patched_rom, easter_egg)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set legendary pokemon
|
|
|
|
_set_legendary_encounters(world, patched_rom)
|
|
|
|
|
|
|
|
# Set misc pokemon
|
|
|
|
_set_misc_pokemon(world, patched_rom)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set starters
|
|
|
|
_set_starters(world, patched_rom)
|
|
|
|
|
|
|
|
# Set TM moves
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_tm_moves(world, patched_rom, easter_egg)
|
|
|
|
|
|
|
|
# Randomize move tutor moves
|
|
|
|
_randomize_move_tutor_moves(world, patched_rom, easter_egg)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set TM/HM compatibility
|
|
|
|
_set_tmhm_compatibility(world, patched_rom)
|
|
|
|
|
|
|
|
# Randomize opponent double or single
|
|
|
|
_randomize_opponent_battle_type(world, patched_rom)
|
|
|
|
|
|
|
|
# Options
|
|
|
|
# struct ArchipelagoOptions
|
|
|
|
# {
|
2024-03-14 11:37:10 +00:00
|
|
|
# /* 0x00 */ u16 birchPokemon;
|
|
|
|
# /* 0x02 */ bool8 advanceTextWithHoldA;
|
|
|
|
# /* 0x03 */ u8 receivedItemMessageFilter; // 0 = Show All; 1 = Show Progression Only; 2 = Show None
|
|
|
|
# /* 0x04 */ bool8 betterShopsEnabled;
|
|
|
|
# /* 0x05 */ bool8 reusableTms;
|
|
|
|
# /* 0x06 */ bool8 guaranteedCatch;
|
|
|
|
# /* 0x07 */ bool8 purgeSpinners;
|
|
|
|
# /* 0x08 */ bool8 areTrainersBlind;
|
|
|
|
# /* 0x09 */ u16 expMultiplierNumerator;
|
|
|
|
# /* 0x0B */ u16 expMultiplierDenominator;
|
|
|
|
# /* 0x0D */ bool8 matchTrainerLevels;
|
|
|
|
# /* 0x0E */ s8 matchTrainerLevelBonus;
|
|
|
|
# /* 0x0F */ bool8 eliteFourRequiresGyms;
|
|
|
|
# /* 0x10 */ u8 eliteFourRequiredCount;
|
|
|
|
# /* 0x11 */ bool8 normanRequiresGyms;
|
|
|
|
# /* 0x12 */ u8 normanRequiredCount;
|
|
|
|
# /* 0x13 */ u8 startingBadges;
|
|
|
|
# /* 0x14 */ u32 hmTotalBadgeRequirements;
|
|
|
|
# /* 0x18 */ u8 hmSpecificBadgeRequirements[8];
|
|
|
|
# /* 0x20 */ u8 freeFlyLocation;
|
|
|
|
# /* 0x21 */ u8 terraCaveLocationId:4;
|
|
|
|
# u8 marineCaveLocationId:4;
|
|
|
|
# /* 0x22 */ bool8 addRoute115Boulders;
|
|
|
|
# /* 0x23 */ bool8 addBumpySlopes;
|
|
|
|
# /* 0x24 */ bool8 modifyRoute118;
|
|
|
|
# /* 0x25 */ u16 removedBlockers;
|
|
|
|
# /* 0x27 */ bool8 berryTreesRandomized;
|
|
|
|
# /* 0x28 */ bool8 isDexsanity;
|
|
|
|
# /* 0x29 */ bool8 isTrainersanity;
|
|
|
|
# /* 0x2A */ bool8 isWarpRando;
|
|
|
|
# /* 0x2B */ u8 activeEasterEgg;
|
|
|
|
# /* 0x2C */ bool8 normalizeEncounterRates;
|
|
|
|
# /* 0x2D */ bool8 allowWonderTrading;
|
|
|
|
# /* 0x2E */ u16 matchTrainerLevelMultiplierNumerator;
|
|
|
|
# /* 0x30 */ u16 matchTrainerLevelMultiplierDenominator;
|
|
|
|
# /* 0x32 */ bool8 allowSkippingFanfares;
|
2023-11-12 21:39:34 +00:00
|
|
|
# };
|
|
|
|
options_address = data.rom_addresses["gArchipelagoOptions"]
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set Birch pokemon
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
options_address + 0x00,
|
|
|
|
2,
|
|
|
|
world.random.choice(list(data.species.keys()))
|
|
|
|
)
|
|
|
|
|
2023-11-12 21:39:34 +00:00
|
|
|
# Set hold A to advance text
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(patched_rom, options_address + 0x02, 1, 1 if world.options.turbo_a else 0)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set receive item messages type
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x03, 1, world.options.receive_item_messages.value)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set better shops
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x04, 1, 1 if world.options.better_shops else 0)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set reusable TMs
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x05, 1, 1 if world.options.reusable_tms_tutors else 0)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set guaranteed catch
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x06, 1, 1 if world.options.guaranteed_catch else 0)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set purge spinners
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x07, 1, 1 if world.options.purge_spinners else 0)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set blind trainers
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x08, 1, 1 if world.options.blind_trainers else 0)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Set exp modifier
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x09, 2, min(max(world.options.exp_modifier.value, 0), 2**16 - 1))
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x0B, 2, 100)
|
|
|
|
|
|
|
|
# Set match trainer levels
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x0D, 1, 1 if world.options.match_trainer_levels else 0)
|
|
|
|
|
|
|
|
# Set match trainer levels bonus
|
|
|
|
if world.options.match_trainer_levels == MatchTrainerLevels.option_additive:
|
|
|
|
match_trainer_levels_bonus = max(min(world.options.match_trainer_levels_bonus.value, 100), -100)
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x0E, 1, match_trainer_levels_bonus) # Works with negatives
|
|
|
|
elif world.options.match_trainer_levels == MatchTrainerLevels.option_multiplicative:
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x2E, 2, world.options.match_trainer_levels_bonus.value + 100)
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x30, 2, 100)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set elite four requirement
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
options_address + 0x0F,
|
|
|
|
1,
|
|
|
|
1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0
|
|
|
|
)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set elite four count
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(patched_rom, options_address + 0x10, 1, min(max(world.options.elite_four_count.value, 0), 8))
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set norman requirement
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
options_address + 0x11,
|
|
|
|
1,
|
|
|
|
1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0
|
|
|
|
)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set norman count
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(patched_rom, options_address + 0x12, 1, min(max(world.options.norman_count.value, 0), 8))
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set starting badges
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(patched_rom, options_address + 0x13, 1, starting_badges)
|
|
|
|
|
|
|
|
# Set HM badge requirements
|
|
|
|
field_move_order = [
|
|
|
|
"HM01 Cut",
|
|
|
|
"HM05 Flash",
|
|
|
|
"HM06 Rock Smash",
|
|
|
|
"HM04 Strength",
|
|
|
|
"HM03 Surf",
|
|
|
|
"HM02 Fly",
|
|
|
|
"HM08 Dive",
|
|
|
|
"HM07 Waterfall",
|
|
|
|
]
|
|
|
|
badge_to_bit = {
|
|
|
|
"Stone Badge": 1 << 0,
|
|
|
|
"Knuckle Badge": 1 << 1,
|
|
|
|
"Dynamo Badge": 1 << 2,
|
|
|
|
"Heat Badge": 1 << 3,
|
|
|
|
"Balance Badge": 1 << 4,
|
|
|
|
"Feather Badge": 1 << 5,
|
|
|
|
"Mind Badge": 1 << 6,
|
|
|
|
"Rain Badge": 1 << 7,
|
|
|
|
}
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Number of badges
|
|
|
|
# Uses 4 bits per HM. 0-8 means it's a valid requirement, otherwise use specific badges.
|
|
|
|
hm_badge_counts = 0
|
|
|
|
for i, hm in enumerate(field_move_order):
|
|
|
|
hm_badge_counts |= (world.hm_requirements[hm] if isinstance(world.hm_requirements[hm], int) else 0xF) << (i * 4)
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x14, 4, hm_badge_counts)
|
|
|
|
|
|
|
|
# Specific badges
|
|
|
|
for i, hm in enumerate(field_move_order):
|
|
|
|
if isinstance(world.hm_requirements, list):
|
|
|
|
bitfield = 0
|
|
|
|
for badge in world.hm_requirements:
|
|
|
|
bitfield |= badge_to_bit[badge]
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x18 + i, 1, bitfield)
|
|
|
|
|
|
|
|
# Set terra/marine cave locations
|
|
|
|
terra_cave_id = CAVE_EVENT_NAME_TO_ID[world.multiworld.get_location("TERRA_CAVE_LOCATION", world.player).item.name]
|
|
|
|
marine_cave_id = CAVE_EVENT_NAME_TO_ID[world.multiworld.get_location("MARINE_CAVE_LOCATION", world.player).item.name]
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x21, 1, terra_cave_id | (marine_cave_id << 4))
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set route 115 boulders
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(patched_rom, options_address + 0x22, 1, 1 if world.options.extra_boulders else 0)
|
|
|
|
|
|
|
|
# Swap route 115 layout if bumpy slope enabled
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x23, 1, 1 if world.options.extra_bumpy_slope else 0)
|
|
|
|
|
|
|
|
# Swap route 115 layout if bumpy slope enabled
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x24, 1, 1 if world.options.modify_118 else 0)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Set removed blockers
|
|
|
|
removed_roadblocks = world.options.remove_roadblocks.value
|
|
|
|
removed_roadblocks_bitfield = 0
|
|
|
|
removed_roadblocks_bitfield |= (1 << 0) if "Safari Zone Construction Workers" in removed_roadblocks else 0
|
|
|
|
removed_roadblocks_bitfield |= (1 << 1) if "Lilycove City Wailmer" in removed_roadblocks else 0
|
|
|
|
removed_roadblocks_bitfield |= (1 << 2) if "Route 110 Aqua Grunts" in removed_roadblocks else 0
|
|
|
|
removed_roadblocks_bitfield |= (1 << 3) if "Aqua Hideout Grunts" in removed_roadblocks else 0
|
|
|
|
removed_roadblocks_bitfield |= (1 << 4) if "Route 119 Aqua Grunts" in removed_roadblocks else 0
|
|
|
|
removed_roadblocks_bitfield |= (1 << 5) if "Route 112 Magma Grunts" in removed_roadblocks else 0
|
|
|
|
removed_roadblocks_bitfield |= (1 << 6) if "Seafloor Cavern Aqua Grunt" in removed_roadblocks else 0
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(patched_rom, options_address + 0x25, 2, removed_roadblocks_bitfield)
|
|
|
|
|
|
|
|
# Mark berry trees as randomized
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x27, 1, 1 if world.options.berry_trees else 0)
|
|
|
|
|
|
|
|
# Mark dexsanity as enabled
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x28, 1, 1 if world.options.dexsanity else 0)
|
|
|
|
|
|
|
|
# Mark trainersanity as enabled
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x29, 1, 1 if world.options.trainersanity else 0)
|
|
|
|
|
|
|
|
# Set easter egg data
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x2B, 1, easter_egg[0])
|
|
|
|
|
|
|
|
# Set normalize encounter rates
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x2C, 1, 1 if world.options.normalize_encounter_rates else 0)
|
|
|
|
|
|
|
|
# Set allow wonder trading
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x2D, 1, 1 if world.options.enable_wonder_trading else 0)
|
|
|
|
|
|
|
|
# Set allowed to skip fanfares
|
|
|
|
_set_bytes_le(patched_rom, options_address + 0x32, 1, 1 if world.options.fanfares else 0)
|
|
|
|
|
|
|
|
if easter_egg[0] == 2:
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (easter_egg[1] * 12) + 4, 1, 50)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_CUT"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_FLY"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_SURF"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_STRENGTH"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_FLASH"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_ROCK_SMASH"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_WATERFALL"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_DIVE"] * 12) + 4, 1, 1)
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_DIG"] * 12) + 4, 1, 1)
|
|
|
|
|
|
|
|
# Set slot auth
|
|
|
|
for i, byte in enumerate(world.auth):
|
|
|
|
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoInfo"] + i, 1, byte)
|
|
|
|
|
|
|
|
# Randomize music
|
|
|
|
if world.options.music:
|
|
|
|
# The "randomized sound table" is a patchboard that redirects sounds just before they get played
|
|
|
|
randomized_looping_music = copy.copy(_LOOPING_MUSIC)
|
|
|
|
world.random.shuffle(randomized_looping_music)
|
|
|
|
for original_music, randomized_music in zip(_LOOPING_MUSIC, randomized_looping_music):
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[original_music] * 2),
|
|
|
|
2,
|
|
|
|
data.constants[randomized_music]
|
|
|
|
)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
# Randomize fanfares
|
|
|
|
if world.options.fanfares:
|
|
|
|
# Shuffle the lists, pair new tracks with original tracks, set the new track ids, and set new fanfare durations
|
|
|
|
randomized_fanfares = [fanfare_name for fanfare_name in _FANFARES]
|
|
|
|
world.random.shuffle(randomized_fanfares)
|
|
|
|
for i, fanfare_pair in enumerate(zip(_FANFARES.keys(), randomized_fanfares)):
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[fanfare_pair[0]] * 2),
|
|
|
|
2,
|
|
|
|
data.constants[fanfare_pair[1]]
|
|
|
|
)
|
|
|
|
_set_bytes_le(
|
|
|
|
patched_rom,
|
|
|
|
data.rom_addresses["sFanfares"] + (i * 4) + 2,
|
|
|
|
2,
|
|
|
|
_FANFARES[fanfare_pair[1]]
|
|
|
|
)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Write Output
|
|
|
|
out_file_name = world.multiworld.get_out_file_name_base(world.player)
|
|
|
|
output_path = os.path.join(output_directory, f"{out_file_name}.gba")
|
|
|
|
with open(output_path, "wb") as out_file:
|
|
|
|
out_file.write(patched_rom)
|
|
|
|
patch = PokemonEmeraldDeltaPatch(os.path.splitext(output_path)[0] + ".apemerald", player=world.player,
|
2024-03-14 11:37:10 +00:00
|
|
|
player_name=world.multiworld.get_player_name(world.player),
|
|
|
|
patched_path=output_path)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
patch.write()
|
|
|
|
os.unlink(output_path)
|
|
|
|
|
|
|
|
|
|
|
|
def get_base_rom_as_bytes() -> bytes:
|
|
|
|
with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
|
|
|
|
base_rom_bytes = bytes(infile.read())
|
|
|
|
|
|
|
|
return base_rom_bytes
|
|
|
|
|
|
|
|
|
|
|
|
def _set_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
|
|
|
"""
|
|
|
|
Encounter tables are lists of
|
|
|
|
struct {
|
|
|
|
min_level: 0x01 bytes,
|
|
|
|
max_level: 0x01 bytes,
|
|
|
|
species_id: 0x02 bytes
|
|
|
|
}
|
|
|
|
"""
|
2024-03-14 11:37:10 +00:00
|
|
|
for map_data in world.modified_maps.values():
|
2023-11-12 21:39:34 +00:00
|
|
|
tables = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters]
|
|
|
|
for table in tables:
|
|
|
|
if table is not None:
|
|
|
|
for i, species_id in enumerate(table.slots):
|
2024-03-14 11:37:10 +00:00
|
|
|
address = table.address + 2 + (4 * i)
|
|
|
|
_set_bytes_le(rom, address, 2, species_id)
|
|
|
|
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
def _set_species_info(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
|
|
|
for species in world.modified_species.values():
|
|
|
|
_set_bytes_le(rom, species.address + 6, 1, species.types[0])
|
|
|
|
_set_bytes_le(rom, species.address + 7, 1, species.types[1])
|
|
|
|
_set_bytes_le(rom, species.address + 8, 1, species.catch_rate)
|
|
|
|
_set_bytes_le(rom, species.address + 22, 1, species.abilities[0])
|
|
|
|
_set_bytes_le(rom, species.address + 23, 1, species.abilities[1])
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
if easter_egg[0] == 3:
|
|
|
|
_set_bytes_le(rom, species.address + 22, 1, easter_egg[1])
|
|
|
|
_set_bytes_le(rom, species.address + 23, 1, easter_egg[1])
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
for i, learnset_move in enumerate(species.learnset):
|
|
|
|
level_move = learnset_move.level << 9 | learnset_move.move_id
|
|
|
|
if easter_egg[0] == 2:
|
|
|
|
level_move = learnset_move.level << 9 | easter_egg[1]
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(rom, species.learnset_address + (i * 2), 2, level_move)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
|
|
|
|
def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
2023-11-12 21:39:34 +00:00
|
|
|
for trainer in world.modified_trainers:
|
2024-03-14 11:37:10 +00:00
|
|
|
party_address = trainer.party.address
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
pokemon_data_size: int
|
|
|
|
if trainer.party.pokemon_data_type in {TrainerPokemonDataTypeEnum.NO_ITEM_DEFAULT_MOVES, TrainerPokemonDataTypeEnum.ITEM_DEFAULT_MOVES}:
|
|
|
|
pokemon_data_size = 8
|
|
|
|
else: # Custom Moves
|
|
|
|
pokemon_data_size = 16
|
|
|
|
|
|
|
|
for i, pokemon in enumerate(trainer.party.pokemon):
|
|
|
|
pokemon_address = party_address + (i * pokemon_data_size)
|
|
|
|
|
|
|
|
# Replace species
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(rom, pokemon_address + 0x04, 2, pokemon.species_id)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
# Replace custom moves if applicable
|
|
|
|
if trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.NO_ITEM_CUSTOM_MOVES:
|
2024-03-14 11:37:10 +00:00
|
|
|
if easter_egg[0] == 2:
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x06, 2, easter_egg[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x08, 2, easter_egg[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0A, 2, easter_egg[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0C, 2, easter_egg[1])
|
|
|
|
else:
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x06, 2, pokemon.moves[0])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x08, 2, pokemon.moves[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0A, 2, pokemon.moves[2])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0C, 2, pokemon.moves[3])
|
2023-11-12 21:39:34 +00:00
|
|
|
elif trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.ITEM_CUSTOM_MOVES:
|
2024-03-14 11:37:10 +00:00
|
|
|
if easter_egg[0] == 2:
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x08, 2, easter_egg[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0A, 2, easter_egg[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0C, 2, easter_egg[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0E, 2, easter_egg[1])
|
|
|
|
else:
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x08, 2, pokemon.moves[0])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0A, 2, pokemon.moves[1])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0C, 2, pokemon.moves[2])
|
|
|
|
_set_bytes_le(rom, pokemon_address + 0x0E, 2, pokemon.moves[3])
|
|
|
|
|
|
|
|
|
|
|
|
def _set_legendary_encounters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
|
|
|
for encounter in world.modified_legendary_encounters:
|
|
|
|
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
def _set_misc_pokemon(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
|
|
|
for encounter in world.modified_misc_pokemon:
|
|
|
|
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _set_starters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
|
|
|
address = data.rom_addresses["sStarterMon"]
|
|
|
|
(starter_1, starter_2, starter_3) = world.modified_starters
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(rom, address + 0, 2, starter_1)
|
|
|
|
_set_bytes_le(rom, address + 2, 2, starter_2)
|
|
|
|
_set_bytes_le(rom, address + 4, 2, starter_3)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
2023-11-12 21:39:34 +00:00
|
|
|
tmhm_list_address = data.rom_addresses["sTMHMMoves"]
|
|
|
|
|
|
|
|
for i, move in enumerate(world.modified_tmhm_moves):
|
|
|
|
# Don't modify HMs
|
|
|
|
if i >= 50:
|
|
|
|
break
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
_set_bytes_le(rom, tmhm_list_address + (i * 2), 2, move)
|
|
|
|
if easter_egg[0] == 2:
|
|
|
|
_set_bytes_le(rom, tmhm_list_address + (i * 2), 2, easter_egg[1])
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
|
|
|
learnsets_address = data.rom_addresses["gTMHMLearnsets"]
|
|
|
|
|
2024-03-14 11:37:10 +00:00
|
|
|
for species in world.modified_species.values():
|
|
|
|
_set_bytes_le(rom, learnsets_address + (species.species_id * 8), 8, species.tm_hm_compatibility)
|
2023-11-12 21:39:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
|
|
|
probability = world.options.double_battle_chance.value / 100
|
|
|
|
|
|
|
|
battle_type_map = {
|
|
|
|
0: 4,
|
|
|
|
1: 8,
|
|
|
|
2: 6,
|
|
|
|
3: 13,
|
|
|
|
}
|
|
|
|
|
|
|
|
for trainer_data in data.trainers:
|
2024-03-14 11:37:10 +00:00
|
|
|
if trainer_data.script_address != 0 and len(trainer_data.party.pokemon) > 1:
|
|
|
|
original_battle_type = rom[trainer_data.script_address + 1]
|
|
|
|
if original_battle_type in battle_type_map: # Don't touch anything other than regular single battles
|
|
|
|
if world.random.random() < probability:
|
|
|
|
# Set the trainer to be a double battle
|
|
|
|
_set_bytes_le(rom, trainer_data.address + 0x18, 1, 1)
|
|
|
|
|
|
|
|
# Swap the battle type in the script for the purpose of loading the right text
|
|
|
|
# and setting data to the right places
|
|
|
|
_set_bytes_le(
|
2023-11-12 21:39:34 +00:00
|
|
|
rom,
|
2024-03-14 11:37:10 +00:00
|
|
|
trainer_data.script_address + 1,
|
2023-11-12 21:39:34 +00:00
|
|
|
1,
|
|
|
|
battle_type_map[original_battle_type]
|
|
|
|
)
|
2024-03-14 11:37:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
|
|
|
if easter_egg[0] == 2:
|
|
|
|
for i in range(30):
|
|
|
|
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (i * 2), 2, easter_egg[1])
|
|
|
|
else:
|
|
|
|
if world.options.tm_tutor_moves:
|
|
|
|
new_tutor_moves = []
|
|
|
|
for i in range(30):
|
|
|
|
new_move = get_random_move(world.random, set(new_tutor_moves) | world.blacklisted_moves | HM_MOVES)
|
|
|
|
new_tutor_moves.append(new_move)
|
|
|
|
|
|
|
|
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (i * 2), 2, new_move)
|
|
|
|
|
|
|
|
# Always set Fortree move tutor to Dig
|
|
|
|
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (24 * 2), 2, data.constants["MOVE_DIG"])
|
|
|
|
|
|
|
|
# Modify compatibility
|
|
|
|
if world.options.tm_tutor_compatibility.value != -1:
|
|
|
|
for species in data.species.values():
|
|
|
|
_set_bytes_le(
|
|
|
|
rom,
|
|
|
|
data.rom_addresses["sTutorLearnsets"] + (species.species_id * 4),
|
|
|
|
4,
|
|
|
|
bool_array_to_int([world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value for _ in range(32)])
|
|
|
|
)
|