Added Timespinner support (#77)

AP side for 0.1.8 inclusion, Client and Documentation outstanding.
This commit is contained in:
Jarno Westhof 2021-09-24 04:07:32 +02:00 committed by GitHub
parent 7ac9bd8591
commit 822e8941ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1009 additions and 0 deletions

257
worlds/timespinner/Items.py Normal file
View File

@ -0,0 +1,257 @@
from typing import Dict, Tuple, NamedTuple
class ItemData(NamedTuple):
category: str
code: int
count: int = 1
progression: bool = False
# A lot of items arent normally dropped by the randomizer as they are mostly enemy drops, but they can be enabled if desired
item_table: Dict[str, ItemData] = {
'Eternal Crown': ItemData('Equipment', 1337000),
#'Security Visor': ItemData('Equipment', 1337001),
#'Engineer Goggles': ItemData('Equipment', 1337002),
#'Leather Helmet': ItemData('Equipment', 1337003),
#'Copper Helmet': ItemData('Equipment', 1337004),
'Pointy Hat': ItemData('Equipment', 1337005),
#'Dragoon Helmet': ItemData('Equipment', 1337006),
'Buckle Hat': ItemData('Equipment', 1337007),
#'Advisor Hat': ItemData('Equipment', 1337008),
'Librarian Hat': ItemData('Equipment', 1337009),
#'Combat Helmet': ItemData('Equipment', 1337010),
'Captain\'s Cap': ItemData('Equipment', 1337011),
'Lab Glasses': ItemData('Equipment', 1337012),
'Empire Crown': ItemData('Equipment', 1337013),
'Viletian Crown': ItemData('Equipment', 1337014),
#'Sunglasses': ItemData('Equipment', 1337015),
'Old Coat': ItemData('Equipment', 1337016),
#'Trendy Jacket': ItemData('Equipment', 1337017),
#'Security Vest': ItemData('Equipment', 1337018),
#'Leather Jerkin': ItemData('Equipment', 1337019),
#'Copper Breastplate': ItemData('Equipment', 1337020),
'Traveler\'s Cloak': ItemData('Equipment', 1337021),
#'Dragoon Armor': ItemData('Equipment', 1337022),
'Midnight Cloak': ItemData('Equipment', 1337023),
#'Advisor Robe': ItemData('Equipment', 1337024),
'Librarian Robe': ItemData('Equipment', 1337025),
#'Military Armor': ItemData('Equipment', 1337026),
'Captain\'s Uniform': ItemData('Equipment', 1337027),
'Lab Coat': ItemData('Equipment', 1337028),
'Empress Robe': ItemData('Equipment', 1337029),
'Princess Dress': ItemData('Equipment', 1337030),
'Eternal Coat': ItemData('Equipment', 1337031),
#'Synthetic Plume': ItemData('Equipment', 1337032),
#'Cheveur Plume': ItemData('Equipment', 1337033),
'Metal Wristband': ItemData('Equipment', 1337034),
#'Nymph Hairband': ItemData('Equipment', 1337035),
#'Mother o\' Pearl': ItemData('Equipment', 1337036),
'Bird Statue': ItemData('Equipment', 1337037),
#'Chaos Stole': ItemData('Equipment', 1337038),
'Pendulum': ItemData('Equipment', 1337039),
#'Chaos Horn': ItemData('Equipment', 1337040),
'Filigree Clasp': ItemData('Equipment', 1337041),
#'Azure Stole': ItemData('Equipment', 1337042),
'Ancient Coin': ItemData('Equipment', 1337043),
#'Shiny Rock': ItemData('Equipment', 1337044),
'Galaxy Earrings': ItemData('Equipment', 1337045),
'Selen\'s Bangle': ItemData('Equipment', 1337046),
'Glass Pumpkin': ItemData('Equipment', 1337047),
'Gilded Egg': ItemData('Equipment', 1337048),
'Meyef': ItemData('Familiar', 1337049),
'Griffin': ItemData('Familiar', 1337050),
'Merchant Crow': ItemData('Familiar', 1337051),
'Kobo': ItemData('Familiar', 1337052),
'Sprite': ItemData('Familiar', 1337053),
'Demon': ItemData('Familiar', 1337054),
'Potion': ItemData('UseItem', 1337055, 0),
'Ether': ItemData('UseItem', 1337056, 0),
'Sand Vial': ItemData('UseItem', 1337057, 0),
'Hi-Potion': ItemData('UseItem', 1337058, 0),
'Hi-Ether': ItemData('UseItem', 1337059, 0),
'Sand Bottle': ItemData('UseItem', 1337060, 0),
'Berry Pick-Mi-Up': ItemData('UseItem', 1337061, 0),
'Berry Pick-Mi-Up+': ItemData('UseItem', 1337062, 0),
'Mind Refresh': ItemData('UseItem', 1337063, 0),
'Mind Refresh ULTRA': ItemData('UseItem', 1337064, 0),
'Antidote': ItemData('UseItem', 1337065, 0),
'Chaos Rose': ItemData('UseItem', 1337066, 0),
'Warp Shard': ItemData('UseItem', 1337067),
#'Dream Wisp': ItemData('UseItem', 1337068),
#'PlaceHolderItem1': ItemData('UseItem', 1337069),
#'Lachiemi Sun': ItemData('UseItem', 1337070),
'Jerky': ItemData('UseItem', 1337071),
#'Biscuit': ItemData('UseItem', 1337072),
#'Fried Cheveur': ItemData('UseItem', 1337073),
#'Sautéed Wyvern Tail': ItemData('UseItem', 1337074),
#'Unagi Roll': ItemData('UseItem', 1337075),
#'Cheveur au Vin': ItemData('UseItem', 1337076),
#'Royal Casserole': ItemData('UseItem', 1337077),
'Spaghetti': ItemData('UseItem', 1337078),
#'Plump Maggot': ItemData('UseItem', 1337079),
#'Orange Juice': ItemData('UseItem', 1337080),
'Filigree Tea': ItemData('UseItem', 1337081),
#'Empress Cake': ItemData('UseItem', 1337082),
#'Rotten Tail': ItemData('UseItem', 1337083),
#'Alchemy Tools': ItemData('UseItem', 1337084),
'Galaxy Stone': ItemData('UseItem', 1337085),
#1337086 Used interally
#'Essence Crystal': ItemData('UseItem', 1337087),
#'Gold Ring': ItemData('UseItem', 1337088),
#'Gold Necklace': ItemData('UseItem', 1337089),
'Herb': ItemData('UseItem', 1337090),
#'Mushroom': ItemData('UseItem', 1337091),
#'Plasma Crystal': ItemData('UseItem', 1337092),
'Plasma IV Bag': ItemData('UseItem', 1337093),
#'Cheveur Drumstick': ItemData('UseItem', 1337094),
#'Wyvern Tail': ItemData('UseItem', 1337095),
#'Eel Meat': ItemData('UseItem', 1337096),
#'Cheveux Breast': ItemData('UseItem', 1337097),
'Food Synthesizer': ItemData('UseItem', 1337098),
#'Cheveux Feather': ItemData('UseItem', 1337099),
#'Siren Ink': ItemData('UseItem', 1337100),
#'Plasma Core': ItemData('UseItem', 1337101),
#'Silver Ore': ItemData('UseItem', 1337102),
#'Historical Documents': ItemData('UseItem', 1337103),
#'MapReveal 0': ItemData('UseItem', 1337104),
#'MapReveal 1': ItemData('UseItem', 1337105),
#'MapReveal 2': ItemData('UseItem', 1337106),
'Timespinner Wheel': ItemData('Relic', 1337107, progression=True),
'Timespinner Spindle': ItemData('Relic', 1337108, progression=True),
'Timespinner Gear 1': ItemData('Relic', 1337109, progression=True),
'Timespinner Gear 2': ItemData('Relic', 1337110, progression=True),
'Timespinner Gear 3': ItemData('Relic', 1337111, progression=True),
'Twin Pyramid Key': ItemData('Relic', 1337112, progression=True),
'Celestial Sash': ItemData('Relic', 1337113, progression=True),
'Succubus Hairpin': ItemData('Relic', 1337114, progression=True),
'Talaria Attachment': ItemData('Relic', 1337115, progression=True),
'Water Mask': ItemData('Relic', 1337116, progression=True),
'Gas Mask': ItemData('Relic', 1337117, progression=True),
'Soul Scanner': ItemData('Relic', 1337118),
'Security Keycard A': ItemData('Relic', 1337119, progression=True),
'Security Keycard B': ItemData('Relic', 1337120, progression=True),
'Security Keycard C': ItemData('Relic', 1337121, progression=True),
'Security Keycard D': ItemData('Relic', 1337122, progression=True),
'Library Keycard V': ItemData('Relic', 1337123, progression=True),
'Tablet': ItemData('Relic', 1337124, progression=True),
'Elevator Keycard': ItemData('Relic', 1337125, progression=True),
'Jewelry Box': ItemData('Relic', 1337126),
'Goddess Brooch': ItemData('Relic', 1337127),
'Wyrm Brooch': ItemData('Relic', 1337128),
'Greed Brooch': ItemData('Relic', 1337129),
'Eternal Brooch': ItemData('Relic', 1337130),
'Blue Orb': ItemData('Orb Melee', 1337131),
'Blade Orb': ItemData('Orb Melee', 1337132),
'Fire Orb': ItemData('Orb Melee', 1337133, progression=True),
'Plasma Orb': ItemData('Orb Melee', 1337134, progression=True),
'Iron Orb': ItemData('Orb Melee', 1337135),
'Ice Orb': ItemData('Orb Melee', 1337136),
'Wind Orb': ItemData('Orb Melee', 1337137),
'Gun Orb': ItemData('Orb Melee', 1337138),
'Umbra Orb': ItemData('Orb Melee', 1337139),
'Empire Orb': ItemData('Orb Melee', 1337140),
'Eye Orb': ItemData('Orb Melee', 1337141),
'Blood Orb': ItemData('Orb Melee', 1337142),
'Forbidden Tome': ItemData('Orb Melee', 1337143),
'Shattered Orb': ItemData('Orb Melee', 1337144),
'Nether Orb': ItemData('Orb Melee', 1337145),
'Radiant Orb': ItemData('Orb Melee', 1337146),
'Aura Blast': ItemData('Orb Spell', 1337147),
'Colossal Blade': ItemData('Orb Spell', 1337148),
'Infernal Flames': ItemData('Orb Spell', 1337149, progression=True),
'Plasma Geyser': ItemData('Orb Spell', 1337150, progression=True),
'Colossal Hammer': ItemData('Orb Spell', 1337151),
'Frozen Spires': ItemData('Orb Spell', 1337152),
'Storm Eye': ItemData('Orb Spell', 1337153),
'Arm Cannon': ItemData('Orb Spell', 1337154),
'Dark Flames': ItemData('Orb Spell', 1337155),
'Aura Serpent': ItemData('Orb Spell', 1337156),
'Chaos Blades': ItemData('Orb Spell', 1337157),
'Crimson Vortex': ItemData('Orb Spell', 1337158),
'Djinn Inferno': ItemData('Orb Spell', 1337159, progression=True),
'Bombardment': ItemData('Orb Spell', 1337160),
'Corruption': ItemData('Orb Spell', 1337161),
'Lightwall': ItemData('Orb Spell', 1337162, progression=True),
'Bleak Ring': ItemData('Orb Passive', 1337163),
'Scythe Ring': ItemData('Orb Passive', 1337164),
'Pyro Ring': ItemData('Orb Passive', 1337165, progression=True),
'Royal Ring': ItemData('Orb Passive', 1337166, progression=True),
'Shield Ring': ItemData('Orb Passive', 1337167),
'Icicle Ring': ItemData('Orb Passive', 1337168),
'Tailwind Ring': ItemData('Orb Passive', 1337169),
'Economizer Ring': ItemData('Orb Passive', 1337170),
'Dusk Ring': ItemData('Orb Passive', 1337171),
'Star of Lachiem': ItemData('Orb Passive', 1337172),
'Oculus Ring': ItemData('Orb Passive', 1337173, progression=True),
'Sanguine Ring': ItemData('Orb Passive', 1337174),
'Sun Ring': ItemData('Orb Passive', 1337175),
'Silence Ring': ItemData('Orb Passive', 1337176),
'Shadow Seal': ItemData('Orb Passive', 1337177),
'Hope Ring': ItemData('Orb Passive', 1337178),
'Max HP': ItemData('Stat', 1337179, 12),
'Max Aura': ItemData('Stat', 1337180, 13),
# 1337181 - 1337248 Reserved
'Max Sand': ItemData('Stat', 1337249, 14)
}
starter_melee_weapons: Tuple[str] = (
'Blue Orb',
'Blade Orb',
'Fire Orb',
'Iron Orb',
'Ice Orb',
'Wind Orb',
'Gun Orb',
'Umbra Orb',
'Empire Orb',
'Eye Orb',
'Blood Orb',
'Forbidden Tome',
'Shattered Orb',
'Nether Orb',
'Radiant Orb'
)
starter_spells: Tuple[str] = (
'Colossal Blade',
'Infernal Flames',
'Plasma Geyser',
'Colossal Hammer',
'Frozen Spires',
'Storm Eye',
'Arm Cannon',
'Dark Flames',
'Aura Serpent',
'Chaos Blades',
'Crimson Vortex',
'Djinn Inferno',
'Bombardment',
'Corruption'
)
# weighted
starter_progression_items: Tuple[str] = (
'Talaria Attachment',
'Talaria Attachment',
'Succubus Hairpin',
'Succubus Hairpin',
'Timespinner Wheel',
'Timespinner Wheel',
'Twin Pyramid Key',
'Celestial Sash',
'Lightwall'
)
filler_items: Tuple[str] = (
'Potion',
'Ether',
'Hi-Potion',
'Hi-Ether',
'Sand Vial',
'Sand Bottle',
'Berry Pick-Mi-Up',
'Berry Pick-Mi-Up+',
'Mind Refresh',
'Mind Refresh ULTRA',
'Antidote',
'Chaos Rose'
)

View File

@ -0,0 +1,225 @@
from typing import Tuple, Optional, Callable, NamedTuple
from BaseClasses import MultiWorld
from .Options import is_option_enabled
EventId: Optional[int] = None
class LocationData(NamedTuple):
region: str
name: str
code: Optional[int]
rule: Callable = lambda state: True
def get_locations(world: MultiWorld, player: int):
location_table: Tuple[LocationData] = (
# PresentItemLocations
LocationData('Tutorial', 'Yo Momma 1', 1337000),
LocationData('Tutorial', 'Yo Momma 2', 1337001),
LocationData('Lake desolation', 'Starter chest 2', 1337002),
LocationData('Lake desolation', 'Starter chest 3', 1337003),
LocationData('Lake desolation', 'Starter chest 1', 1337004),
LocationData('Lake desolation', 'Timespinner Wheel room', 1337005),
LocationData('Upper lake desolation', 'Forget me not chest', 1337006),
LocationData('Lower lake desolation', 'Chicken chest', 1337007, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Lower lake desolation', 'Not so secret room', 1337008, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Lower lake desolation', 'Tank chest', 1337009, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Upper lake desolation', 'Oxygen recovery room', 1337010),
LocationData('Upper lake desolation', 'Lake secret', 1337011, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Upper lake desolation', 'Double jump cave floor', 1337012, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Upper lake desolation', 'Double jump cave platform', 1337013),
LocationData('Upper lake desolation', 'Fire-Locked sparrow chest', 1337014),
LocationData('Upper lake desolation', 'Crash site pedestal', 1337015),
LocationData('Upper lake desolation', 'Crash site chest 1', 1337016, lambda state: state.has_all(['Killed Maw', 'Gas Mask'], player)),
LocationData('Upper lake desolation', 'Crash site chest 2', 1337017, lambda state: state.has_all(['Killed Maw', 'Gas Mask'], player)),
LocationData('Upper lake desolation', 'Kitty Boss', 1337018),
LocationData('Libary', 'Basement', 1337019),
LocationData('Libary', 'Consolation', 1337020),
LocationData('Libary', 'Librarian', 1337021),
LocationData('Libary', 'Reading nook chest', 1337022),
LocationData('Libary', 'Storage room chest 1', 1337023, lambda state: state._timespinner_has_keycard_D(world, player)),
LocationData('Libary', 'Storage room chest 2', 1337024, lambda state: state._timespinner_has_keycard_D(world, player)),
LocationData('Libary', 'Storage room chest 3', 1337025, lambda state: state._timespinner_has_keycard_D(world, player)),
LocationData('Libary top', 'Backer room chest 5', 1337026),
LocationData('Libary top', 'Backer room chest 4', 1337027),
LocationData('Libary top', 'Backer room chest 3', 1337028),
LocationData('Libary top', 'Backer room chest 2', 1337029),
LocationData('Libary top', 'Backer room chest 1', 1337030),
LocationData('Varndagroth tower left', 'Elevator Key not required', 1337031),
LocationData('Varndagroth tower left', 'Ye olde Timespinner', 1337032),
LocationData('Varndagroth tower left', 'C Keycard chest', 1337033, lambda state: state._timespinner_has_keycard_C(world, player)),
LocationData('Varndagroth tower left', 'Left air vents secret', 1337034, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Varndagroth tower left', 'Left elevator chest', 1337035, lambda state: state.has('Elevator Keycard', player)),
LocationData('Varndagroth tower right (upper)', 'Spider heck room', 1337036),
LocationData('Varndagroth tower right (elevator)', 'Right elevator chest', 1337037),
LocationData('Varndagroth tower right (upper)', 'Elevator card chest', 1337038, lambda state: state.has('Elevator Keycard', player) or state._timespinner_has_doublejump(world, player)),
LocationData('Varndagroth tower right (upper)', 'Air vents left', 1337039, lambda state: state.has('Elevator Keycard', player) or state._timespinner_has_doublejump(world, player)),
LocationData('Varndagroth tower right (upper)', 'Air Vents right', 1337040, lambda state: state.has('Elevator Keycard', player) or state._timespinner_has_doublejump(world, player)),
LocationData('Varndagroth tower right (lower)', 'Right side bottom floor', 1337041),
LocationData('Varndagroth tower right (elevator)', 'Varndagroth', 1337042, lambda state: state._timespinner_has_keycard_C(world, player)),
LocationData('Varndagroth tower right (elevator)', 'Varndagroth Spider hell', 1337043, lambda state: state._timespinner_has_keycard_A(world, player)),
LocationData('Skeleton Shaft', 'Skeleton', 1337044),
LocationData('Sealed Caves (Xarion)', 'Shroom jump room', 1337045, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Sealed Caves (Xarion)', 'Double shroom room', 1337046),
LocationData('Sealed Caves (Xarion)', 'Mini jackpot room', 1337047, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Sealed Caves (Xarion)', 'Below mini jackpot room', 1337048),
LocationData('Sealed Caves (Xarion)', 'Sealed cave secret room', 1337049, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Sealed Caves (Xarion)', 'Below Sealed cave secret', 1337050),
LocationData('Sealed Caves (Xarion)', 'Last chance before Xarion', 1337051, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Sealed Caves (Xarion)', 'Xarion', 1337052),
LocationData('Sealed Caves (Sirens)', 'Solo siren chest', 1337053, lambda state: state.has('Water Mask', player)),
LocationData('Sealed Caves (Sirens)', 'Big siren room right', 1337054, lambda state: state.has('Water Mask', player)),
LocationData('Sealed Caves (Sirens)', 'Big siren Room left', 1337055, lambda state: state.has('Water Mask', player)),
LocationData('Sealed Caves (Sirens)', 'Room after sirens chest 2', 1337056),
LocationData('Sealed Caves (Sirens)', 'Room after sirens chest 1', 1337057),
LocationData('Militairy Fortress', 'Militairy Bomber chest', 1337058, lambda state: state.has('Timespinner Wheel', player) and state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Militairy Fortress', 'Close combat room', 1337059),
LocationData('Militairy Fortress', 'Bridge full of soldiers', 1337060),
LocationData('Militairy Fortress', 'Giantess Room', 1337061),
LocationData('Militairy Fortress', 'Bridge with Giantess', 1337062),
LocationData('Militairy Fortress', 'Military B door chest 2', 1337063, lambda state: state._timespinner_has_doublejump(world, player) and state._timespinner_has_keycard_B(world, player)),
LocationData('Militairy Fortress', 'Military B door chest 1', 1337064, lambda state: state._timespinner_has_doublejump(world, player) and state._timespinner_has_keycard_B(world, player)),
LocationData('Militairy Fortress', 'Military pedestal', 1337065, lambda state: state._timespinner_has_doublejump(world, player) and (state._timespinner_has_doublejump_of_npc(world, player) or state._timespinner_has_forwarddash_doublejump(world, player))),
LocationData('The lab', 'Coffee Break chest', 1337066),
LocationData('The lab', 'Lower trash right', 1337067, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('The lab', 'Lower trash left', 1337068, lambda state: state._timespinner_has_upwarddash(world, player)),
LocationData('The lab', 'Single turret room', 1337069, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('The lab (power off)', 'Trash jump room', 1337070),
LocationData('The lab (power off)', 'Dynamo Works', 1337071),
LocationData('The lab (upper)', 'Blob mom', 1337072),
LocationData('The lab (power off)', 'Experiment #13', 1337073),
LocationData('The lab (upper)', 'Download and chest room', 1337074),
LocationData('The lab (upper)', 'Lab secret', 1337075, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('The lab (power off)', 'Lab Spider hell', 1337076, lambda state: state._timespinner_has_keycard_A(world, player)),
LocationData('Emperors tower', 'Bottom', 1337077),
LocationData('Emperors tower', 'After Courtyard Floor Secret', 1337078, lambda state: state._timespinner_has_upwarddash(world, player) and state._timespinner_can_break_walls(world, player)),
LocationData('Emperors tower', 'After Courtyard Chest', 1337079, lambda state: state._timespinner_has_upwarddash(world, player)),
LocationData('Emperors tower', 'Galactic Sage Room', 1337080),
LocationData('Emperors tower', 'Bottom of Right Tower', 1337081),
LocationData('Emperors tower', 'Wayyyy up there', 1337082),
LocationData('Emperors tower', 'Left tower balcony', 1337083),
LocationData('Emperors tower', 'Dad\'s Chambers chest', 1337084),
LocationData('Emperors tower', 'Dad\'s Chambers pedestal', 1337085),
# PastItemLocations
LocationData('Refugee Camp', 'Neliste\'s Bra', 1337086),
LocationData('Refugee Camp', 'Refugee camp storage chest 3', 1337087),
LocationData('Refugee Camp', 'Refugee camp storage chest 2', 1337088),
LocationData('Refugee Camp', 'Refugee camp storage chest 1', 1337089),
LocationData('Forest', 'Refugee camp roof', 1337090),
LocationData('Forest', 'Bat jump chest', 1337091, lambda state: state._timespinner_has_doublejump_of_npc(world, player) or state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Forest', 'Green platform secret', 1337092, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Forest', 'Rats guarded chest', 1337093),
LocationData('Forest', 'Waterfall chest 1', 1337094, lambda state: state.has('Water Mask', player)),
LocationData('Forest', 'Waterfall chest 2', 1337095, lambda state: state.has('Water Mask', player)),
LocationData('Forest', 'Batcave', 1337096),
LocationData('Forest', 'Bridge Chest', 1337097),
LocationData('Left Side forest Caves', 'Solitary bat room', 1337098),
LocationData('Upper Lake Sirine', 'Rat nest', 1337099),
LocationData('Upper Lake Sirine', 'Double jump cave platform (past)', 1337100, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Upper Lake Sirine', 'Double jump cave floor (past)', 1337101),
LocationData('Upper Lake Sirine', 'West lake serene cave secret', 1337102, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Upper Lake Sirine', 'Chest behind vines', 1337103),
LocationData('Upper Lake Sirine', 'Pyramid keys room', 1337104),
LocationData('Lower Lake Sirine', 'Deep dive', 1337105),
LocationData('Lower Lake Sirine', 'Under the eels', 1337106),
LocationData('Lower Lake Sirine', 'Water spikes room', 1337107),
LocationData('Lower Lake Sirine', 'Underwater secret', 1337108, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Lower Lake Sirine', 'T chest', 1337109),
LocationData('Lower Lake Sirine', 'Past the eels', 1337110),
LocationData('Lower Lake Sirine', 'Underwater pedestal', 1337111),
LocationData('Caves of Banishment (upper)', 'Mushroom double jump', 1337112, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Caves of banishment secret room', 1337113),
LocationData('Caves of Banishment (upper)', 'Below caves of banishment secret', 1337114),
LocationData('Caves of Banishment (upper)', 'Single shroom room', 1337115),
LocationData('Caves of Banishment (upper)', 'Jackpot room chest 1', 1337116, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Jackpot room chest 2', 1337117, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Jackpot room chest 3', 1337118, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Jackpot room chest 4', 1337119, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Banishment pedestal', 1337120),
LocationData('Caves of Banishment (Maw)', 'Last chance before Maw', 1337121, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Caves of Banishment (Maw)', 'Killed Maw', EventId, lambda state: state.has('Gas Mask', player)),
LocationData('Caves of Banishment (Maw)', 'Mineshaft', 1337122, lambda state: state.has('Gas Mask', player)),
LocationData('Caves of Banishment (Sirens)', 'Wyvern room', 1337123),
LocationData('Caves of Banishment (Sirens)', 'Above water sirens', 1337124),
LocationData('Caves of Banishment (Sirens)', 'Underwater sirens left', 1337125, lambda state: state.has('Water Mask', player)),
LocationData('Caves of Banishment (Sirens)', 'Underwater sirens right', 1337126, lambda state: state.has('Water Mask', player)),
LocationData('Caves of Banishment (Sirens)', 'Water hook', 1337127),
LocationData('Caste Ramparts', 'Caste Bomber chest', 1337128, lambda state: state._timespinner_has_multiple_small_jumps_of_npc(world, player)),
LocationData('Caste Ramparts', 'Freeze the engineer', 1337129, lambda state: state.has('Talaria Attachment', player) or state._timespinner_has_timestop(world, player)),
LocationData('Caste Ramparts', 'Giantess guarded room', 1337130),
LocationData('Caste Ramparts', 'Knight and archer guarded room', 1337131),
LocationData('Caste Ramparts', 'Castle pedestal', 1337132),
LocationData('Caste Keep', 'Basement secret pedestal', 1337133, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Caste Keep', 'Break the wall', 1337134),
LocationData('Royal towers (lower)', 'Yas queen room', 1337135, lambda state: state._timespinner_has_pink(world, player)),
LocationData('Caste Keep', 'Basement hammer', 1337136),
LocationData('Caste Keep', 'Omelette chest', 1337137),
LocationData('Caste Keep', 'Just an egg', 1337138),
LocationData('Caste Keep', 'Out of the way', 1337139),
LocationData('Caste Keep', 'Killed Twins', EventId, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Caste Keep', 'Twins', 1337140, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Caste Keep', 'Royal guard tiny room', 1337141, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Royal towers (lower)', 'Royal tower floor secret', 1337142, lambda state: state._timespinner_has_doublejump(world, player) and state._timespinner_can_break_walls(world, player)),
LocationData('Royal towers', 'Above the gap', 1337143),
LocationData('Royal towers', 'Under the ice mage', 1337144),
LocationData('Royal towers (upper)', 'Next to easy struggle juggle room', 1337145),
LocationData('Royal towers (upper)', 'Easy struggle juggle', 1337146, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Royal towers (upper)', 'Hard struggle juggle', 1337147, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Royal towers (upper)', 'No sturggle required', 1337148, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Royal towers', 'Right tower freebie', 1337149),
LocationData('Royal towers (upper)', 'Above the cide mage', 1337150),
LocationData('Royal towers (upper)', 'Royal guard big room', 1337151),
LocationData('Royal towers (upper)', 'Before Aelana', 1337152),
LocationData('Royal towers (upper)', 'Killed Aelana', EventId),
LocationData('Royal towers (upper)', 'Statue room', 1337153, lambda state: state._timespinner_has_upwarddash(world, player)),
LocationData('Royal towers (upper)', 'Aelana\'s pedestal', 1337154),
LocationData('Royal towers (upper)', 'After Aelana', 1337155),
# 1337157 - 1337170 Downloads
# 1337171 - 1337238 Reserved
# PyramidItemLocations
#LocationData('Temporal Gyre', 'Transition chest 1', 1337239),
#LocationData('Temporal Gyre', 'Transition chest 2', 1337240),
#LocationData('Temporal Gyre', 'Transition chest 3', 1337241),
#LocationData('Temporal Gyre', 'Ravenlord pre fight', 1337242),
#LocationData('Temporal Gyre', 'Ravenlord post fight', 1337243),
#LocationData('Temporal Gyre', 'Ifrid pre fight', 1337244),
#LocationData('Temporal Gyre', 'Ifrid post fight', 1337245),
LocationData('Ancient Pyramid (left)', 'Why not it\'s right there', 1337246),
LocationData('Ancient Pyramid (left)', 'Conviction guarded room', 1337247),
LocationData('Ancient Pyramid (right)', 'Pit secret room', 1337248, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Ancient Pyramid (right)', 'Regret chest', 1337249, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Ancient Pyramid (right)', 'Killed Nightmare', EventId)
)
downloadable_items: Tuple[LocationData] = (
# DownloadTerminals
LocationData('Libary', 'Library terminal 1', 1337157, lambda state: state.has('Tablet', player)),
LocationData('Libary', 'Library terminal 2', 1337156, lambda state: state.has('Tablet', player)),
LocationData('Libary', 'Library terminal 3', 1337159, lambda state: state.has('Tablet', player)),
LocationData('Libary', 'V terminal 1', 1337160, lambda state: state.has_all(['Tablet', 'Library Keycard V'], player)),
LocationData('Libary', 'V terminal 2', 1337161, lambda state: state.has_all(['Tablet', 'Library Keycard V'], player)),
LocationData('Libary', 'V terminal 3', 1337162, lambda state: state.has_all(['Tablet', 'Library Keycard V'], player)),
LocationData('Libary top', 'Backer room terminal', 1337163, lambda state: state.has('Tablet', player)),
LocationData('Varndagroth tower right (elevator)', 'Medbay', 1337164, lambda state: state.has('Tablet', player) and state._timespinner_has_keycard_B(world, player)),
LocationData('The lab (upper)', 'Chest and download terminal', 1337165, lambda state: state.has('Tablet', player)),
LocationData('The lab (power off)', 'Lab terminal middle', 1337166, lambda state: state.has('Tablet', player)),
LocationData('The lab (power off)', 'Sentry platform terminal', 1337167, lambda state: state.has('Tablet', player)),
LocationData('The lab', 'Experiment 13 terminal', 1337168, lambda state: state.has('Tablet', player)),
LocationData('The lab', 'Lab terminal left', 1337169, lambda state: state.has('Tablet', player)),
LocationData('The lab (power off)', 'Lab terminal right', 1337170, lambda state: state.has('Tablet', player))
)
if not world or is_option_enabled(world, player, "DownloadableItems"):
return ( *location_table, *downloadable_items )
else:
return location_table
starter_progression_locations: Tuple[str] = (
'Starter chest 2',
'Starter chest 3',
'Starter chest 1',
'Timespinner Wheel room'
)

View File

@ -0,0 +1,58 @@
from BaseClasses import MultiWorld
from ..AutoWorld import LogicMixin
from .Options import is_option_enabled
class TimespinnerLogic(LogicMixin):
def _timespinner_has_timestop(self, world: MultiWorld, player: int) -> bool:
return self.has_any(['Timespinner Wheel', 'Succubus Hairpin', 'Lightwall', 'Celestial Sash'], player)
def _timespinner_has_doublejump(self, world: MultiWorld, player: int) -> bool:
return self.has_any(['Succubus Hairpin', 'Lightwall', 'Celestial Sash'], player)
def _timespinner_has_forwarddash_doublejump(self, world: MultiWorld, player: int) -> bool:
return self._timespinner_has_upwarddash(world, player) or (self.has('Talaria Attachment', player) and self._timespinner_has_doublejump(world, player))
def _timespinner_has_doublejump_of_npc(self, world: MultiWorld, player: int) -> bool:
return self._timespinner_has_upwarddash(world, player) or (self.has('Timespinner Wheel', player) and self._timespinner_has_doublejump(world, player))
def _timespinner_has_multiple_small_jumps_of_npc(self, world: MultiWorld, player: int) -> bool:
return self.has('Timespinner Wheel', player) or self._timespinner_has_upwarddash(world, player)
def _timespinner_has_upwarddash(self, world: MultiWorld, player: int) -> bool:
return self.has_any(['Lightwall', 'Celestial Sash'], player)
def _timespinner_has_fire(self, world: MultiWorld, player: int) -> bool:
return self.has_any(['Fire Orb', 'Infernal Flames', 'Pyro Ring', 'Djinn Inferno'], player)
def _timespinner_has_pink(self, world: MultiWorld, player: int) -> bool:
return self.has_any(['Plasma Orb', 'Plasma Geyser', 'Royal Ring'], player)
def _timespinner_has_keycard_A(self, world: MultiWorld, player: int) -> bool:
return self.has('Security Keycard A', player)
def _timespinner_has_keycard_B(self, world: MultiWorld, player: int) -> bool:
if is_option_enabled(world, player, "SpecificKeycards"):
return self.has('Security Keycard B', player)
else:
return self.has_any(['Security Keycard A', 'Security Keycard B'], player)
def _timespinner_has_keycard_C(self, world: MultiWorld, player: int) -> bool:
if is_option_enabled(world, player, "SpecificKeycards"):
return self.has('Security Keycard C', player)
else:
return self.has_any(['Security Keycard A', 'Security Keycard B', 'Security Keycard C'], player)
def _timespinner_has_keycard_D(self, world: MultiWorld, player: int) -> bool:
if is_option_enabled(world, player, "SpecificKeycards"):
return self.has('Security Keycard D', player)
else:
return self.has_any(['Security Keycard A', 'Security Keycard B', 'Security Keycard C', 'Security Keycard D'], player)
def _timespinner_can_break_walls(self, world: MultiWorld, player: int) -> bool:
if is_option_enabled(world, player, "FacebookMode"):
return self.has('Oculus Ring', player)
else:
return True
def _timespinner_can_kill_all_3_bosses(self, world: MultiWorld, player: int) -> bool:
return self.has_all(['Killed Maw', 'Killed Twins', 'Killed Aelana'], player)

View File

@ -0,0 +1,65 @@
from typing import Dict
from BaseClasses import MultiWorld
from Options import Toggle
class StartWithJewelryBox(Toggle):
"Start with Jewelry Box unlocked"
display_name = "Start with Jewelry Box"
#class ProgressiveVerticalMovement(Toggle):
# "Always find vertical movement in the following order Succubus Hairpin -> Light Wall -> Celestial Sash"
# display_name = "Progressive vertical movement"
#class ProgressiveKeycards(Toggle):
# "Always find Security Keycard's in the following order D -> C -> B -> A"
# display_name = "Progressive keycards"
class DownloadableItems(Toggle):
"With the tablet you will be able to download items at terminals"
display_name = "Downloadable items"
class FacebookMode(Toggle):
"With the tablet you will be able to download items at terminals"
display_name = "Facebook mode"
class StartWithMeyef(Toggle):
"Start with Meyef, ideal for when you want to play multiplayer."
display_name = "Start with Meyef"
class QuickSeed(Toggle):
"Start with Talaria Attachment, Nyoom!"
display_name = "Quick seed"
class SpecificKeycards(Toggle):
"Keycards can only open corresponding doors"
display_name = "Specific Keycards"
class Inverted(Toggle):
"Start in the past"
display_name = "Inverted"
#class StinkyMaw(Toggle):
# "Require gassmask for Maw"
# display_name = "Stinky Maw"
# Some options that are available in the timespinner randomizer arent currently implemented
timespinner_options: Dict[str, Toggle] = {
"StartWithJewelryBox": StartWithJewelryBox,
#"ProgressiveVerticalMovement": ProgressiveVerticalMovement,
#"ProgressiveKeycards": ProgressiveKeycards,
"DownloadableItems": DownloadableItems,
"FacebookMode": FacebookMode,
"StartWithMeyef": StartWithMeyef,
"QuickSeed": QuickSeed,
"SpecificKeycards": SpecificKeycards,
"Inverted": Inverted,
#"StinkyMaw": StinkyMaw
}
def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool:
option = getattr(world, name, None)
if option == None:
return False
return int(option[player].value) > 0

View File

@ -0,0 +1,30 @@
from typing import Tuple
from BaseClasses import MultiWorld
from .Options import is_option_enabled
def get_pyramid_keys_unlock(world: MultiWorld, player: int) -> str:
present_teleportation_gates: Tuple[str] = (
"GateKittyBoss",
"GateLeftLibrary",
"GateMilitairyGate",
"GateSealedCaves",
"GateSealedSirensCave",
"GateLakeDesolation"
)
past_teleportation_gates: Tuple[str] = (
"GateLakeSirineRight",
"GateAccessToPast",
"GateCastleRamparts",
"GateCastleKeep",
"GateRoyalTowers",
"GateMaw",
"GateCavesOfBanishment"
)
if is_option_enabled(world, player, "Inverted"):
gates = present_teleportation_gates
else:
gates = (*past_teleportation_gates, *present_teleportation_gates)
return world.random.choice(gates)

View File

@ -0,0 +1,220 @@
from typing import List, Dict, Tuple, Optional, Callable
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
from .Options import is_option_enabled
from .Locations import LocationData
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData], pyramid_keys_unlock: str):
locations_per_region = get_locations_per_region(locations)
world.regions += [
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, 'Libary'),
create_region(world, player, locations_per_region, 'Libary 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, 'Militairy Fortress'),
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 Sirine'),
create_region(world, player, locations_per_region, 'Lower Lake Sirine'),
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, 'Caste Ramparts'),
create_region(world, player, locations_per_region, 'Caste Keep'),
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, 'Ancient Pyramid (left)'),
create_region(world, player, locations_per_region, 'Ancient Pyramid (right)'),
create_region(world, player, locations_per_region, 'Space time continuum')
]
connectStartingRegion(world, player)
names = {}
connect(world, player, names, 'Lake desolation', 'Lower lake desolation', lambda state: state._timespinner_has_timestop(world, player or state.has('Talaria Attachment', player)))
connect(world, player, names, 'Lake desolation', 'Upper lake desolation', lambda state: state._timespinner_has_fire(world, player) and state.can_reach('Upper Lake Sirine', 'Region', player))
connect(world, player, names, 'Lake desolation', 'Skeleton Shaft', lambda state: state._timespinner_has_doublejump(world, player))
connect(world, player, names, 'Lake desolation', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Upper lake desolation', 'Lake desolation')
connect(world, player, names, 'Upper lake desolation', 'Lower lake desolation')
connect(world, player, names, 'Lower lake desolation', 'Lake desolation')
connect(world, player, names, 'Lower lake desolation', 'Libary')
connect(world, player, names, 'Lower lake desolation', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Libary', 'Lower lake desolation')
connect(world, player, names, 'Libary', 'Libary top', lambda state: state._timespinner_has_doublejump(world, player) or state.has('Talaria Attachment', player))
connect(world, player, names, 'Libary', 'Varndagroth tower left', lambda state: state._timespinner_has_keycard_C(world, player))
connect(world, player, names, 'Libary', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Libary top', 'Libary')
connect(world, player, names, 'Varndagroth tower left', 'Libary')
connect(world, player, names, 'Varndagroth tower left', 'Varndagroth tower right (upper)', lambda state: state._timespinner_has_keycard_C(world, player))
connect(world, player, names, 'Varndagroth tower left', 'Varndagroth tower right (lower)', lambda state: state._timespinner_has_keycard_B(world, player))
connect(world, player, names, 'Varndagroth tower left', 'Sealed Caves (Sirens)', lambda state: state._timespinner_has_keycard_B(world, player) 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')
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: state._timespinner_has_keycard_B(world, player) and state.has('Elevator Keycard', player))
connect(world, player, names, 'Varndagroth tower right (lower)', 'Militairy Fortress', lambda state: state._timespinner_can_kill_all_3_bosses(world, player))
connect(world, player, names, 'Varndagroth tower right (lower)', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
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', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Militairy Fortress', 'Varndagroth tower right (lower)', lambda state: state._timespinner_can_kill_all_3_bosses(world, player))
connect(world, player, names, 'Militairy Fortress', 'The lab', lambda state: state._timespinner_has_keycard_B(world, player) and state._timespinner_has_doublejump(world, player))
connect(world, player, names, 'The lab', 'Militairy Fortress')
connect(world, player, names, 'The lab', 'The lab (power off)', lambda state: state._timespinner_has_doublejump_of_npc(world, player))
connect(world, player, names, 'The lab (power off)', 'The lab')
connect(world, player, names, 'The lab (power off)', 'The lab (upper)', lambda state: state._timespinner_has_forwarddash_doublejump(world, player))
connect(world, player, names, 'The lab (upper)', 'The lab (power off)')
connect(world, player, names, 'The lab (upper)', 'Emperors tower', lambda state: state._timespinner_has_forwarddash_doublejump(world, player))
connect(world, player, names, 'The lab (upper)', 'Ancient Pyramid (left)', 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)', lambda state: state._timespinner_has_keycard_A(world, player))
connect(world, player, names, 'Skeleton Shaft', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Sealed Caves (upper)', 'Skeleton Shaft')
connect(world, player, names, 'Sealed Caves (upper)', 'Sealed Caves (Xarion)', lambda state: state.has('Twin Pyramid Key', player) or state._timespinner_has_forwarddash_doublejump(world, player))
connect(world, player, names, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', lambda state: state._timespinner_has_forwarddash_doublejump(world, player))
connect(world, player, names, 'Sealed Caves (Xarion)', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Refugee Camp', 'Forest')
connect(world, player, names, 'Refugee Camp', 'Libary', lambda state: is_option_enabled(world, player, "Inverted"))
connect(world, player, names, 'Refugee Camp', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Forest', 'Refugee Camp')
connect(world, player, names, 'Forest', 'Left Side forest Caves', lambda state: state.has('Talaria Attachment', player) or state._timespinner_has_timestop(world, player))
connect(world, player, names, 'Forest', 'Caves of Banishment (Sirens)')
connect(world, player, names, 'Forest', 'Caste Ramparts')
connect(world, player, names, 'Left Side forest Caves', 'Forest')
connect(world, player, names, 'Left Side forest Caves', 'Upper Lake Sirine', lambda state: state._timespinner_has_timestop(world, player))
connect(world, player, names, 'Left Side forest Caves', 'Lower Lake Sirine', lambda state: state.has('Water Mask', player))
connect(world, player, names, 'Left Side forest Caves', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Upper Lake Sirine', 'Left Side forest Caves')
connect(world, player, names, 'Upper Lake Sirine', 'Lower Lake Sirine', lambda state: state.has('Water Mask', player))
connect(world, player, names, 'Lower Lake Sirine', 'Upper Lake Sirine')
connect(world, player, names, 'Lower Lake Sirine', 'Left Side forest Caves')
connect(world, player, names, 'Lower Lake Sirine', 'Caves of Banishment (upper)')
connect(world, player, names, 'Caves of Banishment (upper)', 'Upper Lake Sirine', lambda state: state.has('Water Mask', player))
connect(world, player, names, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: state.has('Twin Pyramid Key', player) or state._timespinner_has_forwarddash_doublejump(world, player))
connect(world, player, names, 'Caves of Banishment (upper)', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Caves of Banishment (Maw)', 'Caves of Banishment (upper)', lambda state: state._timespinner_has_forwarddash_doublejump(world, 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', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Caves of Banishment (Sirens)', 'Forest')
connect(world, player, names, 'Caste Ramparts', 'Forest')
connect(world, player, names, 'Caste Ramparts', 'Caste Keep')
connect(world, player, names, 'Caste Ramparts', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Caste Keep', 'Caste Ramparts')
connect(world, player, names, 'Caste Keep', 'Royal towers (lower)', lambda state: state._timespinner_has_doublejump(world, player))
connect(world, player, names, 'Caste Keep', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Royal towers (lower)', 'Caste Keep')
connect(world, player, names, 'Royal towers (lower)', 'Royal towers', lambda state: state.has('Timespinner Wheel', player) or state._timespinner_has_forwarddash_doublejump(world, player))
connect(world, player, names, 'Royal towers (lower)', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Royal towers', 'Royal towers (lower)')
connect(world, player, names, 'Royal towers', 'Royal towers (upper)', lambda state: state._timespinner_has_doublejump(world, player))
connect(world, player, names, 'Royal towers (upper)', 'Royal towers')
connect(world, player, names, 'Ancient Pyramid (left)', 'The lab (upper)')
connect(world, player, names, 'Ancient Pyramid (left)', 'Ancient Pyramid (right)', lambda state: state._timespinner_has_upwarddash(world, player))
connect(world, player, names, 'Ancient Pyramid (right)', 'Ancient Pyramid (left)', lambda state: state._timespinner_has_upwarddash(world, player))
connect(world, player, names, 'Space time continuum', 'Lake desolation', lambda state: pyramid_keys_unlock == "GateLakeDesolation")
connect(world, player, names, 'Space time continuum', 'Lower lake desolation', lambda state: pyramid_keys_unlock == "GateKittyBoss")
connect(world, player, names, 'Space time continuum', 'Libary', lambda state: pyramid_keys_unlock == "GateLeftLibrary")
connect(world, player, names, 'Space time continuum', 'Varndagroth tower right (lower)', lambda state: pyramid_keys_unlock == "GateMilitairyGate")
connect(world, player, names, 'Space time continuum', 'Skeleton Shaft', lambda state: pyramid_keys_unlock == "GateSealedCaves")
connect(world, player, names, 'Space time continuum', 'Sealed Caves (Sirens)', lambda state: pyramid_keys_unlock == "GateSealedSirensCave")
connect(world, player, names, 'Space time continuum', 'Left Side forest Caves', lambda state: pyramid_keys_unlock == "GateLakeSirineRight")
connect(world, player, names, 'Space time continuum', 'Refugee Camp', lambda state: pyramid_keys_unlock == "GateAccessToPast")
connect(world, player, names, 'Space time continuum', 'Caste Ramparts', lambda state: pyramid_keys_unlock == "GateCastleRamparts")
connect(world, player, names, 'Space time continuum', 'Caste Keep', lambda state: pyramid_keys_unlock == "GateCastleKeep")
connect(world, player, names, 'Space time continuum', 'Royal towers (lower)', lambda state: pyramid_keys_unlock == "GateRoyalTowers")
connect(world, player, names, 'Space time continuum', 'Caves of Banishment (Maw)', lambda state: pyramid_keys_unlock == "GateMaw")
connect(world, player, names, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: pyramid_keys_unlock == "GateCavesOfBanishment")
def create_location(player: int, name: str, id: Optional[int], region: Region, rule: Callable) -> Location:
location = Location(player, name, id, region)
location.access_rule = rule
if id is None:
location.event = True
location.locked = True
return location
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], name: str) -> Region:
region = Region(name, RegionType.Generic, name, player)
region.world = world
if name in locations_per_region:
for location_data in locations_per_region[name]:
location = create_location(player, location_data.name, location_data.code, region, location_data.rule)
region.locations.append(location)
return region
def connectStartingRegion(world: MultiWorld, player: int):
menu = world.get_region('Menu', player)
tutorial = world.get_region('Tutorial', player)
space_time_continuum = world.get_region('Space time continuum', player)
if is_option_enabled(world, player, "Inverted"):
starting_region = world.get_region('Refugee Camp', player)
else:
starting_region = world.get_region('Lake desolation', player)
menu_to_tutorial = Entrance(player, 'Tutorial', menu)
menu_to_tutorial.connect(tutorial)
menu.exits.append(menu_to_tutorial)
tutorial_to_start = Entrance(player, 'Start Game', tutorial)
tutorial_to_start.connect(starting_region)
tutorial.exits.append(tutorial_to_start)
teleport_back_to_start = Entrance(player, 'Teleport back to start', space_time_continuum)
teleport_back_to_start.connect(starting_region)
space_time_continuum.exits.append(teleport_back_to_start)
def connect(world: MultiWorld, player: int, used_names : Dict[str, int], source: str, target: str, rule: Optional[Callable] = 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)
if rule:
connection.access_rule = rule
sourceRegion.exits.append(connection)
connection.connect(targetRegion)
def get_locations_per_region(locations: Tuple[LocationData]) -> Dict[str, List[LocationData]]:
per_region: Dict[str, List[LocationData]] = {}
for location in locations:
per_region[location.region] = [ location ] if location.region not in per_region else per_region[location.region] + [ location ]
return per_region

View File

@ -0,0 +1,154 @@
from typing import Dict, List
from BaseClasses import Item, MultiWorld
from ..AutoWorld import World
from .LogicMixin import TimespinnerLogic
from .Items import item_table, starter_melee_weapons, starter_spells, starter_progression_items, filler_items
from .Locations import get_locations, starter_progression_locations, EventId
from .Regions import create_regions
from .Options import is_option_enabled, timespinner_options
from .PyramidKeys import get_pyramid_keys_unlock
class TimespinnerWorld(World):
options = timespinner_options
game = "Timespinner"
topology_present = True
data_version = 1
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)}
locked_locations: Dict[int, List[str]] = {}
pyramid_keys_unlock: Dict[int, str] = {}
def generate_early(self):
self.locked_locations[self.player] = []
self.pyramid_keys_unlock[self.player] = get_pyramid_keys_unlock(self.world, self.player)
self.item_name_groups = get_item_name_groups()
def create_regions(self):
create_regions(self.world, self.player, get_locations(self.world, self.player), self.pyramid_keys_unlock[self.player])
def create_item(self, name: str) -> Item:
return create_item(name, self.player)
def set_rules(self):
setup_events(self.world, self.player, self.locked_locations[self.player])
self.world.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player)
def generate_basic(self):
excluded_items = get_excluded_items_based_on_options(self.world, self.player)
assign_starter_items(self.world, self.player, excluded_items, self.locked_locations[self.player])
if not is_option_enabled(self.world, self.player, "QuickSeed") or not is_option_enabled(self.world, self.player, "Inverted"):
place_first_progression_item(self.world, self.player, excluded_items, self.locked_locations[self.player])
pool = get_item_pool(self.world, self.player, excluded_items)
fill_item_pool_with_dummy_items(self.world, self.player, self.locked_locations[self.player], pool)
self.world.itempool += pool
def fill_slot_data(self) -> Dict:
slot_data = {}
for option_name in timespinner_options:
option = getattr(self.world, option_name)[self.player]
slot_data[option_name] = int(option.value)
slot_data["StinkyMaw"] = 1
slot_data["ProgressiveVerticalMovement"] = 0
slot_data["ProgressiveKeycards"] = 0
slot_data["PyramidKeysGate"] = self.pyramid_keys_unlock[self.player]
return slot_data
def create_item(name: str, player: int) -> Item:
data = item_table[name]
return Item(name, data.progression, data.code, player)
def get_excluded_items_based_on_options(world: MultiWorld, player: int) -> List[str]:
excluded_items = []
if is_option_enabled(world, player, "StartWithJewelryBox"):
excluded_items.append('Jewelry Box')
if is_option_enabled(world, player, "StartWithMeyef"):
excluded_items.append('Meyef')
if is_option_enabled(world, player, "QuickSeed"):
excluded_items.append('Talaria Attachment')
return excluded_items
def assign_starter_items(world: MultiWorld, player: int, excluded_items: List[str], locked_locations: List[str]):
melee_weapon = world.random.choice(starter_melee_weapons)
spell = world.random.choice(starter_spells)
excluded_items.append(melee_weapon)
excluded_items.append(spell)
melee_weapon_item = create_item(melee_weapon, player)
spell_item = create_item(spell, player)
world.get_location('Yo Momma 1', player).place_locked_item(melee_weapon_item)
world.get_location('Yo Momma 2', player).place_locked_item(spell_item)
locked_locations.append('Yo Momma 1')
locked_locations.append('Yo Momma 2')
def get_item_pool(world: MultiWorld, player: int, excluded_items: List[str]) -> List[Item]:
pool = []
for name, data in item_table.items():
if not name in excluded_items:
for _ in range(data.count):
item = update_progressive_state_based_flags(world, player, name, create_item(name, player))
pool.append(item)
return pool
def fill_item_pool_with_dummy_items(world: MultiWorld, player: int, locked_locations: List[str], pool: List[Item]):
for _ in range(len(get_locations(world, player)) - len(locked_locations) - len(pool)):
item = create_item(world.random.choice(filler_items), player)
pool.append(item)
def place_first_progression_item(world: MultiWorld, player: int, excluded_items: List[str], locked_locations: List[str]):
progression_item = world.random.choice(starter_progression_items)
location = world.random.choice(starter_progression_locations)
excluded_items.append(progression_item)
locked_locations.append(location)
item = create_item(progression_item, player)
world.get_location(location, player).place_locked_item(item)
def update_progressive_state_based_flags(world: MultiWorld, player: int, name: str, data: Item) -> Item:
if not data.advancement:
return data
if (name == 'Tablet' or name == 'Library Keycard V') and not is_option_enabled(world, player, "DownloadableItems"):
data.advancement = False
if name == 'Oculus Ring' and not is_option_enabled(world, player, "FacebookMode"):
data.advancement = False
return data
def setup_events(world: MultiWorld, player: int, locked_locations: List[str]):
for location in get_locations(world, player):
if location.code == EventId:
location = world.get_location(location.name, player)
item = Item(location.name, True, EventId, player)
locked_locations.append(location.name)
location.place_locked_item(item)
def get_item_name_groups() -> Dict[str, List[str]]:
groups: Dict[str, List[str]] = {}
for name, data in item_table.items():
groups[data.category] = [ name ] if data.category not in groups else groups[data.category] + [ name ]
return groups