Pokemon Emerald: Convert to procedure patch (#2995)
* Pokemon Emerald: Convert to procedure patch * Pokemon Emerald: Remove assertion for vanilla rom's existence * Pokemon Emerald: Add APPP implication to changelog * Pokemon Emerald: Move procedure patch changelog line to new version * Pokemon Emerald: Modify changelog versions * Pokemon Emerald: Fix patch file download not appearing --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
parent
7603b4a88f
commit
7e61211365
|
@ -1,3 +1,13 @@
|
|||
# 2.1.1
|
||||
|
||||
### Features
|
||||
|
||||
- You no longer need a copy of Pokemon Emerald to generate a game, patch files generate much faster.
|
||||
|
||||
# 2.1.0
|
||||
|
||||
_Separately released, branching from 2.0.0. Included procedure patch migration, but none of the 2.0.1 fixes._
|
||||
|
||||
# 2.0.1
|
||||
|
||||
### Fixes
|
||||
|
|
|
@ -5,6 +5,7 @@ from collections import Counter
|
|||
import copy
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union
|
||||
|
||||
from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType
|
||||
|
@ -25,7 +26,7 @@ from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType,
|
|||
from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilities, randomize_learnsets,
|
||||
randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters,
|
||||
randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters)
|
||||
from .rom import PokemonEmeraldDeltaPatch, create_patch
|
||||
from .rom import PokemonEmeraldProcedurePatch, write_tokens
|
||||
|
||||
|
||||
class PokemonEmeraldWebWorld(WebWorld):
|
||||
|
@ -60,7 +61,7 @@ class PokemonEmeraldSettings(settings.Group):
|
|||
"""File name of your English Pokemon Emerald ROM"""
|
||||
description = "Pokemon Emerald ROM File"
|
||||
copy_to = "Pokemon - Emerald Version (USA, Europe).gba"
|
||||
md5s = [PokemonEmeraldDeltaPatch.hash]
|
||||
md5s = [PokemonEmeraldProcedurePatch.hash]
|
||||
|
||||
rom_file: PokemonEmeraldRomFile = PokemonEmeraldRomFile(PokemonEmeraldRomFile.copy_to)
|
||||
|
||||
|
@ -126,9 +127,6 @@ class PokemonEmeraldWorld(World):
|
|||
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
|
||||
from .sanity_check import validate_regions
|
||||
|
||||
if not os.path.exists(cls.settings.rom_file):
|
||||
raise FileNotFoundError(cls.settings.rom_file)
|
||||
|
||||
assert validate_regions()
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
|
@ -591,7 +589,9 @@ class PokemonEmeraldWorld(World):
|
|||
randomize_opponent_parties(self)
|
||||
randomize_starters(self)
|
||||
|
||||
create_patch(self, output_directory)
|
||||
patch = PokemonEmeraldProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
||||
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/base_patch.bsdiff4"))
|
||||
write_tokens(self, patch)
|
||||
|
||||
del self.modified_trainers
|
||||
del self.modified_tmhm_moves
|
||||
|
@ -600,6 +600,10 @@ class PokemonEmeraldWorld(World):
|
|||
del self.modified_starters
|
||||
del self.modified_species
|
||||
|
||||
# Write Output
|
||||
out_file_name = self.multiworld.get_out_file_name_base(self.player)
|
||||
patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}"))
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO):
|
||||
if self.options.dexsanity:
|
||||
from collections import defaultdict
|
||||
|
|
|
@ -289,6 +289,7 @@ class TrainerData:
|
|||
party: TrainerPartyData
|
||||
address: int
|
||||
script_address: int
|
||||
battle_type: int
|
||||
|
||||
|
||||
class PokemonEmeraldData:
|
||||
|
@ -1422,7 +1423,8 @@ def _init() -> None:
|
|||
trainer_json["party_address"]
|
||||
),
|
||||
trainer_json["address"],
|
||||
trainer_json["script_address"]
|
||||
trainer_json["script_address"],
|
||||
trainer_json["battle_type"]
|
||||
))
|
||||
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,12 +3,10 @@ Classes and functions related to creating a ROM patch
|
|||
"""
|
||||
import copy
|
||||
import os
|
||||
import pkgutil
|
||||
import struct
|
||||
from typing import TYPE_CHECKING, Dict, List, Tuple
|
||||
|
||||
import bsdiff4
|
||||
|
||||
from worlds.Files import APDeltaPatch
|
||||
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
|
||||
from settings import get_settings
|
||||
|
||||
from .data import TrainerPokemonDataTypeEnum, BASE_OFFSET, data
|
||||
|
@ -96,38 +94,32 @@ CAVE_EVENT_NAME_TO_ID = {
|
|||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class PokemonEmeraldDeltaPatch(APDeltaPatch):
|
||||
class PokemonEmeraldProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
game = "Pokemon Emerald"
|
||||
hash = "605b89b67018abcea91e693a4dd25be3"
|
||||
patch_file_ending = ".apemerald"
|
||||
result_file_ending = ".gba"
|
||||
|
||||
procedure = [
|
||||
("apply_bsdiff4", ["base_patch.bsdiff4"]),
|
||||
("apply_tokens", ["token_data.bin"])
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
return get_base_rom_as_bytes()
|
||||
with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
|
||||
base_rom_bytes = bytes(infile.read())
|
||||
|
||||
return base_rom_bytes
|
||||
|
||||
|
||||
def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
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))
|
||||
|
||||
def write_tokens(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
# Set free fly location
|
||||
if world.options.free_fly_location:
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoOptions"] + 0x20,
|
||||
1,
|
||||
world.free_fly_location_id
|
||||
struct.pack("<B", world.free_fly_location_id)
|
||||
)
|
||||
|
||||
location_info: List[Tuple[int, int, str]] = []
|
||||
|
@ -141,26 +133,32 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
# 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,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
location.item_address,
|
||||
2,
|
||||
reverse_offset_item_value(location.item.code)
|
||||
struct.pack("<H", location.item.code - BASE_OFFSET)
|
||||
)
|
||||
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))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
address,
|
||||
struct.pack("<H", location.item.code - BASE_OFFSET)
|
||||
)
|
||||
else:
|
||||
if type(location.item_address) is int:
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
location.item_address,
|
||||
2,
|
||||
data.constants["ITEM_ARCHIPELAGO_PROGRESSION"]
|
||||
struct.pack("<H", 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"])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
address,
|
||||
struct.pack("<H", 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
|
||||
|
@ -194,9 +192,21 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
# 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0,
|
||||
struct.pack("<H", flag)
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2,
|
||||
struct.pack("<H", 0)
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4,
|
||||
struct.pack("<B", 0)
|
||||
)
|
||||
else:
|
||||
player_name = world.multiworld.player_name[item_player]
|
||||
|
||||
|
@ -207,11 +217,10 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
|
||||
player_name_ids[player_name] = len(player_name_ids)
|
||||
for j, b in enumerate(encode_string(player_name, 17)):
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoPlayerNames"] + (player_name_ids[player_name] * 17) + j,
|
||||
1,
|
||||
b
|
||||
struct.pack("<B", b)
|
||||
)
|
||||
|
||||
if item_name not in item_name_offsets:
|
||||
|
@ -224,18 +233,28 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
|
||||
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
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoItemNames"] + (item_name_offsets[item_name]),
|
||||
encode_string(item_name) + b"\xFF"
|
||||
)
|
||||
|
||||
# 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])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0,
|
||||
struct.pack("<H", flag)
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2,
|
||||
struct.pack("<H", item_name_offsets[item_name])
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4,
|
||||
struct.pack("<B", player_name_ids[player_name])
|
||||
)
|
||||
|
||||
easter_egg = get_easter_egg(world.options.easter_egg.value)
|
||||
|
||||
|
@ -282,40 +301,40 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
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]])
|
||||
_set_bytes_le(patched_rom, address + 0, 2, item)
|
||||
_set_bytes_le(patched_rom, address + 2, 2, slot[1])
|
||||
patch.write_token(APTokenTypes.WRITE, address + 0, struct.pack("<H", item))
|
||||
patch.write_token(APTokenTypes.WRITE, address + 2, struct.pack("<H", slot[1]))
|
||||
|
||||
# Set species data
|
||||
_set_species_info(world, patched_rom, easter_egg)
|
||||
_set_species_info(world, patch, easter_egg)
|
||||
|
||||
# Set encounter tables
|
||||
if world.options.wild_pokemon != RandomizeWildPokemon.option_vanilla:
|
||||
_set_encounter_tables(world, patched_rom)
|
||||
_set_encounter_tables(world, patch)
|
||||
|
||||
# Set opponent data
|
||||
if world.options.trainer_parties != RandomizeTrainerParties.option_vanilla or easter_egg[0] == 2:
|
||||
_set_opponents(world, patched_rom, easter_egg)
|
||||
_set_opponents(world, patch, easter_egg)
|
||||
|
||||
# Set legendary pokemon
|
||||
_set_legendary_encounters(world, patched_rom)
|
||||
_set_legendary_encounters(world, patch)
|
||||
|
||||
# Set misc pokemon
|
||||
_set_misc_pokemon(world, patched_rom)
|
||||
_set_misc_pokemon(world, patch)
|
||||
|
||||
# Set starters
|
||||
_set_starters(world, patched_rom)
|
||||
_set_starters(world, patch)
|
||||
|
||||
# Set TM moves
|
||||
_set_tm_moves(world, patched_rom, easter_egg)
|
||||
_set_tm_moves(world, patch, easter_egg)
|
||||
|
||||
# Randomize move tutor moves
|
||||
_randomize_move_tutor_moves(world, patched_rom, easter_egg)
|
||||
_randomize_move_tutor_moves(world, patch, easter_egg)
|
||||
|
||||
# Set TM/HM compatibility
|
||||
_set_tmhm_compatibility(world, patched_rom)
|
||||
_set_tmhm_compatibility(world, patch)
|
||||
|
||||
# Randomize opponent double or single
|
||||
_randomize_opponent_battle_type(world, patched_rom)
|
||||
_randomize_opponent_battle_type(world, patch)
|
||||
|
||||
# Options
|
||||
# struct ArchipelagoOptions
|
||||
|
@ -360,73 +379,118 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
options_address = data.rom_addresses["gArchipelagoOptions"]
|
||||
|
||||
# Set Birch pokemon
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x00,
|
||||
2,
|
||||
world.random.choice(list(data.species.keys()))
|
||||
struct.pack("<H", world.random.choice(list(data.species.keys())))
|
||||
)
|
||||
|
||||
# Set hold A to advance text
|
||||
_set_bytes_le(patched_rom, options_address + 0x02, 1, 1 if world.options.turbo_a else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x02,
|
||||
struct.pack("<B", 1 if world.options.turbo_a else 0)
|
||||
)
|
||||
|
||||
# Set receive item messages type
|
||||
_set_bytes_le(patched_rom, options_address + 0x03, 1, world.options.receive_item_messages.value)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x03,
|
||||
struct.pack("<B", world.options.receive_item_messages.value)
|
||||
)
|
||||
|
||||
# Set better shops
|
||||
_set_bytes_le(patched_rom, options_address + 0x04, 1, 1 if world.options.better_shops else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x04,
|
||||
struct.pack("<B", 1 if world.options.better_shops else 0)
|
||||
)
|
||||
|
||||
# Set reusable TMs
|
||||
_set_bytes_le(patched_rom, options_address + 0x05, 1, 1 if world.options.reusable_tms_tutors else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x05,
|
||||
struct.pack("<B", 1 if world.options.reusable_tms_tutors else 0)
|
||||
)
|
||||
|
||||
# Set guaranteed catch
|
||||
_set_bytes_le(patched_rom, options_address + 0x06, 1, 1 if world.options.guaranteed_catch else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x06,
|
||||
struct.pack("<B", 1 if world.options.guaranteed_catch else 0)
|
||||
)
|
||||
|
||||
# Set purge spinners
|
||||
_set_bytes_le(patched_rom, options_address + 0x07, 1, 1 if world.options.purge_spinners else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x07,
|
||||
struct.pack("<B", 1 if world.options.purge_spinners else 0)
|
||||
)
|
||||
|
||||
# Set blind trainers
|
||||
_set_bytes_le(patched_rom, options_address + 0x08, 1, 1 if world.options.blind_trainers else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x08,
|
||||
struct.pack("<B", 1 if world.options.blind_trainers else 0)
|
||||
)
|
||||
|
||||
# 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x09,
|
||||
struct.pack("<H", min(max(world.options.exp_modifier.value, 0), 2**16 - 1))
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x0B,
|
||||
struct.pack("<H", 100)
|
||||
)
|
||||
|
||||
# Set match trainer levels
|
||||
_set_bytes_le(patched_rom, options_address + 0x0D, 1, 1 if world.options.match_trainer_levels else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x0D,
|
||||
struct.pack("<B", 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
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x0E, struct.pack("<b", match_trainer_levels_bonus))
|
||||
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)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x2E, struct.pack("<H", world.options.match_trainer_levels_bonus.value + 100))
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x30, struct.pack("<H", 100))
|
||||
|
||||
# Set elite four requirement
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x0F,
|
||||
1,
|
||||
1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0
|
||||
struct.pack("<B", 1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0)
|
||||
)
|
||||
|
||||
# Set elite four count
|
||||
_set_bytes_le(patched_rom, options_address + 0x10, 1, min(max(world.options.elite_four_count.value, 0), 8))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x10,
|
||||
struct.pack("<B", min(max(world.options.elite_four_count.value, 0), 8))
|
||||
)
|
||||
|
||||
# Set norman requirement
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x11,
|
||||
1,
|
||||
1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0
|
||||
struct.pack("<B", 1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0)
|
||||
)
|
||||
|
||||
# Set norman count
|
||||
_set_bytes_le(patched_rom, options_address + 0x12, 1, min(max(world.options.norman_count.value, 0), 8))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x12,
|
||||
struct.pack("<B", min(max(world.options.norman_count.value, 0), 8))
|
||||
)
|
||||
|
||||
# Set starting badges
|
||||
_set_bytes_le(patched_rom, options_address + 0x13, 1, starting_badges)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x13, struct.pack("<B", starting_badges))
|
||||
|
||||
# Set HM badge requirements
|
||||
field_move_order = [
|
||||
|
@ -455,7 +519,7 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
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)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x14, struct.pack("<I", hm_badge_counts))
|
||||
|
||||
# Specific badges
|
||||
for i, hm in enumerate(field_move_order):
|
||||
|
@ -463,21 +527,37 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
bitfield = 0
|
||||
for badge in world.hm_requirements:
|
||||
bitfield |= badge_to_bit[badge]
|
||||
_set_bytes_le(patched_rom, options_address + 0x18 + i, 1, bitfield)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x18, struct.pack("<B", 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))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x21,
|
||||
struct.pack("<B", terra_cave_id | (marine_cave_id << 4))
|
||||
)
|
||||
|
||||
# Set route 115 boulders
|
||||
_set_bytes_le(patched_rom, options_address + 0x22, 1, 1 if world.options.extra_boulders else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x22,
|
||||
struct.pack("<B", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x23,
|
||||
struct.pack("<B", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x24,
|
||||
struct.pack("<B", 1 if world.options.modify_118 else 0)
|
||||
)
|
||||
|
||||
# Set removed blockers
|
||||
removed_roadblocks = world.options.remove_roadblocks.value
|
||||
|
@ -489,44 +569,72 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
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
|
||||
_set_bytes_le(patched_rom, options_address + 0x25, 2, removed_roadblocks_bitfield)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x25, struct.pack("<H", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x27,
|
||||
struct.pack("<B", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x28,
|
||||
struct.pack("<B", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x29,
|
||||
struct.pack("<B", 1 if world.options.trainersanity else 0)
|
||||
)
|
||||
|
||||
# Set easter egg data
|
||||
_set_bytes_le(patched_rom, options_address + 0x2B, 1, easter_egg[0])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x2B,
|
||||
struct.pack("<B", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x2C,
|
||||
struct.pack("<B", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x2D,
|
||||
struct.pack("<B", 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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x32,
|
||||
struct.pack("<B", 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)
|
||||
offset = data.rom_addresses["gBattleMoves"] + 4
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (easter_egg[1] * 12), struct.pack("<B", 50))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_CUT"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_FLY"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_SURF"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_STRENGTH"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_FLASH"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_ROCK_SMASH"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_WATERFALL"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_DIVE"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_DIG"] * 12), struct.pack("<B", 1))
|
||||
|
||||
# Set slot auth
|
||||
for i, byte in enumerate(world.auth):
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoInfo"] + i, 1, byte)
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["gArchipelagoInfo"], world.auth)
|
||||
|
||||
# Randomize music
|
||||
if world.options.music:
|
||||
|
@ -534,11 +642,10 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
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,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[original_music] * 2),
|
||||
2,
|
||||
data.constants[randomized_music]
|
||||
struct.pack("<H", data.constants[randomized_music])
|
||||
)
|
||||
|
||||
# Randomize fanfares
|
||||
|
@ -547,40 +654,21 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
|||
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,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[fanfare_pair[0]] * 2),
|
||||
2,
|
||||
data.constants[fanfare_pair[1]]
|
||||
struct.pack("<H", data.constants[fanfare_pair[1]])
|
||||
)
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["sFanfares"] + (i * 4) + 2,
|
||||
2,
|
||||
_FANFARES[fanfare_pair[1]]
|
||||
struct.pack("<H", _FANFARES[fanfare_pair[1]])
|
||||
)
|
||||
|
||||
# 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,
|
||||
player_name=world.multiworld.get_player_name(world.player),
|
||||
patched_path=output_path)
|
||||
|
||||
patch.write()
|
||||
os.unlink(output_path)
|
||||
patch.write_file("token_data.bin", patch.get_token_binary())
|
||||
|
||||
|
||||
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:
|
||||
def _set_encounter_tables(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
"""
|
||||
Encounter tables are lists of
|
||||
struct {
|
||||
|
@ -595,30 +683,31 @@ def _set_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
|||
if table is not None:
|
||||
for i, species_id in enumerate(table.slots):
|
||||
address = table.address + 2 + (4 * i)
|
||||
_set_bytes_le(rom, address, 2, species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, address, struct.pack("<H", species_id))
|
||||
|
||||
|
||||
def _set_species_info(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _set_species_info(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, 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])
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 6, struct.pack("<B", species.types[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 7, struct.pack("<B", species.types[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 8, struct.pack("<B", species.catch_rate))
|
||||
|
||||
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])
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 22, struct.pack("<B", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 23, struct.pack("<B", easter_egg[1]))
|
||||
else:
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 22, struct.pack("<B", species.abilities[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 23, struct.pack("<B", species.abilities[1]))
|
||||
|
||||
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]
|
||||
|
||||
_set_bytes_le(rom, species.learnset_address + (i * 2), 2, level_move)
|
||||
patch.write_token(APTokenTypes.WRITE, species.learnset_address + (i * 2), struct.pack("<H", level_move))
|
||||
|
||||
|
||||
def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _set_opponents(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
||||
for trainer in world.modified_trainers:
|
||||
party_address = trainer.party.address
|
||||
|
||||
|
@ -632,53 +721,50 @@ def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tup
|
|||
pokemon_address = party_address + (i * pokemon_data_size)
|
||||
|
||||
# Replace species
|
||||
_set_bytes_le(rom, pokemon_address + 0x04, 2, pokemon.species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x04, struct.pack("<H", pokemon.species_id))
|
||||
|
||||
# Replace custom moves if applicable
|
||||
if trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.NO_ITEM_CUSTOM_MOVES:
|
||||
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])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x06, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", 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])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x06, struct.pack("<H", pokemon.moves[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", pokemon.moves[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", pokemon.moves[2]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", pokemon.moves[3]))
|
||||
elif trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.ITEM_CUSTOM_MOVES:
|
||||
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])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0E, struct.pack("<H", 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])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", pokemon.moves[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", pokemon.moves[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", pokemon.moves[2]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0E, struct.pack("<H", pokemon.moves[3]))
|
||||
|
||||
|
||||
def _set_legendary_encounters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _set_legendary_encounters(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
for encounter in world.modified_legendary_encounters:
|
||||
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, encounter.address, struct.pack("<H", encounter.species_id))
|
||||
|
||||
|
||||
def _set_misc_pokemon(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _set_misc_pokemon(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
for encounter in world.modified_misc_pokemon:
|
||||
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, encounter.address, struct.pack("<H", encounter.species_id))
|
||||
|
||||
|
||||
def _set_starters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
address = data.rom_addresses["sStarterMon"]
|
||||
(starter_1, starter_2, starter_3) = world.modified_starters
|
||||
|
||||
_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)
|
||||
def _set_starters(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 0, struct.pack("<H", world.modified_starters[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 2, struct.pack("<H", world.modified_starters[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 4, struct.pack("<H", world.modified_starters[2]))
|
||||
|
||||
|
||||
def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _set_tm_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
||||
tmhm_list_address = data.rom_addresses["sTMHMMoves"]
|
||||
|
||||
for i, move in enumerate(world.modified_tmhm_moves):
|
||||
|
@ -686,19 +772,24 @@ def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tupl
|
|||
if i >= 50:
|
||||
break
|
||||
|
||||
_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])
|
||||
patch.write_token(APTokenTypes.WRITE, tmhm_list_address + (i * 2), struct.pack("<H", easter_egg[1]))
|
||||
else:
|
||||
patch.write_token(APTokenTypes.WRITE, tmhm_list_address + (i * 2), struct.pack("<H", move))
|
||||
|
||||
|
||||
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
learnsets_address = data.rom_addresses["gTMHMLearnsets"]
|
||||
|
||||
for species in world.modified_species.values():
|
||||
_set_bytes_le(rom, learnsets_address + (species.species_id * 8), 8, species.tm_hm_compatibility)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
learnsets_address + (species.species_id * 8),
|
||||
struct.pack("<Q", species.tm_hm_compatibility)
|
||||
)
|
||||
|
||||
|
||||
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
probability = world.options.double_battle_chance.value / 100
|
||||
|
||||
battle_type_map = {
|
||||
|
@ -710,26 +801,29 @@ def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray
|
|||
|
||||
for trainer_data in data.trainers:
|
||||
if trainer_data.script_address != 0 and len(trainer_data.party.pokemon) > 1:
|
||||
original_battle_type = rom[trainer_data.script_address + 1]
|
||||
original_battle_type = trainer_data.battle_type
|
||||
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)
|
||||
patch.write_token(APTokenTypes.WRITE, trainer_data.address + 0x18, struct.pack("<B", 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(
|
||||
rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
trainer_data.script_address + 1,
|
||||
1,
|
||||
battle_type_map[original_battle_type]
|
||||
struct.pack("<B", battle_type_map[original_battle_type])
|
||||
)
|
||||
|
||||
|
||||
def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, 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])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gTutorMoves"] + (i * 2),
|
||||
struct.pack("<H", easter_egg[1])
|
||||
)
|
||||
else:
|
||||
if world.options.tm_tutor_moves:
|
||||
new_tutor_moves = []
|
||||
|
@ -737,17 +831,27 @@ def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", rom: bytearray, ea
|
|||
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)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gTutorMoves"] + (i * 2),
|
||||
struct.pack("<H", new_move)
|
||||
)
|
||||
|
||||
# Always set Fortree move tutor to Dig
|
||||
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (24 * 2), 2, data.constants["MOVE_DIG"])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gTutorMoves"] + (24 * 2),
|
||||
struct.pack("<H", data.constants["MOVE_DIG"])
|
||||
)
|
||||
|
||||
# Modify compatibility
|
||||
if world.options.tm_tutor_compatibility.value != -1:
|
||||
for species in data.species.values():
|
||||
_set_bytes_le(
|
||||
rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
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)])
|
||||
struct.pack("<I", bool_array_to_int([
|
||||
world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value
|
||||
for _ in range(32)
|
||||
]))
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue