LttP: move more stuff out of core (#5049)

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
Fabian Dill
2025-08-01 22:30:30 +02:00
committed by GitHub
parent 37a9d94865
commit 9ad6959559
7 changed files with 70 additions and 71 deletions

View File

@@ -154,17 +154,11 @@ class MultiWorld():
self.algorithm = 'balanced' self.algorithm = 'balanced'
self.groups = {} self.groups = {}
self.regions = self.RegionManager(players) self.regions = self.RegionManager(players)
self.shops = []
self.itempool = [] self.itempool = []
self.seed = None self.seed = None
self.seed_name: str = "Unavailable" self.seed_name: str = "Unavailable"
self.precollected_items = {player: [] for player in self.player_ids} self.precollected_items = {player: [] for player in self.player_ids}
self.required_locations = [] self.required_locations = []
self.light_world_light_cone = False
self.dark_world_light_cone = False
self.rupoor_cost = 10
self.aga_randomness = True
self.save_and_quit_from_boss = True
self.custom = False self.custom = False
self.customitemarray = [] self.customitemarray = []
self.shuffle_ganon = True self.shuffle_ganon = True

View File

@@ -223,7 +223,7 @@ items_reduction_table = (
def generate_itempool(world): def generate_itempool(world):
player = world.player player: int = world.player
multiworld = world.multiworld multiworld = world.multiworld
if world.options.item_pool.current_key not in difficulties: if world.options.item_pool.current_key not in difficulties:
@@ -280,7 +280,6 @@ def generate_itempool(world):
if multiworld.custom: if multiworld.custom:
pool, placed_items, precollected_items, clock_mode, treasure_hunt_required = ( pool, placed_items, precollected_items, clock_mode, treasure_hunt_required = (
make_custom_item_pool(multiworld, player)) make_custom_item_pool(multiworld, player))
multiworld.rupoor_cost = min(multiworld.customitemarray[67], 9999)
else: else:
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total, (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total,
additional_triforce_pieces) = get_pool_core(multiworld, player) additional_triforce_pieces) = get_pool_core(multiworld, player)
@@ -386,8 +385,8 @@ def generate_itempool(world):
if world.options.retro_bow: if world.options.retro_bow:
shop_items = 0 shop_items = 0
shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop_locations = [location for shop_locations in (shop.region.locations for shop in world.shops if
shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if shop.type == ShopType.Shop) for location in shop_locations if
location.shop_slot is not None] location.shop_slot is not None]
for location in shop_locations: for location in shop_locations:
if location.shop.inventory[location.shop_slot]["item"] == "Single Arrow": if location.shop.inventory[location.shop_slot]["item"] == "Single Arrow":
@@ -546,7 +545,7 @@ def set_up_take_anys(multiworld, world, player):
connect_entrance(multiworld, entrance.name, old_man_take_any.name, player) connect_entrance(multiworld, entrance.name, old_man_take_any.name, player)
entrance.target = 0x58 entrance.target = 0x58
old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True, total_shop_slots) old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True, total_shop_slots)
multiworld.shops.append(old_man_take_any.shop) world.shops.append(old_man_take_any.shop)
sword_indices = [ sword_indices = [
index for index, item in enumerate(multiworld.itempool) if item.player == player and item.type == 'Sword' index for index, item in enumerate(multiworld.itempool) if item.player == player and item.type == 'Sword'
@@ -574,7 +573,7 @@ def set_up_take_anys(multiworld, world, player):
connect_entrance(multiworld, entrance.name, take_any.name, player) connect_entrance(multiworld, entrance.name, take_any.name, player)
entrance.target = target entrance.target = target
take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True, total_shop_slots + num + 1) take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True, total_shop_slots + num + 1)
multiworld.shops.append(take_any.shop) world.shops.append(take_any.shop)
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0) take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0) take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any) location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any)

View File

@@ -1002,14 +1002,19 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# set light cones # set light cones
rom.write_byte(0x180038, 0x01 if local_world.options.mode == "standard" else 0x00) rom.write_byte(0x180038, 0x01 if local_world.options.mode == "standard" else 0x00)
rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00) # light world light cone
rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00) rom.write_byte(0x180039, local_world.light_world_light_cone)
# dark world light cone
rom.write_byte(0x18003A, local_world.dark_world_light_cone)
GREEN_TWENTY_RUPEES = 0x47 GREEN_TWENTY_RUPEES = 0x47
GREEN_CLOCK = item_table["Green Clock"].item_code GREEN_CLOCK = item_table["Green Clock"].item_code
rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on
# Rupoor negative value
rom.write_int16(0x180036, local_world.rupoor_cost)
# handle item_functionality # handle item_functionality
if local_world.options.item_functionality == 'hard': if local_world.options.item_functionality == 'hard':
rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon
@@ -1027,8 +1032,6 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Disable catching fairies # Disable catching fairies
rom.write_byte(0x34FD6, 0x80) rom.write_byte(0x34FD6, 0x80)
overflow_replacement = GREEN_TWENTY_RUPEES overflow_replacement = GREEN_TWENTY_RUPEES
# Rupoor negative value
rom.write_int16(0x180036, world.rupoor_cost)
# Set stun items # Set stun items
rom.write_byte(0x180180, 0x02) # Hookshot only rom.write_byte(0x180180, 0x02) # Hookshot only
elif local_world.options.item_functionality == 'expert': elif local_world.options.item_functionality == 'expert':
@@ -1047,8 +1050,6 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Disable catching fairies # Disable catching fairies
rom.write_byte(0x34FD6, 0x80) rom.write_byte(0x34FD6, 0x80)
overflow_replacement = GREEN_TWENTY_RUPEES overflow_replacement = GREEN_TWENTY_RUPEES
# Rupoor negative value
rom.write_int16(0x180036, world.rupoor_cost)
# Set stun items # Set stun items
rom.write_byte(0x180180, 0x00) # Nothing rom.write_byte(0x180180, 0x00) # Nothing
else: else:
@@ -1066,8 +1067,6 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x18004F, 0x01) rom.write_byte(0x18004F, 0x01)
# Enable catching fairies # Enable catching fairies
rom.write_byte(0x34FD6, 0xF0) rom.write_byte(0x34FD6, 0xF0)
# Rupoor negative value
rom.write_int16(0x180036, world.rupoor_cost)
# Set stun items # Set stun items
rom.write_byte(0x180180, 0x03) # All standard items rom.write_byte(0x180180, 0x03) # All standard items
# Set overflow items for progressive equipment # Set overflow items for progressive equipment
@@ -1313,7 +1312,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x18008C, 0x01 if local_world.options.crystals_needed_for_gt == 0 else 0x00) # GT pre-opened if crystal requirement is 0 rom.write_byte(0x18008C, 0x01 if local_world.options.crystals_needed_for_gt == 0 else 0x00) # GT pre-opened if crystal requirement is 0
rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5D73, 0xF0) # bees are catchable
rom.write_byte(0xF5F10, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable
rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness rom.write_byte(0x180086, 0x00) # set blue ball and ganon warp randomness
rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
rom.write_byte(0x180174, 0x01 if local_world.fix_fake_world else 0x00) rom.write_byte(0x180174, 0x01 if local_world.fix_fake_world else 0x00)
@@ -1618,7 +1617,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
rom.write_byte(0x1800A4, 0x01 if local_world.options.glitches_required != 'no_logic' else 0x00) # enable POD EG fix rom.write_byte(0x1800A4, 0x01 if local_world.options.glitches_required != 'no_logic' else 0x00) # enable POD EG fix
rom.write_byte(0x186383, 0x01 if local_world.options.glitches_required == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room rom.write_byte(0x186383, 0x01 if local_world.options.glitches_required == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room
rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill rom.write_byte(0x180042, 0x01 if local_world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
# remove shield from uncle # remove shield from uncle
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
@@ -1739,8 +1738,7 @@ def get_price_data(price: int, price_type: int) -> List[int]:
def write_custom_shops(rom, world, player): def write_custom_shops(rom, world, player):
shops = sorted([shop for shop in world.shops if shop.custom and shop.region.player == player], shops = sorted([shop for shop in world.worlds[player].shops if shop.custom], key=lambda shop: shop.sram_offset)
key=lambda shop: shop.sram_offset)
shop_data = bytearray() shop_data = bytearray()
items_data = bytearray() items_data = bytearray()

View File

@@ -147,7 +147,6 @@ def set_defeat_dungeon_boss_rule(location):
add_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state)) add_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state))
def set_always_allow(spot, rule): def set_always_allow(spot, rule):
spot.always_allow = rule spot.always_allow = rule
@@ -980,18 +979,19 @@ def check_is_dark_world(region):
return False return False
def add_conditional_lamps(world, player): def add_conditional_lamps(multiworld, player):
# Light cones in standard depend on which world we actually are in, not which one the location would normally be # Light cones in standard depend on which world we actually are in, not which one the location would normally be
# We add Lamp requirements only to those locations which lie in the dark world (or everything if open # We add Lamp requirements only to those locations which lie in the dark world (or everything if open
local_world = multiworld.worlds[player]
def add_conditional_lamp(spot, region, spottype='Location', accessible_torch=False): def add_conditional_lamp(spot, region, spottype='Location', accessible_torch=False):
if (not world.dark_world_light_cone and check_is_dark_world(world.get_region(region, player))) or ( if (not local_world.dark_world_light_cone and check_is_dark_world(local_world.get_region(region))) or (
not world.light_world_light_cone and not check_is_dark_world(world.get_region(region, player))): not local_world.light_world_light_cone and not check_is_dark_world(local_world.get_region(region))):
if spottype == 'Location': if spottype == 'Location':
spot = world.get_location(spot, player) spot = local_world.get_location(spot)
else: else:
spot = world.get_entrance(spot, player) spot = local_world.get_entrance(spot)
add_lamp_requirement(world, spot, player, accessible_torch) add_lamp_requirement(multiworld, spot, player, accessible_torch)
add_conditional_lamp('Misery Mire (Vitreous)', 'Misery Mire (Entrance)', 'Entrance') add_conditional_lamp('Misery Mire (Vitreous)', 'Misery Mire (Entrance)', 'Entrance')
add_conditional_lamp('Turtle Rock (Dark Room) (North)', 'Turtle Rock (Entrance)', 'Entrance') add_conditional_lamp('Turtle Rock (Dark Room) (North)', 'Turtle Rock (Entrance)', 'Entrance')
@@ -1002,7 +1002,7 @@ def add_conditional_lamps(world, player):
'Location', True) 'Location', True)
add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)', add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)',
'Location', True) 'Location', True)
if world.worlds[player].options.mode != 'inverted': if multiworld.worlds[player].options.mode != 'inverted':
add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance') add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance')
add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower') add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower')
add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Agahnims Tower') add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Agahnims Tower')
@@ -1024,10 +1024,10 @@ def add_conditional_lamps(world, player):
add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location', True) add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location', True)
add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location', True) add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location', True)
if not world.worlds[player].options.mode == "standard": if not multiworld.worlds[player].options.mode == "standard":
add_lamp_requirement(world, world.get_location('Sewers - Dark Cross', player), player) add_lamp_requirement(multiworld, local_world.get_location("Sewers - Dark Cross"), player)
add_lamp_requirement(world, world.get_entrance('Sewers Back Door', player), player) add_lamp_requirement(multiworld, local_world.get_entrance("Sewers Back Door"), player)
add_lamp_requirement(world, world.get_entrance('Throne Room', player), player) add_lamp_requirement(multiworld, local_world.get_entrance("Throne Room"), player)
def open_rules(world, player): def open_rules(world, player):

View File

@@ -14,8 +14,6 @@ from .Items import item_name_groups
from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows
logger = logging.getLogger("Shops")
@unique @unique
class ShopType(IntEnum): class ShopType(IntEnum):
@@ -162,7 +160,10 @@ shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
def push_shop_inventories(multiworld): def push_shop_inventories(multiworld):
shop_slots = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop.type all_shops = []
for world in multiworld.get_game_worlds(ALttPLocation.game):
all_shops.extend(world.shops)
shop_slots = [location for shop_locations in (shop.region.locations for shop in all_shops if shop.type
!= ShopType.TakeAny) for location in shop_locations if location.shop_slot is not None] != ShopType.TakeAny) for location in shop_locations if location.shop_slot is not None]
for location in shop_slots: for location in shop_slots:
@@ -178,7 +179,7 @@ def push_shop_inventories(multiworld):
get_price(multiworld, location.shop.inventory[location.shop_slot], location.player, get_price(multiworld, location.shop.inventory[location.shop_slot], location.player,
location.shop_price_type)[1]) location.shop_price_type)[1])
for world in multiworld.get_game_worlds("A Link to the Past"): for world in multiworld.get_game_worlds(ALttPLocation.game):
world.pushed_shop_inventories.set() world.pushed_shop_inventories.set()
@@ -225,7 +226,7 @@ def create_shops(multiworld, player: int):
if locked is None: if locked is None:
shop.locked = True shop.locked = True
region.shop = shop region.shop = shop
multiworld.shops.append(shop) multiworld.worlds[player].shops.append(shop)
for index, item in enumerate(inventory): for index, item in enumerate(inventory):
shop.add_inventory(index, *item) shop.add_inventory(index, *item)
if not locked and (num_slots or type == ShopType.UpgradeShop): if not locked and (num_slots or type == ShopType.UpgradeShop):
@@ -309,50 +310,50 @@ def set_up_shops(multiworld, player: int):
from .Options import small_key_shuffle from .Options import small_key_shuffle
# TODO: move hard+ mode changes for shields here, utilizing the new shops # TODO: move hard+ mode changes for shields here, utilizing the new shops
if multiworld.worlds[player].options.retro_bow: local_world = multiworld.worlds[player]
if local_world.options.retro_bow:
rss = multiworld.get_region('Red Shield Shop', player).shop rss = multiworld.get_region('Red Shield Shop', player).shop
# Can't just replace the single arrow with 10 arrows as retro doesn't need them.
replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50], replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50],
['Blue Shield', 50], ['Small Heart', ['Blue Shield', 50], ['Small Heart', 10]]
10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them. if local_world.options.small_key_shuffle == small_key_shuffle.option_universal:
if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
replacement_items.append(['Small Key (Universal)', 100]) replacement_items.append(['Small Key (Universal)', 100])
replacement_item = multiworld.random.choice(replacement_items) replacement_item = multiworld.random.choice(replacement_items)
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1]) rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
rss.locked = True rss.locked = True
if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal or multiworld.worlds[player].options.retro_bow: if local_world.options.small_key_shuffle == small_key_shuffle.option_universal or local_world.options.retro_bow:
for shop in multiworld.random.sample([s for s in multiworld.shops if for shop in multiworld.random.sample([s for s in local_world.shops if
s.custom and not s.locked and s.type == ShopType.Shop s.custom and not s.locked and s.type == ShopType.Shop], 5):
and s.region.player == player], 5):
shop.locked = True shop.locked = True
slots = [0, 1, 2] slots = [0, 1, 2]
multiworld.random.shuffle(slots) multiworld.random.shuffle(slots)
slots = iter(slots) slots = iter(slots)
if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: if local_world.options.small_key_shuffle == small_key_shuffle.option_universal:
shop.add_inventory(next(slots), 'Small Key (Universal)', 100) shop.add_inventory(next(slots), 'Small Key (Universal)', 100)
if multiworld.worlds[player].options.retro_bow: if local_world.options.retro_bow:
shop.push_inventory(next(slots), 'Single Arrow', 80) shop.push_inventory(next(slots), 'Single Arrow', 80)
if multiworld.worlds[player].options.shuffle_capacity_upgrades: if local_world.options.shuffle_capacity_upgrades:
for shop in multiworld.shops: for shop in local_world.shops:
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \ if shop.type == ShopType.UpgradeShop and \
shop.region.name == "Capacity Upgrade": shop.region.name == "Capacity Upgrade":
shop.clear_inventory() shop.clear_inventory()
if (multiworld.worlds[player].options.shuffle_shop_inventories or multiworld.worlds[player].options.randomize_shop_prices if (local_world.options.shuffle_shop_inventories or local_world.options.randomize_shop_prices
or multiworld.worlds[player].options.randomize_cost_types): or local_world.options.randomize_cost_types):
shops = [] shops = []
total_inventory = [] total_inventory = []
for shop in multiworld.shops: for shop in local_world.shops:
if shop.region.player == player: if shop.type == ShopType.Shop and not shop.locked:
if shop.type == ShopType.Shop and not shop.locked: shops.append(shop)
shops.append(shop) total_inventory.extend(shop.inventory)
total_inventory.extend(shop.inventory)
for item in total_inventory: for item in total_inventory:
item["price_type"], item["price"] = get_price(multiworld, item, player) item["price_type"], item["price"] = get_price(multiworld, item, player)
if multiworld.worlds[player].options.shuffle_shop_inventories: if local_world.options.shuffle_shop_inventories:
multiworld.random.shuffle(total_inventory) multiworld.random.shuffle(total_inventory)
i = 0 i = 0
@@ -407,7 +408,7 @@ price_rate_display = {
} }
def get_price_modifier(item): def get_price_modifier(item) -> float:
if item.game == "A Link to the Past": if item.game == "A Link to the Past":
if any(x in item.name for x in if any(x in item.name for x in
['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']): ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']):
@@ -418,9 +419,9 @@ def get_price_modifier(item):
elif any(x in item.name for x in ['Small Key', 'Heart']): elif any(x in item.name for x in ['Small Key', 'Heart']):
return 0.5 return 0.5
else: else:
return 1 return 1.0
if item.advancement: if item.advancement:
return 1 return 1.0
elif item.useful: elif item.useful:
return 0.5 return 0.5
else: else:
@@ -471,7 +472,7 @@ def get_price(multiworld, item, player: int, price_type=None):
def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation): def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation):
if location.shop_price_type == ShopPriceType.Hearts: if location.shop_price_type == ShopPriceType.Hearts:
return has_hearts(state, player, (location.shop_price / 8) + 1) return has_hearts(state, player, (location.shop_price // 8) + 1)
elif location.shop_price_type == ShopPriceType.Bombs: elif location.shop_price_type == ShopPriceType.Bombs:
return can_use_bombs(state, player, location.shop_price) return can_use_bombs(state, player, location.shop_price)
elif location.shop_price_type == ShopPriceType.Arrows: elif location.shop_price_type == ShopPriceType.Arrows:

View File

@@ -14,13 +14,13 @@ def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bo
def can_buy_unlimited(state: CollectionState, item: str, player: int) -> bool: def can_buy_unlimited(state: CollectionState, item: str, player: int) -> bool:
return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(state) for return any(shop.has_unlimited(item) and shop.region.can_reach(state) for
shop in state.multiworld.shops) shop in state.multiworld.worlds[player].shops)
def can_buy(state: CollectionState, item: str, player: int) -> bool: def can_buy(state: CollectionState, item: str, player: int) -> bool:
return any(shop.region.player == player and shop.has(item) and shop.region.can_reach(state) for return any(shop.has(item) and shop.region.can_reach(state) for
shop in state.multiworld.shops) shop in state.multiworld.worlds[player].shops)
def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool: def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool:

View File

@@ -236,6 +236,8 @@ class ALTTPWorld(World):
required_client_version = (0, 4, 1) required_client_version = (0, 4, 1)
web = ALTTPWeb() web = ALTTPWeb()
shops: list[Shop]
pedestal_credit_texts: typing.Dict[int, str] = \ pedestal_credit_texts: typing.Dict[int, str] = \
{data.item_code: data.pedestal_credit for data in item_table.values() if data.pedestal_credit} {data.item_code: data.pedestal_credit for data in item_table.values() if data.pedestal_credit}
sickkid_credit_texts: typing.Dict[int, str] = \ sickkid_credit_texts: typing.Dict[int, str] = \
@@ -282,6 +284,10 @@ class ALTTPWorld(World):
clock_mode: str = "" clock_mode: str = ""
treasure_hunt_required: int = 0 treasure_hunt_required: int = 0
treasure_hunt_total: int = 0 treasure_hunt_total: int = 0
light_world_light_cone: bool = False
dark_world_light_cone: bool = False
save_and_quit_from_boss: bool = True
rupoor_cost: int = 10
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.dungeon_local_item_names = set() self.dungeon_local_item_names = set()
@@ -298,6 +304,7 @@ class ALTTPWorld(World):
self.fix_trock_exit = None self.fix_trock_exit = None
self.required_medallions = ["Ether", "Quake"] self.required_medallions = ["Ether", "Quake"]
self.escape_assist = [] self.escape_assist = []
self.shops = []
super(ALTTPWorld, self).__init__(*args, **kwargs) super(ALTTPWorld, self).__init__(*args, **kwargs)
@classmethod @classmethod
@@ -800,7 +807,7 @@ class ALTTPWorld(World):
return shop_data return shop_data
if shop_info := [build_shop_info(shop) for shop in self.multiworld.shops if shop.custom]: if shop_info := [build_shop_info(shop) for shop in self.shops if shop.custom]:
spoiler_handle.write('\n\nShops:\n\n') spoiler_handle.write('\n\nShops:\n\n')
for shop_data in shop_info: for shop_data in shop_info:
spoiler_handle.write("{} [{}]\n {}\n".format(shop_data['location'], shop_data['type'], "\n ".join( spoiler_handle.write("{} [{}]\n {}\n".format(shop_data['location'], shop_data['type'], "\n ".join(