mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-05-20 14:51:45 -07:00
126 lines
3.7 KiB
Python
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
|
|
)
|