diff --git a/worlds/timespinner/Locations.py b/worlds/timespinner/Locations.py index 960444ac..03f1c025 100644 --- a/worlds/timespinner/Locations.py +++ b/worlds/timespinner/Locations.py @@ -14,7 +14,7 @@ class LocationData(NamedTuple): rule: Callable[[CollectionState], bool] = lambda state: True -def get_locations(world: Optional[MultiWorld], player: Optional[int], +def get_location_datas(world: Optional[MultiWorld], player: Optional[int], precalculated_weights: PreCalculatedWeights) -> Tuple[LocationData, ...]: flooded: PreCalculatedWeights = precalculated_weights diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index ab8ee97a..00260624 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -1,64 +1,64 @@ from typing import List, Set, Dict, Tuple, Optional, Callable from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location from .Options import is_option_enabled -from .Locations import LocationData +from .Locations import LocationData, get_location_datas from .PreCalculatedWeights import PreCalculatedWeights from .LogicExtensions import TimespinnerLogic -def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location], - precalculated_weights: PreCalculatedWeights): +def create_regions_and_locations(world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights): + locationn_datas: Tuple[LocationData] = get_location_datas(world, player, precalculated_weights) - locations_per_region = get_locations_per_region(locations) + locations_per_region: Dict[str, List[LocationData]] = split_location_datas_per_region(locationn_datas) regions = [ - create_region(world, player, locations_per_region, location_cache, 'Menu'), - create_region(world, player, locations_per_region, location_cache, 'Tutorial'), - create_region(world, player, locations_per_region, location_cache, 'Lake desolation'), - create_region(world, player, locations_per_region, location_cache, 'Upper lake desolation'), - create_region(world, player, locations_per_region, location_cache, 'Lower lake desolation'), - create_region(world, player, locations_per_region, location_cache, 'Eastern lake desolation'), - create_region(world, player, locations_per_region, location_cache, 'Library'), - create_region(world, player, locations_per_region, location_cache, 'Library top'), - create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower left'), - create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (upper)'), - create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (lower)'), - create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (elevator)'), - create_region(world, player, locations_per_region, location_cache, 'Sealed Caves (Sirens)'), - create_region(world, player, locations_per_region, location_cache, 'Military Fortress'), - create_region(world, player, locations_per_region, location_cache, 'Military Fortress (hangar)'), - create_region(world, player, locations_per_region, location_cache, 'The lab'), - create_region(world, player, locations_per_region, location_cache, 'The lab (power off)'), - create_region(world, player, locations_per_region, location_cache, 'The lab (upper)'), - create_region(world, player, locations_per_region, location_cache, 'Emperors tower'), - create_region(world, player, locations_per_region, location_cache, 'Skeleton Shaft'), - create_region(world, player, locations_per_region, location_cache, 'Sealed Caves (upper)'), - create_region(world, player, locations_per_region, location_cache, 'Sealed Caves (Xarion)'), - create_region(world, player, locations_per_region, location_cache, 'Refugee Camp'), - create_region(world, player, locations_per_region, location_cache, 'Forest'), - create_region(world, player, locations_per_region, location_cache, 'Left Side forest Caves'), - create_region(world, player, locations_per_region, location_cache, 'Upper Lake Serene'), - create_region(world, player, locations_per_region, location_cache, 'Lower Lake Serene'), - create_region(world, player, locations_per_region, location_cache, 'Caves of Banishment (upper)'), - create_region(world, player, locations_per_region, location_cache, 'Caves of Banishment (Maw)'), - create_region(world, player, locations_per_region, location_cache, 'Caves of Banishment (Sirens)'), - create_region(world, player, locations_per_region, location_cache, 'Castle Ramparts'), - create_region(world, player, locations_per_region, location_cache, 'Castle Keep'), - create_region(world, player, locations_per_region, location_cache, 'Castle Basement'), - create_region(world, player, locations_per_region, location_cache, 'Royal towers (lower)'), - create_region(world, player, locations_per_region, location_cache, 'Royal towers'), - create_region(world, player, locations_per_region, location_cache, 'Royal towers (upper)'), - create_region(world, player, locations_per_region, location_cache, 'Temporal Gyre'), - create_region(world, player, locations_per_region, location_cache, 'Ancient Pyramid (entrance)'), - create_region(world, player, locations_per_region, location_cache, 'Ancient Pyramid (left)'), - create_region(world, player, locations_per_region, location_cache, 'Ancient Pyramid (right)'), - create_region(world, player, locations_per_region, location_cache, 'Space time continuum') + create_region(world, player, locations_per_region, 'Menu'), + create_region(world, player, locations_per_region, 'Tutorial'), + create_region(world, player, locations_per_region, 'Lake desolation'), + create_region(world, player, locations_per_region, 'Upper lake desolation'), + create_region(world, player, locations_per_region, 'Lower lake desolation'), + create_region(world, player, locations_per_region, 'Eastern lake desolation'), + create_region(world, player, locations_per_region, 'Library'), + create_region(world, player, locations_per_region, 'Library top'), + create_region(world, player, locations_per_region, 'Varndagroth tower left'), + create_region(world, player, locations_per_region, 'Varndagroth tower right (upper)'), + create_region(world, player, locations_per_region, 'Varndagroth tower right (lower)'), + create_region(world, player, locations_per_region, 'Varndagroth tower right (elevator)'), + create_region(world, player, locations_per_region, 'Sealed Caves (Sirens)'), + create_region(world, player, locations_per_region, 'Military Fortress'), + create_region(world, player, locations_per_region, 'Military Fortress (hangar)'), + create_region(world, player, locations_per_region, 'The lab'), + create_region(world, player, locations_per_region, 'The lab (power off)'), + create_region(world, player, locations_per_region, 'The lab (upper)'), + create_region(world, player, locations_per_region, 'Emperors tower'), + create_region(world, player, locations_per_region, 'Skeleton Shaft'), + create_region(world, player, locations_per_region, 'Sealed Caves (upper)'), + create_region(world, player, locations_per_region, 'Sealed Caves (Xarion)'), + create_region(world, player, locations_per_region, 'Refugee Camp'), + create_region(world, player, locations_per_region, 'Forest'), + create_region(world, player, locations_per_region, 'Left Side forest Caves'), + create_region(world, player, locations_per_region, 'Upper Lake Serene'), + create_region(world, player, locations_per_region, 'Lower Lake Serene'), + create_region(world, player, locations_per_region, 'Caves of Banishment (upper)'), + create_region(world, player, locations_per_region, 'Caves of Banishment (Maw)'), + create_region(world, player, locations_per_region, 'Caves of Banishment (Sirens)'), + create_region(world, player, locations_per_region, 'Castle Ramparts'), + create_region(world, player, locations_per_region, 'Castle Keep'), + create_region(world, player, locations_per_region, 'Castle Basement'), + create_region(world, player, locations_per_region, 'Royal towers (lower)'), + create_region(world, player, locations_per_region, 'Royal towers'), + create_region(world, player, locations_per_region, 'Royal towers (upper)'), + create_region(world, player, locations_per_region, 'Temporal Gyre'), + create_region(world, player, locations_per_region, 'Ancient Pyramid (entrance)'), + create_region(world, player, locations_per_region, 'Ancient Pyramid (left)'), + create_region(world, player, locations_per_region, 'Ancient Pyramid (right)'), + create_region(world, player, locations_per_region, 'Space time continuum') ] if is_option_enabled(world, player, "GyreArchives"): regions.extend([ - create_region(world, player, locations_per_region, location_cache, 'Ravenlord\'s Lair'), - create_region(world, player, locations_per_region, location_cache, 'Ifrit\'s Lair'), + create_region(world, player, locations_per_region, 'Ravenlord\'s Lair'), + create_region(world, player, locations_per_region, 'Ifrit\'s Lair'), ]) if __debug__: @@ -70,127 +70,126 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData flooded: PreCalculatedWeights = precalculated_weights logic = TimespinnerLogic(world, player, precalculated_weights) - names: Dict[str, int] = {} - connect(world, player, names, 'Lake desolation', 'Lower lake desolation', lambda state: logic.has_timestop(state) or state.has('Talaria Attachment', player) or flooded.flood_lake_desolation) - connect(world, player, names, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player)) - connect(world, player, names, 'Lake desolation', 'Skeleton Shaft', lambda state: logic.has_doublejump(state) or flooded.flood_lake_desolation) - connect(world, player, names, 'Lake desolation', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Upper lake desolation', 'Lake desolation') - connect(world, player, names, 'Upper lake desolation', 'Eastern lake desolation') - connect(world, player, names, 'Lower lake desolation', 'Lake desolation') - connect(world, player, names, 'Lower lake desolation', 'Eastern lake desolation') - connect(world, player, names, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Eastern lake desolation', 'Library') - connect(world, player, names, 'Eastern lake desolation', 'Lower lake desolation') - connect(world, player, names, 'Eastern lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player)) - connect(world, player, names, 'Library', 'Eastern lake desolation') - connect(world, player, names, 'Library', 'Library top', lambda state: logic.has_doublejump(state) or state.has('Talaria Attachment', player)) - connect(world, player, names, 'Library', 'Varndagroth tower left', logic.has_keycard_D) - connect(world, player, names, 'Library', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Library top', 'Library') - connect(world, player, names, 'Varndagroth tower left', 'Library') - connect(world, player, names, 'Varndagroth tower left', 'Varndagroth tower right (upper)', logic.has_keycard_C) - connect(world, player, names, 'Varndagroth tower left', 'Varndagroth tower right (lower)', logic.has_keycard_B) - connect(world, player, names, 'Varndagroth tower left', 'Sealed Caves (Sirens)', lambda state: logic.has_keycard_B(state) and state.has('Elevator Keycard', player)) - connect(world, player, names, 'Varndagroth tower left', 'Refugee Camp', lambda state: state.has('Timespinner Wheel', player) and state.has('Timespinner Spindle', player)) - connect(world, player, names, 'Varndagroth tower right (upper)', 'Varndagroth tower left') - connect(world, player, names, 'Varndagroth tower right (upper)', 'Varndagroth tower right (elevator)', lambda state: state.has('Elevator Keycard', player)) - connect(world, player, names, 'Varndagroth tower right (elevator)', 'Varndagroth tower right (upper)') - connect(world, player, names, 'Varndagroth tower right (elevator)', 'Varndagroth tower right (lower)') - connect(world, player, names, 'Varndagroth tower right (lower)', 'Varndagroth tower left', logic.has_keycard_B) - connect(world, player, names, 'Varndagroth tower right (lower)', 'Varndagroth tower right (elevator)', lambda state: state.has('Elevator Keycard', player)) - connect(world, player, names, 'Varndagroth tower right (lower)', 'Sealed Caves (Sirens)', lambda state: logic.has_keycard_B(state) and state.has('Elevator Keycard', player)) - connect(world, player, names, 'Varndagroth tower right (lower)', 'Military Fortress', logic.can_kill_all_3_bosses) - connect(world, player, names, 'Varndagroth tower right (lower)', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Sealed Caves (Sirens)', 'Varndagroth tower left', lambda state: state.has('Elevator Keycard', player)) - connect(world, player, names, 'Sealed Caves (Sirens)', 'Varndagroth tower right (lower)', lambda state: state.has('Elevator Keycard', player)) - connect(world, player, names, 'Sealed Caves (Sirens)', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Military Fortress', 'Varndagroth tower right (lower)', logic.can_kill_all_3_bosses) - connect(world, player, names, 'Military Fortress', 'Temporal Gyre', lambda state: state.has('Timespinner Wheel', player)) - connect(world, player, names, 'Military Fortress', 'Military Fortress (hangar)', logic.has_doublejump) - connect(world, player, names, 'Military Fortress (hangar)', 'Military Fortress') - connect(world, player, names, 'Military Fortress (hangar)', 'The lab', lambda state: logic.has_keycard_B(state) and logic.has_doublejump(state)) - connect(world, player, names, 'Temporal Gyre', 'Military Fortress') - connect(world, player, names, 'The lab', 'Military Fortress') - connect(world, player, names, 'The lab', 'The lab (power off)', logic.has_doublejump_of_npc) - connect(world, player, names, 'The lab (power off)', 'The lab') - connect(world, player, names, 'The lab (power off)', 'The lab (upper)', logic.has_forwarddash_doublejump) - connect(world, player, names, 'The lab (upper)', 'The lab (power off)') - connect(world, player, names, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump) - connect(world, player, names, 'The lab (upper)', 'Ancient Pyramid (entrance)', lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player)) - connect(world, player, names, 'Emperors tower', 'The lab (upper)') - connect(world, player, names, 'Skeleton Shaft', 'Lake desolation') - connect(world, player, names, 'Skeleton Shaft', 'Sealed Caves (upper)', logic.has_keycard_A) - connect(world, player, names, 'Skeleton Shaft', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Sealed Caves (upper)', 'Skeleton Shaft') - connect(world, player, names, 'Sealed Caves (upper)', 'Sealed Caves (Xarion)', lambda state: logic.has_teleport(state) or logic.has_doublejump(state)) - connect(world, player, names, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', logic.has_doublejump) - connect(world, player, names, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Refugee Camp', 'Forest') - #connect(world, player, names, 'Refugee Camp', 'Library', lambda state: not is_option_enabled(world, player, "Inverted")) - connect(world, player, names, 'Refugee Camp', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Forest', 'Refugee Camp') - connect(world, player, names, 'Forest', 'Left Side forest Caves', lambda state: state.has('Talaria Attachment', player) or logic.has_timestop(state)) - connect(world, player, names, 'Forest', 'Caves of Banishment (Sirens)') - connect(world, player, names, 'Forest', 'Castle Ramparts') - connect(world, player, names, 'Left Side forest Caves', 'Forest') - connect(world, player, names, 'Left Side forest Caves', 'Upper Lake Serene', logic.has_timestop) - connect(world, player, names, 'Left Side forest Caves', 'Lower Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene) - connect(world, player, names, 'Left Side forest Caves', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Upper Lake Serene', 'Left Side forest Caves') - connect(world, player, names, 'Upper Lake Serene', 'Lower Lake Serene', lambda state: state.has('Water Mask', player)) - connect(world, player, names, 'Lower Lake Serene', 'Upper Lake Serene') - connect(world, player, names, 'Lower Lake Serene', 'Left Side forest Caves') - connect(world, player, names, 'Lower Lake Serene', 'Caves of Banishment (upper)') - connect(world, player, names, 'Caves of Banishment (upper)', 'Upper Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene) - connect(world, player, names, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: logic.has_doublejump(state) or state.has_any({'Gas Mask', 'Twin Pyramid Key'}, player)) - connect(world, player, names, 'Caves of Banishment (upper)', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Caves of Banishment (Maw)', 'Caves of Banishment (upper)', lambda state: logic.has_doublejump(state) if not flooded.flood_maw else state.has('Water Mask', player)) - connect(world, player, names, 'Caves of Banishment (Maw)', 'Caves of Banishment (Sirens)', lambda state: state.has('Gas Mask', player)) - connect(world, player, names, 'Caves of Banishment (Maw)', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Caves of Banishment (Sirens)', 'Forest') - connect(world, player, names, 'Castle Ramparts', 'Forest') - connect(world, player, names, 'Castle Ramparts', 'Castle Keep') - connect(world, player, names, 'Castle Ramparts', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Castle Keep', 'Castle Ramparts') - connect(world, player, names, 'Castle Keep', 'Castle Basement', lambda state: state.has('Water Mask', player) or not flooded.flood_basement) - connect(world, player, names, 'Castle Keep', 'Royal towers (lower)', logic.has_doublejump) - connect(world, player, names, 'Castle Keep', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Royal towers (lower)', 'Castle Keep') - connect(world, player, names, 'Royal towers (lower)', 'Royal towers', lambda state: state.has('Timespinner Wheel', player) or logic.has_forwarddash_doublejump(state)) - connect(world, player, names, 'Royal towers (lower)', 'Space time continuum', logic.has_teleport) - connect(world, player, names, 'Royal towers', 'Royal towers (lower)') - connect(world, player, names, 'Royal towers', 'Royal towers (upper)', logic.has_doublejump) - connect(world, player, names, 'Royal towers (upper)', 'Royal towers') - #connect(world, player, names, 'Ancient Pyramid (entrance)', 'The lab (upper)', lambda state: not is_option_enabled(world, player, "EnterSandman")) - connect(world, player, names, 'Ancient Pyramid (entrance)', 'Ancient Pyramid (left)', logic.has_doublejump) - connect(world, player, names, 'Ancient Pyramid (left)', 'Ancient Pyramid (entrance)') - connect(world, player, names, 'Ancient Pyramid (left)', 'Ancient Pyramid (right)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft) - connect(world, player, names, 'Ancient Pyramid (right)', 'Ancient Pyramid (left)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft) - connect(world, player, names, 'Space time continuum', 'Lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateLakeDesolation")) - connect(world, player, names, 'Space time continuum', 'Lower lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateKittyBoss")) - connect(world, player, names, 'Space time continuum', 'Library', lambda state: logic.can_teleport_to(state, "Present", "GateLeftLibrary")) - connect(world, player, names, 'Space time continuum', 'Varndagroth tower right (lower)', lambda state: logic.can_teleport_to(state, "Present", "GateMilitaryGate")) - connect(world, player, names, 'Space time continuum', 'Skeleton Shaft', lambda state: logic.can_teleport_to(state, "Present", "GateSealedCaves")) - connect(world, player, names, 'Space time continuum', 'Sealed Caves (Sirens)', lambda state: logic.can_teleport_to(state, "Present", "GateSealedSirensCave")) - connect(world, player, names, 'Space time continuum', 'Upper Lake Serene', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneLeft")) - connect(world, player, names, 'Space time continuum', 'Left Side forest Caves', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneRight")) - connect(world, player, names, 'Space time continuum', 'Refugee Camp', lambda state: logic.can_teleport_to(state, "Past", "GateAccessToPast")) - connect(world, player, names, 'Space time continuum', 'Castle Ramparts', lambda state: logic.can_teleport_to(state, "Past", "GateCastleRamparts")) - connect(world, player, names, 'Space time continuum', 'Castle Keep', lambda state: logic.can_teleport_to(state, "Past", "GateCastleKeep")) - connect(world, player, names, 'Space time continuum', 'Royal towers (lower)', lambda state: logic.can_teleport_to(state, "Past", "GateRoyalTowers")) - connect(world, player, names, 'Space time continuum', 'Caves of Banishment (Maw)', lambda state: logic.can_teleport_to(state, "Past", "GateMaw")) - connect(world, player, names, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: logic.can_teleport_to(state, "Past", "GateCavesOfBanishment")) - connect(world, player, names, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not is_option_enabled(world, player, "UnchainedKeys") and is_option_enabled(world, player, "EnterSandman"))) - connect(world, player, names, 'Space time continuum', 'Ancient Pyramid (left)', lambda state: logic.can_teleport_to(state, "Time", "GateLeftPyramid")) - connect(world, player, names, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid")) + connect(world, player, 'Lake desolation', 'Lower lake desolation', lambda state: logic.has_timestop(state) or state.has('Talaria Attachment', player) or flooded.flood_lake_desolation) + connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player)) + connect(world, player, 'Lake desolation', 'Skeleton Shaft', lambda state: logic.has_doublejump(state) or flooded.flood_lake_desolation) + connect(world, player, 'Lake desolation', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Upper lake desolation', 'Lake desolation') + connect(world, player, 'Upper lake desolation', 'Eastern lake desolation') + connect(world, player, 'Lower lake desolation', 'Lake desolation') + connect(world, player, 'Lower lake desolation', 'Eastern lake desolation') + connect(world, player, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Eastern lake desolation', 'Library') + connect(world, player, 'Eastern lake desolation', 'Lower lake desolation') + connect(world, player, 'Eastern lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player)) + connect(world, player, 'Library', 'Eastern lake desolation') + connect(world, player, 'Library', 'Library top', lambda state: logic.has_doublejump(state) or state.has('Talaria Attachment', player)) + connect(world, player, 'Library', 'Varndagroth tower left', logic.has_keycard_D) + connect(world, player, 'Library', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Library top', 'Library') + connect(world, player, 'Varndagroth tower left', 'Library') + connect(world, player, 'Varndagroth tower left', 'Varndagroth tower right (upper)', logic.has_keycard_C) + connect(world, player, 'Varndagroth tower left', 'Varndagroth tower right (lower)', logic.has_keycard_B) + connect(world, player, 'Varndagroth tower left', 'Sealed Caves (Sirens)', lambda state: logic.has_keycard_B(state) and state.has('Elevator Keycard', player)) + connect(world, player, 'Varndagroth tower left', 'Refugee Camp', lambda state: state.has('Timespinner Wheel', player) and state.has('Timespinner Spindle', player)) + connect(world, player, 'Varndagroth tower right (upper)', 'Varndagroth tower left') + connect(world, player, 'Varndagroth tower right (upper)', 'Varndagroth tower right (elevator)', lambda state: state.has('Elevator Keycard', player)) + connect(world, player, 'Varndagroth tower right (elevator)', 'Varndagroth tower right (upper)') + connect(world, player, 'Varndagroth tower right (elevator)', 'Varndagroth tower right (lower)') + connect(world, player, 'Varndagroth tower right (lower)', 'Varndagroth tower left', logic.has_keycard_B) + connect(world, player, 'Varndagroth tower right (lower)', 'Varndagroth tower right (elevator)', lambda state: state.has('Elevator Keycard', player)) + connect(world, player, 'Varndagroth tower right (lower)', 'Sealed Caves (Sirens)', lambda state: logic.has_keycard_B(state) and state.has('Elevator Keycard', player)) + connect(world, player, 'Varndagroth tower right (lower)', 'Military Fortress', logic.can_kill_all_3_bosses) + connect(world, player, 'Varndagroth tower right (lower)', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Sealed Caves (Sirens)', 'Varndagroth tower left', lambda state: state.has('Elevator Keycard', player)) + connect(world, player, 'Sealed Caves (Sirens)', 'Varndagroth tower right (lower)', lambda state: state.has('Elevator Keycard', player)) + connect(world, player, 'Sealed Caves (Sirens)', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Military Fortress', 'Varndagroth tower right (lower)', logic.can_kill_all_3_bosses) + connect(world, player, 'Military Fortress', 'Temporal Gyre', lambda state: state.has('Timespinner Wheel', player)) + connect(world, player, 'Military Fortress', 'Military Fortress (hangar)', logic.has_doublejump) + connect(world, player, 'Military Fortress (hangar)', 'Military Fortress') + connect(world, player, 'Military Fortress (hangar)', 'The lab', lambda state: logic.has_keycard_B(state) and logic.has_doublejump(state)) + connect(world, player, 'Temporal Gyre', 'Military Fortress') + connect(world, player, 'The lab', 'Military Fortress') + connect(world, player, 'The lab', 'The lab (power off)', logic.has_doublejump_of_npc) + connect(world, player, 'The lab (power off)', 'The lab') + connect(world, player, 'The lab (power off)', 'The lab (upper)', logic.has_forwarddash_doublejump) + connect(world, player, 'The lab (upper)', 'The lab (power off)') + connect(world, player, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump) + connect(world, player, 'The lab (upper)', 'Ancient Pyramid (entrance)', lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player)) + connect(world, player, 'Emperors tower', 'The lab (upper)') + connect(world, player, 'Skeleton Shaft', 'Lake desolation') + connect(world, player, 'Skeleton Shaft', 'Sealed Caves (upper)', logic.has_keycard_A) + connect(world, player, 'Skeleton Shaft', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Sealed Caves (upper)', 'Skeleton Shaft') + connect(world, player, 'Sealed Caves (upper)', 'Sealed Caves (Xarion)', lambda state: logic.has_teleport(state) or logic.has_doublejump(state)) + connect(world, player, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', logic.has_doublejump) + connect(world, player, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Refugee Camp', 'Forest') + #connect(world, player, 'Refugee Camp', 'Library', lambda state: not is_option_enabled(world, player, "Inverted")) + connect(world, player, 'Refugee Camp', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Forest', 'Refugee Camp') + connect(world, player, 'Forest', 'Left Side forest Caves', lambda state: state.has('Talaria Attachment', player) or logic.has_timestop(state)) + connect(world, player, 'Forest', 'Caves of Banishment (Sirens)') + connect(world, player, 'Forest', 'Castle Ramparts') + connect(world, player, 'Left Side forest Caves', 'Forest') + connect(world, player, 'Left Side forest Caves', 'Upper Lake Serene', logic.has_timestop) + connect(world, player, 'Left Side forest Caves', 'Lower Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene) + connect(world, player, 'Left Side forest Caves', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Upper Lake Serene', 'Left Side forest Caves') + connect(world, player, 'Upper Lake Serene', 'Lower Lake Serene', lambda state: state.has('Water Mask', player)) + connect(world, player, 'Lower Lake Serene', 'Upper Lake Serene') + connect(world, player, 'Lower Lake Serene', 'Left Side forest Caves') + connect(world, player, 'Lower Lake Serene', 'Caves of Banishment (upper)') + connect(world, player, 'Caves of Banishment (upper)', 'Upper Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene) + connect(world, player, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: logic.has_doublejump(state) or state.has_any({'Gas Mask', 'Twin Pyramid Key'}, player)) + connect(world, player, 'Caves of Banishment (upper)', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Caves of Banishment (Maw)', 'Caves of Banishment (upper)', lambda state: logic.has_doublejump(state) if not flooded.flood_maw else state.has('Water Mask', player)) + connect(world, player, 'Caves of Banishment (Maw)', 'Caves of Banishment (Sirens)', lambda state: state.has_any({'Gas Mask', 'Talaria Attachment'}, player) ) + connect(world, player, 'Caves of Banishment (Maw)', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Caves of Banishment (Sirens)', 'Forest') + connect(world, player, 'Castle Ramparts', 'Forest') + connect(world, player, 'Castle Ramparts', 'Castle Keep') + connect(world, player, 'Castle Ramparts', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Castle Keep', 'Castle Ramparts') + connect(world, player, 'Castle Keep', 'Castle Basement', lambda state: state.has('Water Mask', player) or not flooded.flood_basement) + connect(world, player, 'Castle Keep', 'Royal towers (lower)', logic.has_doublejump) + connect(world, player, 'Castle Keep', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Royal towers (lower)', 'Castle Keep') + connect(world, player, 'Royal towers (lower)', 'Royal towers', lambda state: state.has('Timespinner Wheel', player) or logic.has_forwarddash_doublejump(state)) + connect(world, player, 'Royal towers (lower)', 'Space time continuum', logic.has_teleport) + connect(world, player, 'Royal towers', 'Royal towers (lower)') + connect(world, player, 'Royal towers', 'Royal towers (upper)', logic.has_doublejump) + connect(world, player, 'Royal towers (upper)', 'Royal towers') + #connect(world, player, 'Ancient Pyramid (entrance)', 'The lab (upper)', lambda state: not is_option_enabled(world, player, "EnterSandman")) + connect(world, player, 'Ancient Pyramid (entrance)', 'Ancient Pyramid (left)', logic.has_doublejump) + connect(world, player, 'Ancient Pyramid (left)', 'Ancient Pyramid (entrance)') + connect(world, player, 'Ancient Pyramid (left)', 'Ancient Pyramid (right)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft) + connect(world, player, 'Ancient Pyramid (right)', 'Ancient Pyramid (left)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft) + connect(world, player, 'Space time continuum', 'Lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateLakeDesolation")) + connect(world, player, 'Space time continuum', 'Lower lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateKittyBoss")) + connect(world, player, 'Space time continuum', 'Library', lambda state: logic.can_teleport_to(state, "Present", "GateLeftLibrary")) + connect(world, player, 'Space time continuum', 'Varndagroth tower right (lower)', lambda state: logic.can_teleport_to(state, "Present", "GateMilitaryGate")) + connect(world, player, 'Space time continuum', 'Skeleton Shaft', lambda state: logic.can_teleport_to(state, "Present", "GateSealedCaves")) + connect(world, player, 'Space time continuum', 'Sealed Caves (Sirens)', lambda state: logic.can_teleport_to(state, "Present", "GateSealedSirensCave")) + connect(world, player, 'Space time continuum', 'Upper Lake Serene', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneLeft")) + connect(world, player, 'Space time continuum', 'Left Side forest Caves', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneRight")) + connect(world, player, 'Space time continuum', 'Refugee Camp', lambda state: logic.can_teleport_to(state, "Past", "GateAccessToPast")) + connect(world, player, 'Space time continuum', 'Castle Ramparts', lambda state: logic.can_teleport_to(state, "Past", "GateCastleRamparts")) + connect(world, player, 'Space time continuum', 'Castle Keep', lambda state: logic.can_teleport_to(state, "Past", "GateCastleKeep")) + connect(world, player, 'Space time continuum', 'Royal towers (lower)', lambda state: logic.can_teleport_to(state, "Past", "GateRoyalTowers")) + connect(world, player, 'Space time continuum', 'Caves of Banishment (Maw)', lambda state: logic.can_teleport_to(state, "Past", "GateMaw")) + connect(world, player, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: logic.can_teleport_to(state, "Past", "GateCavesOfBanishment")) + connect(world, player, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not is_option_enabled(world, player, "UnchainedKeys") and is_option_enabled(world, player, "EnterSandman"))) + connect(world, player, 'Space time continuum', 'Ancient Pyramid (left)', lambda state: logic.can_teleport_to(state, "Time", "GateLeftPyramid")) + connect(world, player, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid")) if is_option_enabled(world, player, "GyreArchives"): - connect(world, player, names, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player)) - connect(world, player, names, 'Ravenlord\'s Lair', 'The lab (upper)') - connect(world, player, names, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player)) - connect(world, player, names, 'Ifrit\'s Lair', 'Library top') + connect(world, player, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player)) + connect(world, player, 'Ravenlord\'s Lair', 'The lab (upper)') + connect(world, player, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player)) + connect(world, player, 'Ifrit\'s Lair', 'Library top') def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]): @@ -203,7 +202,7 @@ def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: raise Exception("Timespinner: the following regions are used in locations: {}, but no such region exists".format(regionNames - existingRegions)) -def create_location(player: int, location_data: LocationData, region: Region, location_cache: List[Location]) -> Location: +def create_location(player: int, location_data: LocationData, region: Region) -> Location: location = Location(player, location_data.name, location_data.code, region) location.access_rule = location_data.rule @@ -211,17 +210,15 @@ def create_location(player: int, location_data: LocationData, region: Region, lo location.event = True location.locked = True - location_cache.append(location) - return location -def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], location_cache: List[Location], name: str) -> Region: +def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], name: str) -> Region: region = Region(name, player, world) if name in locations_per_region: for location_data in locations_per_region[name]: - location = create_location(player, location_data, region, location_cache) + location = create_location(player, location_data, region) region.locations.append(location) return region @@ -250,19 +247,13 @@ def connectStartingRegion(world: MultiWorld, player: int): space_time_continuum.exits.append(teleport_back_to_start) -def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: str, target: str, +def connect(world: MultiWorld, player: int, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None): + sourceRegion = world.get_region(source, player) targetRegion = world.get_region(target, player) - if target not in used_names: - used_names[target] = 1 - name = target - else: - used_names[target] += 1 - name = target + (' ' * used_names[target]) - - connection = Entrance(player, name, sourceRegion) + connection = Entrance(player, "", sourceRegion) if rule: connection.access_rule = rule @@ -271,7 +262,7 @@ def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: connection.connect(targetRegion) -def get_locations_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: +def split_location_datas_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: per_region: Dict[str, List[LocationData]] = {} for location in locations: diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py index cb52459b..d31a4f19 100644 --- a/worlds/timespinner/__init__.py +++ b/worlds/timespinner/__init__.py @@ -1,10 +1,10 @@ -from typing import Dict, List, Set, Tuple, TextIO -from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification +from typing import Dict, List, Set, Tuple, TextIO, Union +from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification from .Items import get_item_names_per_category, item_table, starter_melee_weapons, starter_spells, filler_items -from .Locations import get_locations, EventId +from .Locations import get_location_datas, EventId from .Options import is_option_enabled, get_option_value, timespinner_options from .PreCalculatedWeights import PreCalculatedWeights -from .Regions import create_regions +from .Regions import create_regions_and_locations from worlds.AutoWorld import World, WebWorld class TimespinnerWebWorld(WebWorld): @@ -29,7 +29,6 @@ class TimespinnerWebWorld(WebWorld): tutorials = [setup, setup_de] - class TimespinnerWorld(World): """ Timespinner is a beautiful metroidvania inspired by classic 90s action-platformers. @@ -44,21 +43,16 @@ class TimespinnerWorld(World): required_client_version = (0, 3, 7) item_name_to_id = {name: data.code for name, data in item_table.items()} - location_name_to_id = {location.name: location.code for location in get_locations(None, None, None)} + location_name_to_id = {location.name: location.code for location in get_location_datas(None, None, None)} item_name_groups = get_item_names_per_category() - locked_locations: List[str] - location_cache: List[Location] precalculated_weights: PreCalculatedWeights def __init__(self, world: MultiWorld, player: int): super().__init__(world, player) - - self.locked_locations = [] - self.location_cache = [] self.precalculated_weights = PreCalculatedWeights(world, player) - def generate_early(self): + def generate_early(self) -> None: # in generate_early the start_inventory isnt copied over to precollected_items yet, so we can still modify the options directly if self.multiworld.start_inventory[self.player].value.pop('Meyef', 0) > 0: self.multiworld.StartWithMeyef[self.player].value = self.multiworld.StartWithMeyef[self.player].option_true @@ -67,44 +61,27 @@ class TimespinnerWorld(World): if self.multiworld.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0: self.multiworld.StartWithJewelryBox[self.player].value = self.multiworld.StartWithJewelryBox[self.player].option_true - def create_regions(self): - locations = get_locations(self.multiworld, self.player, self.precalculated_weights) - create_regions(self.multiworld, self.player, locations, self.location_cache, self.precalculated_weights) + def create_regions(self) -> None: + create_regions_and_locations(self.multiworld, self.player, self.precalculated_weights) - def create_item(self, name: str) -> Item: - return create_item_with_correct_settings(self.multiworld, self.player, name) + def create_items(self) -> None: + self.create_and_assign_event_items() - def get_filler_item_name(self) -> str: - trap_chance: int = get_option_value(self.multiworld, self.player, "TrapChance") - enabled_traps: List[str] = get_option_value(self.multiworld, self.player, "Traps") + excluded_items: Set[str] = self.get_excluded_items() - if self.multiworld.random.random() < (trap_chance / 100) and enabled_traps: - return self.multiworld.random.choice(enabled_traps) - else: - return self.multiworld.random.choice(filler_items) + self.assign_starter_items(excluded_items) - def set_rules(self): - setup_events(self.player, self.locked_locations, self.location_cache) + self.multiworld.itempool += self.get_item_pool(excluded_items) + def set_rules(self) -> None: final_boss: str - if is_option_enabled(self.multiworld, self.player, "DadPercent"): + if self.is_option_enabled("DadPercent"): final_boss = "Killed Emperor" else: final_boss = "Killed Nightmare" self.multiworld.completion_condition[self.player] = lambda state: state.has(final_boss, self.player) - def generate_basic(self): - excluded_items: Set[str] = get_excluded_items(self, self.multiworld, self.player) - - assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations) - - pool = get_item_pool(self.multiworld, self.player, excluded_items) - - fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool) - - self.multiworld.itempool += pool - def fill_slot_data(self) -> Dict[str, object]: slot_data: Dict[str, object] = {} @@ -112,12 +89,12 @@ class TimespinnerWorld(World): for option_name in timespinner_options: if (option_name not in ap_specific_settings): - slot_data[option_name] = get_option_value(self.multiworld, self.player, option_name) + slot_data[option_name] = self.get_option_value(option_name) slot_data["StinkyMaw"] = True slot_data["ProgressiveVerticalMovement"] = False slot_data["ProgressiveKeycards"] = False - slot_data["PersonalItems"] = get_personal_items(self.player, self.location_cache) + slot_data["PersonalItems"] = self.get_personal_items() slot_data["PyramidKeysGate"] = self.precalculated_weights.pyramid_keys_unlock slot_data["PresentGate"] = self.precalculated_weights.present_key_unlock slot_data["PastGate"] = self.precalculated_weights.past_key_unlock @@ -135,17 +112,17 @@ class TimespinnerWorld(World): return slot_data - def write_spoiler_header(self, spoiler_handle: TextIO): - if is_option_enabled(self.multiworld, self.player, "UnchainedKeys"): + def write_spoiler_header(self, spoiler_handle: TextIO) -> None: + if self.is_option_enabled("UnchainedKeys"): spoiler_handle.write(f'Modern Warp Beacon unlock: {self.precalculated_weights.present_key_unlock}\n') spoiler_handle.write(f'Timeworn Warp Beacon unlock: {self.precalculated_weights.past_key_unlock}\n') - if is_option_enabled(self.multiworld, self.player, "EnterSandman"): + if self.is_option_enabled("EnterSandman"): spoiler_handle.write(f'Mysterious Warp Beacon unlock: {self.precalculated_weights.time_key_unlock}\n') else: spoiler_handle.write(f'Twin Pyramid Keys unlock: {self.precalculated_weights.pyramid_keys_unlock}\n') - if is_option_enabled(self.multiworld, self.player, "RisingTides"): + if self.is_option_enabled("RisingTides"): flooded_areas: List[str] = [] if self.precalculated_weights.flood_basement: @@ -177,133 +154,131 @@ class TimespinnerWorld(World): spoiler_handle.write(f'Flooded Areas: {flooded_areas_string}\n') + def create_item(self, name: str) -> Item: + data = item_table[name] -def get_excluded_items(self: TimespinnerWorld, world: MultiWorld, player: int) -> Set[str]: - excluded_items: Set[str] = set() - - if is_option_enabled(world, player, "StartWithJewelryBox"): - excluded_items.add('Jewelry Box') - if is_option_enabled(world, player, "StartWithMeyef"): - excluded_items.add('Meyef') - if is_option_enabled(world, player, "QuickSeed"): - excluded_items.add('Talaria Attachment') - - if is_option_enabled(world, player, "UnchainedKeys"): - excluded_items.add('Twin Pyramid Key') - - if not is_option_enabled(world, player, "EnterSandman"): - excluded_items.add('Mysterious Warp Beacon') - else: - excluded_items.add('Timeworn Warp Beacon') - excluded_items.add('Modern Warp Beacon') - excluded_items.add('Mysterious Warp Beacon') - - for item in world.precollected_items[player]: - if item.name not in self.item_name_groups['UseItem']: - excluded_items.add(item.name) - - return excluded_items - - -def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]): - non_local_items = world.non_local_items[player].value - - local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if item not in non_local_items) - if not local_starter_melee_weapons: - if 'Plasma Orb' in non_local_items: - raise Exception("Atleast one melee orb must be local") + if data.useful: + classification = ItemClassification.useful + elif data.progression: + classification = ItemClassification.progression + elif data.trap: + classification = ItemClassification.trap else: - local_starter_melee_weapons = ('Plasma Orb',) + classification = ItemClassification.filler + + item = Item(name, classification, data.code, self.player) - local_starter_spells = tuple(item for item in starter_spells if item not in non_local_items) - if not local_starter_spells: - if 'Lightwall' in non_local_items: - raise Exception("Atleast one spell must be local") - else: - local_starter_spells = ('Lightwall',) + if not item.advancement: + return item - assign_starter_item(world, player, excluded_items, locked_locations, 'Tutorial: Yo Momma 1', local_starter_melee_weapons) - assign_starter_item(world, player, excluded_items, locked_locations, 'Tutorial: Yo Momma 2', local_starter_spells) + if (name == 'Tablet' or name == 'Library Keycard V') and not self.is_option_enabled("DownloadableItems"): + item.classification = ItemClassification.filler + elif name == 'Oculus Ring' and not self.is_option_enabled("EyeSpy"): + item.classification = ItemClassification.filler + elif (name == 'Kobo' or name == 'Merchant Crow') and not self.is_option_enabled("GyreArchives"): + item.classification = ItemClassification.filler + elif name in {"Timeworn Warp Beacon", "Modern Warp Beacon", "Mysterious Warp Beacon"} \ + and not self.is_option_enabled("UnchainedKeys"): + item.classification = ItemClassification.filler - -def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str], - location: str, item_list: Tuple[str, ...]): - - item_name = world.random.choice(item_list) - - excluded_items.add(item_name) - - item = create_item_with_correct_settings(world, player, item_name) - - world.get_location(location, player).place_locked_item(item) - - locked_locations.append(location) - - -def get_item_pool(world: MultiWorld, player: int, excluded_items: Set[str]) -> List[Item]: - pool: List[Item] = [] - - for name, data in item_table.items(): - if name not in excluded_items: - for _ in range(data.count): - item = create_item_with_correct_settings(world, player, name) - pool.append(item) - - return pool - - -def fill_item_pool_with_dummy_items(self: TimespinnerWorld, world: MultiWorld, player: int, locked_locations: List[str], - location_cache: List[Location], pool: List[Item]): - for _ in range(len(location_cache) - len(locked_locations) - len(pool)): - item = create_item_with_correct_settings(world, player, self.get_filler_item_name()) - pool.append(item) - - -def create_item_with_correct_settings(world: MultiWorld, player: int, name: str) -> Item: - data = item_table[name] - - if data.useful: - classification = ItemClassification.useful - elif data.progression: - classification = ItemClassification.progression - elif data.trap: - classification = ItemClassification.trap - else: - classification = ItemClassification.filler - - item = Item(name, classification, data.code, player) - - if not item.advancement: return item - if (name == 'Tablet' or name == 'Library Keycard V') and not is_option_enabled(world, player, "DownloadableItems"): - item.classification = ItemClassification.filler - elif name == 'Oculus Ring' and not is_option_enabled(world, player, "EyeSpy"): - item.classification = ItemClassification.filler - elif (name == 'Kobo' or name == 'Merchant Crow') and not is_option_enabled(world, player, "GyreArchives"): - item.classification = ItemClassification.filler - elif name in {"Timeworn Warp Beacon", "Modern Warp Beacon", "Mysterious Warp Beacon"} \ - and not is_option_enabled(world, player, "UnchainedKeys"): - item.classification = ItemClassification.filler + def get_filler_item_name(self) -> str: + trap_chance: int = self.get_option_value("TrapChance") + enabled_traps: List[str] = self.get_option_value("Traps") - return item + if self.multiworld.random.random() < (trap_chance / 100) and enabled_traps: + return self.multiworld.random.choice(enabled_traps) + else: + return self.multiworld.random.choice(filler_items) + def get_excluded_items(self) -> Set[str]: + excluded_items: Set[str] = set() -def setup_events(player: int, locked_locations: List[str], location_cache: List[Location]): - for location in location_cache: - if location.address == EventId: - item = Item(location.name, ItemClassification.progression, EventId, player) + if self.is_option_enabled("StartWithJewelryBox"): + excluded_items.add('Jewelry Box') + if self.is_option_enabled("StartWithMeyef"): + excluded_items.add('Meyef') + if self.is_option_enabled("QuickSeed"): + excluded_items.add('Talaria Attachment') - locked_locations.append(location.name) + if self.is_option_enabled("UnchainedKeys"): + excluded_items.add('Twin Pyramid Key') - location.place_locked_item(item) + if not self.is_option_enabled("EnterSandman"): + excluded_items.add('Mysterious Warp Beacon') + else: + excluded_items.add('Timeworn Warp Beacon') + excluded_items.add('Modern Warp Beacon') + excluded_items.add('Mysterious Warp Beacon') + for item in self.multiworld.precollected_items[self.player]: + if item.name not in self.item_name_groups['UseItem']: + excluded_items.add(item.name) -def get_personal_items(player: int, locations: List[Location]) -> Dict[int, int]: - personal_items: Dict[int, int] = {} + return excluded_items - for location in locations: - if location.address and location.item and location.item.code and location.item.player == player: - personal_items[location.address] = location.item.code + def assign_starter_items(self, excluded_items: Set[str]) -> None: + non_local_items: Set[str] = self.multiworld.non_local_items[self.player].value - return personal_items + local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if item not in non_local_items) + if not local_starter_melee_weapons: + if 'Plasma Orb' in non_local_items: + raise Exception("Atleast one melee orb must be local") + else: + local_starter_melee_weapons = ('Plasma Orb',) + + local_starter_spells = tuple(item for item in starter_spells if item not in non_local_items) + if not local_starter_spells: + if 'Lightwall' in non_local_items: + raise Exception("Atleast one spell must be local") + else: + local_starter_spells = ('Lightwall',) + + self.assign_starter_item(excluded_items, 'Tutorial: Yo Momma 1', local_starter_melee_weapons) + self.assign_starter_item(excluded_items, 'Tutorial: Yo Momma 2', local_starter_spells) + + def assign_starter_item(self, excluded_items: Set[str], location: str, item_list: Tuple[str, ...]) -> None: + item_name = self.multiworld.random.choice(item_list) + + excluded_items.add(item_name) + + item = self.create_item(item_name) + + self.multiworld.get_location(location, self.player).place_locked_item(item) + + def get_item_pool(self, excluded_items: Set[str]) -> List[Item]: + pool: List[Item] = [] + + for name, data in item_table.items(): + if name not in excluded_items: + for _ in range(data.count): + item = self.create_item(name) + pool.append(item) + + for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(pool)): + item = self.create_item(self.get_filler_item_name()) + pool.append(item) + + return pool + + def create_and_assign_event_items(self) -> None: + for location in self.multiworld.get_locations(self.player): + if location.address == EventId: + item = Item(location.name, ItemClassification.progression, EventId, self.player) + location.place_locked_item(item) + + def get_personal_items(self) -> Dict[int, int]: + personal_items: Dict[int, int] = {} + + for location in self.multiworld.get_locations(self.player): + if location.address and location.item and location.item.code and location.item.player == self.player: + personal_items[location.address] = location.item.code + + return personal_items + + def is_option_enabled(self, option: str) -> bool: + return is_option_enabled(self.multiworld, self.player, option) + + def get_option_value(self, option: str) -> Union[int, Dict, List]: + return get_option_value(self.multiworld, self.player, option)