diff --git a/worlds/timespinner/Locations.py b/worlds/timespinner/Locations.py index 70c76b863842..7b378b4637fa 100644 --- a/worlds/timespinner/Locations.py +++ b/worlds/timespinner/Locations.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Optional, Callable, NamedTuple +from typing import List, Optional, Callable, NamedTuple from BaseClasses import MultiWorld, CollectionState from .Options import is_option_enabled from .PreCalculatedWeights import PreCalculatedWeights @@ -11,11 +11,11 @@ class LocationData(NamedTuple): region: str name: str code: Optional[int] - rule: Callable[[CollectionState], bool] = lambda state: True + rule: Optional[Callable[[CollectionState], bool]] = None def get_location_datas(world: Optional[MultiWorld], player: Optional[int], - precalculated_weights: PreCalculatedWeights) -> Tuple[LocationData, ...]: + precalculated_weights: PreCalculatedWeights) -> List[LocationData]: flooded: PreCalculatedWeights = precalculated_weights logic = TimespinnerLogic(world, player, precalculated_weights) @@ -88,9 +88,9 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], 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: 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('Military Fortress (hangar)', 'Military Fortress: B door chest 2', 1337063, lambda state: logic.has_keycard_B(state) and (state.has('Water Mask', player) if flooded.flood_lab else logic.has_doublejump(state))), + LocationData('Military Fortress (hangar)', 'Military Fortress: B door chest 1', 1337064, lambda state: logic.has_keycard_B(state) and (state.has('Water Mask', player) if flooded.flood_lab else logic.has_doublejump(state))), + LocationData('Military Fortress (hangar)', 'Military Fortress: Pedestal', 1337065, lambda state: state.has('Water Mask', player) if flooded.flood_lab else (logic.has_doublejump_of_npc(state) or logic.has_forwarddash_doublejump(state))), LocationData('The lab', 'Lab: Coffee break', 1337066), LocationData('The lab', 'Lab: Lower trash right', 1337067, logic.has_doublejump), LocationData('The lab', 'Lab: Lower trash left', 1337068, logic.has_upwarddash), @@ -139,17 +139,17 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], 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, logic.can_break_walls), - LocationData('Lower Lake Serene', 'Lake Serene (Lower): T chest', 1337109, lambda state: not flooded.dry_lake_serene or logic.has_doublejump_of_npc(state)), + LocationData('Lower Lake Serene', 'Lake Serene (Lower): T chest', 1337109, lambda state: flooded.flood_lake_serene or logic.has_doublejump_of_npc(state)), LocationData('Lower Lake Serene', 'Lake Serene (Lower): Past the eels', 1337110), - LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater pedestal', 1337111, lambda state: not flooded.dry_lake_serene or logic.has_doublejump(state)), - LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Shroom jump room', 1337112, lambda state: not flooded.flood_maw or logic.has_doublejump(state)), + LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater pedestal', 1337111, lambda state: flooded.flood_lake_serene or logic.has_doublejump(state)), + LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Shroom jump room', 1337112, lambda state: flooded.flood_maw or logic.has_doublejump(state)), LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Secret room', 1337113, lambda state: logic.can_break_walls(state) and (not flooded.flood_maw or state.has('Water Mask', player))), LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Bottom left room', 1337114, lambda state: not flooded.flood_maw or state.has('Water Mask', player)), 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: 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): Jackpot room chest 1', 1337116, lambda state: flooded.flood_maw or logic.has_forwarddash_doublejump(state)), + LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 2', 1337117, lambda state: flooded.flood_maw or logic.has_forwarddash_doublejump(state)), + LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 3', 1337118, lambda state: flooded.flood_maw or logic.has_forwarddash_doublejump(state)), + LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Jackpot room chest 4', 1337119, lambda state: flooded.flood_maw or logic.has_forwarddash_doublejump(state)), LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Pedestal', 1337120, lambda state: not flooded.flood_maw or state.has('Water Mask', player)), 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 (not flooded.flood_maw or state.has('Water Mask', player))), @@ -197,7 +197,7 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], 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: logic.can_break_walls(state) and (not flooded.flood_pyramid_shaft or state.has('Water Mask', player))), - LocationData('Ancient Pyramid (left)', 'Ancient Pyramid: Regret chest', 1337249, lambda state: logic.can_break_walls(state) and (not flooded.flood_pyramid_shaft or state.has('Water Mask', player))), + 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 logic.has_doublejump(state))), LocationData('Ancient Pyramid (right)', 'Ancient Pyramid: Nightmare Door chest', 1337236, lambda state: not flooded.flood_pyramid_back or state.has('Water Mask', player)), 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 (not flooded.flood_pyramid_back or state.has('Water Mask', player))) ] @@ -271,4 +271,4 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], LocationData('Ifrit\'s Lair', 'Ifrit: Post fight (chest)', 1337245), ) - return tuple(location_table) + return location_table diff --git a/worlds/timespinner/Options.py b/worlds/timespinner/Options.py index 8b111849442c..f7921fcb81e0 100644 --- a/worlds/timespinner/Options.py +++ b/worlds/timespinner/Options.py @@ -54,14 +54,23 @@ class LoreChecks(Toggle): display_name = "Lore Checks" -class BossRando(Toggle): - "Shuffles the positions of all bosses." +class BossRando(Choice): + "Wheter all boss locations are shuffled, and if their damage/hp should be scaled." display_name = "Boss Randomization" + option_off = 0 + option_scaled = 1 + option_unscaled = 2 + alias_true = 1 -class BossScaling(DefaultOnToggle): - "When Boss Rando is enabled, scales the bosses' HP, XP, and ATK to the stats of the location they replace (Recommended)" - display_name = "Scale Random Boss Stats" +class EnemyRando(Choice): + "Wheter enemies will be randomized, and if their damage/hp should be scaled." + display_name = "Enemy Randomization" + option_off = 0 + option_scaled = 1 + option_unscaled = 2 + option_ryshia = 3 + alias_true = 1 class DamageRando(Choice): @@ -336,6 +345,7 @@ def rising_tide_option(location: str, with_save_point_option: bool = False) -> D 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""" + display_name = "Rising Tides Overrides" schema = Schema({ **rising_tide_option("Xarion"), **rising_tide_option("Maw"), @@ -345,9 +355,10 @@ class RisingTidesOverrides(OptionDict): **rising_tide_option("CastleBasement", with_save_point_option=True), **rising_tide_option("CastleCourtyard"), **rising_tide_option("LakeDesolation"), - **rising_tide_option("LakeSerene") + **rising_tide_option("LakeSerene"), + **rising_tide_option("LakeSereneBridge"), + **rising_tide_option("Lab"), }) - display_name = "Rising Tides Overrides" default = { "Xarion": { "Dry": 67, "Flooded": 33 }, "Maw": { "Dry": 67, "Flooded": 33 }, @@ -358,6 +369,8 @@ class RisingTidesOverrides(OptionDict): "CastleCourtyard": { "Dry": 67, "Flooded": 33 }, "LakeDesolation": { "Dry": 67, "Flooded": 33 }, "LakeSerene": { "Dry": 33, "Flooded": 67 }, + "LakeSereneBridge": { "Dry": 67, "Flooded": 33 }, + "Lab": { "Dry": 67, "Flooded": 33 }, } @@ -383,6 +396,11 @@ class Traps(OptionList): default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" ] +class PresentAccessWithWheelAndSpindle(Toggle): + """When inverted, allows using the refugee camp warp when both the Timespinner Wheel and Spindle is acquired.""" + display_name = "Past Wheel & Spindle Warp" + + # Some options that are available in the timespinner randomizer arent currently implemented timespinner_options: Dict[str, Option] = { "StartWithJewelryBox": StartWithJewelryBox, @@ -396,7 +414,7 @@ class Traps(OptionList): "Cantoran": Cantoran, "LoreChecks": LoreChecks, "BossRando": BossRando, - "BossScaling": BossScaling, + "EnemyRando": EnemyRando, "DamageRando": DamageRando, "DamageRandoOverrides": DamageRandoOverrides, "HpCap": HpCap, @@ -419,6 +437,7 @@ class Traps(OptionList): "UnchainedKeys": UnchainedKeys, "TrapChance": TrapChance, "Traps": Traps, + "PresentAccessWithWheelAndSpindle": PresentAccessWithWheelAndSpindle, "DeathLink": DeathLink, } diff --git a/worlds/timespinner/PreCalculatedWeights.py b/worlds/timespinner/PreCalculatedWeights.py index 64243e25edcc..ff7f031d3b67 100644 --- a/worlds/timespinner/PreCalculatedWeights.py +++ b/worlds/timespinner/PreCalculatedWeights.py @@ -1,4 +1,4 @@ -from typing import Tuple, Dict, Union +from typing import Tuple, Dict, Union, List from BaseClasses import MultiWorld from .Options import timespinner_options, is_option_enabled, get_option_value @@ -17,7 +17,9 @@ class PreCalculatedWeights: flood_moat: bool flood_courtyard: bool flood_lake_desolation: bool - dry_lake_serene: bool + flood_lake_serene: bool + flood_lake_serene_bridge: bool + flood_lab: bool def __init__(self, world: MultiWorld, player: int): if world and is_option_enabled(world, player, "RisingTides"): @@ -32,8 +34,9 @@ def __init__(self, world: MultiWorld, player: int): 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") - flood_lake_serene, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeSerene") - self.dry_lake_serene = not flood_lake_serene + self.flood_lake_serene, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeSerene") + self.flood_lake_serene_bridge, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeSereneBridge") + self.flood_lab, _ = self.roll_flood_setting(world, player, weights_overrrides, "Lab") else: self.flood_basement = False self.flood_basement_high = False @@ -44,30 +47,32 @@ def __init__(self, world: MultiWorld, player: int): self.flood_moat = False self.flood_courtyard = False self.flood_lake_desolation = False - self.dry_lake_serene = False + self.flood_lake_serene = True + self.flood_lake_serene_bridge = False + self.flood_lab = False self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \ - self.get_pyramid_keys_unlocks(world, player, self.flood_maw) + self.get_pyramid_keys_unlocks(world, player, self.flood_maw, self.flood_xarion) @staticmethod - def get_pyramid_keys_unlocks(world: MultiWorld, player: int, is_maw_flooded: bool) -> Tuple[str, str, str, str]: - present_teleportation_gates: Tuple[str, ...] = ( + def get_pyramid_keys_unlocks(world: MultiWorld, player: int, is_maw_flooded: bool, is_xarion_flooded: bool) -> Tuple[str, str, str, str]: + present_teleportation_gates: List[str] = [ "GateKittyBoss", "GateLeftLibrary", "GateMilitaryGate", "GateSealedCaves", "GateSealedSirensCave", "GateLakeDesolation" - ) + ] - past_teleportation_gates: Tuple[str, ...] = ( + past_teleportation_gates: List[str] = [ "GateLakeSereneRight", "GateAccessToPast", "GateCastleRamparts", "GateCastleKeep", "GateRoyalTowers", "GateCavesOfBanishment" - ) + ] ancient_pyramid_teleportation_gates: Tuple[str, ...] = ( "GateGyre", @@ -84,7 +89,10 @@ def get_pyramid_keys_unlocks(world: MultiWorld, player: int, is_maw_flooded: boo ) if not is_maw_flooded: - past_teleportation_gates += ("GateMaw", ) + past_teleportation_gates.append("GateMaw") + + if not is_xarion_flooded: + present_teleportation_gates.append("GateXarion") if is_option_enabled(world, player, "Inverted"): all_gates: Tuple[str, ...] = present_teleportation_gates diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index 905cae867ebe..fc7535642949 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -1,4 +1,4 @@ -from typing import List, Set, Dict, Tuple, Optional, Callable +from typing import List, Set, Dict, Optional, Callable from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location from .Options import is_option_enabled from .Locations import LocationData, get_location_datas @@ -7,9 +7,8 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights): - locationn_datas: Tuple[LocationData] = get_location_datas(world, player, precalculated_weights) - - locations_per_region: Dict[str, List[LocationData]] = split_location_datas_per_region(locationn_datas) + locations_per_region: Dict[str, List[LocationData]] = split_location_datas_per_region( + get_location_datas(world, player, precalculated_weights)) regions = [ create_region(world, player, locations_per_region, 'Menu'), @@ -32,7 +31,6 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w create_region(world, player, locations_per_region, 'The lab (upper)'), create_region(world, player, locations_per_region, 'Emperors tower'), create_region(world, player, locations_per_region, 'Skeleton Shaft'), - create_region(world, player, locations_per_region, 'Sealed Caves (upper)'), create_region(world, player, locations_per_region, 'Sealed Caves (Xarion)'), create_region(world, player, locations_per_region, 'Refugee Camp'), create_region(world, player, locations_per_region, 'Forest'), @@ -63,7 +61,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w if __debug__: throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys()) - + world.regions += regions connectStartingRegion(world, player) @@ -71,9 +69,9 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w flooded: PreCalculatedWeights = precalculated_weights logic = TimespinnerLogic(world, player, precalculated_weights) - connect(world, player, '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, 'Lake desolation', 'Lower lake desolation', lambda state: flooded.flood_lake_desolation or logic.has_timestop(state) or state.has('Talaria Attachment', player)) connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player)) - connect(world, player, 'Lake desolation', 'Skeleton Shaft', lambda state: logic.has_doublejump(state) or flooded.flood_lake_desolation) + connect(world, player, 'Lake desolation', 'Skeleton Shaft', lambda state: flooded.flood_lake_desolation or logic.has_doublejump(state)) connect(world, player, 'Lake desolation', 'Space time continuum', logic.has_teleport) connect(world, player, 'Upper lake desolation', 'Lake desolation') connect(world, player, 'Upper lake desolation', 'Eastern lake desolation') @@ -109,40 +107,38 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w connect(world, player, 'Military Fortress', 'Temporal Gyre', lambda state: state.has('Timespinner Wheel', player)) connect(world, player, 'Military Fortress', 'Military Fortress (hangar)', logic.has_doublejump) connect(world, player, 'Military Fortress (hangar)', 'Military Fortress') - connect(world, player, 'Military Fortress (hangar)', 'The lab', lambda state: logic.has_keycard_B(state) and logic.has_doublejump(state)) + connect(world, player, 'Military Fortress (hangar)', 'The lab', lambda state: logic.has_keycard_B(state) and (state.has('Water Mask', player) if flooded.flood_lab else logic.has_doublejump(state))) connect(world, player, 'Temporal Gyre', 'Military Fortress') connect(world, player, 'The lab', 'Military Fortress') connect(world, player, 'The lab', 'The lab (power off)', logic.has_doublejump_of_npc) - connect(world, player, 'The lab (power off)', 'The lab') + connect(world, player, 'The lab (power off)', 'The lab', lambda state: not flooded.flood_lab or state.has('Water Mask', player)) connect(world, player, 'The lab (power off)', 'The lab (upper)', logic.has_forwarddash_doublejump) connect(world, player, 'The lab (upper)', 'The lab (power off)') connect(world, player, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump) connect(world, player, 'The lab (upper)', 'Ancient Pyramid (entrance)', lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player)) connect(world, player, 'Emperors tower', 'The lab (upper)') connect(world, player, 'Skeleton Shaft', 'Lake desolation') - connect(world, player, 'Skeleton Shaft', 'Sealed Caves (upper)', logic.has_keycard_A) + connect(world, player, 'Skeleton Shaft', 'Sealed Caves (Xarion)', logic.has_keycard_A) connect(world, player, 'Skeleton Shaft', 'Space time continuum', logic.has_teleport) - connect(world, player, 'Sealed Caves (upper)', 'Skeleton Shaft') - connect(world, player, 'Sealed Caves (upper)', 'Sealed Caves (Xarion)', lambda state: logic.has_teleport(state) or logic.has_doublejump(state)) - connect(world, player, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', logic.has_doublejump) + connect(world, player, 'Sealed Caves (Xarion)', 'Skeleton Shaft') connect(world, player, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Refugee Camp', 'Forest') - #connect(world, player, 'Refugee Camp', 'Library', lambda state: not is_option_enabled(world, player, "Inverted")) + connect(world, player, 'Refugee Camp', 'Library', lambda state: is_option_enabled(world, player, "Inverted") and is_option_enabled(world, player, "PresentAccessWithWheelAndSpindle") and state.has_all({'Timespinner Wheel', 'Timespinner Spindle'}, player)) connect(world, player, 'Refugee Camp', 'Space time continuum', logic.has_teleport) connect(world, player, 'Forest', 'Refugee Camp') - connect(world, player, 'Forest', 'Left Side forest Caves', lambda state: state.has('Talaria Attachment', player) or logic.has_timestop(state)) + connect(world, player, 'Forest', 'Left Side forest Caves', lambda state: flooded.flood_lake_serene_bridge or state.has('Talaria Attachment', player) or logic.has_timestop(state)) connect(world, player, 'Forest', 'Caves of Banishment (Sirens)') connect(world, player, 'Forest', 'Castle Ramparts') connect(world, player, 'Left Side forest Caves', 'Forest') connect(world, player, 'Left Side forest Caves', 'Upper Lake Serene', logic.has_timestop) - connect(world, player, 'Left Side forest Caves', 'Lower Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene) + connect(world, player, 'Left Side forest Caves', 'Lower Lake Serene', lambda state: not flooded.flood_lake_serene or state.has('Water Mask', player)) connect(world, player, 'Left Side forest Caves', 'Space time continuum', logic.has_teleport) connect(world, player, 'Upper Lake Serene', 'Left Side forest Caves') - connect(world, player, 'Upper Lake Serene', 'Lower Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene) + connect(world, player, 'Upper Lake Serene', 'Lower Lake Serene', lambda state: not flooded.flood_lake_serene or state.has('Water Mask', player)) connect(world, player, 'Lower Lake Serene', 'Upper Lake Serene') connect(world, player, 'Lower Lake Serene', 'Left Side forest Caves') - connect(world, player, 'Lower Lake Serene', 'Caves of Banishment (upper)', lambda state: not flooded.dry_lake_serene or logic.has_doublejump(state)) - connect(world, player, 'Caves of Banishment (upper)', 'Upper Lake Serene', lambda state: state.has('Water Mask', player) or flooded.dry_lake_serene) + connect(world, player, 'Lower Lake Serene', 'Caves of Banishment (upper)', lambda state: flooded.flood_lake_serene or logic.has_doublejump(state)) + connect(world, player, 'Caves of Banishment (upper)', 'Lower Lake Serene', lambda state: not flooded.flood_lake_serene or state.has('Water Mask', player)) connect(world, player, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: logic.has_doublejump(state) or state.has_any({'Gas Mask', 'Talaria Attachment'} or logic.has_teleport(state), player)) connect(world, player, 'Caves of Banishment (upper)', 'Space time continuum', logic.has_teleport) connect(world, player, '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)) @@ -153,7 +149,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w connect(world, player, 'Castle Ramparts', 'Castle Keep') connect(world, player, 'Castle Ramparts', 'Space time continuum', logic.has_teleport) connect(world, player, 'Castle Keep', 'Castle Ramparts') - connect(world, player, 'Castle Keep', 'Castle Basement', lambda state: state.has('Water Mask', player) or not flooded.flood_basement) + connect(world, player, 'Castle Keep', 'Castle Basement', lambda state: not flooded.flood_basement or state.has('Water Mask', player)) connect(world, player, 'Castle Keep', 'Royal towers (lower)', logic.has_doublejump) connect(world, player, 'Castle Keep', 'Space time continuum', logic.has_teleport) connect(world, player, 'Royal towers (lower)', 'Castle Keep') @@ -165,14 +161,15 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w #connect(world, player, 'Ancient Pyramid (entrance)', 'The lab (upper)', lambda state: not is_option_enabled(world, player, "EnterSandman")) connect(world, player, 'Ancient Pyramid (entrance)', 'Ancient Pyramid (left)', logic.has_doublejump) connect(world, player, 'Ancient Pyramid (left)', 'Ancient Pyramid (entrance)') - connect(world, player, 'Ancient Pyramid (left)', 'Ancient Pyramid (right)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft) - connect(world, player, 'Ancient Pyramid (right)', 'Ancient Pyramid (left)', lambda state: logic.has_upwarddash(state) or flooded.flood_pyramid_shaft) + connect(world, player, 'Ancient Pyramid (left)', 'Ancient Pyramid (right)', lambda state: flooded.flood_pyramid_shaft or logic.has_upwarddash(state)) + connect(world, player, 'Ancient Pyramid (right)', 'Ancient Pyramid (left)', lambda state: flooded.flood_pyramid_shaft or logic.has_upwarddash(state)) connect(world, player, 'Space time continuum', 'Lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateLakeDesolation")) connect(world, player, 'Space time continuum', 'Lower lake desolation', lambda state: logic.can_teleport_to(state, "Present", "GateKittyBoss")) connect(world, player, 'Space time continuum', 'Library', lambda state: logic.can_teleport_to(state, "Present", "GateLeftLibrary")) connect(world, player, 'Space time continuum', 'Varndagroth tower right (lower)', lambda state: logic.can_teleport_to(state, "Present", "GateMilitaryGate")) connect(world, player, 'Space time continuum', 'Skeleton Shaft', lambda state: logic.can_teleport_to(state, "Present", "GateSealedCaves")) connect(world, player, 'Space time continuum', 'Sealed Caves (Sirens)', lambda state: logic.can_teleport_to(state, "Present", "GateSealedSirensCave")) + connect(world, player, 'Space time continuum', 'Sealed Caves (Xarion)', lambda state: logic.can_teleport_to(state, "Present", "GateXarion")) connect(world, player, 'Space time continuum', 'Upper Lake Serene', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneLeft")) connect(world, player, 'Space time continuum', 'Left Side forest Caves', lambda state: logic.can_teleport_to(state, "Past", "GateLakeSereneRight")) connect(world, player, 'Space time continuum', 'Refugee Camp', lambda state: logic.can_teleport_to(state, "Past", "GateAccessToPast")) @@ -204,12 +201,13 @@ def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: def create_location(player: int, location_data: LocationData, region: Region) -> Location: location = Location(player, location_data.name, location_data.code, region) - location.access_rule = location_data.rule + + if location_data.rule: + location.access_rule = location_data.rule if id is None: location.event = True location.locked = True - return location @@ -220,7 +218,6 @@ def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str for location_data in locations_per_region[name]: location = create_location(player, location_data, region) region.locations.append(location) - return region @@ -237,11 +234,9 @@ def connectStartingRegion(world: MultiWorld, player: int): menu_to_tutorial = Entrance(player, 'Tutorial', menu) menu_to_tutorial.connect(tutorial) menu.exits.append(menu_to_tutorial) - tutorial_to_start = Entrance(player, 'Start Game', tutorial) tutorial_to_start.connect(starting_region) tutorial.exits.append(tutorial_to_start) - teleport_back_to_start = Entrance(player, 'Teleport back to start', space_time_continuum) teleport_back_to_start.connect(starting_region) space_time_continuum.exits.append(teleport_back_to_start) @@ -249,7 +244,7 @@ def connectStartingRegion(world: MultiWorld, player: int): def connect(world: MultiWorld, player: int, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None): - + sourceRegion = world.get_region(source, player) targetRegion = world.get_region(target, player) @@ -257,15 +252,13 @@ def connect(world: MultiWorld, player: int, source: str, target: str, if rule: connection.access_rule = rule - sourceRegion.exits.append(connection) connection.connect(targetRegion) -def split_location_datas_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: +def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]: per_region: Dict[str, List[LocationData]] = {} for location in locations: per_region.setdefault(location.region, []).append(location) - - return per_region + return per_region \ No newline at end of file diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py index 24230862bdf6..ff7b3515e605 100644 --- a/worlds/timespinner/__init__.py +++ b/worlds/timespinner/__init__.py @@ -39,9 +39,9 @@ class TimespinnerWorld(World): option_definitions = timespinner_options game = "Timespinner" topology_present = True - data_version = 11 + data_version = 12 web = TimespinnerWebWorld() - required_client_version = (0, 3, 7) + required_client_version = (0, 4, 2) 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_location_datas(None, None, None)} @@ -108,7 +108,9 @@ def fill_slot_data(self) -> Dict[str, object]: 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 + slot_data["DryLakeSerene"] = not self.precalculated_weights.flood_lake_serene + slot_data["LakeSereneBridge"] = self.precalculated_weights.flood_lake_serene_bridge + slot_data["Lab"] = self.precalculated_weights.flood_lab return slot_data @@ -144,8 +146,12 @@ def write_spoiler_header(self, spoiler_handle: TextIO) -> None: flooded_areas.append("Castle Courtyard") if self.precalculated_weights.flood_lake_desolation: flooded_areas.append("Lake Desolation") - if not self.precalculated_weights.dry_lake_serene: + if self.precalculated_weights.flood_lake_serene: flooded_areas.append("Lake Serene") + if self.precalculated_weights.flood_lake_serene_bridge: + flooded_areas.append("Lake Serene Bridge") + if self.precalculated_weights.flood_lab: + flooded_areas.append("Lab") if len(flooded_areas) == 0: flooded_areas_string: str = "None" @@ -220,15 +226,18 @@ def get_excluded_items(self) -> Set[str]: def assign_starter_items(self, excluded_items: Set[str]) -> None: non_local_items: Set[str] = self.multiworld.non_local_items[self.player].value + local_items: Set[str] = self.multiworld.local_items[self.player].value - local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if item not in non_local_items) + local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if + item in local_items or not item in non_local_items) if not local_starter_melee_weapons: if 'Plasma Orb' in non_local_items: raise Exception("Atleast one melee orb must be local") else: local_starter_melee_weapons = ('Plasma Orb',) - local_starter_spells = tuple(item for item in starter_spells if item not in non_local_items) + local_starter_spells = tuple(item for item in starter_spells if + item in local_items or not item in non_local_items) if not local_starter_spells: if 'Lightwall' in non_local_items: raise Exception("Atleast one spell must be local")