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 <scipiowright@gmail.com>

* 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 <sgrunt1987@gmail.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
This commit is contained in:
sgrunt
2025-03-08 09:54:23 -07:00
committed by GitHub
parent 33a75fb2cb
commit 5662da6f7d
10 changed files with 196 additions and 34 deletions

View File

@@ -75,6 +75,27 @@
#inventory-table img.acquired.green{ /*32CD32*/ #inventory-table img.acquired.green{ /*32CD32*/
filter: hue-rotate(84deg) saturate(10) brightness(0.7); 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{ #inventory-table div.image-stack{
display: grid; display: grid;

View File

@@ -99,6 +99,52 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% if 'PrismBreak' in options or 'LockKeyAmadeus' in options or 'GateKeep' in options %}
<div class="table-row">
{% if 'PrismBreak' in options %}
<div class="C1">
<div class="image-stack">
<div class="stack-front">
<div class="stack-top-left">
<img src="{{ icons['Laser Access'] }}" class="hotpink {{ 'acquired' if 'Laser Access A' in acquired_items }}" title="Laser Access A" />
</div>
<div class="stack-top-right">
<img src="{{ icons['Laser Access'] }}" class="lightsalmon {{ 'acquired' if 'Laser Access I' in acquired_items }}" title="Laser Access I" />
</div>
<div class="stack-bottum-left">
<img src="{{ icons['Laser Access'] }}" class="crimson {{ 'acquired' if 'Laser Access M' in acquired_items }}" title="Laser Access M" />
</div>
</div>
</div>
</div>
{% endif %}
{% if 'LockKeyAmadeus' in options %}
<div class="C2">
<div class="image-stack">
<div class="stack-front">
<div class="stack-top-left">
<img src="{{ icons['Lab Glasses'] }}" class="{{ 'acquired' if 'Lab Access Genza' in acquired_items }}" title="Lab Access Genza" />
</div>
<div class="stack-top-right">
<img src="{{ icons['Eye Orb'] }}" class="{{ 'acquired' if 'Lab Access Dynamo' in acquired_items }}" title="Lab Access Dynamo" />
</div>
<div class="stack-bottum-left">
<img src="{{ icons['Lab Coat'] }}" class="{{ 'acquired' if 'Lab Access Research' in acquired_items }}" title="Lab Access Research" />
</div>
<div class="stack-bottum-right">
<img src="{{ icons['Demon'] }}" class="{{ 'acquired' if 'Lab Access Experiment' in acquired_items }}" title="Lab Access Experiment" />
</div>
</div>
</div>
</div>
{% endif %}
{% if 'GateKeep' in options %}
<div class="C3">
<span class="{{ 'acquired' if 'Drawbridge Key' in acquired_items }}" title="Drawbridge Key">&#10070;</span>
</div>
{% endif %}
</div>
{% endif %}
</div> </div>
<table id="location-table"> <table id="location-table">

View File

@@ -1071,6 +1071,11 @@ if "Timespinner" in network_data_package["games"]:
"Plasma Orb": "https://timespinnerwiki.com/mediawiki/images/4/44/Plasma_Orb.png", "Plasma Orb": "https://timespinnerwiki.com/mediawiki/images/4/44/Plasma_Orb.png",
"Kobo": "https://timespinnerwiki.com/mediawiki/images/c/c6/Familiar_Kobo.png", "Kobo": "https://timespinnerwiki.com/mediawiki/images/c/c6/Familiar_Kobo.png",
"Merchant Crow": "https://timespinnerwiki.com/mediawiki/images/4/4e/Familiar_Crow.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 = { timespinner_location_ids = {
@@ -1118,6 +1123,9 @@ if "Timespinner" in network_data_package["games"]:
timespinner_location_ids["Ancient Pyramid"] += [ timespinner_location_ids["Ancient Pyramid"] += [
1337237, 1337238, 1337239, 1337237, 1337238, 1337239,
1337240, 1337241, 1337242, 1337243, 1337244, 1337245] 1337240, 1337241, 1337242, 1337243, 1337244, 1337245]
if (slot_data["PyramidStart"]):
timespinner_location_ids["Ancient Pyramid"] += [
1337233, 1337234, 1337235]
display_data = {} display_data = {}

View File

@@ -199,11 +199,16 @@ item_table: Dict[str, ItemData] = {
'Chaos Trap': ItemData('Trap', 1337186, 0, trap=True), 'Chaos Trap': ItemData('Trap', 1337186, 0, trap=True),
'Neurotoxin Trap': ItemData('Trap', 1337187, 0, trap=True), 'Neurotoxin Trap': ItemData('Trap', 1337187, 0, trap=True),
'Bee Trap': ItemData('Trap', 1337188, 0, trap=True), 'Bee Trap': ItemData('Trap', 1337188, 0, trap=True),
'Laser Access A': ItemData('Relic', 1337189, progression=True), 'Laser Access A': ItemData('Laser Access', 1337189, progression=True),
'Laser Access I': ItemData('Relic', 1337191, progression=True), 'Laser Access I': ItemData('Laser Access', 1337191, progression=True),
'Laser Access M': ItemData('Relic', 1337192, progression=True), 'Laser Access M': ItemData('Laser Access', 1337192, progression=True),
'Throw Stun Trap': ItemData('Trap', 1337193, 0, trap=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) 'Max Sand': ItemData('Stat', 1337249, 14)
} }
@@ -259,6 +264,17 @@ starter_progression_items: Tuple[str, ...] = (
'Mysterious Warp Beacon' '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, ...] = ( filler_items: Tuple[str, ...] = (
'Potion', 'Potion',
'Ether', 'Ether',
@@ -280,4 +296,4 @@ def get_item_names_per_category() -> Dict[str, Set[str]]:
for name, data in item_table.items(): for name, data in item_table.items():
categories.setdefault(data.category, set()).add(name) categories.setdefault(data.category, set()).add(name)
return categories return categories

View File

@@ -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('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: Coffee break', 1337066),
LocationData('The lab', 'Lab: Lower trash right', 1337067, logic.has_doublejump), 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', 'Lab: Below lab entrance', 1337069, logic.has_doublejump),
LocationData('The lab (power off)', 'Lab: Trash jump room', 1337070), 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), 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 (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: Download and chest room chest', 1337074),
LocationData('The lab (upper)', 'Lab: Lab secret', 1337075, logic.can_break_walls), 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 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 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)), 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('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('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 (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: 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)), 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: 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', '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 # 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)) 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 # 1337237 - 1337245 GyreArchives
if not options or options.gyre_archives: if not options or options.gyre_archives:

View File

@@ -10,6 +10,7 @@ class TimespinnerLogic:
flag_unchained_keys: bool flag_unchained_keys: bool
flag_eye_spy: bool flag_eye_spy: bool
flag_specific_keycards: bool flag_specific_keycards: bool
flag_prism_break: bool
pyramid_keys_unlock: Optional[str] pyramid_keys_unlock: Optional[str]
present_keys_unlock: Optional[str] present_keys_unlock: Optional[str]
past_keys_unlock: Optional[str] past_keys_unlock: Optional[str]

View File

@@ -60,6 +60,7 @@ class EnemyRando(Choice):
option_scaled = 1 option_scaled = 1
option_unscaled = 2 option_unscaled = 2
option_ryshia = 3 option_ryshia = 3
option_no_hell_spiders = 4
alias_true = 1 alias_true = 1
class DamageRando(Choice): 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 """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.""" instead of needing to beat the Golden Idol, Aelana, and The Maw."""
display_name = "Prism Break" 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 @dataclass
class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin): class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
@@ -415,6 +436,11 @@ class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
unchained_keys: UnchainedKeys unchained_keys: UnchainedKeys
back_to_the_future: PresentAccessWithWheelAndSpindle back_to_the_future: PresentAccessWithWheelAndSpindle
prism_break: PrismBreak prism_break: PrismBreak
lock_key_amadeus: LockKeyAmadeus
risky_warps: RiskyWarps
pyramid_start: PyramidStart
gate_keep: GateKeep
royal_roadblock: RoyalRoadblock
trap_chance: TrapChance trap_chance: TrapChance
traps: Traps traps: Traps

View File

@@ -52,11 +52,12 @@ class PreCalculatedWeights:
self.flood_lab = False self.flood_lab = False
self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \ 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 @staticmethod
def get_pyramid_keys_unlocks(options: TimespinnerOptions, random: Random, 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] = [ present_teleportation_gates: List[str] = [
"GateKittyBoss", "GateKittyBoss",
@@ -85,10 +86,15 @@ class PreCalculatedWeights:
if not is_maw_flooded: if not is_maw_flooded:
past_teleportation_gates.append("GateMaw") past_teleportation_gates.append("GateMaw")
if not is_xarion_flooded: if options.risky_warps:
present_teleportation_gates.append("GateXarion") 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 all_gates: Tuple[str, ...] = present_teleportation_gates
else: else:
all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates

View File

@@ -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)', '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, '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', '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', 'Military Fortress (hangar)', logic.has_doublejump)
connect(world, player, 'Military Fortress (hangar)', 'Military Fortress') 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, '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, 'Temporal Gyre', 'Military Fortress')
connect(world, player, 'The lab', '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', 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)', 'The lab (power off)')
connect(world, player, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump) connect(world, player, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump)
connect(world, player, 'The lab (upper)', 'Ancient Pyramid (entrance)', lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player)) connect(world, player, '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)', 'Skeleton Shaft')
connect(world, player, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport)
connect(world, player, 'Refugee Camp', 'Forest') 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, 'Refugee Camp', 'Space time continuum', logic.has_teleport)
connect(world, player, 'Forest', 'Refugee Camp') 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', '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', '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', 'Forest')
connect(world, player, '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, 'Left Side forest Caves', 'Lower Lake Serene', lambda state: not flooded.flood_lake_serene or state.has('Water Mask', player)) 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 Ramparts', 'Space time continuum', logic.has_teleport)
connect(world, player, 'Castle Keep', 'Castle Ramparts') 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', '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, 'Castle Keep', 'Space time continuum', logic.has_teleport)
connect(world, player, 'Royal towers (lower)', 'Castle Keep') connect(world, player, 'Royal towers (lower)', 'Castle Keep')
connect(world, player, 'Royal towers (lower)', 'Royal towers', lambda state: state.has('Timespinner Wheel', player) or logic.has_forwarddash_doublejump(state)) connect(world, player, 'Royal towers (lower)', '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, '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)', '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)', '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 (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)', '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)', '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', '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', 'Lower lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateKittyBoss"))
connect(world, player, 'Space time continuum', 'Library', lambda state: logic.can_teleport_to(state, "Present", "GateLeftLibrary")) connect(world, player, 'Space time continuum', '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', '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 (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', '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', 'Military Fortress (hangar)', lambda state: logic.can_teleport_to(state, "Present", "GateLabEntrance"))
connect(world, player, 'Space time continuum', 'Ancient Pyramid (left)', lambda state: logic.can_teleport_to(state, "Time", "GateLeftPyramid")) 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")) connect(world, player, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid"))
if options.gyre_archives: if options.gyre_archives:
@@ -227,7 +231,9 @@ def connectStartingRegion(world: MultiWorld, player: int, options: TimespinnerOp
tutorial = world.get_region('Tutorial', player) tutorial = world.get_region('Tutorial', player)
space_time_continuum = world.get_region('Space time continuum', 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) starting_region = world.get_region('Refugee Camp', player)
else: else:
starting_region = world.get_region('Lake desolation', player) 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: for location in locations:
per_region.setdefault(location.region, []).append(location) per_region.setdefault(location.region, []).append(location)
return per_region return per_region

View File

@@ -1,7 +1,7 @@
from typing import Dict, List, Set, Tuple, TextIO, Any, Optional from typing import Dict, List, Set, Tuple, TextIO, Any, Optional
from BaseClasses import Item, Tutorial, ItemClassification from BaseClasses import Item, Tutorial, ItemClassification
from .Items import get_item_names_per_category 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 .Locations import get_location_datas, EventId
from .Options import BackwardsCompatiableTimespinnerOptions, Toggle from .Options import BackwardsCompatiableTimespinnerOptions, Toggle
from .PreCalculatedWeights import PreCalculatedWeights from .PreCalculatedWeights import PreCalculatedWeights
@@ -126,6 +126,11 @@ class TimespinnerWorld(World):
"UnchainedKeys": self.options.unchained_keys.value, "UnchainedKeys": self.options.unchained_keys.value,
"PresentAccessWithWheelAndSpindle": self.options.back_to_the_future.value, "PresentAccessWithWheelAndSpindle": self.options.back_to_the_future.value,
"PrismBreak": self.options.prism_break.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, "Traps": self.options.traps.value,
"DeathLink": self.options.death_link.value, "DeathLink": self.options.death_link.value,
"StinkyMaw": True, "StinkyMaw": True,
@@ -203,7 +208,7 @@ class TimespinnerWorld(World):
self.precalculated_weights.past_key_unlock = slot_data["PastGate"] self.precalculated_weights.past_key_unlock = slot_data["PastGate"]
self.precalculated_weights.time_key_unlock = slot_data["TimeGate"] self.precalculated_weights.time_key_unlock = slot_data["TimeGate"]
# rising tides # rising tides
if (slot_data["Basement"] > 1): if (slot_data["Basement"] > 0):
self.precalculated_weights.flood_basement = True self.precalculated_weights.flood_basement = True
if (slot_data["Basement"] == 2): if (slot_data["Basement"] == 2):
self.precalculated_weights.flood_basement_high = True 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"} \ elif name in {"Laser Access A", "Laser Access I", "Laser Access M"} \
and not self.options.prism_break: and not self.options.prism_break:
item.classification = ItemClassification.filler 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 return item
@@ -341,6 +351,15 @@ class TimespinnerWorld(World):
excluded_items.add('Laser Access I') excluded_items.add('Laser Access I')
excluded_items.add('Laser Access M') 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]: for item in self.multiworld.precollected_items[self.player]:
if item.name not in self.item_name_groups['UseItem']: if item.name not in self.item_name_groups['UseItem']:
excluded_items.add(item.name) excluded_items.add(item.name)
@@ -376,15 +395,18 @@ class TimespinnerWorld(World):
self.place_locked_item(excluded_items, location, item_name) self.place_locked_item(excluded_items, location, item_name)
def place_first_progression_item(self, excluded_items: Set[str]) -> None: 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 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(): 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 return
local_starter_progression_items = tuple( 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 item not in excluded_items and item not in self.options.non_local_items.value)
if not local_starter_progression_items: if not local_starter_progression_items: