Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timespinner: support new flags and settings from the randomizer #4559

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions WebHostLib/static/styles/timespinnerTracker.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@
#inventory-table img.acquired.green{ /*32CD32*/
filter: hue-rotate(84deg) saturate(10) brightness(0.7);
}
#inventory-table img.acquired.hotpink{ /*FF69B4*/
filter: hue-rotate(330deg) saturate(10) brightness(0.71);
}
#inventory-table img.acquired.lightsalmon{ /*FFA07A*/
filter: hue-rotate(17deg) saturate(10) brightness(0.74);
}
#inventory-table img.acquired.crimson{ /*DB143B*/
filter: hue-rotate(348deg) saturate(8.3) brightness(0.47);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three colours are the same as those used by the randomiser's item tracker for the three Laser Access items.


#inventory-table div.image-stack{
display: grid;
Expand Down
41 changes: 41 additions & 0 deletions WebHostLib/templates/tracker__Timespinner.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,47 @@
{% endif %}
</div>
</div>
{% if 'PrismBreak' in options or 'LockKeyAmadeus' 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 %}
</div>
{% endif %}
</div>

<table id="location-table">
Expand Down
8 changes: 8 additions & 0 deletions WebHostLib/tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,11 @@ def render_Timespinner_tracker(tracker_data: TrackerData, team: int, player: int
"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 = {
Expand Down Expand Up @@ -1118,6 +1123,9 @@ def render_Timespinner_tracker(tracker_data: TrackerData, team: int, player: int
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 = {}

Expand Down
8 changes: 6 additions & 2 deletions worlds/timespinner/Items.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ class ItemData(NamedTuple):
'Laser Access I': ItemData('Relic', 1337191, progression=True),
'Laser Access M': ItemData('Relic', 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),
# 1337198 - 1337248 Reserved
'Max Sand': ItemData('Stat', 1337249, 14)
}

Expand Down Expand Up @@ -280,4 +284,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
return categories
28 changes: 19 additions & 9 deletions worlds/timespinner/Locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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('Lab Access Research', player) and state.has('Lab Access Dynamo', player)) ),
sgrunt marked this conversation as resolved.
Show resolved Hide resolved
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)),
Expand Down Expand Up @@ -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 Research', player))),
Copy link
Contributor Author

@sgrunt sgrunt Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This matches the upstream logic, but I think this is incorrect - the room this is in is gated behind Lab Access Genza, not Lab Access Research. Check upstream and update the other PR too if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this here, but I need to verify that the problem is what I think it is and also fix in the main rando if so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rando PR has been updated with the fix.

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
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions worlds/timespinner/LogicExtensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
36 changes: 36 additions & 0 deletions worlds/timespinner/Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -377,6 +378,18 @@ 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"

@dataclass
class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
Expand Down Expand Up @@ -415,6 +428,9 @@ 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
trap_chance: TrapChance
traps: Traps

Expand Down Expand Up @@ -482,6 +498,10 @@ class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions):
RisingTidesOverrides: HiddenRisingTidesOverrides
UnchainedKeys: hidden(UnchainedKeys) # type: ignore
PresentAccessWithWheelAndSpindle: hidden(PresentAccessWithWheelAndSpindle) # type: ignore
PrismBreak: hidden(PrismBreak) # type: ignore
LockKeyAmadeus: hidden(LockKeyAmadeus) # type: ignore
RiskyWarps: hidden(RiskyWarps) # type: ignore
PyramidStart: hidden(PyramidStart) # type: ignore
TrapChance: hidden(TrapChance) # type: ignore
Traps: HiddenTraps # type: ignore
DeathLink: HiddenDeathLink # type: ignore
Expand Down Expand Up @@ -620,6 +640,22 @@ def handle_backward_compatibility(self) -> None:
self.back_to_the_future == PresentAccessWithWheelAndSpindle.default:
self.back_to_the_future.value = self.PresentAccessWithWheelAndSpindle.value
self.has_replaced_options.value = Toggle.option_true
if self.PrismBreak != PrismBreak.default and \
self.prism_break == PrismBreak.default:
self.prism_break.value = self.PrismBreak.value
self.has_replaced_options.value = Toggle.option_true
if self.LockKeyAmadeus != LockKeyAmadeus.default and \
self.lock_key_amadeus == LockKeyAmadeus.default:
self.lock_key_amadeus.value = self.LockKeyAmadeus.value
self.has_replaced_options.value = Toggle.option_true
if self.RiskyWarps != RiskyWarps.default and \
self.risky_warps == RiskyWarps.default:
self.risky_warps.value = self.RiskyWarps.value
self.has_replaced_options.value = Toggle.option_true
if self.PyramidStart != PyramidStart.default and \
self.pyramid_start == PyramidStart.default:
self.pyramid_start.value = self.PyramidStart.value
self.has_replaced_options.value = Toggle.option_true
sgrunt marked this conversation as resolved.
Show resolved Hide resolved
if self.TrapChance != TrapChance.default and \
self.trap_chance == TrapChance.default:
self.trap_chance.value = self.TrapChance.value
Expand Down
16 changes: 11 additions & 5 deletions worlds/timespinner/PreCalculatedWeights.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ def __init__(self, options: TimespinnerOptions, random: Random):
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",
Expand Down Expand Up @@ -85,10 +86,15 @@ def get_pyramid_keys_unlocks(options: TimespinnerOptions, random: Random,
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):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking this doesn't match the in-randomiser logic (which will allow both past and present for pyramid start mode based on my understanding), but generation tends to fail out if I don't restrict this case to present gates.

all_gates: Tuple[str, ...] = present_teleportation_gates
else:
all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates
Expand Down
Loading
Loading