Files
Archipelago/worlds/alttp/PotShuffle.py
T

126 lines
3.7 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from Utils import snes_to_pc
from .enemizer_data.pot_shuffle_data import POT_ROOMS
if TYPE_CHECKING:
from . import ALTTPWorld
from .Rom import LocalRom
POT_ITEM_POINTER_TABLE = 0xDB67
POT_KEY = 0x08
POT_ARROW = 0x09
POT_BLUE_RUPEE = 0x07
POT_SWITCH = 0x88
POT_HOLE = 0x80
@dataclass(frozen=True)
class PotData:
x: int
y: int
reserved: int
@dataclass(frozen=True)
class PotRoomData:
room_id: int
pots: tuple[PotData, ...]
items: tuple[int, ...]
@dataclass(frozen=True)
class FilledPot:
x: int
y: int
item: int
def generate_pot_shuffle(world: "ALTTPWorld") -> dict[int, tuple[FilledPot, ...]]:
room_data = _load_pot_room_data()
shuffled_pots: dict[int, tuple[FilledPot, ...]] = {}
for room in room_data:
room_items = [item for item in room.items if item != POT_HOLE]
if world.options.retro_bow:
room_items = [POT_BLUE_RUPEE if item == POT_ARROW else item for item in room_items]
empty_pots: list[PotData] = []
filled_pots: list[FilledPot] = []
for pot in room.pots:
if pot.reserved == 3:
filled_pots.append(FilledPot(pot.x, pot.y, POT_HOLE))
else:
empty_pots.append(pot)
while POT_KEY in room_items:
candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 1]
if not candidate_indices:
break
pot_index = world.random.choice(candidate_indices)
pot = empty_pots.pop(pot_index)
room_items.remove(POT_KEY)
filled_pots.append(FilledPot(pot.x, pot.y, POT_KEY))
while POT_SWITCH in room_items:
candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 2]
if not candidate_indices:
break
pot_index = world.random.choice(candidate_indices)
pot = empty_pots.pop(pot_index)
room_items.remove(POT_SWITCH)
filled_pots.append(FilledPot(pot.x, pot.y, POT_SWITCH))
while room_items and empty_pots:
pot_index = world.random.randrange(len(empty_pots))
item_index = world.random.randrange(len(room_items))
pot = empty_pots.pop(pot_index)
item = room_items.pop(item_index)
filled_pots.append(FilledPot(pot.x, pot.y, item))
shuffled_pots[room.room_id] = tuple(filled_pots)
return shuffled_pots
def apply_pot_shuffle(rom: "LocalRom", shuffled_pots: dict[int, tuple[FilledPot, ...]]) -> None:
for room_id, pots in shuffled_pots.items():
pointer_address = POT_ITEM_POINTER_TABLE + (room_id * 2)
snes_address = rom.read_byte(pointer_address) | (rom.read_byte(pointer_address + 1) << 8) | (0x01 << 16)
address = snes_to_pc(snes_address)
for index, pot in enumerate(pots):
rom.write_bytes(address + (index * 3), (pot.x, pot.y, pot.item))
def get_unique_pot_item_position(
shuffled_pots: dict[int, tuple[FilledPot, ...]],
room_id: int,
item: int,
) -> tuple[int, int]:
positions = [
(pot.x, pot.y)
for pot in shuffled_pots.get(room_id, ())
if pot.item == item
]
if len(positions) != 1:
raise ValueError(
f"Expected exactly one pot item {hex(item)} in room {hex(room_id)}, found {len(positions)}"
)
return positions[0]
def _load_pot_room_data() -> tuple[PotRoomData, ...]:
return tuple(
PotRoomData(
room_id=room.room_id,
pots=tuple(PotData(x=pot.x, y=pot.y, reserved=pot.reserved) for pot in room.pots),
items=room.items,
)
for room in POT_ROOMS
)