diff --git a/BaseClasses.py b/BaseClasses.py index 9c2c6d6a2a36..11f9160c1ffb 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -113,7 +113,6 @@ def __init__(self, players: int): self.dark_world_light_cone = False self.rupoor_cost = 10 self.aga_randomness = True - self.lock_aga_door_in_escape = False self.save_and_quit_from_boss = True self.custom = False self.customitemarray = [] @@ -122,6 +121,7 @@ def __init__(self, players: int): self.early_items = {player: {} for player in self.player_ids} self.local_early_items = {player: {} for player in self.player_ids} self.indirect_connections = {} + self.start_inventory_from_pool = {player: Options.StartInventoryPool({}) for player in range(1, players + 1)} self.fix_trock_doors = self.AttributeProxy( lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted') self.fix_skullwoods_exit = self.AttributeProxy( diff --git a/Main.py b/Main.py index 03b2e1b155b4..6eda78f5b400 100644 --- a/Main.py +++ b/Main.py @@ -115,6 +115,9 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No for item_name, count in world.start_inventory[player].value.items(): for _ in range(count): world.push_precollected(world.create_item(item_name, player)) + for item_name, count in world.start_inventory_from_pool[player].value.items(): + for _ in range(count): + world.push_precollected(world.create_item(item_name, player)) logger.info('Creating World.') AutoWorld.call_all(world, "create_regions") @@ -149,6 +152,32 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No AutoWorld.call_all(world, "generate_basic") + # remove starting inventory from pool items. + # Because some worlds don't actually create items during create_items this has to be as late as possible. + if any(world.start_inventory_from_pool[player].value for player in world.player_ids): + new_items: List[Item] = [] + depletion_pool: Dict[int, Dict[str, int]] = { + player: world.start_inventory_from_pool[player].value.copy() for player in world.player_ids} + for player, items in depletion_pool.items(): + player_world: AutoWorld.World = world.worlds[player] + for count in items.values(): + new_items.append(player_world.create_filler()) + target: int = sum(sum(items.values()) for items in depletion_pool.values()) + for item in world.itempool: + if depletion_pool[item.player].get(item.name, 0): + target -= 1 + depletion_pool[item.player][item.name] -= 1 + # quick abort if we have found all items + if not target: + break + else: + new_items.append(item) + for player, remaining_items in depletion_pool.items(): + if remaining_items: + raise Exception(f"{world.get_player_name(player)}" + f" is trying to remove items from their pool that don't exist: {remaining_items}") + world.itempool[:] = new_items + # temporary home for item links, should be moved out of Main for group_id, group in world.groups.items(): def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ diff --git a/Options.py b/Options.py index 48d802b5b11a..50c16a893d72 100644 --- a/Options.py +++ b/Options.py @@ -897,6 +897,13 @@ class StartInventory(ItemDict): display_name = "Start Inventory" +class StartInventoryPool(StartInventory): + """Start with these items and don't place them in the world. + The game decides what the replacement items will be.""" + verify_item_name = True + display_name = "Start Inventory from Pool" + + class StartHints(ItemSet): """Start with these item's locations prefilled into the !hint command.""" display_name = "Start Hints" diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index dd007954e108..b4b0958ac23f 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -1,7 +1,7 @@ import typing from BaseClasses import MultiWorld -from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, TextChoice, PlandoBosses +from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses class Logic(Choice): @@ -466,5 +466,6 @@ class AllowCollect(Toggle): "beemizer_total_chance": BeemizerTotalChance, "beemizer_trap_chance": BeemizerTrapChance, "death_link": DeathLink, - "allow_collect": AllowCollect + "allow_collect": AllowCollect, + "start_inventory_from_pool": StartInventoryPool, } diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 6b0ad9ad643f..99826157d81f 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -1247,8 +1247,8 @@ def chunk(l, n): # assorted fixes rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world[ player] else 0x00) # Toggle whether to be in real/fake dark world when dying in a DW dungeon before killing aga1 - rom.write_byte(0x180169, - 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence. + # Lock or unlock aga tower door during escape sequence. + rom.write_byte(0x180169, 0x00) if world.mode[player] == 'inverted': rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted rom.write_byte(0x180171, diff --git a/worlds/factorio/Options.py b/worlds/factorio/Options.py index 85394cae444c..0331c2d013ea 100644 --- a/worlds/factorio/Options.py +++ b/worlds/factorio/Options.py @@ -2,7 +2,8 @@ import typing import datetime -from Options import Choice, OptionDict, OptionSet, ItemDict, Option, DefaultOnToggle, Range, DeathLink, Toggle +from Options import Choice, OptionDict, OptionSet, ItemDict, Option, DefaultOnToggle, Range, DeathLink, Toggle, \ + StartInventoryPool from schema import Schema, Optional, And, Or # schema helpers @@ -454,6 +455,7 @@ class EnergyLink(Toggle): "evolution_trap_increase": EvolutionTrapIncrease, "death_link": DeathLink, "energy_link": EnergyLink, + "start_inventory_from_pool": StartInventoryPool, } # spoilers below. If you spoil it for yourself, please at least don't spoil it for anyone else. diff --git a/worlds/subnautica/Options.py b/worlds/subnautica/Options.py index 91c4866142e5..fa66026d7d4a 100644 --- a/worlds/subnautica/Options.py +++ b/worlds/subnautica/Options.py @@ -1,6 +1,6 @@ import typing -from Options import Choice, Range, DeathLink, DefaultOnToggle +from Options import Choice, Range, DeathLink, DefaultOnToggle, StartInventoryPool from .Creatures import all_creatures, Definitions @@ -104,4 +104,5 @@ class SubnauticaDeathLink(DeathLink): "creature_scans": CreatureScans, "creature_scan_logic": AggressiveScanLogic, "death_link": SubnauticaDeathLink, + "start_inventory_from_pool": StartInventoryPool, }