mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00
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:
@@ -154,17 +154,11 @@ class MultiWorld():
|
||||
self.algorithm = 'balanced'
|
||||
self.groups = {}
|
||||
self.regions = self.RegionManager(players)
|
||||
self.shops = []
|
||||
self.itempool = []
|
||||
self.seed = None
|
||||
self.seed_name: str = "Unavailable"
|
||||
self.precollected_items = {player: [] for player in self.player_ids}
|
||||
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.customitemarray = []
|
||||
self.shuffle_ganon = True
|
||||
|
@@ -223,7 +223,7 @@ items_reduction_table = (
|
||||
|
||||
|
||||
def generate_itempool(world):
|
||||
player = world.player
|
||||
player: int = world.player
|
||||
multiworld = world.multiworld
|
||||
|
||||
if world.options.item_pool.current_key not in difficulties:
|
||||
@@ -280,7 +280,6 @@ def generate_itempool(world):
|
||||
if multiworld.custom:
|
||||
pool, placed_items, precollected_items, clock_mode, treasure_hunt_required = (
|
||||
make_custom_item_pool(multiworld, player))
|
||||
multiworld.rupoor_cost = min(multiworld.customitemarray[67], 9999)
|
||||
else:
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total,
|
||||
additional_triforce_pieces) = get_pool_core(multiworld, player)
|
||||
@@ -386,8 +385,8 @@ def generate_itempool(world):
|
||||
|
||||
if world.options.retro_bow:
|
||||
shop_items = 0
|
||||
shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if
|
||||
shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if
|
||||
shop_locations = [location for shop_locations in (shop.region.locations for shop in world.shops if
|
||||
shop.type == ShopType.Shop) for location in shop_locations if
|
||||
location.shop_slot is not None]
|
||||
for location in shop_locations:
|
||||
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)
|
||||
entrance.target = 0x58
|
||||
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 = [
|
||||
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)
|
||||
entrance.target = target
|
||||
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(1, 'Boss Heart Container', 0, 0)
|
||||
location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any)
|
||||
|
@@ -1002,14 +1002,19 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
|
||||
|
||||
# set light cones
|
||||
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)
|
||||
rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00)
|
||||
# light world light cone
|
||||
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_CLOCK = item_table["Green Clock"].item_code
|
||||
|
||||
rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on
|
||||
|
||||
# Rupoor negative value
|
||||
rom.write_int16(0x180036, local_world.rupoor_cost)
|
||||
|
||||
# handle item_functionality
|
||||
if local_world.options.item_functionality == 'hard':
|
||||
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
|
||||
rom.write_byte(0x34FD6, 0x80)
|
||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||
# Rupoor negative value
|
||||
rom.write_int16(0x180036, world.rupoor_cost)
|
||||
# Set stun items
|
||||
rom.write_byte(0x180180, 0x02) # Hookshot only
|
||||
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
|
||||
rom.write_byte(0x34FD6, 0x80)
|
||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||
# Rupoor negative value
|
||||
rom.write_int16(0x180036, world.rupoor_cost)
|
||||
# Set stun items
|
||||
rom.write_byte(0x180180, 0x00) # Nothing
|
||||
else:
|
||||
@@ -1066,8 +1067,6 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
|
||||
rom.write_byte(0x18004F, 0x01)
|
||||
# Enable catching fairies
|
||||
rom.write_byte(0x34FD6, 0xF0)
|
||||
# Rupoor negative value
|
||||
rom.write_int16(0x180036, world.rupoor_cost)
|
||||
# Set stun items
|
||||
rom.write_byte(0x180180, 0x03) # All standard items
|
||||
# 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(0xF5D73, 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(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)
|
||||
@@ -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(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(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
|
||||
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):
|
||||
shops = sorted([shop for shop in world.shops if shop.custom and shop.region.player == player],
|
||||
key=lambda shop: shop.sram_offset)
|
||||
shops = sorted([shop for shop in world.worlds[player].shops if shop.custom], key=lambda shop: shop.sram_offset)
|
||||
|
||||
shop_data = bytearray()
|
||||
items_data = bytearray()
|
||||
|
@@ -147,7 +147,6 @@ def set_defeat_dungeon_boss_rule(location):
|
||||
add_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state))
|
||||
|
||||
|
||||
|
||||
def set_always_allow(spot, rule):
|
||||
spot.always_allow = rule
|
||||
|
||||
@@ -980,18 +979,19 @@ def check_is_dark_world(region):
|
||||
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
|
||||
# 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):
|
||||
if (not world.dark_world_light_cone and check_is_dark_world(world.get_region(region, player))) or (
|
||||
not world.light_world_light_cone and not check_is_dark_world(world.get_region(region, player))):
|
||||
if (not local_world.dark_world_light_cone and check_is_dark_world(local_world.get_region(region))) or (
|
||||
not local_world.light_world_light_cone and not check_is_dark_world(local_world.get_region(region))):
|
||||
if spottype == 'Location':
|
||||
spot = world.get_location(spot, player)
|
||||
spot = local_world.get_location(spot)
|
||||
else:
|
||||
spot = world.get_entrance(spot, player)
|
||||
add_lamp_requirement(world, spot, player, accessible_torch)
|
||||
spot = local_world.get_entrance(spot)
|
||||
add_lamp_requirement(multiworld, spot, player, accessible_torch)
|
||||
|
||||
add_conditional_lamp('Misery Mire (Vitreous)', 'Misery Mire (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)
|
||||
add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)',
|
||||
'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('Castle Tower - Dark Maze', '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 - Prize', 'Eastern Palace', 'Location', True)
|
||||
|
||||
if not world.worlds[player].options.mode == "standard":
|
||||
add_lamp_requirement(world, world.get_location('Sewers - Dark Cross', player), player)
|
||||
add_lamp_requirement(world, world.get_entrance('Sewers Back Door', player), player)
|
||||
add_lamp_requirement(world, world.get_entrance('Throne Room', player), player)
|
||||
if not multiworld.worlds[player].options.mode == "standard":
|
||||
add_lamp_requirement(multiworld, local_world.get_location("Sewers - Dark Cross"), player)
|
||||
add_lamp_requirement(multiworld, local_world.get_entrance("Sewers Back Door"), player)
|
||||
add_lamp_requirement(multiworld, local_world.get_entrance("Throne Room"), player)
|
||||
|
||||
|
||||
def open_rules(world, player):
|
||||
|
@@ -14,8 +14,6 @@ from .Items import item_name_groups
|
||||
|
||||
from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows
|
||||
|
||||
logger = logging.getLogger("Shops")
|
||||
|
||||
|
||||
@unique
|
||||
class ShopType(IntEnum):
|
||||
@@ -162,7 +160,10 @@ shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
||||
|
||||
|
||||
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]
|
||||
|
||||
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,
|
||||
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()
|
||||
|
||||
|
||||
@@ -225,7 +226,7 @@ def create_shops(multiworld, player: int):
|
||||
if locked is None:
|
||||
shop.locked = True
|
||||
region.shop = shop
|
||||
multiworld.shops.append(shop)
|
||||
multiworld.worlds[player].shops.append(shop)
|
||||
for index, item in enumerate(inventory):
|
||||
shop.add_inventory(index, *item)
|
||||
if not locked and (num_slots or type == ShopType.UpgradeShop):
|
||||
@@ -309,42 +310,42 @@ def set_up_shops(multiworld, player: int):
|
||||
from .Options import small_key_shuffle
|
||||
# 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
|
||||
# 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],
|
||||
['Blue Shield', 50], ['Small Heart',
|
||||
10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them.
|
||||
if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
['Blue Shield', 50], ['Small Heart', 10]]
|
||||
if local_world.options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
replacement_items.append(['Small Key (Universal)', 100])
|
||||
replacement_item = multiworld.random.choice(replacement_items)
|
||||
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
|
||||
rss.locked = True
|
||||
|
||||
if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal or multiworld.worlds[player].options.retro_bow:
|
||||
for shop in multiworld.random.sample([s for s in multiworld.shops if
|
||||
s.custom and not s.locked and s.type == ShopType.Shop
|
||||
and s.region.player == player], 5):
|
||||
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 local_world.shops if
|
||||
s.custom and not s.locked and s.type == ShopType.Shop], 5):
|
||||
shop.locked = True
|
||||
slots = [0, 1, 2]
|
||||
multiworld.random.shuffle(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)
|
||||
if multiworld.worlds[player].options.retro_bow:
|
||||
if local_world.options.retro_bow:
|
||||
shop.push_inventory(next(slots), 'Single Arrow', 80)
|
||||
|
||||
if multiworld.worlds[player].options.shuffle_capacity_upgrades:
|
||||
for shop in multiworld.shops:
|
||||
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
|
||||
if local_world.options.shuffle_capacity_upgrades:
|
||||
for shop in local_world.shops:
|
||||
if shop.type == ShopType.UpgradeShop and \
|
||||
shop.region.name == "Capacity Upgrade":
|
||||
shop.clear_inventory()
|
||||
|
||||
if (multiworld.worlds[player].options.shuffle_shop_inventories or multiworld.worlds[player].options.randomize_shop_prices
|
||||
or multiworld.worlds[player].options.randomize_cost_types):
|
||||
if (local_world.options.shuffle_shop_inventories or local_world.options.randomize_shop_prices
|
||||
or local_world.options.randomize_cost_types):
|
||||
shops = []
|
||||
total_inventory = []
|
||||
for shop in multiworld.shops:
|
||||
if shop.region.player == player:
|
||||
for shop in local_world.shops:
|
||||
if shop.type == ShopType.Shop and not shop.locked:
|
||||
shops.append(shop)
|
||||
total_inventory.extend(shop.inventory)
|
||||
@@ -352,7 +353,7 @@ def set_up_shops(multiworld, player: int):
|
||||
for item in total_inventory:
|
||||
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)
|
||||
|
||||
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 any(x in item.name for x in
|
||||
['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']):
|
||||
return 0.5
|
||||
else:
|
||||
return 1
|
||||
return 1.0
|
||||
if item.advancement:
|
||||
return 1
|
||||
return 1.0
|
||||
elif item.useful:
|
||||
return 0.5
|
||||
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):
|
||||
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:
|
||||
return can_use_bombs(state, player, location.shop_price)
|
||||
elif location.shop_price_type == ShopPriceType.Arrows:
|
||||
|
@@ -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:
|
||||
return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(state) for
|
||||
shop in state.multiworld.shops)
|
||||
return any(shop.has_unlimited(item) and shop.region.can_reach(state) for
|
||||
shop in state.multiworld.worlds[player].shops)
|
||||
|
||||
|
||||
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
|
||||
shop in state.multiworld.shops)
|
||||
return any(shop.has(item) and shop.region.can_reach(state) for
|
||||
shop in state.multiworld.worlds[player].shops)
|
||||
|
||||
|
||||
def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool:
|
||||
|
@@ -236,6 +236,8 @@ class ALTTPWorld(World):
|
||||
required_client_version = (0, 4, 1)
|
||||
web = ALTTPWeb()
|
||||
|
||||
shops: list[Shop]
|
||||
|
||||
pedestal_credit_texts: typing.Dict[int, str] = \
|
||||
{data.item_code: data.pedestal_credit for data in item_table.values() if data.pedestal_credit}
|
||||
sickkid_credit_texts: typing.Dict[int, str] = \
|
||||
@@ -282,6 +284,10 @@ class ALTTPWorld(World):
|
||||
clock_mode: str = ""
|
||||
treasure_hunt_required: 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):
|
||||
self.dungeon_local_item_names = set()
|
||||
@@ -298,6 +304,7 @@ class ALTTPWorld(World):
|
||||
self.fix_trock_exit = None
|
||||
self.required_medallions = ["Ether", "Quake"]
|
||||
self.escape_assist = []
|
||||
self.shops = []
|
||||
super(ALTTPWorld, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
@@ -800,7 +807,7 @@ class ALTTPWorld(World):
|
||||
|
||||
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')
|
||||
for shop_data in shop_info:
|
||||
spoiler_handle.write("{} [{}]\n {}\n".format(shop_data['location'], shop_data['type'], "\n ".join(
|
||||
|
Reference in New Issue
Block a user