From 5662da6f7d2e9d498ea3fd536bc27981aae10774 Mon Sep 17 00:00:00 2001 From: sgrunt Date: Sat, 8 Mar 2025 09:54:23 -0700 Subject: [PATCH] Timespinner: Support new flags and settings from the randomizer (#4559) * Timespinner: Add "no hell spiders" enemy rando option that is present in upstream settings * Timespinner: Prism Break support tweaks (including tracker support) * Timespinner: Add support for upstream Lock Key Amadeus flag * Timespinner: Add support for upstream Risky Warps flag * Timespinner: Add support for upstream Pyramid Start flag * Timespinner: fix error in lab connectivity logic * Timespinner: use has_all to simplify one check Per PR suggestion. Co-authored-by: Scipio Wright * Timespinner: fix apparent logic error inherited from in-rando logic * Timespinner: adjust "Origins" location logic slightly further to account for a Risky Warps case * Timespinner: remove the backward compat options for the recent flag additions * Timespinner: add newly added Gate Keep option from rando * Timespinner: adjust the laser access colours in the tracker * Timespinner: fix an item description in the tracker * Timespinner: based on testing feedback, put Laser Access items in their own category * Timespinner: add support for new upstream flag Royal Roadblock * Timespinner: also ensure the new flag gets put in slot data * Timespinner: fix bug in universal tracker support indicating castle basement is accessible at the lower Rising Tides flooding level * Timespinner: exclude Talaria Attachment and Timespinner Wheel from pyramid start starter progression items * Timespinner: fix region logic for the left pyramid warp * Timespinner: fix main Gyre access logic when Risky Warps warps you behind the lasers * Timespinner: apply suggested spacing fix Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: sgrunt Co-authored-by: Scipio Wright --- .../static/styles/timespinnerTracker.css | 21 +++++++++ .../templates/tracker__Timespinner.html | 46 +++++++++++++++++++ WebHostLib/tracker.py | 8 ++++ worlds/timespinner/Items.py | 26 +++++++++-- worlds/timespinner/Locations.py | 28 +++++++---- worlds/timespinner/LogicExtensions.py | 1 + worlds/timespinner/Options.py | 26 +++++++++++ worlds/timespinner/PreCalculatedWeights.py | 16 +++++-- worlds/timespinner/Regions.py | 26 +++++++---- worlds/timespinner/__init__.py | 32 +++++++++++-- 10 files changed, 196 insertions(+), 34 deletions(-) diff --git a/WebHostLib/static/styles/timespinnerTracker.css b/WebHostLib/static/styles/timespinnerTracker.css index 007c6a19..640b5846 100644 --- a/WebHostLib/static/styles/timespinnerTracker.css +++ b/WebHostLib/static/styles/timespinnerTracker.css @@ -75,6 +75,27 @@ #inventory-table img.acquired.green{ /*32CD32*/ filter: hue-rotate(84deg) saturate(10) brightness(0.7); } +#inventory-table img.acquired.hotpink{ /*FF69B4*/ + filter: sepia(100%) hue-rotate(300deg) saturate(10); +} +#inventory-table img.acquired.lightsalmon{ /*FFA07A*/ + filter: sepia(100%) hue-rotate(347deg) saturate(10); +} +#inventory-table img.acquired.crimson{ /*DB143B*/ + filter: sepia(100%) hue-rotate(318deg) saturate(10) brightness(0.86); +} + +#inventory-table span{ + color: #B4B4A0; + font-size: 40px; + max-width: 40px; + max-height: 40px; + filter: grayscale(100%) contrast(75%) brightness(30%); +} + +#inventory-table span.acquired{ + filter: none; +} #inventory-table div.image-stack{ display: grid; diff --git a/WebHostLib/templates/tracker__Timespinner.html b/WebHostLib/templates/tracker__Timespinner.html index b118c338..aa856765 100644 --- a/WebHostLib/templates/tracker__Timespinner.html +++ b/WebHostLib/templates/tracker__Timespinner.html @@ -99,6 +99,52 @@ {% endif %} + {% if 'PrismBreak' in options or 'LockKeyAmadeus' in options or 'GateKeep' in options %} +
+ {% if 'PrismBreak' in options %} +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ {% endif %} + {% if 'LockKeyAmadeus' in options %} +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ {% endif %} + {% if 'GateKeep' in options %} +
+ +
+ {% endif %} +
+ {% endif %} diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index 043764a5..3748de97 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -1071,6 +1071,11 @@ if "Timespinner" in network_data_package["games"]: "Plasma Orb": "https://timespinnerwiki.com/mediawiki/images/4/44/Plasma_Orb.png", "Kobo": "https://timespinnerwiki.com/mediawiki/images/c/c6/Familiar_Kobo.png", "Merchant Crow": "https://timespinnerwiki.com/mediawiki/images/4/4e/Familiar_Crow.png", + "Laser Access": "https://timespinnerwiki.com/mediawiki/images/9/99/Historical_Documents.png", + "Lab Glasses": "https://timespinnerwiki.com/mediawiki/images/4/4a/Lab_Glasses.png", + "Eye Orb": "https://timespinnerwiki.com/mediawiki/images/a/a4/Eye_Orb.png", + "Lab Coat": "https://timespinnerwiki.com/mediawiki/images/5/51/Lab_Coat.png", + "Demon": "https://timespinnerwiki.com/mediawiki/images/f/f8/Familiar_Demon.png", } timespinner_location_ids = { @@ -1118,6 +1123,9 @@ if "Timespinner" in network_data_package["games"]: timespinner_location_ids["Ancient Pyramid"] += [ 1337237, 1337238, 1337239, 1337240, 1337241, 1337242, 1337243, 1337244, 1337245] + if (slot_data["PyramidStart"]): + timespinner_location_ids["Ancient Pyramid"] += [ + 1337233, 1337234, 1337235] display_data = {} diff --git a/worlds/timespinner/Items.py b/worlds/timespinner/Items.py index 3beead95..a00fca7e 100644 --- a/worlds/timespinner/Items.py +++ b/worlds/timespinner/Items.py @@ -199,11 +199,16 @@ item_table: Dict[str, ItemData] = { 'Chaos Trap': ItemData('Trap', 1337186, 0, trap=True), 'Neurotoxin Trap': ItemData('Trap', 1337187, 0, trap=True), 'Bee Trap': ItemData('Trap', 1337188, 0, trap=True), - 'Laser Access A': ItemData('Relic', 1337189, progression=True), - 'Laser Access I': ItemData('Relic', 1337191, progression=True), - 'Laser Access M': ItemData('Relic', 1337192, progression=True), + 'Laser Access A': ItemData('Laser Access', 1337189, progression=True), + 'Laser Access I': ItemData('Laser Access', 1337191, progression=True), + 'Laser Access M': ItemData('Laser Access', 1337192, progression=True), 'Throw Stun Trap': ItemData('Trap', 1337193, 0, trap=True), - # 1337194 - 1337248 Reserved + 'Lab Access Genza': ItemData('Lab Access', 1337194, progression=True), + 'Lab Access Experiment': ItemData('Lab Access', 1337195, progression=True), + 'Lab Access Research': ItemData('Lab Access', 1337196, progression=True), + 'Lab Access Dynamo': ItemData('Lab Access', 1337197, progression=True), + 'Drawbridge Key': ItemData('Key', 1337198, progression=True), + # 1337199 - 1337248 Reserved 'Max Sand': ItemData('Stat', 1337249, 14) } @@ -259,6 +264,17 @@ starter_progression_items: Tuple[str, ...] = ( 'Mysterious Warp Beacon' ) +pyramid_start_starter_progression_items: Tuple[str, ...] = ( + 'Succubus Hairpin', + 'Succubus Hairpin', + 'Twin Pyramid Key', + 'Celestial Sash', + 'Lightwall', + 'Modern Warp Beacon', + 'Timeworn Warp Beacon', + 'Mysterious Warp Beacon' +) + filler_items: Tuple[str, ...] = ( 'Potion', 'Ether', @@ -280,4 +296,4 @@ def get_item_names_per_category() -> Dict[str, Set[str]]: for name, data in item_table.items(): categories.setdefault(data.category, set()).add(name) - return categories \ No newline at end of file + return categories diff --git a/worlds/timespinner/Locations.py b/worlds/timespinner/Locations.py index 93ac6ccb..64430473 100644 --- a/worlds/timespinner/Locations.py +++ b/worlds/timespinner/Locations.py @@ -92,15 +92,15 @@ def get_location_datas(player: Optional[int], options: Optional[TimespinnerOptio LocationData('Military Fortress (hangar)', 'Military Fortress: Pedestal', 1337065, lambda state: state.has('Water Mask', player) if flooded.flood_lab else (logic.has_doublejump_of_npc(state) or logic.has_forwarddash_doublejump(state))), LocationData('The lab', 'Lab: Coffee break', 1337066), LocationData('The lab', 'Lab: Lower trash right', 1337067, logic.has_doublejump), - LocationData('The lab', 'Lab: Lower trash left', 1337068, logic.has_upwarddash), + LocationData('The lab', 'Lab: Lower trash left', 1337068, lambda state: logic.has_doublejump_of_npc(state) if options.lock_key_amadeus else logic.has_upwarddash ), LocationData('The lab', 'Lab: Below lab entrance', 1337069, logic.has_doublejump), - LocationData('The lab (power off)', 'Lab: Trash jump room', 1337070), - LocationData('The lab (power off)', 'Lab: Dynamo Works', 1337071), + LocationData('The lab (power off)', 'Lab: Trash jump room', 1337070, lambda state: not options.lock_key_amadeus or logic.has_doublejump_of_npc(state) ), + LocationData('The lab (power off)', 'Lab: Dynamo Works', 1337071, lambda state: not options.lock_key_amadeus or (state.has_all(('Lab Access Research', 'Lab Access Dynamo'), player)) ), LocationData('The lab (upper)', 'Lab: Genza (Blob Mom)', 1337072), - LocationData('The lab (power off)', 'Lab: Experiment #13', 1337073), + LocationData('The lab (power off)', 'Lab: Experiment #13', 1337073, lambda state: not options.lock_key_amadeus or state.has('Lab Access Experiment', player) ), LocationData('The lab (upper)', 'Lab: Download and chest room chest', 1337074), LocationData('The lab (upper)', 'Lab: Lab secret', 1337075, logic.can_break_walls), - LocationData('The lab (power off)', 'Lab: Spider Hell', 1337076, logic.has_keycard_A), + LocationData('The lab (power off)', 'Lab: Spider Hell', 1337076, lambda state: logic.has_keycard_A and not options.lock_key_amadeus or state.has('Lab Access Research', player)), LocationData('Emperors tower', 'Emperor\'s Tower: Courtyard bottom chest', 1337077), LocationData('Emperors tower', 'Emperor\'s Tower: Courtyard floor secret', 1337078, lambda state: logic.has_upwarddash(state) and logic.can_break_walls(state)), LocationData('Emperors tower', 'Emperor\'s Tower: Courtyard upper chest', 1337079, lambda state: logic.has_upwarddash(state)), @@ -214,11 +214,11 @@ def get_location_datas(player: Optional[int], options: Optional[TimespinnerOptio LocationData('Library top', 'Library: Backer room terminal (Vandagray Metropolis Map)', 1337163, lambda state: state.has('Tablet', player)), LocationData('Varndagroth tower right (elevator)', 'Varndagroth Towers (Right): Medbay terminal (Bleakness Research)', 1337164, lambda state: state.has('Tablet', player) and logic.has_keycard_B(state)), LocationData('The lab (upper)', 'Lab: Download and chest room terminal (Experiment #13)', 1337165, lambda state: state.has('Tablet', player)), - LocationData('The lab (power off)', 'Lab: Middle terminal (Amadeus Laboratory Map)', 1337166, lambda state: state.has('Tablet', player)), - LocationData('The lab (power off)', 'Lab: Sentry platform terminal (Origins)', 1337167, lambda state: state.has('Tablet', player)), + LocationData('The lab (power off)', 'Lab: Middle terminal (Amadeus Laboratory Map)', 1337166, lambda state: state.has('Tablet', player) and (not options.lock_key_amadeus or state.has('Lab Access Research', player))), + LocationData('The lab (power off)', 'Lab: Sentry platform terminal (Origins)', 1337167, lambda state: state.has('Tablet', player) and (not options.lock_key_amadeus or state.has('Lab Access Genza', player) or logic.can_teleport_to(state, "Time", "GateDadsTower"))), LocationData('The lab', 'Lab: Experiment 13 terminal (W.R.E.C Farewell)', 1337168, lambda state: state.has('Tablet', player)), LocationData('The lab', 'Lab: Left terminal (Biotechnology)', 1337169, lambda state: state.has('Tablet', player)), - LocationData('The lab (power off)', 'Lab: Right terminal (Experiment #11)', 1337170, lambda state: state.has('Tablet', player)) + LocationData('The lab (power off)', 'Lab: Right terminal (Experiment #11)', 1337170, lambda state: state.has('Tablet', player) and (not options.lock_key_amadeus or state.has('Lab Access Research', player))) ) # 1337176 - 1337176 Cantoran @@ -254,7 +254,17 @@ def get_location_datas(player: Optional[int], options: Optional[TimespinnerOptio LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Journal - Lower Left Caves (Naivety)', 1337198, lambda state: not flooded.flood_maw or state.has('Water Mask', player)) ) - # 1337199 - 1337236 Reserved for future use + # 1337199 - 1337232 Reserved for future use + + # 1337233 - 1337235 Pyramid Start checks + if not options or options.pyramid_start: + location_table += ( + LocationData('Ancient Pyramid (entrance)', 'Dark Forest: Training Dummy', 1337233), + LocationData('Ancient Pyramid (entrance)', 'Temporal Gyre: Forest Entrance', 1337234, lambda state: logic.has_upwarddash(state) or logic.can_teleport_to(state, "Time", "GateGyre")), + LocationData('Ancient Pyramid (entrance)', 'Ancient Pyramid: Rubble', 1337235), + ) + + # 1337236 Nightmare door # 1337237 - 1337245 GyreArchives if not options or options.gyre_archives: diff --git a/worlds/timespinner/LogicExtensions.py b/worlds/timespinner/LogicExtensions.py index 2a0a3587..878b69ae 100644 --- a/worlds/timespinner/LogicExtensions.py +++ b/worlds/timespinner/LogicExtensions.py @@ -10,6 +10,7 @@ class TimespinnerLogic: flag_unchained_keys: bool flag_eye_spy: bool flag_specific_keycards: bool + flag_prism_break: bool pyramid_keys_unlock: Optional[str] present_keys_unlock: Optional[str] past_keys_unlock: Optional[str] diff --git a/worlds/timespinner/Options.py b/worlds/timespinner/Options.py index 72f2d8b3..4cb7fbbc 100644 --- a/worlds/timespinner/Options.py +++ b/worlds/timespinner/Options.py @@ -60,6 +60,7 @@ class EnemyRando(Choice): option_scaled = 1 option_unscaled = 2 option_ryshia = 3 + option_no_hell_spiders = 4 alias_true = 1 class DamageRando(Choice): @@ -377,6 +378,26 @@ class PrismBreak(Toggle): """Adds 3 Laser Access items to the item pool to remove the lasers blocking the military hangar area instead of needing to beat the Golden Idol, Aelana, and The Maw.""" display_name = "Prism Break" + +class LockKeyAmadeus(Toggle): + """Lasers in Amadeus' Laboratory are disabled via items, rather than by de-powering the lab. Experiments will spawn in the lab.""" + display_name = "Lock Key Amadeus" + +class RiskyWarps(Toggle): + """Expanded free-warp eligible locations, including Azure Queen, Xarion, Amadeus' Laboratory, and Emperor's Tower.""" + display_name = "Risky Warps" + +class PyramidStart(Toggle): + """Start in ???. Takes priority over Inverted. Additional chests in Dark Forest and Pyramid. Sandman door behaves as it does in Enter Sandman.""" + display_name = "Pyramid Start" + +class GateKeep(Toggle): + """The castle drawbridge starts raised, and can be lowered via item.""" + display_name = "Gate Keep" + +class RoyalRoadblock(Toggle): + """The Royal Towers entrance door requires a royal orb (Plasma Orb, Plasma Geyser, or Royal Ring) to enter.""" + display_name = "Royal Roadblock" @dataclass class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin): @@ -415,6 +436,11 @@ class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin): unchained_keys: UnchainedKeys back_to_the_future: PresentAccessWithWheelAndSpindle prism_break: PrismBreak + lock_key_amadeus: LockKeyAmadeus + risky_warps: RiskyWarps + pyramid_start: PyramidStart + gate_keep: GateKeep + royal_roadblock: RoyalRoadblock trap_chance: TrapChance traps: Traps diff --git a/worlds/timespinner/PreCalculatedWeights.py b/worlds/timespinner/PreCalculatedWeights.py index c9d80d7a..3ad7c2c7 100644 --- a/worlds/timespinner/PreCalculatedWeights.py +++ b/worlds/timespinner/PreCalculatedWeights.py @@ -52,11 +52,12 @@ class PreCalculatedWeights: self.flood_lab = False self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \ - self.get_pyramid_keys_unlocks(options, random, self.flood_maw, self.flood_xarion) + self.get_pyramid_keys_unlocks(options, random, self.flood_maw, self.flood_xarion, self.flood_lab) @staticmethod def get_pyramid_keys_unlocks(options: TimespinnerOptions, random: Random, - is_maw_flooded: bool, is_xarion_flooded: bool) -> Tuple[str, str, str, str]: + is_maw_flooded: bool, is_xarion_flooded: bool, + is_lab_flooded: bool) -> Tuple[str, str, str, str]: present_teleportation_gates: List[str] = [ "GateKittyBoss", @@ -85,10 +86,15 @@ class PreCalculatedWeights: if not is_maw_flooded: past_teleportation_gates.append("GateMaw") - if not is_xarion_flooded: - present_teleportation_gates.append("GateXarion") + if options.risky_warps: + past_teleportation_gates.append("GateLakeSereneLeft") + present_teleportation_gates.append("GateDadsTower") + if not is_xarion_flooded: + present_teleportation_gates.append("GateXarion") + if not is_lab_flooded: + present_teleportation_gates.append("GateLabEntrance") - if options.inverted: + if options.inverted or (options.pyramid_start and not options.back_to_the_future): all_gates: Tuple[str, ...] = present_teleportation_gates else: all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index f737b461..51b1688f 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -106,15 +106,15 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp connect(world, player, 'Sealed Caves (Sirens)', 'Varndagroth tower right (lower)', lambda state: state.has('Elevator Keycard', player)) connect(world, player, 'Sealed Caves (Sirens)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Military Fortress', 'Varndagroth tower right (lower)', logic.can_kill_all_3_bosses) - connect(world, player, 'Military Fortress', 'Temporal Gyre', lambda state: state.has('Timespinner Wheel', player)) + connect(world, player, 'Military Fortress', 'Temporal Gyre', lambda state: state.has('Timespinner Wheel', player) and logic.can_kill_all_3_bosses(state)) connect(world, player, 'Military Fortress', 'Military Fortress (hangar)', logic.has_doublejump) connect(world, player, 'Military Fortress (hangar)', 'Military Fortress') connect(world, player, 'Military Fortress (hangar)', 'The lab', lambda state: logic.has_keycard_B(state) and (state.has('Water Mask', player) if flooded.flood_lab else logic.has_doublejump(state))) connect(world, player, 'Temporal Gyre', 'Military Fortress') connect(world, player, 'The lab', 'Military Fortress') - connect(world, player, 'The lab', 'The lab (power off)', logic.has_doublejump_of_npc) + connect(world, player, 'The lab', 'The lab (power off)', lambda state: options.lock_key_amadeus or logic.has_doublejump_of_npc(state)) connect(world, player, 'The lab (power off)', 'The lab', lambda state: not flooded.flood_lab or state.has('Water Mask', player)) - connect(world, player, 'The lab (power off)', 'The lab (upper)', logic.has_forwarddash_doublejump) + connect(world, player, 'The lab (power off)', 'The lab (upper)', lambda state: logic.has_forwarddash_doublejump(state) and ((not options.lock_key_amadeus) or state.has('Lab Access Genza', player))) connect(world, player, 'The lab (upper)', 'The lab (power off)') connect(world, player, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump) connect(world, player, 'The lab (upper)', 'Ancient Pyramid (entrance)', lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player)) @@ -125,12 +125,12 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp connect(world, player, 'Sealed Caves (Xarion)', 'Skeleton Shaft') connect(world, player, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Refugee Camp', 'Forest') - connect(world, player, 'Refugee Camp', 'Library', lambda state: options.inverted and options.back_to_the_future and state.has_all({'Timespinner Wheel', 'Timespinner Spindle'}, player)) + connect(world, player, 'Refugee Camp', 'Library', lambda state: (options.pyramid_start or options.inverted) and options.back_to_the_future and state.has_all({'Timespinner Wheel', 'Timespinner Spindle'}, player)) connect(world, player, 'Refugee Camp', 'Space time continuum', logic.has_teleport) connect(world, player, 'Forest', 'Refugee Camp') connect(world, player, 'Forest', 'Left Side forest Caves', lambda state: flooded.flood_lake_serene_bridge or state.has('Talaria Attachment', player) or logic.has_timestop(state)) connect(world, player, 'Forest', 'Caves of Banishment (Sirens)') - connect(world, player, 'Forest', 'Castle Ramparts') + connect(world, player, 'Forest', 'Castle Ramparts', lambda state: not options.gate_keep or state.has('Drawbridge Key', player) or logic.has_upwarddash(state)) connect(world, player, 'Left Side forest Caves', 'Forest') connect(world, player, 'Left Side forest Caves', 'Upper Lake Serene', logic.has_timestop) connect(world, player, 'Left Side forest Caves', 'Lower Lake Serene', lambda state: not flooded.flood_lake_serene or state.has('Water Mask', player)) @@ -152,7 +152,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp connect(world, player, 'Castle Ramparts', 'Space time continuum', logic.has_teleport) connect(world, player, 'Castle Keep', 'Castle Ramparts') connect(world, player, 'Castle Keep', 'Castle Basement', lambda state: not flooded.flood_basement or state.has('Water Mask', player)) - connect(world, player, 'Castle Keep', 'Royal towers (lower)', logic.has_doublejump) + connect(world, player, 'Castle Keep', 'Royal towers (lower)', lambda state: logic.has_doublejump(state) and (not options.royal_roadblock or logic.has_pink(state))) connect(world, player, 'Castle Keep', 'Space time continuum', logic.has_teleport) connect(world, player, 'Royal towers (lower)', 'Castle Keep') connect(world, player, 'Royal towers (lower)', 'Royal towers', lambda state: state.has('Timespinner Wheel', player) or logic.has_forwarddash_doublejump(state)) @@ -162,9 +162,12 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp connect(world, player, 'Royal towers (upper)', 'Royal towers') #connect(world, player, 'Ancient Pyramid (entrance)', 'The lab (upper)', lambda state: not is_option_enabled(world, player, "EnterSandman")) connect(world, player, 'Ancient Pyramid (entrance)', 'Ancient Pyramid (left)', logic.has_doublejump) + connect(world, player, 'Ancient Pyramid (entrance)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Ancient Pyramid (left)', 'Ancient Pyramid (entrance)') connect(world, player, 'Ancient Pyramid (left)', 'Ancient Pyramid (right)', lambda state: flooded.flood_pyramid_shaft or logic.has_upwarddash(state)) + connect(world, player, 'Ancient Pyramid (left)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Ancient Pyramid (right)', 'Ancient Pyramid (left)', lambda state: flooded.flood_pyramid_shaft or logic.has_upwarddash(state)) + connect(world, player, 'Ancient Pyramid (right)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Space time continuum', 'Lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateLakeDesolation")) connect(world, player, 'Space time continuum', 'Lower lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateKittyBoss")) connect(world, player, 'Space time continuum', 'Library', lambda state: logic.can_teleport_to(state, "Present", "GateLeftLibrary")) @@ -180,8 +183,9 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp connect(world, player, 'Space time continuum', 'Royal towers (lower)', lambda state: logic.can_teleport_to(state, "Past", "GateRoyalTowers")) connect(world, player, 'Space time continuum', 'Caves of Banishment (Maw)', lambda state: logic.can_teleport_to(state, "Past", "GateMaw")) connect(world, player, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: logic.can_teleport_to(state, "Past", "GateCavesOfBanishment")) - connect(world, player, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not options.unchained_keys and options.enter_sandman)) - connect(world, player, 'Space time continuum', 'Ancient Pyramid (left)', lambda state: logic.can_teleport_to(state, "Time", "GateLeftPyramid")) + connect(world, player, 'Space time continuum', 'Military Fortress (hangar)', lambda state: logic.can_teleport_to(state, "Present", "GateLabEntrance")) + connect(world, player, 'Space time continuum', 'The lab (upper)', lambda state: logic.can_teleport_to(state, "Present", "GateDadsTower")) + connect(world, player, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or logic.can_teleport_to(state, "Time", "GateLeftPyramid") or (not options.unchained_keys and options.enter_sandman)) connect(world, player, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid")) if options.gyre_archives: @@ -227,7 +231,9 @@ def connectStartingRegion(world: MultiWorld, player: int, options: TimespinnerOp tutorial = world.get_region('Tutorial', player) space_time_continuum = world.get_region('Space time continuum', player) - if options.inverted: + if options.pyramid_start: + starting_region = world.get_region('Ancient Pyramid (entrance)', player) + elif options.inverted: starting_region = world.get_region('Refugee Camp', player) else: starting_region = world.get_region('Lake desolation', player) @@ -264,4 +270,4 @@ def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, for location in locations: per_region.setdefault(location.region, []).append(location) - return per_region \ No newline at end of file + return per_region diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py index ca31d083..4d1efc41 100644 --- a/worlds/timespinner/__init__.py +++ b/worlds/timespinner/__init__.py @@ -1,7 +1,7 @@ from typing import Dict, List, Set, Tuple, TextIO, Any, Optional from BaseClasses import Item, Tutorial, ItemClassification from .Items import get_item_names_per_category -from .Items import item_table, starter_melee_weapons, starter_spells, filler_items, starter_progression_items +from .Items import item_table, starter_melee_weapons, starter_spells, filler_items, starter_progression_items, pyramid_start_starter_progression_items from .Locations import get_location_datas, EventId from .Options import BackwardsCompatiableTimespinnerOptions, Toggle from .PreCalculatedWeights import PreCalculatedWeights @@ -126,6 +126,11 @@ class TimespinnerWorld(World): "UnchainedKeys": self.options.unchained_keys.value, "PresentAccessWithWheelAndSpindle": self.options.back_to_the_future.value, "PrismBreak": self.options.prism_break.value, + "LockKeyAmadeus": self.options.lock_key_amadeus.value, + "RiskyWarps": self.options.risky_warps.value, + "PyramidStart": self.options.pyramid_start.value, + "GateKeep": self.options.gate_keep.value, + "RoyalRoadblock": self.options.royal_roadblock.value, "Traps": self.options.traps.value, "DeathLink": self.options.death_link.value, "StinkyMaw": True, @@ -203,7 +208,7 @@ class TimespinnerWorld(World): self.precalculated_weights.past_key_unlock = slot_data["PastGate"] self.precalculated_weights.time_key_unlock = slot_data["TimeGate"] # rising tides - if (slot_data["Basement"] > 1): + if (slot_data["Basement"] > 0): self.precalculated_weights.flood_basement = True if (slot_data["Basement"] == 2): self.precalculated_weights.flood_basement_high = True @@ -304,6 +309,11 @@ class TimespinnerWorld(World): elif name in {"Laser Access A", "Laser Access I", "Laser Access M"} \ and not self.options.prism_break: item.classification = ItemClassification.filler + elif name in {"Lab Access Genza", "Lab Access Experiment", "Lab Access Research", "Lab Access Dynamo"} \ + and not self.options.lock_key_amadeus: + item.classification = ItemClassification.filler + elif name == "Drawbridge Key" and not self.options.gate_keep: + item.classification = ItemClassification.filler return item @@ -341,6 +351,15 @@ class TimespinnerWorld(World): excluded_items.add('Laser Access I') excluded_items.add('Laser Access M') + if not self.options.lock_key_amadeus: + excluded_items.add('Lab Access Genza') + excluded_items.add('Lab Access Experiment') + excluded_items.add('Lab Access Research') + excluded_items.add('Lab Access Dynamo') + + if not self.options.gate_keep: + excluded_items.add('Drawbridge Key') + for item in self.multiworld.precollected_items[self.player]: if item.name not in self.item_name_groups['UseItem']: excluded_items.add(item.name) @@ -376,15 +395,18 @@ class TimespinnerWorld(World): self.place_locked_item(excluded_items, location, item_name) def place_first_progression_item(self, excluded_items: Set[str]) -> None: - if self.options.quick_seed or self.options.inverted or self.precalculated_weights.flood_lake_desolation: + if (self.options.quick_seed or self.options.inverted or self.precalculated_weights.flood_lake_desolation) \ + and not self.options.pyramid_start: return + enabled_starter_progression_items = pyramid_start_starter_progression_items if self.options.pyramid_start else starter_progression_items + for item_name in self.options.start_inventory.value.keys(): - if item_name in starter_progression_items: + if item_name in enabled_starter_progression_items: return local_starter_progression_items = tuple( - item for item in starter_progression_items + item for item in enabled_starter_progression_items if item not in excluded_items and item not in self.options.non_local_items.value) if not local_starter_progression_items: