Pokemon Red/Blue: Convert to Procedure Patch (#4801)

This commit is contained in:
Bryce Wilson
2025-04-30 07:31:33 -07:00
committed by GitHub
parent 611e1c2b19
commit 227f0bce3d
4 changed files with 334 additions and 329 deletions

View File

@@ -18,7 +18,7 @@ from .regions import create_regions
from .options import PokemonRBOptions from .options import PokemonRBOptions
from .rom_addresses import rom_addresses from .rom_addresses import rom_addresses
from .text import encode_text from .text import encode_text
from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, RedDeltaPatch, BlueDeltaPatch from .rom import generate_output, PokemonRedProcedurePatch, PokemonBlueProcedurePatch
from .pokemon import process_pokemon_data, process_move_data, verify_hm_moves from .pokemon import process_pokemon_data, process_move_data, verify_hm_moves
from .encounters import process_pokemon_locations, process_trainer_data from .encounters import process_pokemon_locations, process_trainer_data
from .rules import set_rules from .rules import set_rules
@@ -33,12 +33,12 @@ class PokemonSettings(settings.Group):
"""File names of the Pokemon Red and Blue roms""" """File names of the Pokemon Red and Blue roms"""
description = "Pokemon Red (UE) ROM File" description = "Pokemon Red (UE) ROM File"
copy_to = "Pokemon Red (UE) [S][!].gb" copy_to = "Pokemon Red (UE) [S][!].gb"
md5s = [RedDeltaPatch.hash] md5s = [PokemonRedProcedurePatch.hash]
class BlueRomFile(settings.UserFilePath): class BlueRomFile(settings.UserFilePath):
description = "Pokemon Blue (UE) ROM File" description = "Pokemon Blue (UE) ROM File"
copy_to = "Pokemon Blue (UE) [S][!].gb" copy_to = "Pokemon Blue (UE) [S][!].gb"
md5s = [BlueDeltaPatch.hash] md5s = [PokemonBlueProcedurePatch.hash]
red_rom_file: RedRomFile = RedRomFile(RedRomFile.copy_to) red_rom_file: RedRomFile = RedRomFile(RedRomFile.copy_to)
blue_rom_file: BlueRomFile = BlueRomFile(BlueRomFile.copy_to) blue_rom_file: BlueRomFile = BlueRomFile(BlueRomFile.copy_to)
@@ -113,16 +113,6 @@ class PokemonRedBlueWorld(World):
self.local_locs = [] self.local_locs = []
self.pc_item = None self.pc_item = None
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld):
versions = set()
for player in multiworld.player_ids:
if multiworld.worlds[player].game == "Pokemon Red and Blue":
versions.add(multiworld.worlds[player].options.game_version.current_key)
for version in versions:
if not os.path.exists(get_base_rom_path(version)):
raise FileNotFoundError(get_base_rom_path(version))
@classmethod @classmethod
def stage_generate_early(cls, multiworld: MultiWorld): def stage_generate_early(cls, multiworld: MultiWorld):

View File

@@ -1,9 +1,17 @@
from copy import deepcopy from copy import deepcopy
import typing
from worlds.Files import APTokenTypes
from . import poke_data, logic from . import poke_data, logic
from .rom_addresses import rom_addresses from .rom_addresses import rom_addresses
if typing.TYPE_CHECKING:
from . import PokemonRedBlueWorld
from .rom import PokemonRedProcedurePatch, PokemonBlueProcedurePatch
def set_mon_palettes(world, random, data):
def set_mon_palettes(world: "PokemonRedBlueWorld", patch: "PokemonRedProcedurePatch | PokemonBlueProcedurePatch"):
if world.options.randomize_pokemon_palettes == "vanilla": if world.options.randomize_pokemon_palettes == "vanilla":
return return
pallet_map = { pallet_map = {
@@ -31,12 +39,9 @@ def set_mon_palettes(world, random, data):
poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"): poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"):
pallet = palettes[-1] pallet = palettes[-1]
else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions) else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions)
pallet = random.choice(list(pallet_map.values())) pallet = world.random.choice(list(pallet_map.values()))
palettes.append(pallet) palettes.append(pallet)
address = rom_addresses["Mon_Palettes"] patch.write_token(APTokenTypes.WRITE, rom_addresses["Mon_Palettes"], bytes(palettes))
for pallet in palettes:
data[address] = pallet
address += 1
def choose_forced_type(chances, random): def choose_forced_type(chances, random):
@@ -253,9 +258,9 @@ def process_pokemon_data(self):
mon_data[f"start move {i}"] = learnsets[mon].pop(0) mon_data[f"start move {i}"] = learnsets[mon].pop(0)
if self.options.randomize_pokemon_catch_rates: if self.options.randomize_pokemon_catch_rates:
mon_data["catch rate"] = self.random.randint(self.options.minimum_catch_rate, 255) mon_data["catch rate"] = self.random.randint(self.options.minimum_catch_rate.value, 255)
else: else:
mon_data["catch rate"] = max(self.options.minimum_catch_rate, mon_data["catch rate"]) mon_data["catch rate"] = max(self.options.minimum_catch_rate.value, mon_data["catch rate"])
def roll_tm_compat(roll_move): def roll_tm_compat(roll_move):
if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]: if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]:

View File

@@ -1,5 +1,55 @@
import random
import typing
from worlds.Files import APTokenTypes
from .rom_addresses import rom_addresses from .rom_addresses import rom_addresses
if typing.TYPE_CHECKING:
from .rom import PokemonBlueProcedurePatch, PokemonRedProcedurePatch
layout1F = [
[20, 22, 32, 34, 20, 25, 22, 32, 34, 20, 25, 25, 25, 22, 20, 25, 22, 2, 2, 2],
[24, 26, 40, 1, 24, 25, 26, 62, 1, 28, 29, 29, 29, 30, 28, 29, 30, 1, 40, 2],
[28, 30, 1, 1, 28, 29, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 23],
[23, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 31],
[31, 1, 1, 1, 1, 1, 31, 32, 34, 2, 1, 1, 2, 32, 34, 32, 34, 1, 1, 23],
[23, 1, 1, 23, 1, 1, 23, 1, 40, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31],
[31, 1, 1, 31, 1, 1, 31, 1, 1, 31, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23],
[23, 1, 1, 23, 1, 1, 1, 1, 1, 2, 32, 34, 32, 34, 32, 34, 32, 34, 2, 31],
[31, 1, 1, 31, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 23, 1, 1, 40, 23],
[23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31, 1, 1, 1, 31, 1, 1, 1, 31],
[31, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 23, 1, 1, 1, 23],
[23, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 31, 1, 1, 1, 31, 1, 1, 1, 31],
[31, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 23],
[ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31, 1, 1, 1, 31],
[20, 21, 21, 21, 22, 42, 1, 1, 1, 1, 20, 21, 22, 1, 1, 1, 1, 1, 1, 23],
[24, 25, 25, 25, 26, 1, 1, 1, 1, 1, 24, 25, 26, 1, 1, 1, 1, 1, 1, 31],
[24, 25, 25, 25, 26, 1, 1, 62, 1, 1, 24, 25, 26, 20, 21, 21, 21, 21, 21, 22],
[28, 29, 29, 29, 30, 78, 81, 82, 77, 78, 28, 29, 30, 28, 29, 29, 29, 29, 29, 30],
]
layout2F = [
[23, 2, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34],
[31, 62, 1, 23, 1, 1, 23, 1, 1, 1, 1, 1, 23, 62, 1, 1, 1, 1, 1, 2],
[23, 1, 1, 31, 1, 1, 31, 1, 1, 1, 1, 1, 31, 1, 1, 1, 1, 1, 1, 23],
[31, 1, 1, 23, 1, 1, 23, 1, 1, 23, 1, 1, 23, 1, 1, 23, 23, 1, 1, 31],
[23, 1, 1, 31, 1, 1, 31, 1, 1, 31, 2, 2, 31, 1, 1, 31, 31, 1, 1, 23],
[31, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 62, 23, 1, 1, 1, 1, 1, 1, 31],
[23, 1, 1, 1, 1, 1, 31, 1, 1, 1, 1, 1, 31, 1, 1, 1, 1, 1, 1, 23],
[31, 1, 1, 23, 1, 1, 1, 1, 1, 23, 32, 34, 32, 34, 32, 34, 1, 1, 1, 31],
[23, 1, 1, 31, 1, 1, 1, 1, 1, 31, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23],
[31, 1, 1, 23, 1, 1, 2, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31],
[23, 1, 1, 31, 1, 1, 2, 1, 1, 31, 1, 1, 1, 32, 34, 32, 34, 32, 34, 23],
[31, 2, 2, 2, 1, 1, 32, 34, 32, 34, 1, 1, 1, 23, 1, 1, 1, 1, 1, 31],
[23, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 31, 1, 1, 62, 1, 1, 23],
[31, 1, 1, 1, 1, 1, 31, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 31],
[23, 32, 34, 32, 34, 32, 34, 1, 1, 32, 34, 32, 34, 31, 1, 1, 1, 1, 1, 23],
[31, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31],
[ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23],
[32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 32, 34, 2, 31]
]
disallowed1F = [[2, 2], [3, 2], [1, 8], [2, 8], [7, 7], [8, 7], [10, 4], [11, 4], [11, 12], disallowed1F = [[2, 2], [3, 2], [1, 8], [2, 8], [7, 7], [8, 7], [10, 4], [11, 4], [11, 12],
[11, 13], [16, 10], [17, 10], [18, 10], [16, 12], [17, 12], [18, 12]] [11, 13], [16, 10], [17, 10], [18, 10], [16, 12], [17, 12], [18, 12]]
disallowed2F = [[16, 2], [17, 2], [18, 2], [15, 5], [15, 6], [10, 10], [11, 10], [12, 10], [7, 14], [8, 14], [1, 15], disallowed2F = [[16, 2], [17, 2], [18, 2], [15, 5], [15, 6], [10, 10], [11, 10], [12, 10], [7, 14], [8, 14], [1, 15],
@@ -7,29 +57,12 @@ disallowed2F = [[16, 2], [17, 2], [18, 2], [15, 5], [15, 6], [10, 10], [11, 10],
[11, 1]] [11, 1]]
def randomize_rock_tunnel(data, random): def randomize_rock_tunnel(patch: "PokemonRedProcedurePatch | PokemonBlueProcedurePatch", random: random.Random):
seed = random.randint(0, 999999999999999999) seed = random.randint(0, 999999999999999999)
random.seed(seed) random.seed(seed)
map1f = [] map1f = [row.copy() for row in layout1F]
map2f = [] map2f = [row.copy() for row in layout2F]
address = rom_addresses["Map_Rock_Tunnel1F"]
for y in range(0, 18):
row = []
for x in range(0, 20):
row.append(data[address])
address += 1
map1f.append(row)
address = rom_addresses["Map_Rock_TunnelB1F"]
for y in range(0, 18):
row = []
for x in range(0, 20):
row.append(data[address])
address += 1
map2f.append(row)
current_map = map1f current_map = map1f
@@ -305,14 +338,6 @@ def randomize_rock_tunnel(data, random):
current_map = map2f current_map = map2f
check_addable_block(map2f, disallowed2F) check_addable_block(map2f, disallowed2F)
address = rom_addresses["Map_Rock_Tunnel1F"] patch.write_token(APTokenTypes.WRITE, rom_addresses["Map_Rock_Tunnel1F"], bytes([b for row in map1f for b in row]))
for y in map1f: patch.write_token(APTokenTypes.WRITE, rom_addresses["Map_Rock_TunnelB1F"], bytes([b for row in map2f for b in row]))
for x in y: return seed
data[address] = x
address += 1
address = rom_addresses["Map_Rock_TunnelB1F"]
for y in map2f:
for x in y:
data[address] = x
address += 1
return seed

View File

@@ -1,21 +1,66 @@
import os import os
import hashlib
import Utils
import bsdiff4
import pkgutil import pkgutil
from worlds.Files import APDeltaPatch import typing
from .text import encode_text
import Utils
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
from . import poke_data
from .items import item_table from .items import item_table
from .text import encode_text
from .pokemon import set_mon_palettes from .pokemon import set_mon_palettes
from .regions import PokemonRBWarp, map_ids, town_map_coords
from .rock_tunnel import randomize_rock_tunnel from .rock_tunnel import randomize_rock_tunnel
from .rom_addresses import rom_addresses from .rom_addresses import rom_addresses
from .regions import PokemonRBWarp, map_ids, town_map_coords
from . import poke_data if typing.TYPE_CHECKING:
from . import PokemonRedBlueWorld
def write_quizzes(world, data, random): class PokemonRedProcedurePatch(APProcedurePatch, APTokenMixin):
game = "Pokemon Red and Blue"
hash = "3d45c1ee9abd5738df46d2bdda8b57dc"
patch_file_ending = ".apred"
result_file_ending = ".gb"
def get_quiz(q, a): procedure = [
("apply_bsdiff4", ["base_patch.bsdiff4"]),
("apply_tokens", ["token_data.bin"]),
]
@classmethod
def get_source_data(cls) -> bytes:
from . import PokemonRedBlueWorld
with open(PokemonRedBlueWorld.settings.red_rom_file, "rb") as infile:
base_rom_bytes = bytes(infile.read())
return base_rom_bytes
class PokemonBlueProcedurePatch(APProcedurePatch, APTokenMixin):
game = "Pokemon Red and Blue"
hash = "50927e843568814f7ed45ec4f944bd8b"
patch_file_ending = ".apblue"
result_file_ending = ".gb"
procedure = [
("apply_bsdiff4", ["base_patch.bsdiff4"]),
("apply_tokens", ["token_data.bin"]),
]
@classmethod
def get_source_data(cls) -> bytes:
from . import PokemonRedBlueWorld
with open(PokemonRedBlueWorld.settings.blue_rom_file, "rb") as infile:
base_rom_bytes = bytes(infile.read())
return base_rom_bytes
def write_quizzes(world: "PokemonRedBlueWorld", patch: PokemonBlueProcedurePatch | PokemonRedProcedurePatch):
random = world.random
def get_quiz(q: int, a: int):
if q == 0: if q == 0:
r = random.randint(0, 3) r = random.randint(0, 3)
if r == 0: if r == 0:
@@ -122,13 +167,13 @@ def write_quizzes(world, data, random):
elif q2 == 1: elif q2 == 1:
if a: if a:
state = random.choice( state = random.choice(
['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', ["Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut",
'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas",
'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota",
'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Jersey', 'New Mexico', "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Jersey", "New Mexico",
'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania",
'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont",
'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']) "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"])
else: else:
state = "New Hampshire" state = "New Hampshire"
return encode_text( return encode_text(
@@ -209,7 +254,7 @@ def write_quizzes(world, data, random):
return encode_text(f"{type1} deals<LINE>{eff}damage to<CONT>{type2} type?<DONE>") return encode_text(f"{type1} deals<LINE>{eff}damage to<CONT>{type2} type?<DONE>")
elif q == 14: elif q == 14:
fossil_level = world.multiworld.get_location("Fossil Level - Trainer Parties", fossil_level = world.multiworld.get_location("Fossil Level - Trainer Parties",
world.player).party_data[0]['level'] world.player).party_data[0]["level"]
if not a: if not a:
fossil_level += random.choice((-5, 5)) fossil_level += random.choice((-5, 5))
return encode_text(f"Fossil #MON<LINE>revive at level<CONT>{fossil_level}?<DONE>") return encode_text(f"Fossil #MON<LINE>revive at level<CONT>{fossil_level}?<DONE>")
@@ -224,46 +269,49 @@ def write_quizzes(world, data, random):
return encode_text(f"According to<LINE>Monash Uni.,<CONT>{fodmap} {are_is}<CONT>considered high<CONT>in FODMAPs?<DONE>") return encode_text(f"According to<LINE>Monash Uni.,<CONT>{fodmap} {are_is}<CONT>considered high<CONT>in FODMAPs?<DONE>")
answers = [random.randint(0, 1) for _ in range(6)] answers = [random.randint(0, 1) for _ in range(6)]
questions = random.sample((range(0, 16)), 6) questions = random.sample((range(0, 16)), 6)
question_texts: list[bytearray] = []
question_texts = []
for i, question in enumerate(questions): for i, question in enumerate(questions):
question_texts.append(get_quiz(question, answers[i])) question_texts.append(get_quiz(question, answers[i]))
for i, quiz in enumerate(["A", "B", "C", "D", "E", "F"]): for i, quiz in enumerate(["A", "B", "C", "D", "E", "F"]):
data[rom_addresses[f"Quiz_Answer_{quiz}"]] = int(not answers[i]) << 4 | (i + 1) patch.write_token(APTokenTypes.WRITE, rom_addresses[f"Quiz_Answer_{quiz}"], bytes([int(not answers[i]) << 4 | (i + 1)]))
write_bytes(data, question_texts[i], rom_addresses[f"Text_Quiz_{quiz}"]) patch.write_token(APTokenTypes.WRITE, rom_addresses[f"Text_Quiz_{quiz}"], bytes(question_texts[i]))
def generate_output(world, output_directory: str): def generate_output(world: "PokemonRedBlueWorld", output_directory: str):
random = world.random
game_version = world.options.game_version.current_key game_version = world.options.game_version.current_key
data = bytes(get_base_rom_bytes(game_version))
base_patch = pkgutil.get_data(__name__, f'basepatch_{game_version}.bsdiff4') patch_type = PokemonBlueProcedurePatch if game_version == "blue" else PokemonRedProcedurePatch
patch = patch_type(player=world.player, player_name=world.player_name)
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, f"basepatch_{game_version}.bsdiff4"))
data = bytearray(bsdiff4.patch(data, base_patch)) def write_bytes(address: int, data: typing.Sequence[int] | int):
if isinstance(data, int):
data = bytes([data])
else:
data = bytes(data)
basemd5 = hashlib.md5() patch.write_token(APTokenTypes.WRITE, address, data)
basemd5.update(data)
pallet_connections = {entrance: world.multiworld.get_entrance(f"Pallet Town to {entrance}", pallet_connections = {entrance: world.multiworld.get_entrance(f"Pallet Town to {entrance}",
world.player).connected_region.name for world.player).connected_region.name
entrance in ["Player's House 1F", "Oak's Lab", for entrance in ["Player's House 1F", "Oak's Lab", "Rival's House"]}
"Rival's House"]}
paths = None paths = None
if pallet_connections["Player's House 1F"] == "Oak's Lab": if pallet_connections["Player's House 1F"] == "Oak's Lab":
paths = ((0x00, 4, 0x80, 5, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x20, 5, 0x80, 5, 0xFF)) paths = (bytes([0x00, 4, 0x80, 5, 0x40, 1, 0xE0, 1, 0xFF]), bytes([0x40, 2, 0x20, 5, 0x80, 5, 0xFF]))
elif pallet_connections["Rival's House"] == "Oak's Lab": elif pallet_connections["Rival's House"] == "Oak's Lab":
paths = ((0x00, 4, 0xC0, 3, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x10, 3, 0x80, 5, 0xFF)) paths = (bytes([0x00, 4, 0xC0, 3, 0x40, 1, 0xE0, 1, 0xFF]), bytes([0x40, 2, 0x10, 3, 0x80, 5, 0xFF]))
if paths: if paths:
write_bytes(data, paths[0], rom_addresses["Path_Pallet_Oak"]) write_bytes(rom_addresses["Path_Pallet_Oak"], paths[0])
write_bytes(data, paths[1], rom_addresses["Path_Pallet_Player"]) write_bytes(rom_addresses["Path_Pallet_Player"], paths[1])
if pallet_connections["Rival's House"] == "Player's House 1F": if pallet_connections["Rival's House"] == "Player's House 1F":
write_bytes(data, [0x2F, 0xC7, 0x06, 0x0D, 0x00, 0x01], rom_addresses["Pallet_Fly_Coords"]) write_bytes(rom_addresses["Pallet_Fly_Coords"], [0x2F, 0xC7, 0x06, 0x0D, 0x00, 0x01])
elif pallet_connections["Oak's Lab"] == "Player's House 1F": elif pallet_connections["Oak's Lab"] == "Player's House 1F":
write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"]) write_bytes(rom_addresses["Pallet_Fly_Coords"], [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00])
for region in world.multiworld.get_regions(world.player): for region in world.multiworld.get_regions(world.player):
for entrance in region.exits: for entrance in region.exits:
@@ -281,16 +329,18 @@ def generate_output(world, output_directory: str):
while i > len(warp_to_ids) - 1: while i > len(warp_to_ids) - 1:
i -= len(warp_to_ids) i -= len(warp_to_ids)
connected_map_name = entrance.connected_region.name.split("-")[0] connected_map_name = entrance.connected_region.name.split("-")[0]
data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i] write_bytes(address, [
data[address + 1] = map_ids[connected_map_name] 0 if "Elevator" in connected_map_name else warp_to_ids[i],
map_ids[connected_map_name]
])
if world.options.door_shuffle == "simple": if world.options.door_shuffle == "simple":
for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values(): for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values():
destination = world.multiworld.get_entrance(entrance, world.player).connected_region.name destination = world.multiworld.get_entrance(entrance, world.player).connected_region.name
(_, x, y, _, _, map_order_entry) = town_map_coords[destination] (_, x, y, _, _, map_order_entry) = town_map_coords[destination]
for map_coord_entry in map_coords_entries: for map_coord_entry in map_coords_entries:
data[rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1] = (y << 4) | x write_bytes(rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1, (y << 4) | x)
data[rom_addresses["Town_Map_Order"] + map_order_entry] = map_ids[map_name] write_bytes(rom_addresses["Town_Map_Order"] + map_order_entry, map_ids[map_name])
if not world.options.key_items_only: if not world.options.key_items_only:
for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM", for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM",
@@ -302,13 +352,13 @@ def generate_output(world, output_directory: str):
try: try:
tm = int(item_name[2:4]) tm = int(item_name[2:4])
move = poke_data.moves[world.local_tms[tm - 1]]["id"] move = poke_data.moves[world.local_tms[tm - 1]]["id"]
data[rom_addresses["Gym_Leader_Moves"] + (2 * i)] = move write_bytes(rom_addresses["Gym_Leader_Moves"] + (2 * i), move)
except KeyError: except KeyError:
pass pass
def set_trade_mon(address, loc): def set_trade_mon(address, loc):
mon = world.multiworld.get_location(loc, world.player).item.name mon = world.multiworld.get_location(loc, world.player).item.name
data[rom_addresses[address]] = poke_data.pokemon_data[mon]["id"] write_bytes(rom_addresses[address], poke_data.pokemon_data[mon]["id"])
world.trade_mons[address] = mon world.trade_mons[address] = mon
if game_version == "red": if game_version == "red":
@@ -325,141 +375,139 @@ def generate_output(world, output_directory: str):
set_trade_mon("Trade_Doris", "Cerulean Cave 1F - Wild Pokemon - 9") set_trade_mon("Trade_Doris", "Cerulean Cave 1F - Wild Pokemon - 9")
set_trade_mon("Trade_Crinkles", "Route 12 - Wild Pokemon - 4") set_trade_mon("Trade_Crinkles", "Route 12 - Wild Pokemon - 4")
data[rom_addresses['Fly_Location']] = world.fly_map_code write_bytes(rom_addresses["Fly_Location"], world.fly_map_code)
data[rom_addresses['Map_Fly_Location']] = world.town_map_fly_map_code write_bytes(rom_addresses["Map_Fly_Location"], world.town_map_fly_map_code)
if world.options.fix_combat_bugs: if world.options.fix_combat_bugs:
data[rom_addresses["Option_Fix_Combat_Bugs"]] = 1 write_bytes(rom_addresses["Option_Fix_Combat_Bugs"], 1)
data[rom_addresses["Option_Fix_Combat_Bugs_Focus_Energy"]] = 0x28 # jr z write_bytes(rom_addresses["Option_Fix_Combat_Bugs_Focus_Energy"], 0x28) # jr z
data[rom_addresses["Option_Fix_Combat_Bugs_HP_Drain_Dream_Eater"]] = 0x1A # ld a, (de) write_bytes(rom_addresses["Option_Fix_Combat_Bugs_HP_Drain_Dream_Eater"], 0x1A) # ld a, (de)
data[rom_addresses["Option_Fix_Combat_Bugs_PP_Restore"]] = 0xe6 # and a, direct write_bytes(rom_addresses["Option_Fix_Combat_Bugs_PP_Restore"], 0xe6) # and a, direct
data[rom_addresses["Option_Fix_Combat_Bugs_PP_Restore"] + 1] = 0b0011111 write_bytes(rom_addresses["Option_Fix_Combat_Bugs_PP_Restore"] + 1, 0b0011111)
data[rom_addresses["Option_Fix_Combat_Bugs_Struggle"]] = 0xe6 # and a, direct write_bytes(rom_addresses["Option_Fix_Combat_Bugs_Struggle"], 0xe6) # and a, direct
data[rom_addresses["Option_Fix_Combat_Bugs_Struggle"] + 1] = 0x3f write_bytes(rom_addresses["Option_Fix_Combat_Bugs_Struggle"] + 1, 0x3f)
data[rom_addresses["Option_Fix_Combat_Bugs_Dig_Fly"]] = 0b10001100 write_bytes(rom_addresses["Option_Fix_Combat_Bugs_Dig_Fly"], 0b10001100)
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"]] = 0x20 # jr nz, write_bytes(rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"], 0x20) # jr nz,
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"] + 1] = 5 # 5 bytes ahead write_bytes(rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"] + 1, 5) # 5 bytes ahead
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Stat_Modifiers"]] = 1 write_bytes(rom_addresses["Option_Fix_Combat_Bugs_Heal_Stat_Modifiers"], 1)
if world.options.poke_doll_skip == "in_logic": if world.options.poke_doll_skip == "in_logic":
data[rom_addresses["Option_Silph_Scope_Skip"]] = 0x00 # nop write_bytes(rom_addresses["Option_Silph_Scope_Skip"], 0x00) # nop
data[rom_addresses["Option_Silph_Scope_Skip"] + 1] = 0x00 # nop write_bytes(rom_addresses["Option_Silph_Scope_Skip"] + 1, 0x00) # nop
data[rom_addresses["Option_Silph_Scope_Skip"] + 2] = 0x00 # nop write_bytes(rom_addresses["Option_Silph_Scope_Skip"] + 2, 0x00) # nop
if world.options.bicycle_gate_skips == "patched": if world.options.bicycle_gate_skips == "patched":
data[rom_addresses["Option_Route_16_Gate_Fix"]] = 0x00 # nop write_bytes(rom_addresses["Option_Route_16_Gate_Fix"], 0x00) # nop
data[rom_addresses["Option_Route_16_Gate_Fix"] + 1] = 0x00 # nop write_bytes(rom_addresses["Option_Route_16_Gate_Fix"] + 1, 0x00) # nop
data[rom_addresses["Option_Route_18_Gate_Fix"]] = 0x00 # nop write_bytes(rom_addresses["Option_Route_18_Gate_Fix"], 0x00) # nop
data[rom_addresses["Option_Route_18_Gate_Fix"] + 1] = 0x00 # nop write_bytes(rom_addresses["Option_Route_18_Gate_Fix"] + 1, 0x00) # nop
if world.options.door_shuffle: if world.options.door_shuffle:
data[rom_addresses["Entrance_Shuffle_Fuji_Warp"]] = 1 # prevent warping to Fuji's House from Pokemon Tower 7F write_bytes(rom_addresses["Entrance_Shuffle_Fuji_Warp"], 1) # prevent warping to Fuji's House from Pokemon Tower 7F
if world.options.all_elevators_locked: if world.options.all_elevators_locked:
data[rom_addresses["Option_Locked_Elevator_Celadon"]] = 0x20 # jr nz write_bytes(rom_addresses["Option_Locked_Elevator_Celadon"], 0x20) # jr nz
data[rom_addresses["Option_Locked_Elevator_Silph"]] = 0x20 # jr nz write_bytes(rom_addresses["Option_Locked_Elevator_Silph"], 0x20) # jr nz
if world.options.tea: if world.options.tea:
data[rom_addresses["Option_Tea"]] = 1 write_bytes(rom_addresses["Option_Tea"], 1)
data[rom_addresses["Guard_Drink_List"]] = 0x54 write_bytes(rom_addresses["Guard_Drink_List"], 0x54)
data[rom_addresses["Guard_Drink_List"] + 1] = 0 write_bytes(rom_addresses["Guard_Drink_List"] + 1, 0)
data[rom_addresses["Guard_Drink_List"] + 2] = 0 write_bytes(rom_addresses["Guard_Drink_List"] + 2, 0)
write_bytes(data, encode_text("<LINE>Gee, I have the<CONT>worst caffeine<CONT>headache though." write_bytes(rom_addresses["Text_Saffron_Gate"],
"<PARA>Oh wait there,<LINE>the road's closed.<DONE>"), encode_text("<LINE>Gee, I have the<CONT>worst caffeine<CONT>headache though."
rom_addresses["Text_Saffron_Gate"]) "<PARA>Oh wait there,<LINE>the road's closed.<DONE>"))
data[rom_addresses["Tea_Key_Item_A"]] = 0x28 # jr .z write_bytes(rom_addresses["Tea_Key_Item_A"], 0x28) # jr .z
data[rom_addresses["Tea_Key_Item_B"]] = 0x28 # jr .z write_bytes(rom_addresses["Tea_Key_Item_B"], 0x28) # jr .z
data[rom_addresses["Tea_Key_Item_C"]] = 0x28 # jr .z write_bytes(rom_addresses["Tea_Key_Item_C"], 0x28) # jr .z
data[rom_addresses["Fossils_Needed_For_Second_Item"]] = ( write_bytes(rom_addresses["Fossils_Needed_For_Second_Item"], world.options.second_fossil_check_condition.value)
world.options.second_fossil_check_condition.value)
data[rom_addresses["Option_Lose_Money"]] = int(not world.options.lose_money_on_blackout.value) write_bytes(rom_addresses["Option_Lose_Money"], int(not world.options.lose_money_on_blackout.value))
if world.options.extra_key_items: if world.options.extra_key_items:
data[rom_addresses['Option_Extra_Key_Items_A']] = 1 write_bytes(rom_addresses["Option_Extra_Key_Items_A"], 1)
data[rom_addresses['Option_Extra_Key_Items_B']] = 1 write_bytes(rom_addresses["Option_Extra_Key_Items_B"], 1)
data[rom_addresses['Option_Extra_Key_Items_C']] = 1 write_bytes(rom_addresses["Option_Extra_Key_Items_C"], 1)
data[rom_addresses['Option_Extra_Key_Items_D']] = 1 write_bytes(rom_addresses["Option_Extra_Key_Items_D"], 1)
data[rom_addresses["Option_Split_Card_Key"]] = world.options.split_card_key.value write_bytes(rom_addresses["Option_Split_Card_Key"], world.options.split_card_key.value)
data[rom_addresses["Option_Blind_Trainers"]] = round(world.options.blind_trainers.value * 2.55) write_bytes(rom_addresses["Option_Blind_Trainers"], round(world.options.blind_trainers.value * 2.55))
data[rom_addresses["Option_Cerulean_Cave_Badges"]] = world.options.cerulean_cave_badges_condition.value write_bytes(rom_addresses["Option_Cerulean_Cave_Badges"], world.options.cerulean_cave_badges_condition.value)
data[rom_addresses["Option_Cerulean_Cave_Key_Items"]] = world.options.cerulean_cave_key_items_condition.total write_bytes(rom_addresses["Option_Cerulean_Cave_Key_Items"], world.options.cerulean_cave_key_items_condition.total)
write_bytes(data, encode_text(str(world.options.cerulean_cave_badges_condition.value)), rom_addresses["Text_Cerulean_Cave_Badges"]) write_bytes(rom_addresses["Text_Cerulean_Cave_Badges"], encode_text(str(world.options.cerulean_cave_badges_condition.value)))
write_bytes(data, encode_text(str(world.options.cerulean_cave_key_items_condition.total) + " key items."), rom_addresses["Text_Cerulean_Cave_Key_Items"]) write_bytes(rom_addresses["Text_Cerulean_Cave_Key_Items"], encode_text(str(world.options.cerulean_cave_key_items_condition.total) + " key items."))
data[rom_addresses['Option_Encounter_Minimum_Steps']] = world.options.minimum_steps_between_encounters.value write_bytes(rom_addresses["Option_Encounter_Minimum_Steps"], world.options.minimum_steps_between_encounters.value)
data[rom_addresses['Option_Route23_Badges']] = world.options.victory_road_condition.value write_bytes(rom_addresses["Option_Route23_Badges"], world.options.victory_road_condition.value)
data[rom_addresses['Option_Victory_Road_Badges']] = world.options.route_22_gate_condition.value write_bytes(rom_addresses["Option_Victory_Road_Badges"], world.options.route_22_gate_condition.value)
data[rom_addresses['Option_Elite_Four_Pokedex']] = world.options.elite_four_pokedex_condition.total write_bytes(rom_addresses["Option_Elite_Four_Pokedex"], world.options.elite_four_pokedex_condition.total)
data[rom_addresses['Option_Elite_Four_Key_Items']] = world.options.elite_four_key_items_condition.total write_bytes(rom_addresses["Option_Elite_Four_Key_Items"], world.options.elite_four_key_items_condition.total)
data[rom_addresses['Option_Elite_Four_Badges']] = world.options.elite_four_badges_condition.value write_bytes(rom_addresses["Option_Elite_Four_Badges"], world.options.elite_four_badges_condition.value)
write_bytes(data, encode_text(str(world.options.elite_four_badges_condition.value)), rom_addresses["Text_Elite_Four_Badges"]) write_bytes(rom_addresses["Text_Elite_Four_Badges"], encode_text(str(world.options.elite_four_badges_condition.value)))
write_bytes(data, encode_text(str(world.options.elite_four_key_items_condition.total) + " key items, and"), rom_addresses["Text_Elite_Four_Key_Items"]) write_bytes(rom_addresses["Text_Elite_Four_Key_Items"], encode_text(str(world.options.elite_four_key_items_condition.total) + " key items, and"))
write_bytes(data, encode_text(str(world.options.elite_four_pokedex_condition.total) + " #MON"), rom_addresses["Text_Elite_Four_Pokedex"]) write_bytes(rom_addresses["Text_Elite_Four_Pokedex"], encode_text(str(world.options.elite_four_pokedex_condition.total) + " #MON"))
write_bytes(data, encode_text(str(world.total_key_items), length=2), rom_addresses["Trainer_Screen_Total_Key_Items"]) write_bytes(rom_addresses["Trainer_Screen_Total_Key_Items"], encode_text(str(world.total_key_items), length=2))
data[rom_addresses['Option_Viridian_Gym_Badges']] = world.options.viridian_gym_condition.value write_bytes(rom_addresses["Option_Viridian_Gym_Badges"], world.options.viridian_gym_condition.value)
data[rom_addresses['Option_EXP_Modifier']] = world.options.exp_modifier.value write_bytes(rom_addresses["Option_EXP_Modifier"], world.options.exp_modifier.value)
if not world.options.require_item_finder: if not world.options.require_item_finder:
data[rom_addresses['Option_Itemfinder']] = 0 # nop write_bytes(rom_addresses["Option_Itemfinder"], 0) # nop
if world.options.extra_strength_boulders: if world.options.extra_strength_boulders:
for i in range(0, 3): for i in range(0, 3):
data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15 write_bytes(rom_addresses["Option_Boulders"] + (i * 3), 0x15)
if world.options.extra_key_items: if world.options.extra_key_items:
for i in range(0, 4): for i in range(0, 4):
data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15 write_bytes(rom_addresses["Option_Rock_Tunnel_Extra_Items"] + (i * 3), 0x15)
if world.options.old_man == "open_viridian_city": if world.options.old_man == "open_viridian_city":
data[rom_addresses['Option_Old_Man']] = 0x11 write_bytes(rom_addresses["Option_Old_Man"], 0x11)
data[rom_addresses['Option_Old_Man_Lying']] = 0x15 write_bytes(rom_addresses["Option_Old_Man_Lying"], 0x15)
data[rom_addresses['Option_Route3_Guard_B']] = world.options.route_3_condition.value write_bytes(rom_addresses["Option_Route3_Guard_B"], world.options.route_3_condition.value)
if world.options.route_3_condition == "open": if world.options.route_3_condition == "open":
data[rom_addresses['Option_Route3_Guard_A']] = 0x11 write_bytes(rom_addresses["Option_Route3_Guard_A"], 0x11)
if not world.options.robbed_house_officer: if not world.options.robbed_house_officer:
data[rom_addresses['Option_Trashed_House_Guard_A']] = 0x15 write_bytes(rom_addresses["Option_Trashed_House_Guard_A"], 0x15)
data[rom_addresses['Option_Trashed_House_Guard_B']] = 0x11 write_bytes(rom_addresses["Option_Trashed_House_Guard_B"], 0x11)
if world.options.require_pokedex: if world.options.require_pokedex:
data[rom_addresses["Require_Pokedex_A"]] = 1 write_bytes(rom_addresses["Require_Pokedex_A"], 1)
data[rom_addresses["Require_Pokedex_B"]] = 1 write_bytes(rom_addresses["Require_Pokedex_B"], 1)
data[rom_addresses["Require_Pokedex_C"]] = 1 write_bytes(rom_addresses["Require_Pokedex_C"], 1)
else: else:
data[rom_addresses["Require_Pokedex_D"]] = 0x18 # jr write_bytes(rom_addresses["Require_Pokedex_D"], 0x18) # jr
if world.options.dexsanity: if world.options.dexsanity:
data[rom_addresses["Option_Dexsanity_A"]] = 1 write_bytes(rom_addresses["Option_Dexsanity_A"], 1)
data[rom_addresses["Option_Dexsanity_B"]] = 1 write_bytes(rom_addresses["Option_Dexsanity_B"], 1)
if world.options.all_pokemon_seen: if world.options.all_pokemon_seen:
data[rom_addresses["Option_Pokedex_Seen"]] = 1 write_bytes(rom_addresses["Option_Pokedex_Seen"], 1)
money = str(world.options.starting_money.value).zfill(6) money = str(world.options.starting_money.value).zfill(6)
data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16) write_bytes(rom_addresses["Starting_Money_High"], int(money[:2], 16))
data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16) write_bytes(rom_addresses["Starting_Money_Middle"], int(money[2:4], 16))
data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16) write_bytes(rom_addresses["Starting_Money_Low"], int(money[4:], 16))
data[rom_addresses["Text_Badges_Needed_Viridian_Gym"]] = encode_text( write_bytes(rom_addresses["Text_Badges_Needed_Viridian_Gym"],
str(world.options.viridian_gym_condition.value))[0] encode_text(str(world.options.viridian_gym_condition.value))[0])
data[rom_addresses["Text_Rt23_Badges_A"]] = encode_text( write_bytes(rom_addresses["Text_Rt23_Badges_A"],
str(world.options.victory_road_condition.value))[0] encode_text(str(world.options.victory_road_condition.value))[0])
data[rom_addresses["Text_Rt23_Badges_B"]] = encode_text( write_bytes(rom_addresses["Text_Rt23_Badges_B"],
str(world.options.victory_road_condition.value))[0] encode_text(str(world.options.victory_road_condition.value))[0])
data[rom_addresses["Text_Rt23_Badges_C"]] = encode_text( write_bytes(rom_addresses["Text_Rt23_Badges_C"],
str(world.options.victory_road_condition.value))[0] encode_text(str(world.options.victory_road_condition.value))[0])
data[rom_addresses["Text_Rt23_Badges_D"]] = encode_text( write_bytes(rom_addresses["Text_Rt23_Badges_D"],
str(world.options.victory_road_condition.value))[0] encode_text(str(world.options.victory_road_condition.value))[0])
data[rom_addresses["Text_Badges_Needed"]] = encode_text( write_bytes(rom_addresses["Text_Badges_Needed"],
str(world.options.elite_four_badges_condition.value))[0] encode_text(str(world.options.elite_four_badges_condition.value))[0])
write_bytes(data, encode_text( write_bytes(rom_addresses["Text_Magikarp_Salesman"],
" ".join(world.multiworld.get_location("Route 4 Pokemon Center - Pokemon For Sale", world.player).item.name.upper().split()[1:])), encode_text(" ".join(world.multiworld.get_location("Route 4 Pokemon Center - Pokemon For Sale", world.player).item.name.upper().split()[1:])))
rom_addresses["Text_Magikarp_Salesman"])
if world.options.badges_needed_for_hm_moves.value == 0: if world.options.badges_needed_for_hm_moves.value == 0:
for hm_move in poke_data.hm_moves: for hm_move in poke_data.hm_moves:
write_bytes(data, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), write_bytes(rom_addresses["HM_" + hm_move + "_Badge_a"], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
rom_addresses["HM_" + hm_move + "_Badge_a"])
elif world.extra_badges: elif world.extra_badges:
written_badges = {} written_badges = {}
badge_codes = {"Boulder Badge": 0x47, "Cascade Badge": 0x4F,
"Thunder Badge": 0x57, "Rainbow Badge": 0x5F,
"Soul Badge": 0x67, "Marsh Badge": 0x6F,
"Volcano Badge": 0x77, "Earth Badge": 0x7F}
for hm_move, badge in world.extra_badges.items(): for hm_move, badge in world.extra_badges.items():
data[rom_addresses["HM_" + hm_move + "_Badge_b"]] = {"Boulder Badge": 0x47, "Cascade Badge": 0x4F, write_bytes(rom_addresses["HM_" + hm_move + "_Badge_b"], badge_codes[badge])
"Thunder Badge": 0x57, "Rainbow Badge": 0x5F,
"Soul Badge": 0x67, "Marsh Badge": 0x6F,
"Volcano Badge": 0x77, "Earth Badge": 0x7F}[badge]
move_text = hm_move move_text = hm_move
if badge not in ["Marsh Badge", "Volcano Badge", "Earth Badge"]: if badge not in ["Marsh Badge", "Volcano Badge", "Earth Badge"]:
move_text = ", " + move_text move_text = ", " + move_text
@@ -467,62 +515,58 @@ def generate_output(world, output_directory: str):
if badge in written_badges: if badge in written_badges:
rom_address += len(written_badges[badge]) rom_address += len(written_badges[badge])
move_text = ", " + move_text move_text = ", " + move_text
write_bytes(data, encode_text(move_text.upper()), rom_address) write_bytes(rom_address, encode_text(move_text.upper()))
written_badges[badge] = move_text written_badges[badge] = move_text
for badge in ["Marsh Badge", "Volcano Badge", "Earth Badge"]: for badge in ["Marsh Badge", "Volcano Badge", "Earth Badge"]:
if badge not in written_badges: if badge not in written_badges:
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")]) write_bytes(rom_addresses["Badge_Text_" + badge.replace(" ", "_")], encode_text("Nothing"))
type_loc = rom_addresses["Type_Chart"] type_loc = rom_addresses["Type_Chart"]
for matchup in world.type_chart: for matchup in world.type_chart:
if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10 if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10
data[type_loc] = poke_data.type_ids[matchup[0]] write_bytes(type_loc, [poke_data.type_ids[matchup[0]], poke_data.type_ids[matchup[1]], matchup[2]])
data[type_loc + 1] = poke_data.type_ids[matchup[1]]
data[type_loc + 2] = matchup[2]
type_loc += 3 type_loc += 3
data[type_loc] = 0xFF write_bytes(type_loc, b"\xFF\xFF\xFF")
data[type_loc + 1] = 0xFF
data[type_loc + 2] = 0xFF
if world.options.normalize_encounter_chances.value: if world.options.normalize_encounter_chances.value:
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255] chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
for i, chance in enumerate(chances): for i, chance in enumerate(chances):
data[rom_addresses['Encounter_Chances'] + (i * 2)] = chance write_bytes(rom_addresses["Encounter_Chances"] + (i * 2), chance)
for mon, mon_data in world.local_poke_data.items(): for mon, mon_data in world.local_poke_data.items():
if mon == "Mew": if mon == "Mew":
address = rom_addresses["Base_Stats_Mew"] address = rom_addresses["Base_Stats_Mew"]
else: else:
address = rom_addresses["Base_Stats"] + (28 * (mon_data["dex"] - 1)) address = rom_addresses["Base_Stats"] + (28 * (mon_data["dex"] - 1))
data[address + 1] = world.local_poke_data[mon]["hp"] write_bytes(address + 1, world.local_poke_data[mon]["hp"])
data[address + 2] = world.local_poke_data[mon]["atk"] write_bytes(address + 2, world.local_poke_data[mon]["atk"])
data[address + 3] = world.local_poke_data[mon]["def"] write_bytes(address + 3, world.local_poke_data[mon]["def"])
data[address + 4] = world.local_poke_data[mon]["spd"] write_bytes(address + 4, world.local_poke_data[mon]["spd"])
data[address + 5] = world.local_poke_data[mon]["spc"] write_bytes(address + 5, world.local_poke_data[mon]["spc"])
data[address + 6] = poke_data.type_ids[world.local_poke_data[mon]["type1"]] write_bytes(address + 6, poke_data.type_ids[world.local_poke_data[mon]["type1"]])
data[address + 7] = poke_data.type_ids[world.local_poke_data[mon]["type2"]] write_bytes(address + 7, poke_data.type_ids[world.local_poke_data[mon]["type2"]])
data[address + 8] = world.local_poke_data[mon]["catch rate"] write_bytes(address + 8, world.local_poke_data[mon]["catch rate"])
data[address + 15] = poke_data.moves[world.local_poke_data[mon]["start move 1"]]["id"] write_bytes(address + 15, poke_data.moves[world.local_poke_data[mon]["start move 1"]]["id"])
data[address + 16] = poke_data.moves[world.local_poke_data[mon]["start move 2"]]["id"] write_bytes(address + 16, poke_data.moves[world.local_poke_data[mon]["start move 2"]]["id"])
data[address + 17] = poke_data.moves[world.local_poke_data[mon]["start move 3"]]["id"] write_bytes(address + 17, poke_data.moves[world.local_poke_data[mon]["start move 3"]]["id"])
data[address + 18] = poke_data.moves[world.local_poke_data[mon]["start move 4"]]["id"] write_bytes(address + 18, poke_data.moves[world.local_poke_data[mon]["start move 4"]]["id"])
write_bytes(data, world.local_poke_data[mon]["tms"], address + 20) write_bytes(address + 20, world.local_poke_data[mon]["tms"])
if mon in world.learnsets and world.learnsets[mon]: if mon in world.learnsets and world.learnsets[mon]:
address = rom_addresses["Learnset_" + mon.replace(" ", "")] address = rom_addresses["Learnset_" + mon.replace(" ", "")]
for i, move in enumerate(world.learnsets[mon]): for i, move in enumerate(world.learnsets[mon]):
data[(address + 1) + i * 2] = poke_data.moves[move]["id"] write_bytes((address + 1) + i * 2, poke_data.moves[move]["id"])
data[rom_addresses["Option_Aide_Rt2"]] = world.options.oaks_aide_rt_2.value write_bytes(rom_addresses["Option_Aide_Rt2"], world.options.oaks_aide_rt_2.value)
data[rom_addresses["Option_Aide_Rt11"]] = world.options.oaks_aide_rt_11.value write_bytes(rom_addresses["Option_Aide_Rt11"], world.options.oaks_aide_rt_11.value)
data[rom_addresses["Option_Aide_Rt15"]] = world.options.oaks_aide_rt_15.value write_bytes(rom_addresses["Option_Aide_Rt15"], world.options.oaks_aide_rt_15.value)
if world.options.safari_zone_normal_battles.value == 1: if world.options.safari_zone_normal_battles.value == 1:
data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255 write_bytes(rom_addresses["Option_Safari_Zone_Battle_Type"], 255)
if world.options.reusable_tms.value: if world.options.reusable_tms.value:
data[rom_addresses["Option_Reusable_TMs"]] = 0xC9 write_bytes(rom_addresses["Option_Reusable_TMs"], 0xC9)
data[rom_addresses["Option_Always_Half_STAB"]] = int(not world.options.same_type_attack_bonus.value) write_bytes(rom_addresses["Option_Always_Half_STAB"], int(not world.options.same_type_attack_bonus.value))
if world.options.better_shops: if world.options.better_shops:
inventory = ["Poke Ball", "Great Ball", "Ultra Ball"] inventory = ["Poke Ball", "Great Ball", "Ultra Ball"]
@@ -531,43 +575,45 @@ def generate_output(world, output_directory: str):
inventory += ["Potion", "Super Potion", "Hyper Potion", "Max Potion", "Full Restore", "Revive", "Antidote", inventory += ["Potion", "Super Potion", "Hyper Potion", "Max Potion", "Full Restore", "Revive", "Antidote",
"Awakening", "Burn Heal", "Ice Heal", "Paralyze Heal", "Full Heal", "Repel", "Super Repel", "Awakening", "Burn Heal", "Ice Heal", "Paralyze Heal", "Full Heal", "Repel", "Super Repel",
"Max Repel", "Escape Rope"] "Max Repel", "Escape Rope"]
shop_data = bytearray([0xFE, len(inventory)]) shop_data = [0xFE, len(inventory)]
shop_data += bytearray([item_table[item].id - 172000000 for item in inventory]) shop_data += [item_table[item].id - 172000000 for item in inventory]
shop_data.append(0xFF) shop_data.append(0xFF)
for shop in range(1, 11): for shop in range(1, 11):
write_bytes(data, shop_data, rom_addresses[f"Shop{shop}"]) write_bytes(rom_addresses[f"Shop{shop}"], shop_data)
if world.options.stonesanity: if world.options.stonesanity:
write_bytes(data, bytearray([0xFE, 1, item_table["Poke Doll"].id - 172000000, 0xFF]), rom_addresses[f"Shop_Stones"]) write_bytes(rom_addresses["Shop_Stones"], [0xFE, 1, item_table["Poke Doll"].id - 172000000, 0xFF])
price = str(world.options.master_ball_price.value).zfill(6) price = str(world.options.master_ball_price.value).zfill(6)
price = bytearray([int(price[:2], 16), int(price[2:4], 16), int(price[4:], 16)]) price = [int(price[:2], 16), int(price[2:4], 16), int(price[4:], 16)]
write_bytes(data, price, rom_addresses["Price_Master_Ball"]) # Money values in Red and Blue are weird write_bytes(rom_addresses["Price_Master_Ball"], price) # Money values in Red and Blue are weird
for item in reversed(world.multiworld.precollected_items[world.player]): from collections import Counter
if data[rom_addresses["Start_Inventory"] + item.code - 172000000] < 255: start_inventory = Counter(item.code for item in reversed(world.multiworld.precollected_items[world.player]))
data[rom_addresses["Start_Inventory"] + item.code - 172000000] += 1 for item, value in start_inventory.items():
write_bytes(rom_addresses["Start_Inventory"] + item - 172000000, min(value, 255))
set_mon_palettes(world, random, data) set_mon_palettes(world, patch)
for move_data in world.local_move_data.values(): for move_data in world.local_move_data.values():
if move_data["id"] == 0: if move_data["id"] == 0:
continue continue
address = rom_addresses["Move_Data"] + ((move_data["id"] - 1) * 6) address = rom_addresses["Move_Data"] + ((move_data["id"] - 1) * 6)
write_bytes(data, bytearray([move_data["id"], move_data["effect"], move_data["power"], write_bytes(address, [move_data["id"], move_data["effect"], move_data["power"],
poke_data.type_ids[move_data["type"]], round(move_data["accuracy"] * 2.55), move_data["pp"]]), address) poke_data.type_ids[move_data["type"]], round(move_data["accuracy"] * 2.55),
move_data["pp"]])
TM_IDs = bytearray([poke_data.moves[move]["id"] for move in world.local_tms]) TM_IDs = [poke_data.moves[move]["id"] for move in world.local_tms]
write_bytes(data, TM_IDs, rom_addresses["TM_Moves"]) write_bytes(rom_addresses["TM_Moves"], TM_IDs)
if world.options.randomize_rock_tunnel: if world.options.randomize_rock_tunnel:
seed = randomize_rock_tunnel(data, random) seed = randomize_rock_tunnel(patch, world.random)
write_bytes(data, encode_text(f"SEED: <LINE>{seed}"), rom_addresses["Text_Rock_Tunnel_Sign"]) write_bytes(rom_addresses["Text_Rock_Tunnel_Sign"], encode_text(f"SEED: <LINE>{seed}"))
mons = [mon["id"] for mon in poke_data.pokemon_data.values()] mons = [mon["id"] for mon in poke_data.pokemon_data.values()]
random.shuffle(mons) world.random.shuffle(mons)
data[rom_addresses['Title_Mon_First']] = mons.pop() write_bytes(rom_addresses["Title_Mon_First"], mons.pop())
for mon in range(0, 16): for mon in range(0, 16):
data[rom_addresses['Title_Mons'] + mon] = mons.pop() write_bytes(rom_addresses["Title_Mons"] + mon, mons.pop())
if world.options.game_version.value: if world.options.game_version.value:
mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name
else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name else else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name else
@@ -576,34 +622,34 @@ def generate_output(world, output_directory: str):
mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name
else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name else else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name else
2 if mon == world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name else 3) 2 if mon == world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name else 3)
write_bytes(data, encode_text(world.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed']) write_bytes(rom_addresses["Title_Seed"], encode_text(world.multiworld.seed_name[-20:], 20, True))
slot_name = world.multiworld.player_name[world.player] slot_name = world.multiworld.player_name[world.player]
slot_name.replace("@", " ") slot_name.replace("@", " ")
slot_name.replace("<", " ") slot_name.replace("<", " ")
slot_name.replace(">", " ") slot_name.replace(">", " ")
write_bytes(data, encode_text(slot_name, 16, True, True), rom_addresses['Title_Slot_Name']) write_bytes(rom_addresses["Title_Slot_Name"], encode_text(slot_name, 16, True, True))
if world.trainer_name == "choose_in_game": if world.trainer_name == "choose_in_game":
data[rom_addresses["Skip_Player_Name"]] = 0 write_bytes(rom_addresses["Skip_Player_Name"], 0)
else: else:
write_bytes(data, world.trainer_name, rom_addresses['Player_Name']) write_bytes(rom_addresses["Player_Name"], world.trainer_name)
if world.rival_name == "choose_in_game": if world.rival_name == "choose_in_game":
data[rom_addresses["Skip_Rival_Name"]] = 0 write_bytes(rom_addresses["Skip_Rival_Name"], 0)
else: else:
write_bytes(data, world.rival_name, rom_addresses['Rival_Name']) write_bytes(rom_addresses["Rival_Name"], world.rival_name)
data[0xFF00] = 2 # client compatibility version write_bytes(0xFF00, 2) # client compatibility version
rom_name = bytearray(f'AP{Utils.__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', rom_name = bytearray(f"AP{Utils.__version__.replace('.', '')[0:3]}_{world.player}_{world.multiworld.seed:11}\0",
'utf8')[:21] "utf8")[:21]
rom_name.extend([0] * (21 - len(rom_name))) rom_name.extend([0] * (21 - len(rom_name)))
write_bytes(data, rom_name, 0xFFC6) write_bytes(0xFFC6, rom_name)
write_bytes(data, world.multiworld.seed_name.encode(), 0xFFDB) write_bytes(0xFFDB, world.multiworld.seed_name.encode())
write_bytes(data, world.multiworld.player_name[world.player].encode(), 0xFFF0) write_bytes(0xFFF0, world.multiworld.player_name[world.player].encode())
world.finished_level_scaling.wait() world.finished_level_scaling.wait()
write_quizzes(world, data, random) write_quizzes(world, patch)
for location in world.multiworld.get_locations(world.player): for location in world.multiworld.get_locations(world.player):
if location.party_data: if location.party_data:
@@ -617,18 +663,18 @@ def generate_output(world, output_directory: str):
levels = party["level"] levels = party["level"]
for address, party in zip(addresses, parties): for address, party in zip(addresses, parties):
if isinstance(levels, int): if isinstance(levels, int):
data[address] = levels write_bytes(address, levels)
address += 1 address += 1
for mon in party: for mon in party:
data[address] = poke_data.pokemon_data[mon]["id"] write_bytes(address, poke_data.pokemon_data[mon]["id"])
address += 1 address += 1
else: else:
address += 1 address += 1
for level, mon in zip(levels, party): for level, mon in zip(levels, party):
data[address] = level write_bytes(address, [level, poke_data.pokemon_data[mon]["id"]])
data[address + 1] = poke_data.pokemon_data[mon]["id"]
address += 2 address += 2
assert data[address] == 0 or location.name == "Fossil Level - Trainer Parties" # This assert can't be done with procedure patch tokens.
# assert data[address] == 0 or location.name == "Fossil Level - Trainer Parties"
continue continue
elif location.rom_address is None: elif location.rom_address is None:
continue continue
@@ -639,85 +685,24 @@ def generate_output(world, output_directory: str):
rom_address = [rom_address] rom_address = [rom_address]
for address in rom_address: for address in rom_address:
if location.item.name in poke_data.pokemon_data.keys(): if location.item.name in poke_data.pokemon_data.keys():
data[address] = poke_data.pokemon_data[location.item.name]["id"] write_bytes(address, poke_data.pokemon_data[location.item.name]["id"])
elif " ".join(location.item.name.split()[1:]) in poke_data.pokemon_data.keys(): elif " ".join(location.item.name.split()[1:]) in poke_data.pokemon_data.keys():
data[address] = poke_data.pokemon_data[" ".join(location.item.name.split()[1:])]["id"] write_bytes(address, poke_data.pokemon_data[" ".join(location.item.name.split()[1:])]["id"])
else: else:
item_id = world.item_name_to_id[location.item.name] - 172000000 item_id = world.item_name_to_id[location.item.name] - 172000000
if item_id > 255: if item_id > 255:
item_id -= 256 item_id -= 256
data[address] = item_id write_bytes(address, item_id)
if location.level: if location.level:
data[location.level_address] = location.level write_bytes(location.level_address, location.level)
else: else:
rom_address = location.rom_address rom_address = location.rom_address
if not isinstance(rom_address, list): if not isinstance(rom_address, list):
rom_address = [rom_address] rom_address = [rom_address]
for address in rom_address: for address in rom_address:
data[address] = 0x2C # AP Item write_bytes(address, 0x2C) # AP Item
outfilepname = f'_P{world.player}' patch.write_file("token_data.bin", patch.get_token_binary())
outfilepname += f"_{world.multiworld.get_file_safe_player_name(world.player).replace(' ', '_')}" \ out_file_name = world.multiworld.get_out_file_name_base(world.player)
if world.multiworld.player_name[world.player] != 'Player%d' % world.player else '' patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}"))
rompath = os.path.join(output_directory, f'AP_{world.multiworld.seed_name}{outfilepname}.gb')
with open(rompath, 'wb') as outfile:
outfile.write(data)
if world.options.game_version.current_key == "red":
patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=world.player,
player_name=world.multiworld.player_name[world.player], patched_path=rompath)
else:
patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=world.player,
player_name=world.multiworld.player_name[world.player], patched_path=rompath)
patch.write()
os.unlink(rompath)
def write_bytes(data, byte_array, address):
for byte in byte_array:
data[address] = byte
address += 1
def get_base_rom_bytes(game_version: str, hash: str="") -> bytes:
file_name = get_base_rom_path(game_version)
with open(file_name, "rb") as file:
base_rom_bytes = bytes(file.read())
if hash:
basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes)
if hash != basemd5.hexdigest():
raise Exception(f"Supplied Base Rom does not match known MD5 for Pokémon {game_version.title()} UE "
"release. Get the correct game and version, then dump it")
return base_rom_bytes
def get_base_rom_path(game_version: str) -> str:
options = Utils.get_options()
file_name = options["pokemon_rb_options"][f"{game_version}_rom_file"]
if not os.path.exists(file_name):
file_name = Utils.user_path(file_name)
return file_name
class BlueDeltaPatch(APDeltaPatch):
patch_file_ending = ".apblue"
hash = "50927e843568814f7ed45ec4f944bd8b"
game_version = "blue"
game = "Pokemon Red and Blue"
result_file_ending = ".gb"
@classmethod
def get_source_data(cls) -> bytes:
return get_base_rom_bytes(cls.game_version, cls.hash)
class RedDeltaPatch(APDeltaPatch):
patch_file_ending = ".apred"
hash = "3d45c1ee9abd5738df46d2bdda8b57dc"
game_version = "red"
game = "Pokemon Red and Blue"
result_file_ending = ".gb"
@classmethod
def get_source_data(cls) -> bytes:
return get_base_rom_bytes(cls.game_version, cls.hash)