Timespinner: Refactorings + fix for #1460 (#1484)

This commit is contained in:
Jarno 2023-03-04 08:16:05 +01:00 committed by GitHub
parent 9fa1f4e85f
commit a4b61118cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 305 additions and 339 deletions

View File

@ -14,7 +14,7 @@ class LocationData(NamedTuple):
rule: Callable[[CollectionState], bool] = lambda state: True 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, ...]: precalculated_weights: PreCalculatedWeights) -> Tuple[LocationData, ...]:
flooded: PreCalculatedWeights = precalculated_weights flooded: PreCalculatedWeights = precalculated_weights

View File

@ -1,64 +1,64 @@
from typing import List, Set, Dict, Tuple, Optional, Callable from typing import List, Set, Dict, Tuple, Optional, Callable
from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location
from .Options import is_option_enabled from .Options import is_option_enabled
from .Locations import LocationData from .Locations import LocationData, get_location_datas
from .PreCalculatedWeights import PreCalculatedWeights from .PreCalculatedWeights import PreCalculatedWeights
from .LogicExtensions import TimespinnerLogic from .LogicExtensions import TimespinnerLogic
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location], def create_regions_and_locations(world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights):
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 = [ regions = [
create_region(world, player, locations_per_region, location_cache, 'Menu'), create_region(world, player, locations_per_region, 'Menu'),
create_region(world, player, locations_per_region, location_cache, 'Tutorial'), create_region(world, player, locations_per_region, 'Tutorial'),
create_region(world, player, locations_per_region, location_cache, 'Lake desolation'), create_region(world, player, locations_per_region, 'Lake desolation'),
create_region(world, player, locations_per_region, location_cache, 'Upper lake desolation'), create_region(world, player, locations_per_region, 'Upper lake desolation'),
create_region(world, player, locations_per_region, location_cache, 'Lower lake desolation'), create_region(world, player, locations_per_region, 'Lower lake desolation'),
create_region(world, player, locations_per_region, location_cache, 'Eastern lake desolation'), create_region(world, player, locations_per_region, 'Eastern lake desolation'),
create_region(world, player, locations_per_region, location_cache, 'Library'), create_region(world, player, locations_per_region, 'Library'),
create_region(world, player, locations_per_region, location_cache, 'Library top'), create_region(world, player, locations_per_region, 'Library top'),
create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower left'), create_region(world, player, locations_per_region, 'Varndagroth tower left'),
create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (upper)'), create_region(world, player, locations_per_region, 'Varndagroth tower right (upper)'),
create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (lower)'), create_region(world, player, locations_per_region, 'Varndagroth tower right (lower)'),
create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (elevator)'), create_region(world, player, locations_per_region, 'Varndagroth tower right (elevator)'),
create_region(world, player, locations_per_region, location_cache, 'Sealed Caves (Sirens)'), create_region(world, player, locations_per_region, 'Sealed Caves (Sirens)'),
create_region(world, player, locations_per_region, location_cache, 'Military Fortress'), create_region(world, player, locations_per_region, 'Military Fortress'),
create_region(world, player, locations_per_region, location_cache, 'Military Fortress (hangar)'), create_region(world, player, locations_per_region, 'Military Fortress (hangar)'),
create_region(world, player, locations_per_region, location_cache, 'The lab'), create_region(world, player, locations_per_region, 'The lab'),
create_region(world, player, locations_per_region, location_cache, 'The lab (power off)'), create_region(world, player, locations_per_region, 'The lab (power off)'),
create_region(world, player, locations_per_region, location_cache, 'The lab (upper)'), create_region(world, player, locations_per_region, 'The lab (upper)'),
create_region(world, player, locations_per_region, location_cache, 'Emperors tower'), create_region(world, player, locations_per_region, 'Emperors tower'),
create_region(world, player, locations_per_region, location_cache, 'Skeleton Shaft'), create_region(world, player, locations_per_region, 'Skeleton Shaft'),
create_region(world, player, locations_per_region, location_cache, 'Sealed Caves (upper)'), create_region(world, player, locations_per_region, 'Sealed Caves (upper)'),
create_region(world, player, locations_per_region, location_cache, 'Sealed Caves (Xarion)'), create_region(world, player, locations_per_region, 'Sealed Caves (Xarion)'),
create_region(world, player, locations_per_region, location_cache, 'Refugee Camp'), create_region(world, player, locations_per_region, 'Refugee Camp'),
create_region(world, player, locations_per_region, location_cache, 'Forest'), create_region(world, player, locations_per_region, 'Forest'),
create_region(world, player, locations_per_region, location_cache, 'Left Side forest Caves'), create_region(world, player, locations_per_region, 'Left Side forest Caves'),
create_region(world, player, locations_per_region, location_cache, 'Upper Lake Serene'), create_region(world, player, locations_per_region, 'Upper Lake Serene'),
create_region(world, player, locations_per_region, location_cache, 'Lower Lake Serene'), create_region(world, player, locations_per_region, 'Lower Lake Serene'),
create_region(world, player, locations_per_region, location_cache, 'Caves of Banishment (upper)'), create_region(world, player, locations_per_region, 'Caves of Banishment (upper)'),
create_region(world, player, locations_per_region, location_cache, 'Caves of Banishment (Maw)'), create_region(world, player, locations_per_region, 'Caves of Banishment (Maw)'),
create_region(world, player, locations_per_region, location_cache, 'Caves of Banishment (Sirens)'), create_region(world, player, locations_per_region, 'Caves of Banishment (Sirens)'),
create_region(world, player, locations_per_region, location_cache, 'Castle Ramparts'), create_region(world, player, locations_per_region, 'Castle Ramparts'),
create_region(world, player, locations_per_region, location_cache, 'Castle Keep'), create_region(world, player, locations_per_region, 'Castle Keep'),
create_region(world, player, locations_per_region, location_cache, 'Castle Basement'), create_region(world, player, locations_per_region, 'Castle Basement'),
create_region(world, player, locations_per_region, location_cache, 'Royal towers (lower)'), create_region(world, player, locations_per_region, 'Royal towers (lower)'),
create_region(world, player, locations_per_region, location_cache, 'Royal towers'), create_region(world, player, locations_per_region, 'Royal towers'),
create_region(world, player, locations_per_region, location_cache, 'Royal towers (upper)'), create_region(world, player, locations_per_region, 'Royal towers (upper)'),
create_region(world, player, locations_per_region, location_cache, 'Temporal Gyre'), create_region(world, player, locations_per_region, 'Temporal Gyre'),
create_region(world, player, locations_per_region, location_cache, 'Ancient Pyramid (entrance)'), create_region(world, player, locations_per_region, 'Ancient Pyramid (entrance)'),
create_region(world, player, locations_per_region, location_cache, 'Ancient Pyramid (left)'), create_region(world, player, locations_per_region, 'Ancient Pyramid (left)'),
create_region(world, player, locations_per_region, location_cache, 'Ancient Pyramid (right)'), create_region(world, player, locations_per_region, 'Ancient Pyramid (right)'),
create_region(world, player, locations_per_region, location_cache, 'Space time continuum') create_region(world, player, locations_per_region, 'Space time continuum')
] ]
if is_option_enabled(world, player, "GyreArchives"): if is_option_enabled(world, player, "GyreArchives"):
regions.extend([ regions.extend([
create_region(world, player, locations_per_region, location_cache, 'Ravenlord\'s Lair'), create_region(world, player, locations_per_region, 'Ravenlord\'s Lair'),
create_region(world, player, locations_per_region, location_cache, 'Ifrit\'s Lair'), create_region(world, player, locations_per_region, 'Ifrit\'s Lair'),
]) ])
if __debug__: if __debug__:
@ -70,127 +70,126 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
flooded: PreCalculatedWeights = precalculated_weights flooded: PreCalculatedWeights = precalculated_weights
logic = TimespinnerLogic(world, player, 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, '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, '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, '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, 'Lake desolation', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Upper lake desolation', 'Lake desolation') connect(world, player, 'Upper lake desolation', 'Lake desolation')
connect(world, player, names, 'Upper lake desolation', 'Eastern lake desolation') connect(world, player, 'Upper lake desolation', 'Eastern lake desolation')
connect(world, player, names, 'Lower lake desolation', 'Lake desolation') connect(world, player, 'Lower lake desolation', 'Lake desolation')
connect(world, player, names, 'Lower lake desolation', 'Eastern lake desolation') connect(world, player, 'Lower lake desolation', 'Eastern lake desolation')
connect(world, player, names, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport) connect(world, player, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Eastern lake desolation', 'Library') connect(world, player, 'Eastern lake desolation', 'Library')
connect(world, player, names, 'Eastern lake desolation', 'Lower lake desolation') connect(world, player, '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, '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, '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, '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, 'Library', 'Varndagroth tower left', logic.has_keycard_D)
connect(world, player, names, 'Library', 'Space time continuum', logic.has_teleport) connect(world, player, 'Library', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Library top', 'Library') connect(world, player, 'Library top', 'Library')
connect(world, player, names, 'Varndagroth tower left', 'Library') connect(world, player, 'Varndagroth tower left', 'Library')
connect(world, player, names, 'Varndagroth tower left', 'Varndagroth tower right (upper)', logic.has_keycard_C) connect(world, player, '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, '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, '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, '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, '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, '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, 'Varndagroth tower right (elevator)', 'Varndagroth tower right (upper)')
connect(world, player, names, 'Varndagroth tower right (elevator)', 'Varndagroth tower right (lower)') connect(world, player, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, 'Military Fortress', 'Military Fortress (hangar)', logic.has_doublejump)
connect(world, player, names, 'Military Fortress (hangar)', 'Military Fortress') connect(world, player, '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, '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, 'Temporal Gyre', 'Military Fortress')
connect(world, player, names, 'The lab', 'Military Fortress') connect(world, player, 'The lab', 'Military Fortress')
connect(world, player, names, 'The lab', 'The lab (power off)', logic.has_doublejump_of_npc) connect(world, player, 'The lab', 'The lab (power off)', logic.has_doublejump_of_npc)
connect(world, player, names, 'The lab (power off)', 'The lab') connect(world, player, 'The lab (power off)', 'The lab')
connect(world, player, names, 'The lab (power off)', 'The lab (upper)', logic.has_forwarddash_doublejump) connect(world, player, '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, 'The lab (upper)', 'The lab (power off)')
connect(world, player, names, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump) connect(world, player, '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, '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, 'Emperors tower', 'The lab (upper)')
connect(world, player, names, 'Skeleton Shaft', 'Lake desolation') connect(world, player, 'Skeleton Shaft', 'Lake desolation')
connect(world, player, names, 'Skeleton Shaft', 'Sealed Caves (upper)', logic.has_keycard_A) connect(world, player, 'Skeleton Shaft', 'Sealed Caves (upper)', logic.has_keycard_A)
connect(world, player, names, 'Skeleton Shaft', 'Space time continuum', logic.has_teleport) connect(world, player, 'Skeleton Shaft', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Sealed Caves (upper)', 'Skeleton Shaft') connect(world, player, '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, '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, '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, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Refugee Camp', 'Forest') connect(world, player, 'Refugee Camp', 'Forest')
#connect(world, player, names, 'Refugee Camp', 'Library', lambda state: not is_option_enabled(world, player, "Inverted")) #connect(world, player, '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, 'Refugee Camp', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Forest', 'Refugee Camp') connect(world, player, '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, '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, 'Forest', 'Caves of Banishment (Sirens)')
connect(world, player, names, 'Forest', 'Castle Ramparts') connect(world, player, 'Forest', 'Castle Ramparts')
connect(world, player, names, 'Left Side forest Caves', 'Forest') connect(world, player, 'Left Side forest Caves', 'Forest')
connect(world, player, names, 'Left Side forest Caves', 'Upper Lake Serene', logic.has_timestop) connect(world, player, '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, '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, 'Left Side forest Caves', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Upper Lake Serene', 'Left Side forest Caves') connect(world, player, '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, '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, 'Lower Lake Serene', 'Upper Lake Serene')
connect(world, player, names, 'Lower Lake Serene', 'Left Side forest Caves') connect(world, player, 'Lower Lake Serene', 'Left Side forest Caves')
connect(world, player, names, 'Lower Lake Serene', 'Caves of Banishment (upper)') connect(world, player, '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, '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, '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, '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, '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, 'Caves of Banishment (Maw)', 'Caves of Banishment (Sirens)', lambda state: state.has_any({'Gas Mask', 'Talaria Attachment'}, player) )
connect(world, player, names, 'Caves of Banishment (Maw)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Caves of Banishment (Maw)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Caves of Banishment (Sirens)', 'Forest') connect(world, player, 'Caves of Banishment (Sirens)', 'Forest')
connect(world, player, names, 'Castle Ramparts', 'Forest') connect(world, player, 'Castle Ramparts', 'Forest')
connect(world, player, names, 'Castle Ramparts', 'Castle Keep') connect(world, player, 'Castle Ramparts', 'Castle Keep')
connect(world, player, names, 'Castle Ramparts', 'Space time continuum', logic.has_teleport) connect(world, player, 'Castle Ramparts', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Castle Keep', 'Castle Ramparts') connect(world, player, '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, '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, 'Castle Keep', 'Royal towers (lower)', logic.has_doublejump)
connect(world, player, names, 'Castle Keep', 'Space time continuum', logic.has_teleport) connect(world, player, 'Castle Keep', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Royal towers (lower)', 'Castle Keep') connect(world, player, '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, '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, 'Royal towers (lower)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Royal towers', 'Royal towers (lower)') connect(world, player, 'Royal towers', 'Royal towers (lower)')
connect(world, player, names, 'Royal towers', 'Royal towers (upper)', logic.has_doublejump) connect(world, player, 'Royal towers', 'Royal towers (upper)', logic.has_doublejump)
connect(world, player, names, 'Royal towers (upper)', 'Royal towers') connect(world, player, '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, '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, 'Ancient Pyramid (entrance)', 'Ancient Pyramid (left)', logic.has_doublejump)
connect(world, player, names, 'Ancient Pyramid (left)', 'Ancient Pyramid (entrance)') connect(world, player, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, '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, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid"))
if is_option_enabled(world, player, "GyreArchives"): 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, '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, '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, '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, 'Ifrit\'s Lair', 'Library top')
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]): 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)) 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 = Location(player, location_data.name, location_data.code, region)
location.access_rule = location_data.rule 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.event = True
location.locked = True location.locked = True
location_cache.append(location)
return 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) region = Region(name, player, world)
if name in locations_per_region: if name in locations_per_region:
for location_data in locations_per_region[name]: 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) region.locations.append(location)
return region return region
@ -250,19 +247,13 @@ def connectStartingRegion(world: MultiWorld, player: int):
space_time_continuum.exits.append(teleport_back_to_start) 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): rule: Optional[Callable[[CollectionState], bool]] = None):
sourceRegion = world.get_region(source, player) sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player) targetRegion = world.get_region(target, player)
if target not in used_names: connection = Entrance(player, "", sourceRegion)
used_names[target] = 1
name = target
else:
used_names[target] += 1
name = target + (' ' * used_names[target])
connection = Entrance(player, name, sourceRegion)
if rule: if rule:
connection.access_rule = rule connection.access_rule = rule
@ -271,7 +262,7 @@ def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source:
connection.connect(targetRegion) 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]] = {} per_region: Dict[str, List[LocationData]] = {}
for location in locations: for location in locations:

View File

@ -1,10 +1,10 @@
from typing import Dict, List, Set, Tuple, TextIO from typing import Dict, List, Set, Tuple, TextIO, Union
from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification 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 .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 .Options import is_option_enabled, get_option_value, timespinner_options
from .PreCalculatedWeights import PreCalculatedWeights from .PreCalculatedWeights import PreCalculatedWeights
from .Regions import create_regions from .Regions import create_regions_and_locations
from worlds.AutoWorld import World, WebWorld from worlds.AutoWorld import World, WebWorld
class TimespinnerWebWorld(WebWorld): class TimespinnerWebWorld(WebWorld):
@ -29,7 +29,6 @@ class TimespinnerWebWorld(WebWorld):
tutorials = [setup, setup_de] tutorials = [setup, setup_de]
class TimespinnerWorld(World): class TimespinnerWorld(World):
""" """
Timespinner is a beautiful metroidvania inspired by classic 90s action-platformers. Timespinner is a beautiful metroidvania inspired by classic 90s action-platformers.
@ -44,21 +43,16 @@ class TimespinnerWorld(World):
required_client_version = (0, 3, 7) required_client_version = (0, 3, 7)
item_name_to_id = {name: data.code for name, data in item_table.items()} 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() item_name_groups = get_item_names_per_category()
locked_locations: List[str]
location_cache: List[Location]
precalculated_weights: PreCalculatedWeights precalculated_weights: PreCalculatedWeights
def __init__(self, world: MultiWorld, player: int): def __init__(self, world: MultiWorld, player: int):
super().__init__(world, player) super().__init__(world, player)
self.locked_locations = []
self.location_cache = []
self.precalculated_weights = PreCalculatedWeights(world, player) 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 # 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: 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 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: 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 self.multiworld.StartWithJewelryBox[self.player].value = self.multiworld.StartWithJewelryBox[self.player].option_true
def create_regions(self): def create_regions(self) -> None:
locations = get_locations(self.multiworld, self.player, self.precalculated_weights) create_regions_and_locations(self.multiworld, self.player, self.precalculated_weights)
create_regions(self.multiworld, self.player, locations, self.location_cache, self.precalculated_weights)
def create_item(self, name: str) -> Item: def create_items(self) -> None:
return create_item_with_correct_settings(self.multiworld, self.player, name) self.create_and_assign_event_items()
def get_filler_item_name(self) -> str: excluded_items: Set[str] = self.get_excluded_items()
trap_chance: int = get_option_value(self.multiworld, self.player, "TrapChance")
enabled_traps: List[str] = get_option_value(self.multiworld, self.player, "Traps")
if self.multiworld.random.random() < (trap_chance / 100) and enabled_traps: self.assign_starter_items(excluded_items)
return self.multiworld.random.choice(enabled_traps)
else:
return self.multiworld.random.choice(filler_items)
def set_rules(self): self.multiworld.itempool += self.get_item_pool(excluded_items)
setup_events(self.player, self.locked_locations, self.location_cache)
def set_rules(self) -> None:
final_boss: str final_boss: str
if is_option_enabled(self.multiworld, self.player, "DadPercent"): if self.is_option_enabled("DadPercent"):
final_boss = "Killed Emperor" final_boss = "Killed Emperor"
else: else:
final_boss = "Killed Nightmare" final_boss = "Killed Nightmare"
self.multiworld.completion_condition[self.player] = lambda state: state.has(final_boss, self.player) 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]: def fill_slot_data(self) -> Dict[str, object]:
slot_data: Dict[str, object] = {} slot_data: Dict[str, object] = {}
@ -112,12 +89,12 @@ class TimespinnerWorld(World):
for option_name in timespinner_options: for option_name in timespinner_options:
if (option_name not in ap_specific_settings): 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["StinkyMaw"] = True
slot_data["ProgressiveVerticalMovement"] = False slot_data["ProgressiveVerticalMovement"] = False
slot_data["ProgressiveKeycards"] = 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["PyramidKeysGate"] = self.precalculated_weights.pyramid_keys_unlock
slot_data["PresentGate"] = self.precalculated_weights.present_key_unlock slot_data["PresentGate"] = self.precalculated_weights.present_key_unlock
slot_data["PastGate"] = self.precalculated_weights.past_key_unlock slot_data["PastGate"] = self.precalculated_weights.past_key_unlock
@ -135,17 +112,17 @@ class TimespinnerWorld(World):
return slot_data return slot_data
def write_spoiler_header(self, spoiler_handle: TextIO): def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
if is_option_enabled(self.multiworld, self.player, "UnchainedKeys"): 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'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') 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') spoiler_handle.write(f'Mysterious Warp Beacon unlock: {self.precalculated_weights.time_key_unlock}\n')
else: else:
spoiler_handle.write(f'Twin Pyramid Keys unlock: {self.precalculated_weights.pyramid_keys_unlock}\n') 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] = [] flooded_areas: List[str] = []
if self.precalculated_weights.flood_basement: if self.precalculated_weights.flood_basement:
@ -177,133 +154,131 @@ class TimespinnerWorld(World):
spoiler_handle.write(f'Flooded Areas: {flooded_areas_string}\n') 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]: if data.useful:
excluded_items: Set[str] = set() classification = ItemClassification.useful
elif data.progression:
if is_option_enabled(world, player, "StartWithJewelryBox"): classification = ItemClassification.progression
excluded_items.add('Jewelry Box') elif data.trap:
if is_option_enabled(world, player, "StartWithMeyef"): classification = ItemClassification.trap
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")
else: 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 item.advancement:
if not local_starter_spells: return item
if 'Lightwall' in non_local_items:
raise Exception("Atleast one spell must be local")
else:
local_starter_spells = ('Lightwall',)
assign_starter_item(world, player, excluded_items, locked_locations, 'Tutorial: Yo Momma 1', local_starter_melee_weapons) if (name == 'Tablet' or name == 'Library Keycard V') and not self.is_option_enabled("DownloadableItems"):
assign_starter_item(world, player, excluded_items, locked_locations, 'Tutorial: Yo Momma 2', local_starter_spells) 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 return item
if (name == 'Tablet' or name == 'Library Keycard V') and not is_option_enabled(world, player, "DownloadableItems"): def get_filler_item_name(self) -> str:
item.classification = ItemClassification.filler trap_chance: int = self.get_option_value("TrapChance")
elif name == 'Oculus Ring' and not is_option_enabled(world, player, "EyeSpy"): enabled_traps: List[str] = self.get_option_value("Traps")
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
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]): if self.is_option_enabled("StartWithJewelryBox"):
for location in location_cache: excluded_items.add('Jewelry Box')
if location.address == EventId: if self.is_option_enabled("StartWithMeyef"):
item = Item(location.name, ItemClassification.progression, EventId, player) 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]: return excluded_items
personal_items: Dict[int, int] = {}
for location in locations: def assign_starter_items(self, excluded_items: Set[str]) -> None:
if location.address and location.item and location.item.code and location.item.player == player: non_local_items: Set[str] = self.multiworld.non_local_items[self.player].value
personal_items[location.address] = location.item.code
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)