Timespinner: many new stuffs (#1433)

* Timespinner: added RisingTides and DadPercent flags

* Implemented logic for DadPercent and RisingTides

* Fixed TODO's

* Logic fixes

* Fixed + removed LogicMixins

* Fixes

* More Fixes

* Added UnchainedKeys flag

* Fixed available items in pool with UnchainedKeys

* Fixed typing callable

* Fixed generation failures

* More refactorings

* Implemented traps

* Fixed more typo

* Fixed copy paste bug

* Fixed teleporter logic

* Fixed traps from pool

* Fixed pyramid gates bug that causes a crash on connecting

* Fixed seed reproduceability

* Fixed logic eye for eye spy
Now consider warp beacons as starter progression items

* Attempt to add tracker icons using table

* Replaced table layout with css grid

* Fixed tracker + added Timespinner was apworld capatible

* Updated archipelago items description

* updated URL

* Cleared up text

* Fixed based on self review of PR

* Fixed unit tests

* Fixed seed reproduceability when the traps yaml option is not provided

* Fixed logic for flooded basement

* Implemented Beserkers review result

I am not sure why, i guess this is just to make adding future games less conflicting?

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Added two new options (thanks to WeffJebster)

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Addition review results

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
Jarno 2023-02-19 21:22:30 +01:00 committed by GitHub
parent df020bb389
commit fc2e555b4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 840 additions and 410 deletions

View File

@ -9,19 +9,54 @@
border-top-left-radius: 4px;
border-top-right-radius: 4px;
padding: 3px 3px 10px;
width: 384px;
width: 374px;
background-color: #8d60a7;
}
#inventory-table td{
width: 40px;
height: 40px;
text-align: center;
vertical-align: middle;
display: grid;
grid-template-rows: repeat(5, 48px);
}
#inventory-table img{
display: block;
}
#inventory-table div.table-row{
display: grid;
grid-template-columns: repeat(5, 1fr);
}
#inventory-table div.C1{
grid-column: 1;
place-content: center;
place-items: center;
display: flex;
}
#inventory-table div.C2{
grid-column: 2;
place-content: center;
place-items: center;
display: flex;
}
#inventory-table div.C3{
grid-column: 3;
place-content: center;
place-items: center;
display: flex;
}
#inventory-table div.C4{
grid-column: 4;
place-content: center;
place-items: center;
display: flex;
}
#inventory-table div.C5{
grid-column: 5;
place-content: center;
place-items: center;
display: flex;
}
#inventory-table img{
height: 100%;
max-width: 40px;
max-height: 40px;
filter: grayscale(100%) contrast(75%) brightness(30%);
@ -31,11 +66,70 @@
filter: none;
}
#inventory-table div.counted-item {
#inventory-table img.acquired.purple{ /*00FFFF*/
filter: hue-rotate(270deg) saturate(6) brightness(0.8);
}
#inventory-table img.acquired.cyan{ /*FF00FF*/
filter: hue-rotate(138deg) saturate(10) brightness(0.8);
}
#inventory-table img.acquired.green{ /*32CD32*/
filter: hue-rotate(84deg) saturate(10) brightness(0.7);
}
#inventory-table div.image-stack{
display: grid;
position: relative;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
#inventory-table div.image-stack div.stack-back{
grid-column: 1;
grid-row: 1;
}
#inventory-table div.image-stack div.stack-front{
grid-column: 1;
grid-row: 1;
display: grid;
grid-template-columns: 20px 20px;
grid-template-rows: 20px 20px;
}
#inventory-table div.image-stack div.stack-top-left{
grid-column: 1;
grid-row: 1;
z-index: 1;
}
#inventory-table div.image-stack div.stack-top-right{
grid-column: 2;
grid-row: 1;
z-index: 1;
}
#inventory-table div.image-stack div.stack-bottum-left{
grid-column: 1;
grid-row: 2;
z-index: 1;
}
#inventory-table div.image-stack div.stack-bottum-right{
grid-column: 2;
grid-row: 2;
z-index: 1;
}
#inventory-table div.image-stack div.stack-front img{
width: 20px;
height: 20px;
}
#inventory-table div.counted-item{
position: relative;
}
#inventory-table div.item-count {
#inventory-table div.item-count{
position: absolute;
color: white;
font-family: "Minecraftia", monospace;
@ -69,16 +163,16 @@
line-height: 20px;
}
#location-table td.counter {
#location-table td.counter{
text-align: right;
font-size: 14px;
}
#location-table td.toggle-arrow {
#location-table td.toggle-arrow{
text-align: right;
}
#location-table tr#Total-header {
#location-table tr#Total-header{
font-weight: bold;
}
@ -88,14 +182,14 @@
max-height: 30px;
}
#location-table tbody.locations {
#location-table tbody.locations{
font-size: 12px;
}
#location-table td.location-name {
#location-table td.location-name{
padding-left: 16px;
}
.hide {
.hide{
display: none;
}

View File

@ -8,79 +8,94 @@
<body>
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
<table id="inventory-table">
<tr>
<td><img src="{{ icons['Timespinner Wheel'] }}" class="{{ 'acquired' if 'Timespinner Wheel' in acquired_items }}" title="Timespinner Wheel" /></td>
<td><img src="{{ icons['Timespinner Spindle'] }}" class="{{ 'acquired' if 'Timespinner Spindle' in acquired_items }}" title="Timespinner Spindle" /></td>
<td><img src="{{ icons['Timespinner Gear 1'] }}" class="{{ 'acquired' if 'Timespinner Gear 1' in acquired_items }}" title="Timespinner Gear 1" /></td>
<td><img src="{{ icons['Timespinner Gear 2'] }}" class="{{ 'acquired' if 'Timespinner Gear 2' in acquired_items }}" title="Timespinner Gear 2" /></td>
<td><img src="{{ icons['Timespinner Gear 3'] }}" class="{{ 'acquired' if 'Timespinner Gear 3' in acquired_items }}" title="Timespinner Gear 3" /></td>
</tr>
<tr>
<td><img src="{{ icons['Talaria Attachment'] }}" class="{{ 'acquired' if 'Talaria Attachment' in acquired_items or 'QuickSeed' in options }}" title="Talaria Attachment" /></td>
<td><img src="{{ icons['Succubus Hairpin'] }}" class="{{ 'acquired' if 'Succubus Hairpin' in acquired_items }}" title="Succubus Hairpin" /></td>
<td><img src="{{ icons['Lightwall'] }}" class="{{ 'acquired' if 'Lightwall' in acquired_items }}" title="Lightwall" /></td>
<td><img src="{{ icons['Celestial Sash'] }}" class="{{ 'acquired' if 'Celestial Sash' in acquired_items }}" title="Celestial Sash" /></td>
<td><img src="{{ icons['Twin Pyramid Key'] }}" class="{{ 'acquired' if 'Twin Pyramid Key' in acquired_items }}" title="Twin Pyramid Key" /></td>
</tr>
<tr>
<td><img src="{{ icons['Security Keycard A'] }}" class="{{ 'acquired' if 'Security Keycard A' in acquired_items }}" title="Security Keycard A" /></td>
<td><img src="{{ icons['Security Keycard B'] }}" class="{{ 'acquired' if 'Security Keycard B' in acquired_items }}" title="Security Keycard B" /></td>
<td><img src="{{ icons['Security Keycard C'] }}" class="{{ 'acquired' if 'Security Keycard C' in acquired_items }}" title="Security Keycard C" /></td>
<td><img src="{{ icons['Security Keycard D'] }}" class="{{ 'acquired' if 'Security Keycard D' in acquired_items }}" title="Security Keycard D" /></td>
{% if 'DownloadableItems' in options %}
<td><img src="{{ icons['Library Keycard V'] }}" class="{{ 'acquired' if 'Library Keycard V' in acquired_items }}" title="Library Keycard V" /></td>
{% else %}
<td></td>
{% endif %}
</tr>
<tr>
{% if 'DownloadableItems' in options %}
<td><img src="{{ icons['Tablet'] }}" class="{{ 'acquired' if 'Tablet' in acquired_items }}" title="Tablet" /></td>
{% else %}
<td></td>
{% endif %}
<td><img src="{{ icons['Elevator Keycard'] }}" class="{{ 'acquired' if 'Elevator Keycard' in acquired_items }}" title="Elevator Keycard" /></td>
{% if 'EyeSpy' in options %}
<td><img src="{{ icons['Oculus Ring'] }}" class="{{ 'acquired' if 'Oculus Ring' in acquired_items }}" title="Oculus Ring" /></td>
{% else %}
<td></td>
{% endif %}
<td><img src="{{ icons['Water Mask'] }}" class="{{ 'acquired' if 'Water Mask' in acquired_items }}" title="Water Mask" /></td>
<td><img src="{{ icons['Gas Mask'] }}" class="{{ 'acquired' if 'Gas Mask' in acquired_items }}" title="Gas Mask" /></td>
</tr>
<tr>
{% if 'GyreArchives' in options %}
<td><img src="{{ icons['Kobo'] }}" class="{{ 'acquired' if 'Kobo' in acquired_items }}" title="Kobo" /></td>
<td><img src="{{ icons['Merchant Crow'] }}" class="{{ 'acquired' if 'Merchant Crow' in acquired_items }}" title="Merchant Crow" /></td>
{% else %}
<td></td>
<td></td>
{% endif %}
<div id="inventory-table">
<div class="table-row">
<div class="C1"><img src="{{ icons['Timespinner Wheel'] }}" class="{{ 'acquired' if 'Timespinner Wheel' in acquired_items }}" title="Timespinner Wheel" /></div>
<div class="C2"><img src="{{ icons['Timespinner Spindle'] }}" class="{{ 'acquired' if 'Timespinner Spindle' in acquired_items }}" title="Timespinner Spindle" /></div>
<div class="C3"><img src="{{ icons['Timespinner Gear 1'] }}" class="{{ 'acquired' if 'Timespinner Gear 1' in acquired_items }}" title="Timespinner Gear 1" /></div>
<div class="C4"><img src="{{ icons['Timespinner Gear 2'] }}" class="{{ 'acquired' if 'Timespinner Gear 2' in acquired_items }}" title="Timespinner Gear 2" /></div>
<div class="C5"><img src="{{ icons['Timespinner Gear 3'] }}" class="{{ 'acquired' if 'Timespinner Gear 3' in acquired_items }}" title="Timespinner Gear 3" /></div>
</div>
<div class="table-row">
<div class="C1"><img src="{{ icons['Talaria Attachment'] }}" class="{{ 'acquired' if 'Talaria Attachment' in acquired_items or 'QuickSeed' in options }}" title="Talaria Attachment" /></div>
<div class="C2"><img src="{{ icons['Succubus Hairpin'] }}" class="{{ 'acquired' if 'Succubus Hairpin' in acquired_items }}" title="Succubus Hairpin" /></div>
<div class="C3"><img src="{{ icons['Lightwall'] }}" class="{{ 'acquired' if 'Lightwall' in acquired_items }}" title="Lightwall" /></div>
<div class="C4"><img src="{{ icons['Celestial Sash'] }}" class="{{ 'acquired' if 'Celestial Sash' in acquired_items }}" title="Celestial Sash" /></div>
<div class="C5">
<div class="image-stack">
<div class="stack-back">
<img src="{{ icons['Twin Pyramid Key'] }}" class="{{ 'acquired' if 'Twin Pyramid Key' in acquired_items or 'UnchainedKeys' in options }}" title="Twin Pyramid Key" />
</div>
<div class="stack-front">
{% if 'UnchainedKeys' in options %}
{% if 'EnterSandman' in options %}
<div class="stack-top-right">
<img src="{{ icons['Twin Pyramid Key'] }}" class="green {{ 'acquired' if 'Mysterious Warp Beacon' in acquired_items }}" title="Mysterious Warp Beacon" />
</div>
{% endif %}
<div class="stack-bottum-left">
<img src="{{ icons['Twin Pyramid Key'] }}" class="cyan {{ 'acquired' if 'Timeworn Warp Beacon' in acquired_items }}" title="Timeworn Warp Beacon" />
</div>
<div class="stack-bottum-right">
<img src="{{ icons['Twin Pyramid Key'] }}" class="purple {{ 'acquired' if 'Modern Warp Beacon' in acquired_items }}" title="Modern Warp Beacon" />
</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="table-row">
<div class="C1"><img src="{{ icons['Security Keycard A'] }}" class="{{ 'acquired' if 'Security Keycard A' in acquired_items }}" title="Security Keycard A" /></div>
<div class="C2"><img src="{{ icons['Security Keycard B'] }}" class="{{ 'acquired' if 'Security Keycard B' in acquired_items }}" title="Security Keycard B" /></div>
<div class="C3"><img src="{{ icons['Security Keycard C'] }}" class="{{ 'acquired' if 'Security Keycard C' in acquired_items }}" title="Security Keycard C" /></div>
<div class="C4"><img src="{{ icons['Security Keycard D'] }}" class="{{ 'acquired' if 'Security Keycard D' in acquired_items }}" title="Security Keycard D" /></div>
{% if 'DownloadableItems' in options %}
<div class="C5"><img src="{{ icons['Library Keycard V'] }}" class="{{ 'acquired' if 'Library Keycard V' in acquired_items }}" title="Library Keycard V" /></div>
{% endif %}
</div>
<div class="table-row">
{% if 'DownloadableItems' in options %}
<div class="C1"><img src="{{ icons['Tablet'] }}" class="{{ 'acquired' if 'Tablet' in acquired_items }}" title="Tablet" /></div>
{% endif %}
<div class="C2"><img src="{{ icons['Elevator Keycard'] }}" class="{{ 'acquired' if 'Elevator Keycard' in acquired_items }}" title="Elevator Keycard" /></div>
{% if 'EyeSpy' in options %}
<div class="C3"><img src="{{ icons['Oculus Ring'] }}" class="{{ 'acquired' if 'Oculus Ring' in acquired_items }}" title="Oculus Ring" /></div>
{% endif %}
<div class="C4"><img src="{{ icons['Water Mask'] }}" class="{{ 'acquired' if 'Water Mask' in acquired_items }}" title="Water Mask" /></div>
<div class="C5"><img src="{{ icons['Gas Mask'] }}" class="{{ 'acquired' if 'Gas Mask' in acquired_items }}" title="Gas Mask" /></div>
</div>
<div class="table-row">
{% if 'GyreArchives' in options %}
<div class="C1"><img src="{{ icons['Kobo'] }}" class="{{ 'acquired' if 'Kobo' in acquired_items }}" title="Kobo" /></div>
<div class="C2"><img src="{{ icons['Merchant Crow'] }}" class="{{ 'acquired' if 'Merchant Crow' in acquired_items }}" title="Merchant Crow" /></div>
{% endif %}
<div class="C3">
{% if 'Djinn Inferno' in acquired_items %}
<td><img src="{{ icons['Djinn Inferno'] }}" class="acquired" title="Djinn Inferno" /></td>
<img src="{{ icons['Djinn Inferno'] }}" class="acquired" title="Djinn Inferno" />
{% elif 'Pyro Ring' in acquired_items %}
<td><img src="{{ icons['Pyro Ring'] }}" class="acquired" title="Pyro Ring" /></td>
<img src="{{ icons['Pyro Ring'] }}" class="acquired" title="Pyro Ring" />
{% elif 'Fire Orb' in acquired_items %}
<td><img src="{{ icons['Fire Orb'] }}" class="acquired" title="Fire Orb" /></td>
<img src="{{ icons['Fire Orb'] }}" class="acquired" title="Fire Orb" />
{% elif 'Infernal Flames' in acquired_items %}
<td><img src="{{ icons['Infernal Flames'] }}" class="acquired" title="Infernal Flames" /></td>
<img src="{{ icons['Infernal Flames'] }}" class="acquired" title="Infernal Flames" />
{% else %}
<td><img src="{{ icons['Djinn Inferno'] }}" title="Djinn Inferno" /></td>
<img src="{{ icons['Djinn Inferno'] }}" title="Djinn Inferno" />
{% endif %}
</div>
<div class="C4">
{% if 'Royal Ring' in acquired_items %}
<td><img src="{{ icons['Royal Ring'] }}" class="acquired" title="Royal Ring" /></td>
<img src="{{ icons['Royal Ring'] }}" class="acquired" title="Royal Ring" />
{% elif 'Plasma Geyser' in acquired_items %}
<td><img src="{{ icons['Plasma Geyser'] }}" class="acquired" title="Plasma Geyser" /></td>
<img src="{{ icons['Plasma Geyser'] }}" class="acquired" title="Plasma Geyser" />
{% elif 'Plasma Orb' in acquired_items %}
<td><img src="{{ icons['Plasma Orb'] }}" class="acquired" title="Plasma Orb" /></td>
<img src="{{ icons['Plasma Orb'] }}" class="acquired" title="Plasma Orb" />
{% else %}
<td><img src="{{ icons['Royal Ring'] }}" title="Royal Ring" /></td>
<img src="{{ icons['Royal Ring'] }}" title="Royal Ring" />
{% endif %}
</tr>
</table>
</div>
</div>
</div>
<table id="location-table">
{% for area in checks_done %}
<tr class="location-category" id="{{area}}-header">

View File

@ -45,6 +45,7 @@ apworlds: set = {
"Rogue Legacy",
"Donkey Kong Country 3",
"Super Mario World",
"Timespinner",
}
if os.path.exists("X:/pw.txt"):

View File

@ -6,6 +6,7 @@ class ItemData(NamedTuple):
count: int = 1
progression: bool = False
useful: bool = False
trap: 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] = {
@ -190,7 +191,15 @@ item_table: Dict[str, ItemData] = {
'Hope Ring': ItemData('Orb Passive', 1337178),
'Max HP': ItemData('Stat', 1337179, 12),
'Max Aura': ItemData('Stat', 1337180, 13),
# 1337181 - 1337248 Reserved
'Timeworn Warp Beacon': ItemData('Relic', 1337181, progression=True),
'Modern Warp Beacon': ItemData('Relic', 1337182, progression=True),
'Mysterious Warp Beacon': ItemData('Relic', 1337183, progression=True),
'Meteor Sparrow Trap': ItemData('Trap', 1337184, 0, trap=True),
'Poison Trap': ItemData('Trap', 1337185, 0, trap=True),
'Chaos Trap': ItemData('Trap', 1337186, 0, trap=True),
'Neurotoxin Trap': ItemData('Trap', 1337187, 0, trap=True),
'Bee Trap': ItemData('Trap', 1337188, 0, trap=True),
# 1337189 - 1337248 Reserved
'Max Sand': ItemData('Stat', 1337249, 14)
}
@ -230,19 +239,6 @@ starter_spells: Tuple[str, ...] = (
'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',

View File

@ -1,6 +1,8 @@
from typing import List, Tuple, Optional, Callable, NamedTuple
from BaseClasses import MultiWorld
from BaseClasses import MultiWorld, CollectionState
from .Options import is_option_enabled
from .PreCalculatedWeights import PreCalculatedWeights
from .LogicExtensions import TimespinnerLogic
EventId: Optional[int] = None
@ -9,10 +11,15 @@ class LocationData(NamedTuple):
region: str
name: str
code: Optional[int]
rule: Callable = lambda state: True
rule: Callable[[CollectionState], bool] = lambda state: True
def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]:
def get_locations(world: Optional[MultiWorld], player: Optional[int],
precalculated_weights: PreCalculatedWeights) -> Tuple[LocationData, ...]:
flooded: PreCalculatedWeights = precalculated_weights
logic = TimespinnerLogic(world, player, precalculated_weights)
# 1337000 - 1337155 Generic locations
# 1337171 - 1337175 New Pickup checks
# 1337246 - 1337249 Ancient Pyramid
@ -24,13 +31,13 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData('Lake desolation', 'Lake Desolation: Starter chest 3', 1337003),
LocationData('Lake desolation', 'Lake Desolation: Starter chest 1', 1337004),
LocationData('Lake desolation', 'Lake Desolation (Lower): Timespinner Wheel room', 1337005),
LocationData('Lake desolation', 'Lake Desolation: Forget me not chest', 1337006, lambda state: state._timespinner_has_fire(world, player) and state.can_reach('Upper Lake Serene', 'Region', player)),
LocationData('Lake desolation', 'Lake Desolation (Lower): Chicken chest', 1337007, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Lower lake desolation', 'Lake Desolation (Lower): Not so secret room', 1337008, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Lower lake desolation', 'Lake Desolation (Upper): Tank chest', 1337009, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Lake desolation', 'Lake Desolation: Forget me not chest', 1337006, lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player)),
LocationData('Lake desolation', 'Lake Desolation (Lower): Chicken chest', 1337007, logic.has_timestop),
LocationData('Lower lake desolation', 'Lake Desolation (Lower): Not so secret room', 1337008, logic.can_break_walls),
LocationData('Lower lake desolation', 'Lake Desolation (Upper): Tank chest', 1337009, logic.has_timestop),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Oxygen recovery room', 1337010),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Secret room', 1337011, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Double jump cave platform', 1337012, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Secret room', 1337011, logic.can_break_walls),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Double jump cave platform', 1337012, logic.has_doublejump),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Double jump cave floor', 1337013),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Sparrow chest', 1337014),
LocationData('Upper lake desolation', 'Lake Desolation (Upper): Crash site pedestal', 1337015),
@ -41,9 +48,9 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData('Library', 'Library: Warp gate', 1337020),
LocationData('Library', 'Library: Librarian', 1337021),
LocationData('Library', 'Library: Reading nook chest', 1337022),
LocationData('Library', 'Library: Storage room chest 1', 1337023, lambda state: state._timespinner_has_keycard_D(world, player)),
LocationData('Library', 'Library: Storage room chest 2', 1337024, lambda state: state._timespinner_has_keycard_D(world, player)),
LocationData('Library', 'Library: Storage room chest 3', 1337025, lambda state: state._timespinner_has_keycard_D(world, player)),
LocationData('Library', 'Library: Storage room chest 1', 1337023, logic.has_keycard_D),
LocationData('Library', 'Library: Storage room chest 2', 1337024, logic.has_keycard_D),
LocationData('Library', 'Library: Storage room chest 3', 1337025, logic.has_keycard_D),
LocationData('Library top', 'Library: Backer room chest 5', 1337026),
LocationData('Library top', 'Library: Backer room chest 4', 1337027),
LocationData('Library top', 'Library: Backer room chest 3', 1337028),
@ -51,59 +58,60 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData('Library top', 'Library: Backer room chest 1', 1337030),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Elevator Key not required', 1337031),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Ye olde Timespinner', 1337032),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Bottom floor', 1337033, lambda state: state._timespinner_has_keycard_C(world, player)),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Air vents secret', 1337034, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Bottom floor', 1337033, logic.has_keycard_C),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Air vents secret', 1337034, logic.can_break_walls),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Elevator chest', 1337035, lambda state: state.has('Elevator Keycard', player)),
LocationData('Varndagroth tower right (upper)', 'Varndagroth Towers: Bridge', 1337036),
LocationData('Varndagroth tower right (elevator)', 'Varndagroth Towers (Right): Elevator chest', 1337037),
LocationData('Varndagroth tower right (upper)', 'Varndagroth Towers (Right): Elevator card chest', 1337038, lambda state: state.has('Elevator Keycard', player) or state._timespinner_has_doublejump(world, player)),
LocationData('Varndagroth tower right (upper)', 'Varndagroth Towers (Right): Air vents right chest', 1337039, lambda state: state.has('Elevator Keycard', player) or state._timespinner_has_doublejump(world, player)),
LocationData('Varndagroth tower right (upper)', 'Varndagroth Towers (Right): Air vents left chest', 1337040, lambda state: state.has('Elevator Keycard', player) or state._timespinner_has_doublejump(world, player)),
LocationData('Varndagroth tower right (upper)', 'Varndagroth Towers (Right): Elevator card chest', 1337038, lambda state: state.has('Elevator Keycard', player) or logic.has_doublejump(state)),
LocationData('Varndagroth tower right (upper)', 'Varndagroth Towers (Right): Air vents right chest', 1337039, lambda state: state.has('Elevator Keycard', player) or logic.has_doublejump(state)),
LocationData('Varndagroth tower right (upper)', 'Varndagroth Towers (Right): Air vents left chest', 1337040, lambda state: state.has('Elevator Keycard', player) or logic.has_doublejump(state)),
LocationData('Varndagroth tower right (lower)', 'Varndagroth Towers (Right): Bottom floor', 1337041),
LocationData('Varndagroth tower right (elevator)', 'Varndagroth Towers (Right): Varndagroth', 1337042, lambda state: state._timespinner_has_keycard_C(world, player)),
LocationData('Varndagroth tower right (elevator)', 'Varndagroth Towers (Right): Spider Hell', 1337043, lambda state: state._timespinner_has_keycard_A(world, player)),
LocationData('Varndagroth tower right (elevator)', 'Varndagroth Towers (Right): Varndagroth', 1337042, logic.has_keycard_C),
LocationData('Varndagroth tower right (elevator)', 'Varndagroth Towers (Right): Spider Hell', 1337043, logic.has_keycard_A),
LocationData('Skeleton Shaft', 'Sealed Caves (Xarion): Skeleton', 1337044),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Shroom jump room', 1337045, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Shroom jump room', 1337045, logic.has_timestop),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Double shroom room', 1337046),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Mini jackpot room', 1337047, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Mini jackpot room', 1337047, logic.has_forwarddash_doublejump),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Below mini jackpot room', 1337048),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Secret room', 1337049, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Secret room', 1337049, logic.can_break_walls),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Bottom left room', 1337050),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Last chance before Xarion', 1337051, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Xarion', 1337052),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Last chance before Xarion', 1337051, logic.has_doublejump),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Xarion', 1337052, lambda state: state.has('Water Mask', player) if flooded.flood_xarion else True),
LocationData('Sealed Caves (Sirens)', 'Sealed Caves (Sirens): Water hook', 1337053, lambda state: state.has('Water Mask', player)),
LocationData('Sealed Caves (Sirens)', 'Sealed Caves (Sirens): Siren room underwater right', 1337054, lambda state: state.has('Water Mask', player)),
LocationData('Sealed Caves (Sirens)', 'Sealed Caves (Sirens): Siren room underwater left', 1337055, lambda state: state.has('Water Mask', player)),
LocationData('Sealed Caves (Sirens)', 'Sealed Caves (Sirens): Cave after sirens chest 1', 1337056),
LocationData('Sealed Caves (Sirens)', 'Sealed Caves (Sirens): Cave after sirens chest 2', 1337057),
LocationData('Military Fortress', 'Military Fortress: Bomber chest', 1337058, lambda state: state.has('Timespinner Wheel', player) and state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Military Fortress', 'Military Fortress: Bomber chest', 1337058, lambda state: state.has('Timespinner Wheel', player) and logic.has_doublejump_of_npc(state)),
LocationData('Military Fortress', 'Military Fortress: Close combat room', 1337059),
LocationData('Military Fortress (hangar)', 'Military Fortress: Soldiers bridge', 1337060),
LocationData('Military Fortress (hangar)', 'Military Fortress: Giantess room', 1337061),
LocationData('Military Fortress (hangar)', 'Military Fortress: Giantess bridge', 1337062),
LocationData('Military Fortress (hangar)', 'Military Fortress: B door chest 2', 1337063, lambda state: state._timespinner_has_doublejump(world, player) and state._timespinner_has_keycard_B(world, player)),
LocationData('Military Fortress (hangar)', 'Military Fortress: B door chest 1', 1337064, lambda state: state._timespinner_has_doublejump(world, player) and state._timespinner_has_keycard_B(world, player)),
LocationData('Military Fortress (hangar)', 'Military Fortress: Pedestal', 1337065, lambda state: state._timespinner_has_doublejump_of_npc(world, player) or state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Military Fortress (hangar)', 'Military Fortress: B door chest 2', 1337063, lambda state: logic.has_doublejump(state) and logic.has_keycard_B(state)),
LocationData('Military Fortress (hangar)', 'Military Fortress: B door chest 1', 1337064, lambda state: logic.has_doublejump(state) and logic.has_keycard_B(state)),
LocationData('Military Fortress (hangar)', 'Military Fortress: Pedestal', 1337065, lambda state: 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, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('The lab', 'Lab: Lower trash left', 1337068, lambda state: state._timespinner_has_upwarddash(world, player)),
LocationData('The lab', 'Lab: Below lab entrance', 1337069, lambda state: state._timespinner_has_doublejump(world, player)),
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: 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 (upper)', 'Lab: Genza (Blob Mom)', 1337072),
LocationData('The lab (power off)', 'Lab: Experiment #13', 1337073),
LocationData('The lab (upper)', 'Lab: Download and chest room chest', 1337074),
LocationData('The lab (upper)', 'Lab: 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('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('Emperors tower', 'Emperor\'s Tower: Courtyard bottom chest', 1337077),
LocationData('Emperors tower', 'Emperor\'s Tower: Courtyard floor secret', 1337078, lambda state: state._timespinner_has_upwarddash(world, player) and state._timespinner_can_break_walls(world, player)),
LocationData('Emperors tower', 'Emperor\'s Tower: Courtyard upper chest', 1337079, lambda state: state._timespinner_has_upwarddash(world, player)),
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: Galactic sage room', 1337080),
LocationData('Emperors tower', 'Emperor\'s Tower: Bottom right tower', 1337081),
LocationData('Emperors tower', 'Emperor\'s Tower: Wayyyy up there', 1337082, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Emperors tower', 'Emperor\'s Tower: Wayyyy up there', 1337082, logic.has_doublejump_of_npc),
LocationData('Emperors tower', 'Emperor\'s Tower: Left tower balcony', 1337083),
LocationData('Emperors tower', 'Emperor\'s Tower: Emperor\'s Chambers chest', 1337084),
LocationData('Emperors tower', 'Emperor\'s Tower: Emperor\'s Chambers pedestal', 1337085),
LocationData('Emperors tower', 'Killed Emperor', EventId),
# Past item locations
LocationData('Refugee Camp', 'Refugee Camp: Neliste\'s Bra', 1337086),
@ -111,18 +119,18 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData('Refugee Camp', 'Refugee Camp: Storage chest 2', 1337088),
LocationData('Refugee Camp', 'Refugee Camp: Storage chest 1', 1337089),
LocationData('Forest', 'Forest: Refugee camp roof', 1337090),
LocationData('Forest', 'Forest: Bat jump ledge', 1337091, lambda state: state._timespinner_has_doublejump_of_npc(world, player) or state._timespinner_has_forwarddash_doublejump(world, player) or state._timespinner_has_fastjump_on_npc(world, player)),
LocationData('Forest', 'Forest: Green platform secret', 1337092, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Forest', 'Forest: Bat jump ledge', 1337091, lambda state: logic.has_doublejump_of_npc(state) or logic.has_forwarddash_doublejump(state) or logic.has_fastjump_on_npc(state)),
LocationData('Forest', 'Forest: Green platform secret', 1337092, logic.can_break_walls),
LocationData('Forest', 'Forest: Rats guarded chest', 1337093),
LocationData('Forest', 'Forest: Waterfall chest 1', 1337094, lambda state: state.has('Water Mask', player)),
LocationData('Forest', 'Forest: Waterfall chest 2', 1337095, lambda state: state.has('Water Mask', player)),
LocationData('Forest', 'Forest: Batcave', 1337096),
LocationData('Forest', 'Castle Ramparts: In the moat', 1337097),
LocationData('Forest', 'Castle Ramparts: In the moat', 1337097, lambda state: state.has('Water Mask', player) if flooded.flood_moat else True),
LocationData('Left Side forest Caves', 'Forest: Before Serene single bat cave', 1337098),
LocationData('Upper Lake Serene', 'Lake Serene (Upper): Rat nest', 1337099),
LocationData('Upper Lake Serene', 'Lake Serene (Upper): Double jump cave platform', 1337100, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Upper Lake Serene', 'Lake Serene (Upper): Double jump cave platform', 1337100, logic.has_doublejump),
LocationData('Upper Lake Serene', 'Lake Serene (Upper): Double jump cave floor', 1337101),
LocationData('Upper Lake Serene', 'Lake Serene (Upper): Cave secret', 1337102, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Upper Lake Serene', 'Lake Serene (Upper): Cave secret', 1337102, logic.can_break_walls),
LocationData('Upper Lake Serene', 'Lake Serene: Before Big Bird', 1337175),
LocationData('Upper Lake Serene', 'Lake Serene: Behind the vines', 1337103),
LocationData('Upper Lake Serene', 'Lake Serene: Pyramid keys room', 1337104),
@ -130,68 +138,68 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Deep dive', 1337105),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Under the eels', 1337106),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Water spikes room', 1337107),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater secret', 1337108, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): T chest', 1337109),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater secret', 1337108, logic.can_break_walls),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): T chest', 1337109, lambda state: logic.has_doublejump_of_npc(state) if flooded.dry_lake_serene else True),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Past the eels', 1337110),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater pedestal', 1337111),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Shroom jump room', 1337112, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Secret room', 1337113),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Bottom left room', 1337114),
LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater pedestal', 1337111, lambda state: logic.has_doublejump(state) if flooded.dry_lake_serene else True),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Shroom jump room', 1337112, lambda state: logic.has_doublejump(state) if not flooded.flood_maw else True),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Secret room', 1337113, lambda state: logic.can_break_walls(state) and (state.has('Water Mask', player) if flooded.flood_maw else True)),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Bottom left room', 1337114, lambda state: state.has('Water Mask', player) if flooded.flood_maw else True),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Single shroom room', 1337115),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 1', 1337116, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 2', 1337117, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 3', 1337118, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 4', 1337119, lambda state: state._timespinner_has_forwarddash_doublejump(world, player)),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Pedestal', 1337120),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Last chance before Maw', 1337121, lambda state: state._timespinner_has_doublejump(world, player)),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Plasma Crystal', 1337173, lambda state: state.has_any({'Gas Mask', 'Talaria Attachment'}, player)),
LocationData('Caves of Banishment (Maw)', 'Killed Maw', EventId, lambda state: state.has('Gas Mask', player)),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Mineshaft', 1337122, lambda state: state.has('Gas Mask', player)),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 1', 1337116, lambda state: logic.has_forwarddash_doublejump(state) or flooded.flood_maw),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 2', 1337117, lambda state: logic.has_forwarddash_doublejump(state) or flooded.flood_maw),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 3', 1337118, lambda state: logic.has_forwarddash_doublejump(state) or flooded.flood_maw),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 4', 1337119, lambda state: logic.has_forwarddash_doublejump(state) or flooded.flood_maw),
LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Pedestal', 1337120, lambda state: state.has('Water Mask', player) if flooded.flood_maw else True),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Last chance before Maw', 1337121, lambda state: state.has('Water Mask', player) if flooded.flood_maw else logic.has_doublejump(state)),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Plasma Crystal', 1337173, lambda state: state.has_any({'Gas Mask', 'Talaria Attachment'}, player) and (state.has('Water Mask', player) if flooded.flood_maw else True)),
LocationData('Caves of Banishment (Maw)', 'Killed Maw', EventId, lambda state: state.has('Gas Mask', player) and (state.has('Water Mask', player) if flooded.flood_maw else True)),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Mineshaft', 1337122, lambda state: state.has_any({'Gas Mask', 'Talaria Attachment'}, player) and (state.has('Water Mask', player) if flooded.flood_maw else True)),
LocationData('Caves of Banishment (Sirens)', 'Caves of Banishment (Sirens): Wyvern room', 1337123),
LocationData('Caves of Banishment (Sirens)', 'Caves of Banishment (Sirens): Siren room above water chest', 1337124),
LocationData('Caves of Banishment (Sirens)', 'Caves of Banishment (Sirens): Siren room underwater left chest', 1337125, lambda state: state.has('Water Mask', player)),
LocationData('Caves of Banishment (Sirens)', 'Caves of Banishment (Sirens): Siren room underwater right chest', 1337126, lambda state: state.has('Water Mask', player)),
LocationData('Caves of Banishment (Sirens)', 'Caves of Banishment (Sirens): Siren room underwater right ground', 1337172, lambda state: state.has('Water Mask', player)),
LocationData('Caves of Banishment (Sirens)', 'Caves of Banishment (Sirens): Water hook', 1337127, lambda state: state.has('Water Mask', player)),
LocationData('Castle Ramparts', 'Castle Ramparts: Bomber chest', 1337128, lambda state: state._timespinner_has_multiple_small_jumps_of_npc(world, player)),
LocationData('Castle Ramparts', 'Castle Ramparts: Freeze the engineer', 1337129, lambda state: state.has('Talaria Attachment', player) or state._timespinner_has_timestop(world, player)),
LocationData('Castle Ramparts', 'Castle Ramparts: Bomber chest', 1337128, logic.has_multiple_small_jumps_of_npc),
LocationData('Castle Ramparts', 'Castle Ramparts: Freeze the engineer', 1337129, lambda state: state.has('Talaria Attachment', player) or logic.has_timestop(state)),
LocationData('Castle Ramparts', 'Castle Ramparts: Giantess guarded room', 1337130),
LocationData('Castle Ramparts', 'Castle Ramparts: Knight and archer guarded room', 1337131),
LocationData('Castle Ramparts', 'Castle Ramparts: Pedestal', 1337132),
LocationData('Castle Keep', 'Castle Keep: Basement secret pedestal', 1337133, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Castle Keep', 'Castle Keep: Clean the castle basement', 1337134),
LocationData('Royal towers (lower)', 'Castle Keep: Yas queen room', 1337135, lambda state: state._timespinner_has_pink(world, player)),
LocationData('Castle Keep', 'Castle Keep: Giantess guarded chest', 1337136),
LocationData('Castle Keep', 'Castle Keep: Omelette chest', 1337137),
LocationData('Castle Keep', 'Castle Keep: Just an egg', 1337138),
LocationData('Castle Basement', 'Castle Basement: Secret pedestal', 1337133, logic.can_break_walls),
LocationData('Castle Basement', 'Castle Basement: Clean the castle basement', 1337134),
LocationData('Royal towers (lower)', 'Castle Keep: Yas queen room', 1337135, logic.has_pink),
LocationData('Castle Basement', 'Castle Basement: Giantess guarded chest', 1337136),
LocationData('Castle Basement', 'Castle Basement: Omelette chest', 1337137),
LocationData('Castle Basement', 'Castle Basement: Just an egg', 1337138),
LocationData('Castle Keep', 'Castle Keep: Under the twins', 1337139),
LocationData('Castle Keep', 'Killed Twins', EventId, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Castle Keep', 'Castle Keep: Advisor jump', 1337171, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Castle Keep', 'Castle Keep: Twins', 1337140, lambda state: state._timespinner_has_timestop(world, player)),
LocationData('Castle Keep', 'Castle Keep: Royal guard tiny room', 1337141, lambda state: state._timespinner_has_doublejump(world, player) or state._timespinner_has_fastjump_on_npc(world,player)),
LocationData('Royal towers (lower)', 'Royal Towers: Floor secret', 1337142, lambda state: state._timespinner_has_doublejump(world, player) and state._timespinner_can_break_walls(world, player)),
LocationData('Castle Keep', 'Killed Twins', EventId, logic.has_timestop),
LocationData('Castle Keep', 'Castle Keep: Advisor jump', 1337171, logic.has_timestop),
LocationData('Castle Keep', 'Castle Keep: Twins', 1337140, logic.has_timestop),
LocationData('Castle Keep', 'Castle Keep: Royal guard tiny room', 1337141, lambda state: logic.has_doublejump(state) or logic.has_fastjump_on_npc(state)),
LocationData('Royal towers (lower)', 'Royal Towers: Floor secret', 1337142, lambda state: logic.has_doublejump(state) and logic.can_break_walls(state)),
LocationData('Royal towers', 'Royal Towers: Pre-climb gap', 1337143),
LocationData('Royal towers', 'Royal Towers: Long balcony', 1337144),
LocationData('Royal towers (upper)', 'Royal Towers: Past bottom struggle juggle', 1337145),
LocationData('Royal towers (upper)', 'Royal Towers: Bottom struggle juggle', 1337146, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Royal towers (upper)', 'Royal Towers: Top struggle juggle', 1337147, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Royal towers (upper)', 'Royal Towers: No struggle required', 1337148, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Royal towers', 'Royal Towers: Long balcony', 1337144, lambda state: state.has('Water Mask', player) if flooded.flood_courtyard else True),
LocationData('Royal towers', 'Royal Towers: Past bottom struggle juggle', 1337145, lambda state: logic.has_doublejump_of_npc(state) if not flooded.flood_courtyard else True),
LocationData('Royal towers', 'Royal Towers: Bottom struggle juggle', 1337146, logic.has_doublejump_of_npc),
LocationData('Royal towers (upper)', 'Royal Towers: Top struggle juggle', 1337147, logic.has_doublejump_of_npc),
LocationData('Royal towers (upper)', 'Royal Towers: No struggle required', 1337148, logic.has_doublejump_of_npc),
LocationData('Royal towers', 'Royal Towers: Right tower freebie', 1337149),
LocationData('Royal towers (upper)', 'Royal Towers: Left tower small balcony', 1337150),
LocationData('Royal towers (upper)', 'Royal Towers: Left tower royal guard', 1337151),
LocationData('Royal towers (upper)', 'Royal Towers: Before Aelana', 1337152),
LocationData('Royal towers (upper)', 'Killed Aelana', EventId),
LocationData('Royal towers (upper)', 'Royal Towers: Aelana\'s attic', 1337153, lambda state: state._timespinner_has_upwarddash(world, player)),
LocationData('Royal towers (upper)', 'Royal Towers: Aelana\'s attic', 1337153, logic.has_upwarddash),
LocationData('Royal towers (upper)', 'Royal Towers: Aelana\'s chest', 1337154),
LocationData('Royal towers (upper)', 'Royal Towers: Aelana\'s pedestal', 1337155),
# Ancient pyramid locations
LocationData('Ancient Pyramid (entrance)', 'Ancient Pyramid: Why not it\'s right there', 1337246),
LocationData('Ancient Pyramid (left)', 'Ancient Pyramid: Conviction guarded room', 1337247),
LocationData('Ancient Pyramid (left)', 'Ancient Pyramid: Pit secret room', 1337248, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Ancient Pyramid (left)', 'Ancient Pyramid: Regret chest', 1337249, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Ancient Pyramid (right)', 'Ancient Pyramid: Nightmare Door chest', 1337236),
LocationData('Ancient Pyramid (right)', 'Killed Nightmare', EventId, lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player))
LocationData('Ancient Pyramid (left)', 'Ancient Pyramid: Pit secret room', 1337248, lambda state: logic.can_break_walls(state) and (state.has('Water Mask', player) if flooded.flood_pyramid_shaft else True)),
LocationData('Ancient Pyramid (left)', 'Ancient Pyramid: Regret chest', 1337249, lambda state: logic.can_break_walls(state) and (state.has('Water Mask', player) if flooded.flood_pyramid_shaft else True)),
LocationData('Ancient Pyramid (right)', 'Ancient Pyramid: Nightmare Door chest', 1337236, lambda state: state.has('Water Mask', player) if flooded.flood_pyramid_back else True),
LocationData('Ancient Pyramid (right)', 'Killed Nightmare', EventId, lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player) and (state.has('Water Mask', player) if flooded.flood_pyramid_back else True))
]
# 1337156 - 1337170 Downloads
@ -205,7 +213,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData('Library', 'Library: V terminal 2 (Lake Desolation Map)', 1337161, lambda state: state.has_all({'Tablet', 'Library Keycard V'}, player)),
LocationData('Library', 'Library: V terminal 3 (Vilete)', 1337162, lambda state: state.has_all({'Tablet', 'Library Keycard V'}, 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 state._timespinner_has_keycard_B(world, 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)),
@ -228,23 +236,23 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData('Library top', 'Library: Memory - Library Gap (Lachiemi Sun)', 1337179),
LocationData('Library top', 'Library: Memory - Mr. Hat Portrait (Moonlit Night)', 1337180),
LocationData('Varndagroth tower left', 'Varndagroth Towers (Left): Memory - Elevator (Nomads)', 1337181, lambda state: state.has('Elevator Keycard', player)),
LocationData('Varndagroth tower right (lower)', 'Varndagroth Towers: Memory - Siren Elevator (Childhood)', 1337182, lambda state: state._timespinner_has_keycard_B(world, player)),
LocationData('Varndagroth tower right (lower)', 'Varndagroth Towers: Memory - Siren Elevator (Childhood)', 1337182, logic.has_keycard_B),
LocationData('Varndagroth tower right (lower)', 'Varndagroth Towers (Right): Memory - Bottom (Faron)', 1337183),
LocationData('Military Fortress', 'Military Fortress: Memory - Bomber Climb (A Solution)', 1337184, lambda state: state.has('Timespinner Wheel', player) and state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('The lab', 'Lab: Memory - Genza\'s Secret Stash 1 (An Old Friend)', 1337185, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('The lab', 'Lab: Memory - Genza\'s Secret Stash 2 (Twilight Dinner)', 1337186, lambda state: state._timespinner_can_break_walls(world, player)),
LocationData('Emperors tower', 'Emperor\'s Tower: Memory - Way Up There (Final Circle)', 1337187, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Military Fortress', 'Military Fortress: Memory - Bomber Climb (A Solution)', 1337184, lambda state: state.has('Timespinner Wheel', player) and logic.has_doublejump_of_npc(state)),
LocationData('The lab', 'Lab: Memory - Genza\'s Secret Stash 1 (An Old Friend)', 1337185, logic.can_break_walls),
LocationData('The lab', 'Lab: Memory - Genza\'s Secret Stash 2 (Twilight Dinner)', 1337186, logic.can_break_walls),
LocationData('Emperors tower', 'Emperor\'s Tower: Memory - Way Up There (Final Circle)', 1337187, logic.has_doublejump_of_npc),
LocationData('Forest', 'Forest: Journal - Rats (Lachiem Expedition)', 1337188),
LocationData('Forest', 'Forest: Journal - Bat Jump Ledge (Peace Treaty)', 1337189, lambda state: state._timespinner_has_doublejump_of_npc(world, player) or state._timespinner_has_forwarddash_doublejump(world, player) or state._timespinner_has_fastjump_on_npc(world, player)),
LocationData('Castle Ramparts', 'Castle Ramparts: Journal - Floating in Moat (Prime Edicts)', 1337190),
LocationData('Forest', 'Forest: Journal - Bat Jump Ledge (Peace Treaty)', 1337189, lambda state: logic.has_doublejump_of_npc(state) or logic.has_forwarddash_doublejump(state) or logic.has_fastjump_on_npc(state)),
LocationData('Forest', 'Forest: Journal - Floating in Moat (Prime Edicts)', 1337190, lambda state: state.has('Water Mask', player) if flooded.flood_moat else True),
LocationData('Castle Ramparts', 'Castle Ramparts: Journal - Archer + Knight (Declaration of Independence)', 1337191),
LocationData('Castle Keep', 'Castle Keep: Journal - Under the Twins (Letter of Reference)', 1337192),
LocationData('Castle Keep', 'Castle Keep: Journal - Castle Loop Giantess (Political Advice)', 1337193),
LocationData('Royal towers (lower)', 'Royal Towers: Journal - Aelana\'s Room (Diplomatic Missive)', 1337194, lambda state: state._timespinner_has_pink(world, player)),
LocationData('Castle Basement', 'Castle Basement: Journal - Castle Loop Giantess (Political Advice)', 1337193),
LocationData('Royal towers (lower)', 'Royal Towers: Journal - Aelana\'s Room (Diplomatic Missive)', 1337194, logic.has_pink),
LocationData('Royal towers (upper)', 'Royal Towers: Journal - Top Struggle Juggle Base (War of the Sisters)', 1337195),
LocationData('Royal towers (upper)', 'Royal Towers: Journal - Aelana Boss (Stained Letter)', 1337196),
LocationData('Royal towers', 'Royal Towers: Journal - Near Bottom Struggle Juggle (Mission Findings)', 1337197, lambda state: state._timespinner_has_doublejump_of_npc(world, player)),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Journal - Lower Left Caves (Naivety)', 1337198)
LocationData('Royal towers', 'Royal Towers: Journal - Near Bottom Struggle Juggle (Mission Findings)', 1337197, lambda state: logic.has_doublejump_of_npc(state) if not flooded.flood_courtyard else True),
LocationData('Caves of Banishment (Maw)', 'Caves of Banishment (Maw): Journal - Lower Left Caves (Naivety)', 1337198, lambda state: state.has('Water Mask', player) if flooded.flood_maw else True)
)
# 1337199 - 1337236 Reserved for future use
@ -264,11 +272,3 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
)
return tuple(location_table)
starter_progression_locations: Tuple[str, ...] = (
'Lake Desolation: Starter chest 2',
'Lake Desolation: Starter chest 3',
'Lake Desolation: Starter chest 1',
'Lake Desolation (Lower): Timespinner Wheel room'
)

View File

@ -0,0 +1,110 @@
from typing import Union
from BaseClasses import MultiWorld, CollectionState
from .Options import is_option_enabled
from .PreCalculatedWeights import PreCalculatedWeights
class TimespinnerLogic:
player: int
flag_unchained_keys: bool
flag_eye_spy: bool
flag_specific_keycards: bool
pyramid_keys_unlock: Union[str, None]
present_keys_unlock: Union[str, None]
past_keys_unlock: Union[str, None]
time_keys_unlock: Union[str, None]
def __init__(self, world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights):
self.player = player
self.flag_specific_keycards = is_option_enabled(world, player, "SpecificKeycards")
self.flag_eye_spy = is_option_enabled(world, player, "EyeSpy")
self.flag_unchained_keys = is_option_enabled(world, player, "UnchainedKeys")
if precalculated_weights:
if self.flag_unchained_keys:
self.pyramid_keys_unlock = None
self.present_keys_unlock = precalculated_weights.present_key_unlock
self.past_keys_unlock = precalculated_weights.past_key_unlock
self.time_keys_unlock = precalculated_weights.time_key_unlock
else:
self.pyramid_keys_unlock = precalculated_weights.pyramid_keys_unlock
self.present_keys_unlock = None
self.past_keys_unlock = None
self.time_keys_unlock = None
def has_timestop(self, state: CollectionState) -> bool:
return state.has_any({'Timespinner Wheel', 'Succubus Hairpin', 'Lightwall', 'Celestial Sash'}, self.player)
def has_doublejump(self, state: CollectionState) -> bool:
return state.has_any({'Succubus Hairpin', 'Lightwall', 'Celestial Sash'}, self.player)
def has_forwarddash_doublejump(self, state: CollectionState) -> bool:
return self.has_upwarddash(state) \
or (state.has('Talaria Attachment', self.player) and self.has_doublejump(state))
def has_doublejump_of_npc(self, state: CollectionState) -> bool:
return self.has_upwarddash(state) \
or (state.has('Timespinner Wheel', self.player) and self.has_doublejump(state))
def has_fastjump_on_npc(self, state: CollectionState) -> bool:
return state.has_all({'Timespinner Wheel', 'Talaria Attachment'}, self.player)
def has_multiple_small_jumps_of_npc(self, state: CollectionState) -> bool:
return state.has('Timespinner Wheel', self.player) or self.has_upwarddash(state)
def has_upwarddash(self, state: CollectionState) -> bool:
return state.has_any({'Lightwall', 'Celestial Sash'}, self.player)
def has_fire(self, state: CollectionState) -> bool:
return state.has_any({'Fire Orb', 'Infernal Flames', 'Pyro Ring', 'Djinn Inferno'}, self.player)
def has_pink(self, state: CollectionState) -> bool:
return state.has_any({'Plasma Orb', 'Plasma Geyser', 'Royal Ring'}, self.player)
def has_keycard_A(self, state: CollectionState) -> bool:
return state.has('Security Keycard A', self.player)
def has_keycard_B(self, state: CollectionState) -> bool:
if self.flag_specific_keycards:
return state.has('Security Keycard B', self.player)
else:
return state.has_any({'Security Keycard A', 'Security Keycard B'}, self.player)
def has_keycard_C(self, state: CollectionState) -> bool:
if self.flag_specific_keycards:
return state.has('Security Keycard C', self.player)
else:
return state.has_any({'Security Keycard A', 'Security Keycard B', 'Security Keycard C'}, self.player)
def has_keycard_D(self, state: CollectionState) -> bool:
if self.flag_specific_keycards:
return state.has('Security Keycard D', self.player)
else:
return state.has_any({'Security Keycard A', 'Security Keycard B', 'Security Keycard C', 'Security Keycard D'}, self.player)
def can_break_walls(self, state: CollectionState) -> bool:
if self.flag_eye_spy:
return state.has('Oculus Ring', self.player)
else:
return True
def can_kill_all_3_bosses(self, state: CollectionState) -> bool:
return state.has_all({'Killed Maw', 'Killed Twins', 'Killed Aelana'}, self.player)
def has_teleport(self, state: CollectionState) -> bool:
return self.flag_unchained_keys or state.has('Twin Pyramid Key', self.player)
def can_teleport_to(self, state: CollectionState, era: str, gate: str) -> bool:
if not self.flag_unchained_keys:
return self.pyramid_keys_unlock == gate
if era == "Present":
return self.present_keys_unlock == gate and state.has("Modern Warp Beacon", self.player)
elif era == "Past":
return self.past_keys_unlock == gate and state.has("Timeworn Warp Beacon", self.player)
elif era == "Time":
return self.time_keys_unlock == gate and state.has("Mysterious Warp Beacon", self.player)
else:
raise Exception("Invallid Era: {}".format(era))

View File

@ -1,61 +0,0 @@
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_fastjump_on_npc(self, world: MultiWorld, player: int) -> bool:
return self.has_all({'Timespinner Wheel', 'Talaria Attachment'}, 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, "EyeSpy"):
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

@ -1,6 +1,6 @@
from typing import Dict, Union
from typing import Dict, Union, List
from BaseClasses import MultiWorld
from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict
from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict, OptionList
from schema import Schema, And, Optional
@ -191,6 +191,22 @@ class HpCap(Range):
default = 999
class LevelCap(Range):
"""Sets the max level Lunais can achieve."""
display_name = "Level Cap"
range_start = 1
range_end = 99
default = 99
class ExtraEarringsXP(Range):
"""Adds additional XP granted by Galaxy Earrings."""
display_name = "Extra Earrings XP"
range_start = 0
range_end = 24
default = 0
class BossHealing(DefaultOnToggle):
"Enables/disables healing after boss fights. NOTE: Currently only applicable when Boss Rando is enabled."
display_name = "Heal After Bosses"
@ -282,6 +298,94 @@ class EnterSandman(Toggle):
display_name = "Enter Sandman"
class DadPercent(Toggle):
"""The win condition is beating the boss of Emperor's Tower"""
display_name = "Dad Percent"
class RisingTides(Toggle):
"""Random areas are flooded or drained, can be further specified with RisingTidesOverrides"""
display_name = "Rising Tides"
class RisingTidesOverrides(OptionDict):
"""Odds for specific areas to be flooded or drained, only has effect when RisingTides is on.
Areas that are not specified will roll with the default 33% chance of getting flooded or drained"""
schema = Schema({
Optional("Xarion"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("Maw"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("AncientPyramidShaft"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("Sandman"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("CastleMoat"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("CastleBasement"): {
"Dry": And(int, lambda n: n >= 0),
"FloodedWithSavePointAvailable": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("CastleCourtyard"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("LakeDesolation"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("LakeSerene"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
}
})
display_name = "Rising Tides Overrides"
default = {
"Xarion": { "Dry": 67, "Flooded": 33 },
"Maw": { "Dry": 67, "Flooded": 33 },
"AncientPyramidShaft": { "Dry": 67, "Flooded": 33 },
"Sandman": { "Dry": 67, "Flooded": 33 },
"CastleMoat": { "Dry": 67, "Flooded": 33 },
"CastleBasement": { "Dry": 66, "Flooded": 17, "FloodedWithSavePointAvailable": 17 },
"CastleCourtyard": { "Dry": 67, "Flooded": 33 },
"LakeDesolation": { "Dry": 67, "Flooded": 33 },
"LakeSerene": { "Dry": 67, "Flooded": 33 },
}
class UnchainedKeys(Toggle):
"""Start with Twin Pyramid Key, which does not give free warp;
warp items for Past, Present, (and ??? with Enter Sandman) can be found."""
display_name = "Unchained Keys"
class TrapChance(Range):
"""Chance of traps in the item pool.
Traps will only replace filler items such as potions, vials and antidotes"""
display_name = "Trap Chance"
range_start = 0
range_end = 100
default = 10
class Traps(OptionList):
"""List of traps that may be in the item pool to find"""
display_name = "Traps Types"
valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" }
default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" ]
# Some options that are available in the timespinner randomizer arent currently implemented
timespinner_options: Dict[str, Option] = {
"StartWithJewelryBox": StartWithJewelryBox,
@ -299,6 +403,8 @@ timespinner_options: Dict[str, Option] = {
"DamageRando": DamageRando,
"DamageRandoOverrides": DamageRandoOverrides,
"HpCap": HpCap,
"LevelCap": LevelCap,
"ExtraEarringsXP": ExtraEarringsXP,
"BossHealing": BossHealing,
"ShopFill": ShopFill,
"ShopWarpShards": ShopWarpShards,
@ -310,6 +416,12 @@ timespinner_options: Dict[str, Option] = {
"ShowBestiary": ShowBestiary,
"ShowDrops": ShowDrops,
"EnterSandman": EnterSandman,
"DadPercent": DadPercent,
"RisingTides": RisingTides,
"RisingTidesOverrides": RisingTidesOverrides,
"UnchainedKeys": UnchainedKeys,
"TrapChance": TrapChance,
"Traps": Traps,
"DeathLink": DeathLink,
}
@ -318,7 +430,7 @@ def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool:
return get_option_value(world, player, name) > 0
def get_option_value(world: MultiWorld, player: int, name: str) -> Union[int, dict]:
def get_option_value(world: MultiWorld, player: int, name: str) -> Union[int, Dict, List]:
option = getattr(world, name, None)
if option == None:
return 0

View File

@ -0,0 +1,126 @@
from typing import Tuple, Dict, Union
from BaseClasses import MultiWorld
from .Options import is_option_enabled, get_option_value
class PreCalculatedWeights:
pyramid_keys_unlock: str
present_key_unlock: str
past_key_unlock: str
time_key_unlock: str
flood_basement: bool
flood_basement_high: bool
flood_xarion: bool
flood_maw: bool
flood_pyramid_shaft: bool
flood_pyramid_back: bool
flood_moat: bool
flood_courtyard: bool
flood_lake_desolation: bool
dry_lake_serene: bool
def __init__(self, world: MultiWorld, player: int):
weights_overrrides: Dict[str, Dict[str, int]] = self.get_flood_weights_overrides(world, player)
self.flood_basement, self.flood_basement_high = \
self.roll_flood_setting_with_available_save(world, player, weights_overrrides, "CastleBasement")
self.flood_xarion = self.roll_flood_setting(world, player, weights_overrrides, "Xarion")
self.flood_maw = self.roll_flood_setting(world, player, weights_overrrides, "Maw")
self.flood_pyramid_shaft = self.roll_flood_setting(world, player, weights_overrrides, "AncientPyramidShaft")
self.flood_pyramid_back = self.roll_flood_setting(world, player, weights_overrrides, "Sandman")
self.flood_moat = self.roll_flood_setting(world, player, weights_overrrides, "CastleMoat")
self.flood_courtyard = self.roll_flood_setting(world, player, weights_overrrides, "CastleCourtyard")
self.flood_lake_desolation = self.roll_flood_setting(world, player, weights_overrrides, "LakeDesolation")
self.dry_lake_serene = self.roll_flood_setting(world, player, weights_overrrides, "LakeSerene")
self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \
self.get_pyramid_keys_unlock(world, player, self.flood_maw)
def get_pyramid_keys_unlock(self, world: MultiWorld, player: int, is_maw_flooded: bool) -> Tuple[str, str, str, str]:
present_teleportation_gates: Tuple[str, ...] = (
"GateKittyBoss",
"GateLeftLibrary",
"GateMilitaryGate",
"GateSealedCaves",
"GateSealedSirensCave",
"GateLakeDesolation"
)
past_teleportation_gates: Tuple[str, ...] = (
"GateLakeSereneRight",
"GateAccessToPast",
"GateCastleRamparts",
"GateCastleKeep",
"GateRoyalTowers",
"GateCavesOfBanishment"
)
ancient_pyramid_teleportation_gates: Tuple[str, ...] = (
"GateGyre",
"GateLeftPyramid",
"GateRightPyramid"
)
if not world:
return (
present_teleportation_gates[0],
present_teleportation_gates[0],
past_teleportation_gates[0],
ancient_pyramid_teleportation_gates[0]
)
if not is_maw_flooded:
past_teleportation_gates += ("GateMaw", )
if is_option_enabled(world, player, "Inverted"):
all_gates: Tuple[str, ...] = present_teleportation_gates
else:
all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates
return (
world.random.choice(all_gates),
world.random.choice(present_teleportation_gates),
world.random.choice(past_teleportation_gates),
world.random.choice(ancient_pyramid_teleportation_gates)
)
@staticmethod
def get_flood_weights_overrides( world: MultiWorld, player: int) -> Dict[str, int]:
weights_overrides_option: Union[int, Dict[str, Dict[str, int]]] = \
get_option_value(world, player, "RisingTidesOverrides")
if weights_overrides_option == 0:
return {}
else:
return weights_overrides_option
@staticmethod
def roll_flood_setting(world: MultiWorld, player: int, weights: Dict[str, Dict[str, int]], key: str) -> bool:
if not world or not is_option_enabled(world, player, "RisingTides"):
return False
weights = weights[key] if key in weights else { "Dry": 67, "Flooded": 33 }
result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
return result == "Flooded"
@staticmethod
def roll_flood_setting_with_available_save(world: MultiWorld, player: int,
weights: Dict[str, Dict[str, int]], key: str) -> Tuple[bool, bool]:
if not world or not is_option_enabled(world, player, "RisingTides"):
return False, False
weights = weights[key] if key in weights else {"Dry": 66, "Flooded": 17, "FloodedWithSavePointAvailable": 17}
result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
if result == "Dry":
return False, False
elif result == "Flooded":
return True, False
elif result == "FloodedWithSavePointAvailable":
return True, True

View File

@ -1,33 +0,0 @@
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)
if not world:
return gates[0]
return world.random.choice(gates)

View File

@ -1,10 +1,14 @@
from typing import List, Set, Dict, Tuple, Optional, Callable
from BaseClasses import MultiWorld, Region, Entrance, Location
from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location
from .Options import is_option_enabled
from .Locations import LocationData
from .PreCalculatedWeights import PreCalculatedWeights
from .LogicExtensions import TimespinnerLogic
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location], pyramid_keys_unlock: str):
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location],
precalculated_weights: PreCalculatedWeights):
locations_per_region = get_locations_per_region(locations)
regions = [
@ -16,7 +20,6 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
create_region(world, player, locations_per_region, location_cache, 'Eastern lake desolation'),
create_region(world, player, locations_per_region, location_cache, 'Library'),
create_region(world, player, locations_per_region, location_cache, 'Library top'),
create_region(world, player, locations_per_region, location_cache, 'Ifrit\'s Lair'),
create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower left'),
create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (upper)'),
create_region(world, player, locations_per_region, location_cache, 'Varndagroth tower right (lower)'),
@ -27,7 +30,6 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
create_region(world, player, locations_per_region, location_cache, 'The lab'),
create_region(world, player, locations_per_region, location_cache, 'The lab (power off)'),
create_region(world, player, locations_per_region, location_cache, 'The lab (upper)'),
create_region(world, player, locations_per_region, location_cache, 'Ravenlord\'s Lair'),
create_region(world, player, locations_per_region, location_cache, 'Emperors tower'),
create_region(world, player, locations_per_region, location_cache, 'Skeleton Shaft'),
create_region(world, player, locations_per_region, location_cache, 'Sealed Caves (upper)'),
@ -42,6 +44,7 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
create_region(world, player, locations_per_region, location_cache, 'Caves of Banishment (Sirens)'),
create_region(world, player, locations_per_region, location_cache, 'Castle Ramparts'),
create_region(world, player, locations_per_region, location_cache, 'Castle Keep'),
create_region(world, player, locations_per_region, location_cache, 'Castle Basement'),
create_region(world, player, locations_per_region, location_cache, 'Royal towers (lower)'),
create_region(world, player, locations_per_region, location_cache, 'Royal towers'),
create_region(world, player, locations_per_region, location_cache, 'Royal towers (upper)'),
@ -52,6 +55,12 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
create_region(world, player, locations_per_region, location_cache, 'Space time continuum')
]
if is_option_enabled(world, player, "GyreArchives"):
regions.extend([
create_region(world, player, locations_per_region, location_cache, 'Ravenlord\'s Lair'),
create_region(world, player, locations_per_region, location_cache, 'Ifrit\'s Lair'),
])
if __debug__:
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
@ -59,125 +68,133 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
connectStartingRegion(world, player)
flooded: PreCalculatedWeights = precalculated_weights
logic = TimespinnerLogic(world, player, precalculated_weights)
names: Dict[str, int] = {}
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 Serene', '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, 'Lake desolation', 'Lower lake desolation', lambda state: logic.has_timestop(state) or state.has('Talaria Attachment', player) or flooded.flood_lake_desolation)
connect(world, player, names, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player))
connect(world, player, names, 'Lake desolation', 'Skeleton Shaft', lambda state: logic.has_doublejump(state) or flooded.flood_lake_desolation)
connect(world, player, names, 'Lake desolation', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Upper lake desolation', 'Lake desolation')
connect(world, player, names, 'Upper lake desolation', 'Eastern lake desolation')
connect(world, player, names, 'Lower lake desolation', 'Lake desolation')
connect(world, player, names, 'Lower lake desolation', 'Eastern lake desolation')
connect(world, player, names, 'Eastern lake desolation', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Eastern lake desolation', 'Library')
connect(world, player, names, 'Eastern lake desolation', 'Lower lake desolation')
connect(world, player, names, 'Eastern lake desolation', 'Upper lake desolation', lambda state: state._timespinner_has_fire(world, player) and state.can_reach('Upper Lake Serene', 'Region', player))
connect(world, player, names, 'Eastern lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player))
connect(world, player, names, 'Library', 'Eastern lake desolation')
connect(world, player, names, 'Library', 'Library top', lambda state: state._timespinner_has_doublejump(world, player) or state.has('Talaria Attachment', player))
connect(world, player, names, 'Library', 'Varndagroth tower left', lambda state: state._timespinner_has_keycard_D(world, player))
connect(world, player, names, 'Library', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Library', 'Library top', lambda state: logic.has_doublejump(state) or state.has('Talaria Attachment', player))
connect(world, player, names, 'Library', 'Varndagroth tower left', logic.has_keycard_D)
connect(world, player, names, 'Library', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Library top', 'Library')
connect(world, player, names, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player))
connect(world, player, names, 'Ifrit\'s Lair', 'Library top')
connect(world, player, names, 'Varndagroth tower left', 'Library')
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', 'Varndagroth tower right (upper)', logic.has_keycard_C)
connect(world, player, names, 'Varndagroth tower left', 'Varndagroth tower right (lower)', logic.has_keycard_B)
connect(world, player, names, 'Varndagroth tower left', 'Sealed Caves (Sirens)', lambda state: logic.has_keycard_B(state) and state.has('Elevator Keycard', player))
connect(world, player, names, 'Varndagroth tower left', 'Refugee Camp', lambda state: state.has('Timespinner Wheel', player) and state.has('Timespinner Spindle', player))
connect(world, player, 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', lambda state: state._timespinner_has_keycard_B(world, player))
connect(world, player, names, 'Varndagroth tower right (lower)', 'Varndagroth tower left', logic.has_keycard_B)
connect(world, player, names, 'Varndagroth tower right (lower)', 'Varndagroth tower right (elevator)', lambda state: state.has('Elevator Keycard', player))
connect(world, player, 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)', 'Military 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, 'Varndagroth tower right (lower)', 'Sealed Caves (Sirens)', lambda state: logic.has_keycard_B(state) and state.has('Elevator Keycard', player))
connect(world, player, names, 'Varndagroth tower right (lower)', 'Military Fortress', logic.can_kill_all_3_bosses)
connect(world, player, names, 'Varndagroth tower right (lower)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Sealed Caves (Sirens)', 'Varndagroth tower left', lambda state: state.has('Elevator Keycard', player))
connect(world, player, 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, 'Military Fortress', 'Varndagroth tower right (lower)', lambda state: state._timespinner_can_kill_all_3_bosses(world, player))
connect(world, player, names, 'Sealed Caves (Sirens)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Military Fortress', 'Varndagroth tower right (lower)', logic.can_kill_all_3_bosses)
connect(world, player, names, 'Military Fortress', 'Temporal Gyre', lambda state: state.has('Timespinner Wheel', player))
connect(world, player, names, 'Military Fortress', 'Military Fortress (hangar)', lambda state: state._timespinner_has_doublejump(world, player))
connect(world, player, names, 'Military Fortress', 'Military Fortress (hangar)', logic.has_doublejump)
connect(world, player, names, 'Military Fortress (hangar)', 'Military Fortress')
connect(world, player, names, 'Military Fortress (hangar)', 'The lab', lambda state: state._timespinner_has_keycard_B(world, player) and state._timespinner_has_doublejump(world, player))
connect(world, player, names, 'Military Fortress (hangar)', 'The lab', lambda state: logic.has_keycard_B(state) and logic.has_doublejump(state))
connect(world, player, names, 'Temporal Gyre', 'Military Fortress')
connect(world, player, names, 'The lab', 'Military 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', 'The lab (power off)', logic.has_doublejump_of_npc)
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 (power off)', 'The lab (upper)', logic.has_forwarddash_doublejump)
connect(world, player, names, 'The lab (upper)', 'The lab (power off)')
connect(world, player, names, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player))
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)', 'Emperors tower', logic.has_forwarddash_doublejump)
connect(world, player, names, 'The lab (upper)', 'Ancient Pyramid (entrance)', lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player))
connect(world, player, names, 'Ravenlord\'s Lair', 'The lab (upper)')
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, 'Skeleton Shaft', 'Sealed Caves (upper)', logic.has_keycard_A)
connect(world, player, names, 'Skeleton Shaft', 'Space time continuum', logic.has_teleport)
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_doublejump(world, player))
connect(world, player, names, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', lambda state: state._timespinner_has_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, 'Sealed Caves (upper)', 'Sealed Caves (Xarion)', lambda state: logic.has_teleport(state) or logic.has_doublejump(state))
connect(world, player, names, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', logic.has_doublejump)
connect(world, player, names, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Refugee Camp', 'Forest')
#connect(world, player, names, 'Refugee Camp', 'Library', lambda state: not 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, 'Refugee Camp', 'Space time continuum', logic.has_teleport)
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', 'Left Side forest Caves', lambda state: state.has('Talaria Attachment', player) or logic.has_timestop(state))
connect(world, player, names, 'Forest', 'Caves of Banishment (Sirens)')
connect(world, player, names, 'Forest', 'Castle Ramparts')
connect(world, player, names, 'Left Side forest Caves', 'Forest')
connect(world, player, names, 'Left Side forest Caves', 'Upper Lake Serene', lambda state: state._timespinner_has_timestop(world, player))
connect(world, player, names, 'Left Side forest Caves', 'Lower Lake Serene', 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, 'Left Side forest Caves', 'Upper Lake Serene', logic.has_timestop)
connect(world, player, names, 'Left Side forest Caves', 'Lower Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene)
connect(world, player, names, 'Left Side forest Caves', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Upper Lake Serene', 'Left Side forest Caves')
connect(world, player, names, 'Upper Lake Serene', 'Lower Lake Serene', lambda state: state.has('Water Mask', player))
connect(world, player, names, 'Lower Lake Serene', 'Upper Lake Serene')
connect(world, player, names, 'Lower Lake Serene', 'Left Side forest Caves')
connect(world, player, names, 'Lower Lake Serene', 'Caves of Banishment (upper)')
connect(world, player, names, 'Caves of Banishment (upper)', 'Upper Lake Serene', lambda state: state.has('Water Mask', player))
connect(world, player, names, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: state.has('Twin Pyramid Key', player) or state._timespinner_has_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_doublejump(world, player))
connect(world, player, names, 'Caves of Banishment (upper)', 'Upper Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene)
connect(world, player, names, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: logic.has_doublejump(state) or state.has_any({'Gas Mask', 'Twin Pyramid Key'}, player))
connect(world, player, names, 'Caves of Banishment (upper)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Caves of Banishment (Maw)', 'Caves of Banishment (upper)', lambda state: logic.has_doublejump(state) if not flooded.flood_maw else state.has('Water Mask', player))
connect(world, player, 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 (Maw)', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Caves of Banishment (Sirens)', 'Forest')
connect(world, player, names, 'Castle Ramparts', 'Forest')
connect(world, player, names, 'Castle Ramparts', 'Castle Keep')
connect(world, player, names, 'Castle Ramparts', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Castle Ramparts', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Castle Keep', 'Castle Ramparts')
connect(world, player, names, 'Castle Keep', 'Royal towers (lower)', lambda state: state._timespinner_has_doublejump(world, player))
connect(world, player, names, 'Castle Keep', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
connect(world, player, names, 'Castle Keep', 'Castle Basement', lambda state: state.has('Water Mask', player) or not flooded.flood_basement)
connect(world, player, names, 'Castle Keep', 'Royal towers (lower)', logic.has_doublejump)
connect(world, player, names, 'Castle Keep', 'Space time continuum', logic.has_teleport)
connect(world, player, names, 'Royal towers (lower)', 'Castle 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 (lower)', 'Royal towers', lambda state: state.has('Timespinner Wheel', player) or logic.has_forwarddash_doublejump(state))
connect(world, player, names, 'Royal towers (lower)', 'Space time continuum', logic.has_teleport)
connect(world, player, 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', 'Royal towers (upper)', logic.has_doublejump)
connect(world, player, names, 'Royal towers (upper)', 'Royal towers')
connect(world, player, names, 'Ancient Pyramid (entrance)', 'The lab (upper)', lambda state: not is_option_enabled(world, player, "EnterSandman"))
connect(world, player, names, 'Ancient Pyramid (entrance)', 'Ancient Pyramid (left)', lambda state: state._timespinner_has_doublejump(world, player))
#connect(world, player, names, 'Ancient Pyramid (entrance)', 'The lab (upper)', lambda state: not is_option_enabled(world, player, "EnterSandman"))
connect(world, player, names, 'Ancient Pyramid (entrance)', 'Ancient Pyramid (left)', logic.has_doublejump)
connect(world, player, names, 'Ancient Pyramid (left)', 'Ancient Pyramid (entrance)')
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', 'Library', 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', 'Castle Ramparts', lambda state: pyramid_keys_unlock == "GateCastleRamparts")
connect(world, player, names, 'Space time continuum', 'Castle 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")
connect(world, player, names, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: is_option_enabled(world, player, "EnterSandman"))
connect(world, player, names, 'Ancient Pyramid (left)', 'Ancient Pyramid (right)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft)
connect(world, player, names, 'Ancient Pyramid (right)', 'Ancient Pyramid (left)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft)
connect(world, player, names, 'Space time continuum', 'Lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateLakeDesolation"))
connect(world, player, names, 'Space time continuum', 'Lower lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateKittyBoss"))
connect(world, player, names, 'Space time continuum', 'Library', lambda state: logic.can_teleport_to(state, "Present", "GateLeftLibrary"))
connect(world, player, names, 'Space time continuum', 'Varndagroth tower right (lower)', lambda state: logic.can_teleport_to(state, "Present", "GateMilitaryGate"))
connect(world, player, names, 'Space time continuum', 'Skeleton Shaft', lambda state: logic.can_teleport_to(state, "Present", "GateSealedCaves"))
connect(world, player, names, 'Space time continuum', 'Sealed Caves (Sirens)', lambda state: logic.can_teleport_to(state, "Present", "GateSealedSirensCave"))
connect(world, player, names, 'Space time continuum', 'Upper Lake Serene', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneLeft"))
connect(world, player, names, 'Space time continuum', 'Left Side forest Caves', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneRight"))
connect(world, player, names, 'Space time continuum', 'Refugee Camp', lambda state: logic.can_teleport_to(state, "Past", "GateAccessToPast"))
connect(world, player, names, 'Space time continuum', 'Castle Ramparts', lambda state: logic.can_teleport_to(state, "Past", "GateCastleRamparts"))
connect(world, player, names, 'Space time continuum', 'Castle Keep', lambda state: logic.can_teleport_to(state, "Past", "GateCastleKeep"))
connect(world, player, names, 'Space time continuum', 'Royal towers (lower)', lambda state: logic.can_teleport_to(state, "Past", "GateRoyalTowers"))
connect(world, player, names, 'Space time continuum', 'Caves of Banishment (Maw)', lambda state: logic.can_teleport_to(state, "Past", "GateMaw"))
connect(world, player, names, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: logic.can_teleport_to(state, "Past", "GateCavesOfBanishment"))
connect(world, player, names, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not is_option_enabled(world, player, "UnchainedKeys") and is_option_enabled(world, player, "EnterSandman")))
connect(world, player, names, 'Space time continuum', 'Ancient Pyramid (left)', lambda state: logic.can_teleport_to(state, "Time", "GateLeftPyramid"))
connect(world, player, names, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid"))
if is_option_enabled(world, player, "GyreArchives"):
connect(world, player, names, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player))
connect(world, player, names, 'Ravenlord\'s Lair', 'The lab (upper)')
connect(world, player, names, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player))
connect(world, player, names, 'Ifrit\'s Lair', 'Library top')
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
existingRegions = set()
existingRegions: Set[str] = set()
for region in regions:
existingRegions.add(region.name)
@ -233,7 +250,8 @@ def connectStartingRegion(world: MultiWorld, player: int):
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):
def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: str, target: str,
rule: Optional[Callable[[CollectionState], bool]] = None):
sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player)

View File

@ -1,15 +1,11 @@
from typing import Dict, List, Set, Tuple, TextIO
from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification
from .Items import get_item_names_per_category, item_table, starter_melee_weapons, starter_spells, \
starter_progression_items, filler_items
from .Locations import get_locations, starter_progression_locations, EventId
from .LogicMixin import TimespinnerLogic
from .Items import get_item_names_per_category, item_table, starter_melee_weapons, starter_spells, filler_items
from .Locations import get_locations, EventId
from .Options import is_option_enabled, get_option_value, timespinner_options
from .PyramidKeys import get_pyramid_keys_unlock
from .PreCalculatedWeights import PreCalculatedWeights
from .Regions import create_regions
from ..AutoWorld import World, WebWorld
from worlds.AutoWorld import World, WebWorld
class TimespinnerWebWorld(WebWorld):
theme = "ice"
@ -43,23 +39,24 @@ class TimespinnerWorld(World):
option_definitions = timespinner_options
game = "Timespinner"
topology_present = True
data_version = 10
data_version = 11
web = TimespinnerWebWorld()
required_client_version = (0, 3, 7)
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)}
location_name_to_id = {location.name: location.code for location in get_locations(None, None, None)}
item_name_groups = get_item_names_per_category()
locked_locations: List[str]
pyramid_keys_unlock: str
location_cache: List[Location]
precalculated_weights: PreCalculatedWeights
def __init__(self, world: MultiWorld, player: int):
super().__init__(world, player)
self.locked_locations = []
self.location_cache = []
self.pyramid_keys_unlock = get_pyramid_keys_unlock(world, player)
self.precalculated_weights = PreCalculatedWeights(world, player)
def generate_early(self):
# in generate_early the start_inventory isnt copied over to precollected_items yet, so we can still modify the options directly
@ -71,28 +68,37 @@ class TimespinnerWorld(World):
self.multiworld.StartWithJewelryBox[self.player].value = self.multiworld.StartWithJewelryBox[self.player].option_true
def create_regions(self):
create_regions(self.multiworld, self.player, get_locations(self.multiworld, self.player),
self.location_cache, self.pyramid_keys_unlock)
locations = get_locations(self.multiworld, self.player, self.precalculated_weights)
create_regions(self.multiworld, self.player, locations, self.location_cache, self.precalculated_weights)
def create_item(self, name: str) -> Item:
return create_item_with_correct_settings(self.multiworld, self.player, name)
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(filler_items)
trap_chance: int = get_option_value(self.multiworld, self.player, "TrapChance")
enabled_traps: List[str] = get_option_value(self.multiworld, self.player, "Traps")
if self.multiworld.random.random() < (trap_chance / 100) and enabled_traps:
return self.multiworld.random.choice(enabled_traps)
else:
return self.multiworld.random.choice(filler_items)
def set_rules(self):
setup_events(self.player, self.locked_locations, self.location_cache)
self.multiworld.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player)
final_boss: str
if is_option_enabled(self.multiworld, self.player, "DadPercent"):
final_boss = "Killed Emperor"
else:
final_boss = "Killed Nightmare"
self.multiworld.completion_condition[self.player] = lambda state: state.has(final_boss, self.player)
def generate_basic(self):
excluded_items = get_excluded_items(self, self.multiworld, self.player)
excluded_items: Set[str] = get_excluded_items(self, self.multiworld, self.player)
assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations)
if not is_option_enabled(self.multiworld, self.player, "QuickSeed") and not is_option_enabled(self.multiworld, self.player, "Inverted"):
place_first_progression_item(self.multiworld, self.player, excluded_items, self.locked_locations)
pool = get_item_pool(self.multiworld, self.player, excluded_items)
fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool)
@ -102,19 +108,74 @@ class TimespinnerWorld(World):
def fill_slot_data(self) -> Dict[str, object]:
slot_data: Dict[str, object] = {}
ap_specific_settings: Set[str] = {"RisingTidesOverrides", "TrapChance"}
for option_name in timespinner_options:
slot_data[option_name] = get_option_value(self.multiworld, self.player, option_name)
if (option_name not in ap_specific_settings):
slot_data[option_name] = get_option_value(self.multiworld, self.player, option_name)
slot_data["StinkyMaw"] = True
slot_data["ProgressiveVerticalMovement"] = False
slot_data["ProgressiveKeycards"] = False
slot_data["PyramidKeysGate"] = self.pyramid_keys_unlock
slot_data["PersonalItems"] = get_personal_items(self.player, self.location_cache)
slot_data["PyramidKeysGate"] = self.precalculated_weights.pyramid_keys_unlock
slot_data["PresentGate"] = self.precalculated_weights.present_key_unlock
slot_data["PastGate"] = self.precalculated_weights.past_key_unlock
slot_data["TimeGate"] = self.precalculated_weights.time_key_unlock
slot_data["Basement"] = int(self.precalculated_weights.flood_basement) + \
int(self.precalculated_weights.flood_basement_high)
slot_data["Xarion"] = self.precalculated_weights.flood_xarion
slot_data["Maw"] = self.precalculated_weights.flood_maw
slot_data["PyramidShaft"] = self.precalculated_weights.flood_pyramid_shaft
slot_data["BackPyramid"] = self.precalculated_weights.flood_pyramid_back
slot_data["CastleMoat"] = self.precalculated_weights.flood_moat
slot_data["CastleCourtyard"] = self.precalculated_weights.flood_courtyard
slot_data["LakeDesolation"] = self.precalculated_weights.flood_lake_desolation
slot_data["DryLakeSerene"] = self.precalculated_weights.dry_lake_serene
return slot_data
def write_spoiler_header(self, spoiler_handle: TextIO):
spoiler_handle.write('Twin Pyramid Keys unlock: %s\n' % (self.pyramid_keys_unlock))
if is_option_enabled(self.multiworld, self.player, "UnchainedKeys"):
spoiler_handle.write(f'Modern Warp Beacon unlock: {self.precalculated_weights.present_key_unlock}\n')
spoiler_handle.write(f'Timeworn Warp Beacon unlock: {self.precalculated_weights.past_key_unlock}\n')
if is_option_enabled(self.multiworld, self.player, "EnterSandman"):
spoiler_handle.write(f'Mysterious Warp Beacon unlock: {self.precalculated_weights.time_key_unlock}\n')
else:
spoiler_handle.write(f'Twin Pyramid Keys unlock: {self.precalculated_weights.pyramid_keys_unlock}\n')
if is_option_enabled(self.multiworld, self.player, "RisingTides"):
flooded_areas: List[str] = []
if self.precalculated_weights.flood_basement:
if self.precalculated_weights.flood_basement_high:
flooded_areas.append("Castle Basement")
else:
flooded_areas.append("Castle Basement (Savepoint available)")
if self.precalculated_weights.flood_xarion:
flooded_areas.append("Xarion (boss)")
if self.precalculated_weights.flood_maw:
flooded_areas.append("Maw (caves + boss)")
if self.precalculated_weights.flood_pyramid_shaft:
flooded_areas.append("Ancient Pyramid Shaft")
if self.precalculated_weights.flood_pyramid_back:
flooded_areas.append("Sandman\\Nightmare (boss)")
if self.precalculated_weights.flood_moat:
flooded_areas.append("Castle Ramparts Moat")
if self.precalculated_weights.flood_courtyard:
flooded_areas.append("Castle Courtyard")
if self.precalculated_weights.flood_lake_desolation:
flooded_areas.append("Lake Desolation")
if self.precalculated_weights.dry_lake_serene:
flooded_areas.append("Dry Lake Serene")
if len(flooded_areas) == 0:
flooded_areas_string: str = "None"
else:
flooded_areas_string: str = ", ".join(flooded_areas)
spoiler_handle.write(f'Flooded Areas: {flooded_areas_string}\n')
def get_excluded_items(self: TimespinnerWorld, world: MultiWorld, player: int) -> Set[str]:
@ -127,6 +188,16 @@ def get_excluded_items(self: TimespinnerWorld, world: MultiWorld, player: int) -
if is_option_enabled(world, player, "QuickSeed"):
excluded_items.add('Talaria Attachment')
if is_option_enabled(world, player, "UnchainedKeys"):
excluded_items.add('Twin Pyramid Key')
if not is_option_enabled(world, player, "EnterSandman"):
excluded_items.add('Mysterious Warp Beacon')
else:
excluded_items.add('Timeworn Warp Beacon')
excluded_items.add('Modern Warp Beacon')
excluded_items.add('Mysterious Warp Beacon')
for item in world.precollected_items[player]:
if item.name not in self.item_name_groups['UseItem']:
excluded_items.add(item.name)
@ -188,38 +259,18 @@ def fill_item_pool_with_dummy_items(self: TimespinnerWorld, world: MultiWorld, p
pool.append(item)
def place_first_progression_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
for item in world.precollected_items[player]:
if item.name in starter_progression_items:
return
local_starter_progression_items = tuple(
item for item in starter_progression_items if item not in world.non_local_items[player].value)
non_excluded_starter_progression_locations = tuple(
location for location in starter_progression_locations if location not in world.exclude_locations[player].value)
if not local_starter_progression_items or not non_excluded_starter_progression_locations:
return
progression_item = world.random.choice(local_starter_progression_items)
location = world.random.choice(non_excluded_starter_progression_locations)
excluded_items.add(progression_item)
locked_locations.append(location)
item = create_item_with_correct_settings(world, player, progression_item)
world.get_location(location, player).place_locked_item(item)
def create_item_with_correct_settings(world: MultiWorld, player: int, name: str) -> Item:
data = item_table[name]
if data.useful:
classification = ItemClassification.useful
elif data.progression:
classification = ItemClassification.progression
elif data.trap:
classification = ItemClassification.trap
else:
classification = ItemClassification.filler
item = Item(name, classification, data.code, player)
if not item.advancement:
@ -231,6 +282,9 @@ def create_item_with_correct_settings(world: MultiWorld, player: int, name: str)
item.classification = ItemClassification.filler
elif (name == 'Kobo' or name == 'Merchant Crow') and not is_option_enabled(world, player, "GyreArchives"):
item.classification = ItemClassification.filler
elif name in {"Timeworn Warp Beacon", "Modern Warp Beacon", "Mysterious Warp Beacon"} \
and not is_option_enabled(world, player, "UnchainedKeys"):
item.classification = ItemClassification.filler
return item

View File

@ -28,9 +28,7 @@ certain items to your own world.
## What does another world's item look like in Timespinner?
Items belonging to other worlds are represented by the vanilla item Elemental
Beads ([Elemental Beads Wiki Page](https://timespinnerwiki.com/Use_Items)), Elemental Beads have no use in the
randomizer.
Items belonging to other worlds are represented by the four orbs icon from the [Orb Collectors (Wiki Page)](https://timespinnerwiki.com/Achievements) achievement.
## When the player receives an item, what happens?