mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00

A new `Subcon Forest - Behind Boss Firewall` region is added for `Subcon Village - Snatcher Statue Chest`. `Subcon Forest Area` connects to this new region, requiring either the first `Progressive Painting Unlock`, or Expert logic + `NoPaintingSkips: false`. A new `Subcon Forest Boss Arena` region is added for `Subcon Forest - Boss Arena Chest` because this is immediately accessible from YCHE. There are connections to this region from `Your Contract has Expired` (no requirements) and from `Subcon Forest - Behind Boss Firewall` (requiring either Hard logic or `Hookshot Badge` + `TOD Access`). A reverse connection is also added to Expert logic, for `Subcon Forest Boss Arena` -> `Subcon Forest - Behind Boss Firewall`. This could be extended to include Hard logic if there is a reasonable Cherry Bridge setup. A reverse connection is also added to Expert logic, for `Subcon Forest - Behind Boss Firewall` -> `Subcon Forest Area`, so long as `NoPaintingSkips: false` because it is impossible to burn the paintings to remove the firewall, from behind the firewall. A new `Your Contract has Expired - Post Fight` region is added for the Snatcher post fight cutscene to prevent the Snatcher Hover trick giving access to YCHE, which would otherwise also give access to the new `Subcon Forest Boss Arena` Region. The paintings and boss arena gap logic for `Snatcher Statue Chest` and `Boss Arena Chest` are now handled using the connections to/from these new regions rather than being on the locations themselves. The logic for `Act Completion (Toilet of Doom)` remains unchanged because it has to be in the `Toilet of Doom` region. In Expert logic, with `NoPaintingSkips: false`, YCHE is added as a rift access region to Subcon Forest Time Rift entrances. The `YCHE Access` event is no longer used and has been removed. - Fixes painting skips logic for Subcon Village - Snatcher Statue Chest - Fixes Subcon Forest - Boss Arena Chest being inaccessible from YCHE - Adds Expert logic to reach `Snatcher Statue Chest` from YCHE - Adds Expert logic to skip the boss firewall in reverse from YCHE so long as painting skips are not removed from logic - Adds Expert logic to access Subcon Forest Time Rift entrances from YCHE so long as painting skips are not removed from logic
1004 lines
54 KiB
Python
1004 lines
54 KiB
Python
from worlds.AutoWorld import CollectionState
|
|
from worlds.generic.Rules import add_rule, set_rule
|
|
from .Locations import location_table, zipline_unlocks, is_location_valid, shop_locations, event_locs
|
|
from .Types import HatType, ChapterIndex, hat_type_to_item, Difficulty, HitType
|
|
from BaseClasses import Location, Entrance, Region
|
|
from typing import TYPE_CHECKING, List, Callable, Union, Dict
|
|
from .Options import EndGoal, CTRLogic, NoTicketSkips
|
|
|
|
if TYPE_CHECKING:
|
|
from . import HatInTimeWorld
|
|
|
|
|
|
act_connections = {
|
|
"Mafia Town - Act 2": ["Mafia Town - Act 1"],
|
|
"Mafia Town - Act 3": ["Mafia Town - Act 1"],
|
|
"Mafia Town - Act 4": ["Mafia Town - Act 2", "Mafia Town - Act 3"],
|
|
"Mafia Town - Act 6": ["Mafia Town - Act 4"],
|
|
"Mafia Town - Act 7": ["Mafia Town - Act 4"],
|
|
"Mafia Town - Act 5": ["Mafia Town - Act 6", "Mafia Town - Act 7"],
|
|
|
|
"Battle of the Birds - Act 2": ["Battle of the Birds - Act 1"],
|
|
"Battle of the Birds - Act 3": ["Battle of the Birds - Act 1"],
|
|
"Battle of the Birds - Act 4": ["Battle of the Birds - Act 2", "Battle of the Birds - Act 3"],
|
|
"Battle of the Birds - Act 5": ["Battle of the Birds - Act 2", "Battle of the Birds - Act 3"],
|
|
"Battle of the Birds - Finale A": ["Battle of the Birds - Act 4", "Battle of the Birds - Act 5"],
|
|
"Battle of the Birds - Finale B": ["Battle of the Birds - Finale A"],
|
|
|
|
"Subcon Forest - Finale": ["Subcon Forest - Act 1", "Subcon Forest - Act 2",
|
|
"Subcon Forest - Act 3", "Subcon Forest - Act 4",
|
|
"Subcon Forest - Act 5"],
|
|
|
|
"The Arctic Cruise - Act 2": ["The Arctic Cruise - Act 1"],
|
|
"The Arctic Cruise - Finale": ["The Arctic Cruise - Act 2"],
|
|
}
|
|
|
|
|
|
def can_use_hat(state: CollectionState, world: "HatInTimeWorld", hat: HatType) -> bool:
|
|
if world.options.HatItems:
|
|
return state.has(hat_type_to_item[hat], world.player)
|
|
|
|
if world.hat_yarn_costs[hat] <= 0: # this means the hat was put into starting inventory
|
|
return True
|
|
|
|
return state.has("Yarn", world.player, get_hat_cost(world, hat))
|
|
|
|
|
|
def get_hat_cost(world: "HatInTimeWorld", hat: HatType) -> int:
|
|
cost = 0
|
|
for h in world.hat_craft_order:
|
|
cost += world.hat_yarn_costs[h]
|
|
if h == hat:
|
|
break
|
|
|
|
return cost
|
|
|
|
|
|
def painting_logic(world: "HatInTimeWorld") -> bool:
|
|
return bool(world.options.ShuffleSubconPaintings)
|
|
|
|
|
|
# -1 = Normal, 0 = Moderate, 1 = Hard, 2 = Expert
|
|
def get_difficulty(world: "HatInTimeWorld") -> Difficulty:
|
|
return Difficulty(world.options.LogicDifficulty)
|
|
|
|
|
|
def has_paintings(state: CollectionState, world: "HatInTimeWorld", count: int, allow_skip: bool = True) -> bool:
|
|
if not painting_logic(world):
|
|
return True
|
|
|
|
if not world.options.NoPaintingSkips and allow_skip:
|
|
# In Moderate there is a very easy trick to skip all the walls, except for the one guarding the boss arena
|
|
if get_difficulty(world) >= Difficulty.MODERATE:
|
|
return True
|
|
|
|
return state.has("Progressive Painting Unlock", world.player, count)
|
|
|
|
|
|
def zipline_logic(world: "HatInTimeWorld") -> bool:
|
|
return bool(world.options.ShuffleAlpineZiplines)
|
|
|
|
|
|
def can_use_hookshot(state: CollectionState, world: "HatInTimeWorld"):
|
|
return state.has("Hookshot Badge", world.player)
|
|
|
|
|
|
def can_hit(state: CollectionState, world: "HatInTimeWorld", umbrella_only: bool = False):
|
|
if not world.options.UmbrellaLogic:
|
|
return True
|
|
|
|
return state.has("Umbrella", world.player) or not umbrella_only and can_use_hat(state, world, HatType.BREWING)
|
|
|
|
|
|
def has_relic_combo(state: CollectionState, world: "HatInTimeWorld", relic: str) -> bool:
|
|
return state.has_group(relic, world.player, len(world.item_name_groups[relic]))
|
|
|
|
|
|
def get_relic_count(state: CollectionState, world: "HatInTimeWorld", relic: str) -> int:
|
|
return state.count_group(relic, world.player)
|
|
|
|
|
|
# This is used to determine if the player can clear an act that's required to unlock a Time Rift
|
|
def can_clear_required_act(state: CollectionState, world: "HatInTimeWorld", act_entrance: str) -> bool:
|
|
entrance: Entrance = world.multiworld.get_entrance(act_entrance, world.player)
|
|
if not state.can_reach(entrance.connected_region, "Region", world.player):
|
|
return False
|
|
|
|
if "Free Roam" in entrance.connected_region.name:
|
|
return True
|
|
|
|
name: str = f"Act Completion ({entrance.connected_region.name})"
|
|
return world.multiworld.get_location(name, world.player).access_rule(state)
|
|
|
|
|
|
def can_clear_alpine(state: CollectionState, world: "HatInTimeWorld") -> bool:
|
|
return state.has("Birdhouse Cleared", world.player) and state.has("Lava Cake Cleared", world.player) \
|
|
and state.has("Windmill Cleared", world.player) and state.has("Twilight Bell Cleared", world.player)
|
|
|
|
|
|
def can_clear_metro(state: CollectionState, world: "HatInTimeWorld") -> bool:
|
|
return state.has("Nyakuza Intro Cleared", world.player) \
|
|
and state.has("Yellow Overpass Station Cleared", world.player) \
|
|
and state.has("Yellow Overpass Manhole Cleared", world.player) \
|
|
and state.has("Green Clean Station Cleared", world.player) \
|
|
and state.has("Green Clean Manhole Cleared", world.player) \
|
|
and state.has("Bluefin Tunnel Cleared", world.player) \
|
|
and state.has("Pink Paw Station Cleared", world.player) \
|
|
and state.has("Pink Paw Manhole Cleared", world.player)
|
|
|
|
|
|
def set_rules(world: "HatInTimeWorld"):
|
|
# First, chapter access
|
|
starting_chapter = ChapterIndex(world.options.StartingChapter)
|
|
world.chapter_timepiece_costs[starting_chapter] = 0
|
|
|
|
# Chapter costs increase progressively. Randomly decide the chapter order, except for Finale
|
|
chapter_list: List[ChapterIndex] = [ChapterIndex.MAFIA, ChapterIndex.BIRDS,
|
|
ChapterIndex.SUBCON, ChapterIndex.ALPINE]
|
|
|
|
final_chapter = ChapterIndex.FINALE
|
|
if world.options.EndGoal == EndGoal.option_rush_hour:
|
|
final_chapter = ChapterIndex.METRO
|
|
chapter_list.append(ChapterIndex.FINALE)
|
|
elif world.options.EndGoal == EndGoal.option_seal_the_deal:
|
|
final_chapter = None
|
|
chapter_list.append(ChapterIndex.FINALE)
|
|
|
|
if world.is_dlc1():
|
|
chapter_list.append(ChapterIndex.CRUISE)
|
|
|
|
if world.is_dlc2() and final_chapter != ChapterIndex.METRO:
|
|
chapter_list.append(ChapterIndex.METRO)
|
|
|
|
chapter_list.remove(starting_chapter)
|
|
world.random.shuffle(chapter_list)
|
|
|
|
# Make sure Alpine is unlocked before any DLC chapters are, as the Alpine door needs to be open to access them
|
|
if starting_chapter != ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()):
|
|
index1 = 69
|
|
index2 = 69
|
|
pos: int
|
|
lowest_index: int
|
|
chapter_list.remove(ChapterIndex.ALPINE)
|
|
|
|
if world.is_dlc1():
|
|
index1 = chapter_list.index(ChapterIndex.CRUISE)
|
|
|
|
if world.is_dlc2() and final_chapter != ChapterIndex.METRO:
|
|
index2 = chapter_list.index(ChapterIndex.METRO)
|
|
|
|
lowest_index = min(index1, index2)
|
|
if lowest_index == 0:
|
|
pos = 0
|
|
else:
|
|
pos = world.random.randint(0, lowest_index)
|
|
|
|
chapter_list.insert(pos, ChapterIndex.ALPINE)
|
|
|
|
lowest_cost: int = world.options.LowestChapterCost.value
|
|
highest_cost: int = world.options.HighestChapterCost.value
|
|
cost_increment: int = world.options.ChapterCostIncrement.value
|
|
min_difference: int = world.options.ChapterCostMinDifference.value
|
|
last_cost = 0
|
|
|
|
for i, chapter in enumerate(chapter_list):
|
|
min_range: int = lowest_cost + (cost_increment * i)
|
|
if min_range >= highest_cost:
|
|
min_range = highest_cost-1
|
|
|
|
value: int = world.random.randint(min_range, min(highest_cost, max(lowest_cost, last_cost + cost_increment)))
|
|
cost = world.random.randint(value, min(value + cost_increment, highest_cost))
|
|
if i >= 1:
|
|
if last_cost + min_difference > cost:
|
|
cost = last_cost + min_difference
|
|
|
|
cost = min(cost, highest_cost)
|
|
world.chapter_timepiece_costs[chapter] = cost
|
|
last_cost = cost
|
|
|
|
if final_chapter is not None:
|
|
final_chapter_cost: int
|
|
if world.options.FinalChapterMinCost == world.options.FinalChapterMaxCost:
|
|
final_chapter_cost = world.options.FinalChapterMaxCost.value
|
|
else:
|
|
final_chapter_cost = world.random.randint(world.options.FinalChapterMinCost.value,
|
|
world.options.FinalChapterMaxCost.value)
|
|
|
|
world.chapter_timepiece_costs[final_chapter] = final_chapter_cost
|
|
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Mafia Town", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.MAFIA]))
|
|
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Battle of the Birds", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS]))
|
|
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Subcon Forest", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.SUBCON]))
|
|
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Alpine Skyline", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE]))
|
|
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.FINALE])
|
|
and can_use_hat(state, world, HatType.BREWING) and can_use_hat(state, world, HatType.DWELLER))
|
|
|
|
if world.is_dlc1():
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Arctic Cruise", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE])
|
|
and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.CRUISE]))
|
|
|
|
if world.is_dlc2():
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Nyakuza Metro", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE])
|
|
and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.METRO])
|
|
and can_use_hat(state, world, HatType.DWELLER) and can_use_hat(state, world, HatType.ICE))
|
|
|
|
if not world.options.ActRandomizer:
|
|
set_default_rift_rules(world)
|
|
|
|
table = {**location_table, **event_locs}
|
|
for (key, data) in table.items():
|
|
if not is_location_valid(world, key):
|
|
continue
|
|
|
|
loc = world.multiworld.get_location(key, world.player)
|
|
|
|
for hat in data.required_hats:
|
|
add_rule(loc, lambda state, h=hat: can_use_hat(state, world, h))
|
|
|
|
if data.hookshot:
|
|
add_rule(loc, lambda state: can_use_hookshot(state, world))
|
|
|
|
if data.paintings > 0 and world.options.ShuffleSubconPaintings:
|
|
add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings))
|
|
|
|
if data.hit_type != HitType.none and world.options.UmbrellaLogic:
|
|
if data.hit_type == HitType.umbrella:
|
|
add_rule(loc, lambda state: state.has("Umbrella", world.player))
|
|
|
|
elif data.hit_type == HitType.umbrella_or_brewing:
|
|
add_rule(loc, lambda state: state.has("Umbrella", world.player)
|
|
or can_use_hat(state, world, HatType.BREWING))
|
|
|
|
elif data.hit_type == HitType.dweller_bell:
|
|
add_rule(loc, lambda state: state.has("Umbrella", world.player)
|
|
or can_use_hat(state, world, HatType.BREWING)
|
|
or can_use_hat(state, world, HatType.DWELLER))
|
|
|
|
for misc in data.misc_required:
|
|
add_rule(loc, lambda state, item=misc: state.has(item, world.player))
|
|
|
|
set_specific_rules(world)
|
|
|
|
# Putting all of this here, so it doesn't get overridden by anything
|
|
# Illness starts the player past the intro
|
|
alpine_entrance = world.multiworld.get_entrance("AFR -> Alpine Skyline Area", world.player)
|
|
add_rule(alpine_entrance, lambda state: can_use_hookshot(state, world))
|
|
if world.options.UmbrellaLogic:
|
|
add_rule(alpine_entrance, lambda state: state.has("Umbrella", world.player))
|
|
|
|
if zipline_logic(world):
|
|
add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player),
|
|
lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player),
|
|
lambda state: state.has("Zipline Unlock - The Lava Cake Path", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("-> The Windmill", world.player),
|
|
lambda state: state.has("Zipline Unlock - The Windmill Path", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player),
|
|
lambda state: state.has("Zipline Unlock - The Twilight Bell Path", world.player))
|
|
|
|
add_rule(world.multiworld.get_location("Act Completion (The Illness has Spread)", world.player),
|
|
lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player)
|
|
and state.has("Zipline Unlock - The Lava Cake Path", world.player)
|
|
and state.has("Zipline Unlock - The Windmill Path", world.player))
|
|
|
|
if zipline_logic(world):
|
|
for (loc, zipline) in zipline_unlocks.items():
|
|
add_rule(world.multiworld.get_location(loc, world.player),
|
|
lambda state, z=zipline: state.has(z, world.player))
|
|
|
|
dummy_entrances: List[Entrance] = []
|
|
|
|
for (key, acts) in act_connections.items():
|
|
if "Arctic Cruise" in key and not world.is_dlc1():
|
|
continue
|
|
|
|
entrance: Entrance = world.multiworld.get_entrance(key, world.player)
|
|
region: Region = entrance.connected_region
|
|
access_rules: List[Callable[[CollectionState], bool]] = []
|
|
dummy_entrances.append(entrance)
|
|
|
|
# Entrances to this act that we have to set access_rules on
|
|
entrances: List[Entrance] = []
|
|
|
|
for i, act in enumerate(acts, start=1):
|
|
act_entrance: Entrance = world.multiworld.get_entrance(act, world.player)
|
|
access_rules.append(act_entrance.access_rule)
|
|
required_region = act_entrance.connected_region
|
|
name: str = f"{key}: Connection {i}"
|
|
new_entrance: Entrance = required_region.connect(region, name)
|
|
entrances.append(new_entrance)
|
|
|
|
# Copy access rules from act completions
|
|
if "Free Roam" not in required_region.name:
|
|
rule: Callable[[CollectionState], bool]
|
|
name = f"Act Completion ({required_region.name})"
|
|
rule = world.multiworld.get_location(name, world.player).access_rule
|
|
access_rules.append(rule)
|
|
|
|
for e in entrances:
|
|
for rules in access_rules:
|
|
add_rule(e, rules)
|
|
|
|
for e in dummy_entrances:
|
|
set_rule(e, lambda state: False)
|
|
|
|
set_event_rules(world)
|
|
|
|
if world.options.EndGoal == EndGoal.option_finale:
|
|
world.multiworld.completion_condition[world.player] = lambda state: state.has("Time Piece Cluster", world.player)
|
|
elif world.options.EndGoal == EndGoal.option_rush_hour:
|
|
world.multiworld.completion_condition[world.player] = lambda state: state.has("Rush Hour Cleared", world.player)
|
|
|
|
|
|
def set_specific_rules(world: "HatInTimeWorld"):
|
|
add_rule(world.multiworld.get_location("Mafia Boss Shop Item", world.player),
|
|
lambda state: state.has("Time Piece", world.player, 12)
|
|
and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS]))
|
|
|
|
set_mafia_town_rules(world)
|
|
set_botb_rules(world)
|
|
set_subcon_rules(world)
|
|
set_alps_rules(world)
|
|
|
|
if world.is_dlc1():
|
|
set_dlc1_rules(world)
|
|
|
|
if world.is_dlc2():
|
|
set_dlc2_rules(world)
|
|
|
|
difficulty: Difficulty = get_difficulty(world)
|
|
|
|
if difficulty >= Difficulty.MODERATE:
|
|
set_moderate_rules(world)
|
|
|
|
if difficulty >= Difficulty.HARD:
|
|
set_hard_rules(world)
|
|
|
|
if difficulty >= Difficulty.EXPERT:
|
|
set_expert_rules(world)
|
|
|
|
|
|
def set_moderate_rules(world: "HatInTimeWorld"):
|
|
# Moderate: Gallery without Brewing Hat
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Gallery)", world.player), lambda state: True)
|
|
|
|
# Moderate: Above Boats via Ice Hat Sliding
|
|
add_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.ICE), "or")
|
|
|
|
# Moderate: Clock Tower Chest + Ruined Tower with nothing
|
|
set_rule(world.multiworld.get_location("Mafia Town - Clock Tower Chest", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_location("Mafia Town - Top of Ruined Tower", world.player), lambda state: True)
|
|
|
|
# Moderate: enter and clear The Subcon Well without Hookshot and without hitting the bell
|
|
for loc in world.multiworld.get_region("The Subcon Well", world.player).locations:
|
|
set_rule(loc, lambda state: has_paintings(state, world, 1))
|
|
|
|
# Moderate: Vanessa Manor with nothing
|
|
for loc in world.multiworld.get_region("Queen Vanessa's Manor", world.player).locations:
|
|
set_rule(loc, lambda state: has_paintings(state, world, 1))
|
|
|
|
set_rule(world.multiworld.get_location("Subcon Forest - Manor Rooftop", world.player),
|
|
lambda state: has_paintings(state, world, 1))
|
|
|
|
# Moderate: Village Time Rift with nothing IF umbrella logic is off
|
|
if not world.options.UmbrellaLogic:
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player), lambda state: True)
|
|
|
|
# Moderate: get to Birdhouse/Yellow Band Hills without Brewing Hat
|
|
set_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player),
|
|
lambda state: can_use_hookshot(state, world))
|
|
set_rule(world.multiworld.get_location("Alpine Skyline - Yellow Band Hills", world.player),
|
|
lambda state: can_use_hookshot(state, world))
|
|
|
|
# Moderate: The Birdhouse - Dweller Platforms Relic with only Birdhouse access
|
|
set_rule(world.multiworld.get_location("Alpine Skyline - The Birdhouse: Dweller Platforms Relic", world.player),
|
|
lambda state: True)
|
|
|
|
# Moderate: Twilight Path without Dweller Mask
|
|
set_rule(world.multiworld.get_location("Alpine Skyline - The Twilight Path", world.player), lambda state: True)
|
|
|
|
# Moderate: Mystifying Time Mesa time trial without hats
|
|
set_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player),
|
|
lambda state: True)
|
|
|
|
# Moderate: Goat Refinery from TIHS with Sprint only
|
|
add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player),
|
|
lambda state: state.has("TIHS Access", world.player)
|
|
and can_use_hat(state, world, HatType.SPRINT), "or")
|
|
|
|
# Moderate: Finale Telescope with only Ice Hat
|
|
add_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.FINALE])
|
|
and can_use_hat(state, world, HatType.ICE), "or")
|
|
|
|
# Moderate: Finale without Hookshot
|
|
set_rule(world.multiworld.get_location("Act Completion (The Finale)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.DWELLER))
|
|
|
|
if world.is_dlc1():
|
|
# Moderate: clear Rock the Boat without Ice Hat
|
|
set_rule(world.multiworld.get_location("Rock the Boat - Post Captain Rescue", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_location("Act Completion (Rock the Boat)", world.player), lambda state: True)
|
|
|
|
# Moderate: clear Deep Sea without Ice Hat
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player),
|
|
lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER))
|
|
|
|
# There is a glitched fall damage volume near the Yellow Overpass time piece that warps the player to Pink Paw.
|
|
# Yellow Overpass time piece can also be reached without Hookshot quite easily.
|
|
if world.is_dlc2():
|
|
# No Hookshot
|
|
set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Station)", world.player),
|
|
lambda state: True)
|
|
|
|
# No Dweller, Hookshot, or Time Stop for these
|
|
set_rule(world.multiworld.get_location("Pink Paw Station - Cat Vacuum", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_location("Pink Paw Station - Behind Fan", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_location("Pink Paw Station - Pink Ticket Booth", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_location("Act Completion (Pink Paw Station)", world.player), lambda state: True)
|
|
for key in shop_locations.keys():
|
|
if "Pink Paw Station Thug" in key and is_location_valid(world, key):
|
|
set_rule(world.multiworld.get_location(key, world.player), lambda state: True)
|
|
|
|
# Moderate: clear Rush Hour without Hookshot
|
|
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
|
lambda state: state.has("Metro Ticket - Pink", world.player)
|
|
and state.has("Metro Ticket - Yellow", world.player)
|
|
and state.has("Metro Ticket - Blue", world.player)
|
|
and can_use_hat(state, world, HatType.ICE)
|
|
and can_use_hat(state, world, HatType.BREWING))
|
|
|
|
# Moderate: Bluefin Tunnel + Pink Paw Station without tickets
|
|
if not world.options.NoTicketSkips:
|
|
set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), lambda state: True)
|
|
|
|
|
|
def set_hard_rules(world: "HatInTimeWorld"):
|
|
# Hard: clear Time Rift - The Twilight Bell with Sprint+Scooter only
|
|
add_rule(world.multiworld.get_location("Act Completion (Time Rift - The Twilight Bell)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.SPRINT)
|
|
and state.has("Scooter Badge", world.player), "or")
|
|
|
|
# No Dweller Mask required
|
|
set_rule(world.multiworld.get_location("Subcon Forest - Dweller Floating Rocks", world.player),
|
|
lambda state: has_paintings(state, world, 3))
|
|
set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player),
|
|
lambda state: has_paintings(state, world, 3))
|
|
|
|
# Cherry bridge over boss arena gap
|
|
set_rule(world.get_entrance("SF Behind Boss Firewall -> SF Boss Arena"), lambda state: True)
|
|
|
|
set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player),
|
|
lambda state: has_paintings(state, world, 2, True))
|
|
set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
|
|
lambda state: has_paintings(state, world, 2, True))
|
|
set_rule(world.multiworld.get_location("Subcon Forest - Tall Tree Hookshot Swing", world.player),
|
|
lambda state: has_paintings(state, world, 3, True))
|
|
|
|
# SDJ
|
|
add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.SPRINT), "or")
|
|
|
|
# Hard: Goat Refinery from TIHS with nothing
|
|
add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player),
|
|
lambda state: state.has("TIHS Access", world.player), "or")
|
|
|
|
if world.is_dlc1():
|
|
# Hard: clear Deep Sea without Dweller Mask
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player),
|
|
lambda state: can_use_hookshot(state, world))
|
|
|
|
if world.is_dlc2():
|
|
# Hard: clear Green Clean Manhole without Dweller Mask
|
|
set_rule(world.multiworld.get_location("Act Completion (Green Clean Manhole)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.ICE))
|
|
|
|
# Hard: clear Rush Hour with Brewing Hat only
|
|
if world.options.NoTicketSkips != NoTicketSkips.option_true:
|
|
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.BREWING))
|
|
else:
|
|
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.BREWING)
|
|
and state.has("Metro Ticket - Yellow", world.player)
|
|
and state.has("Metro Ticket - Blue", world.player)
|
|
and state.has("Metro Ticket - Pink", world.player))
|
|
|
|
|
|
def set_expert_rules(world: "HatInTimeWorld"):
|
|
# Finale Telescope with no hats
|
|
set_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player),
|
|
lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.FINALE]))
|
|
|
|
# Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing
|
|
set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True)
|
|
# There are not enough buckets/beach balls to bucket/ball hover in Heating Up Mafia Town, so any other Mafia Town
|
|
# act is required.
|
|
add_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player),
|
|
lambda state: state.can_reach_region("Mafia Town Area", world.player), "or")
|
|
|
|
# Expert: Clear Dead Bird Studio with nothing
|
|
for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations:
|
|
set_rule(loc, lambda state: True)
|
|
|
|
set_rule(world.multiworld.get_location("Act Completion (Dead Bird Studio)", world.player), lambda state: True)
|
|
|
|
# Expert: Clear Dead Bird Studio Basement without Hookshot
|
|
for loc in world.multiworld.get_region("Dead Bird Studio Basement", world.player).locations:
|
|
set_rule(loc, lambda state: True)
|
|
|
|
# Expert: get to and clear Twilight Bell without Dweller Mask.
|
|
# Dweller Mask OR Sprint Hat OR Brewing Hat OR Time Stop + Umbrella required to complete act.
|
|
add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player),
|
|
lambda state: can_use_hookshot(state, world), "or")
|
|
|
|
add_rule(world.multiworld.get_location("Act Completion (The Twilight Bell)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.BREWING)
|
|
or can_use_hat(state, world, HatType.DWELLER)
|
|
or can_use_hat(state, world, HatType.SPRINT)
|
|
or (can_use_hat(state, world, HatType.TIME_STOP) and state.has("Umbrella", world.player)))
|
|
|
|
# Expert: Time Rift - Curly Tail Trail with nothing
|
|
# Time Rift - Twilight Bell and Time Rift - Village with nothing
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player),
|
|
lambda state: True)
|
|
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player), lambda state: True)
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - The Twilight Bell)", world.player),
|
|
lambda state: True)
|
|
|
|
# Expert: Cherry Hovering
|
|
# Skipping the boss firewall is possible with a Cherry Hover.
|
|
set_rule(world.get_entrance("SF Area -> SF Behind Boss Firewall"),
|
|
lambda state: has_paintings(state, world, 1, True))
|
|
# The boss arena gap can be crossed in reverse with a Cherry Hover.
|
|
subcon_boss_arena = world.get_region("Subcon Forest Boss Arena")
|
|
subcon_behind_boss_firewall = world.get_region("Subcon Forest Behind Boss Firewall")
|
|
subcon_boss_arena.connect(subcon_behind_boss_firewall, "SF Boss Arena -> SF Behind Boss Firewall")
|
|
|
|
subcon_area = world.get_region("Subcon Forest Area")
|
|
|
|
# The boss firewall can be skipped in reverse with a Cherry Hover, but it is not possible to remove the boss
|
|
# firewall from reverse because the paintings to burn to remove the firewall are on the other side of the firewall.
|
|
# Therefore, a painting skip is required. The paintings could be burned by already having access to
|
|
# "Subcon Forest Area" through another entrance, but making a new connection to "Subcon Forest Area" in that case
|
|
# would be pointless.
|
|
if not world.options.NoPaintingSkips:
|
|
# The import cannot be done at the module-level because it would cause a circular import.
|
|
from .Regions import get_region_shuffled_to
|
|
|
|
subcon_behind_boss_firewall.connect(subcon_area, "SF Behind Boss Firewall -> SF Area")
|
|
|
|
# Because the Your Contract has Expired entrance can now reach "Subcon Forest Area", it needs to be connected to
|
|
# each of the Subcon Forest Time Rift entrances, like the other Subcon Forest Acts.
|
|
yche = world.get_region("Your Contract has Expired")
|
|
|
|
def connect_to_shuffled_act_at(original_act_name):
|
|
region_name = get_region_shuffled_to(world, original_act_name)
|
|
return yche.connect(world.get_region(region_name), f"{original_act_name} Portal - Entrance YCHE")
|
|
|
|
# Rules copied from `Rules.set_rift_rules()` with painting logic removed because painting skips must be
|
|
# available.
|
|
entrance = connect_to_shuffled_act_at("Time Rift - Pipe")
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 2"))
|
|
reg_act_connection(world, world.get_entrance("Subcon Forest - Act 2").connected_region, entrance)
|
|
|
|
entrance = connect_to_shuffled_act_at("Time Rift - Village")
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 4"))
|
|
reg_act_connection(world, world.get_entrance("Subcon Forest - Act 4").connected_region, entrance)
|
|
|
|
entrance = connect_to_shuffled_act_at("Time Rift - Sleepy Subcon")
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "UFO"))
|
|
|
|
set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player),
|
|
lambda state: can_use_hookshot(state, world) and can_hit(state, world)
|
|
and has_paintings(state, world, 1, True))
|
|
|
|
# Set painting rules only. Skipping paintings is determined in has_paintings
|
|
set_rule(world.multiworld.get_location("Subcon Forest - Magnet Badge Bush", world.player),
|
|
lambda state: has_paintings(state, world, 3, True))
|
|
|
|
# You can cherry hover to Snatcher's post-fight cutscene, which completes the level without having to fight him
|
|
yche_post_fight = world.get_region("Your Contract has Expired - Post Fight")
|
|
subcon_area.connect(yche_post_fight, "Snatcher Hover")
|
|
# Cherry Hover from YCHE also works, so there are no requirements for the Act Completion.
|
|
set_rule(world.get_location("Act Completion (Your Contract has Expired)"), lambda state: True)
|
|
|
|
if world.is_dlc2():
|
|
# Expert: clear Rush Hour with nothing
|
|
if world.options.NoTicketSkips != NoTicketSkips.option_true:
|
|
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True)
|
|
else:
|
|
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
|
lambda state: state.has("Metro Ticket - Yellow", world.player)
|
|
and state.has("Metro Ticket - Blue", world.player)
|
|
and state.has("Metro Ticket - Pink", world.player))
|
|
|
|
# Expert: Yellow/Green Manhole with nothing using a Boop Clip
|
|
set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Manhole)", world.player),
|
|
lambda state: True)
|
|
set_rule(world.multiworld.get_location("Act Completion (Green Clean Manhole)", world.player),
|
|
lambda state: True)
|
|
|
|
|
|
def set_mafia_town_rules(world: "HatInTimeWorld"):
|
|
add_rule(world.multiworld.get_location("Mafia Town - Behind HQ Chest", world.player),
|
|
lambda state: state.can_reach("Act Completion (Heating Up Mafia Town)", "Location", world.player)
|
|
or state.can_reach("Down with the Mafia!", "Region", world.player)
|
|
or state.can_reach("Cheating the Race", "Region", world.player)
|
|
or state.can_reach("The Golden Vault", "Region", world.player))
|
|
|
|
# Old guys don't appear in SCFOS
|
|
add_rule(world.multiworld.get_location("Mafia Town - Old Man (Steel Beams)", world.player),
|
|
lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player)
|
|
or state.can_reach("Barrel Battle", "Region", world.player)
|
|
or state.can_reach("Cheating the Race", "Region", world.player)
|
|
or state.can_reach("The Golden Vault", "Region", world.player)
|
|
or state.can_reach("Down with the Mafia!", "Region", world.player))
|
|
|
|
add_rule(world.multiworld.get_location("Mafia Town - Old Man (Seaside Spaghetti)", world.player),
|
|
lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player)
|
|
or state.can_reach("Barrel Battle", "Region", world.player)
|
|
or state.can_reach("Cheating the Race", "Region", world.player)
|
|
or state.can_reach("The Golden Vault", "Region", world.player)
|
|
or state.can_reach("Down with the Mafia!", "Region", world.player))
|
|
|
|
# Only available outside She Came from Outer Space
|
|
add_rule(world.multiworld.get_location("Mafia Town - Mafia Geek Platform", world.player),
|
|
lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player)
|
|
or state.can_reach("Barrel Battle", "Region", world.player)
|
|
or state.can_reach("Down with the Mafia!", "Region", world.player)
|
|
or state.can_reach("Cheating the Race", "Region", world.player)
|
|
or state.can_reach("Heating Up Mafia Town", "Region", world.player)
|
|
or state.can_reach("The Golden Vault", "Region", world.player))
|
|
|
|
# Only available outside Down with the Mafia! (for some reason)
|
|
add_rule(world.multiworld.get_location("Mafia Town - On Scaffolding", world.player),
|
|
lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player)
|
|
or state.can_reach("Barrel Battle", "Region", world.player)
|
|
or state.can_reach("She Came from Outer Space", "Region", world.player)
|
|
or state.can_reach("Cheating the Race", "Region", world.player)
|
|
or state.can_reach("Heating Up Mafia Town", "Region", world.player)
|
|
or state.can_reach("The Golden Vault", "Region", world.player))
|
|
|
|
# For some reason, the brewing crate is removed in HUMT
|
|
add_rule(world.multiworld.get_location("Mafia Town - Secret Cave", world.player),
|
|
lambda state: state.has("HUMT Access", world.player), "or")
|
|
|
|
# Can bounce across the lava to get this without Hookshot (need to die though)
|
|
add_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player),
|
|
lambda state: state.has("HUMT Access", world.player), "or")
|
|
|
|
if world.options.CTRLogic == CTRLogic.option_nothing:
|
|
set_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), lambda state: True)
|
|
elif world.options.CTRLogic == CTRLogic.option_sprint:
|
|
add_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.SPRINT), "or")
|
|
elif world.options.CTRLogic == CTRLogic.option_scooter:
|
|
add_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.SPRINT)
|
|
and state.has("Scooter Badge", world.player), "or")
|
|
|
|
|
|
def set_botb_rules(world: "HatInTimeWorld"):
|
|
if not world.options.UmbrellaLogic and get_difficulty(world) < Difficulty.MODERATE:
|
|
set_rule(world.multiworld.get_location("Dead Bird Studio - DJ Grooves Sign Chest", world.player),
|
|
lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING))
|
|
set_rule(world.multiworld.get_location("Dead Bird Studio - Tepee Chest", world.player),
|
|
lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING))
|
|
set_rule(world.multiworld.get_location("Dead Bird Studio - Conductor Chest", world.player),
|
|
lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING))
|
|
set_rule(world.multiworld.get_location("Act Completion (Dead Bird Studio)", world.player),
|
|
lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING))
|
|
|
|
|
|
def set_subcon_rules(world: "HatInTimeWorld"):
|
|
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.BREWING) or state.has("Umbrella", world.player)
|
|
or can_use_hat(state, world, HatType.DWELLER))
|
|
|
|
# You can't skip over the boss arena wall without cherry hover.
|
|
set_rule(world.get_entrance("SF Area -> SF Behind Boss Firewall"),
|
|
lambda state: has_paintings(state, world, 1, False))
|
|
|
|
# The hookpoints to cross the boss arena gap are only present in Toilet of Doom.
|
|
set_rule(world.get_entrance("SF Behind Boss Firewall -> SF Boss Arena"),
|
|
lambda state: state.has("TOD Access", world.player)
|
|
and can_use_hookshot(state, world))
|
|
|
|
# The Act Completion is in the Toilet of Doom region, so the same rules as passing the boss firewall and crossing
|
|
# the boss arena gap are required. "TOD Access" is implied from the region so does not need to be included in the
|
|
# rule.
|
|
set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player),
|
|
lambda state: can_use_hookshot(state, world) and can_hit(state, world)
|
|
and has_paintings(state, world, 1, False))
|
|
|
|
add_rule(world.multiworld.get_entrance("Subcon Forest - Act 2", world.player),
|
|
lambda state: state.has("Snatcher's Contract - The Subcon Well", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("Subcon Forest - Act 3", world.player),
|
|
lambda state: state.has("Snatcher's Contract - Toilet of Doom", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("Subcon Forest - Act 4", world.player),
|
|
lambda state: state.has("Snatcher's Contract - Queen Vanessa's Manor", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("Subcon Forest - Act 5", world.player),
|
|
lambda state: state.has("Snatcher's Contract - Mail Delivery Service", world.player))
|
|
|
|
if painting_logic(world):
|
|
add_rule(world.multiworld.get_location("Act Completion (Contractual Obligations)", world.player),
|
|
lambda state: has_paintings(state, world, 1, False))
|
|
|
|
|
|
def set_alps_rules(world: "HatInTimeWorld"):
|
|
add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player),
|
|
lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.BREWING))
|
|
|
|
add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player),
|
|
lambda state: can_use_hookshot(state, world))
|
|
|
|
add_rule(world.multiworld.get_entrance("-> The Windmill", world.player),
|
|
lambda state: can_use_hookshot(state, world))
|
|
|
|
add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player),
|
|
lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER))
|
|
|
|
add_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player),
|
|
lambda state: can_use_hat(state, world, HatType.SPRINT) or can_use_hat(state, world, HatType.TIME_STOP))
|
|
|
|
add_rule(world.multiworld.get_entrance("Alpine Skyline - Finale", world.player),
|
|
lambda state: can_clear_alpine(state, world))
|
|
|
|
add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player),
|
|
lambda state: state.has("AFR Access", world.player)
|
|
and can_use_hookshot(state, world)
|
|
and can_hit(state, world, True))
|
|
|
|
|
|
def set_dlc1_rules(world: "HatInTimeWorld"):
|
|
add_rule(world.multiworld.get_entrance("Cruise Ship Entrance BV", world.player),
|
|
lambda state: can_use_hookshot(state, world))
|
|
|
|
# This particular item isn't present in Act 3 for some reason, yes in vanilla too
|
|
add_rule(world.multiworld.get_location("The Arctic Cruise - Toilet", world.player),
|
|
lambda state: (state.can_reach("Bon Voyage!", "Region", world.player) and can_use_hookshot(state, world))
|
|
or state.can_reach("Ship Shape", "Region", world.player))
|
|
|
|
|
|
def set_dlc2_rules(world: "HatInTimeWorld"):
|
|
add_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player),
|
|
lambda state: state.has("Metro Ticket - Green", world.player)
|
|
or state.has("Metro Ticket - Blue", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player),
|
|
lambda state: state.has("Metro Ticket - Pink", world.player)
|
|
or state.has("Metro Ticket - Yellow", world.player) and state.has("Metro Ticket - Blue", world.player))
|
|
|
|
add_rule(world.multiworld.get_entrance("Nyakuza Metro - Finale", world.player),
|
|
lambda state: can_clear_metro(state, world))
|
|
|
|
add_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
|
lambda state: state.has("Metro Ticket - Yellow", world.player)
|
|
and state.has("Metro Ticket - Blue", world.player)
|
|
and state.has("Metro Ticket - Pink", world.player))
|
|
|
|
for key in shop_locations.keys():
|
|
if "Green Clean Station Thug B" in key and is_location_valid(world, key):
|
|
add_rule(world.multiworld.get_location(key, world.player),
|
|
lambda state: state.has("Metro Ticket - Yellow", world.player), "or")
|
|
|
|
|
|
def reg_act_connection(world: "HatInTimeWorld", region: Union[str, Region], unlocked_entrance: Union[str, Entrance]):
|
|
reg: Region
|
|
entrance: Entrance
|
|
if isinstance(region, str):
|
|
reg = world.multiworld.get_region(region, world.player)
|
|
else:
|
|
reg = region
|
|
|
|
if isinstance(unlocked_entrance, str):
|
|
entrance = world.multiworld.get_entrance(unlocked_entrance, world.player)
|
|
else:
|
|
entrance = unlocked_entrance
|
|
|
|
world.multiworld.register_indirect_condition(reg, entrance)
|
|
|
|
|
|
# See randomize_act_entrances in Regions.py
|
|
# Called before set_rules
|
|
def set_rift_rules(world: "HatInTimeWorld", regions: Dict[str, Region]):
|
|
|
|
# This is accessing the regions in place of these time rifts, so we can set the rules on all the entrances.
|
|
for entrance in regions["Time Rift - Gallery"].entrances:
|
|
add_rule(entrance, lambda state: can_use_hat(state, world, HatType.BREWING)
|
|
and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS]))
|
|
|
|
for entrance in regions["Time Rift - The Lab"].entrances:
|
|
add_rule(entrance, lambda state: can_use_hat(state, world, HatType.DWELLER)
|
|
and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE]))
|
|
|
|
for entrance in regions["Time Rift - Sewers"].entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 4"))
|
|
reg_act_connection(world, world.multiworld.get_entrance("Mafia Town - Act 4",
|
|
world.player).connected_region, entrance)
|
|
|
|
for entrance in regions["Time Rift - Bazaar"].entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 6"))
|
|
reg_act_connection(world, world.multiworld.get_entrance("Mafia Town - Act 6",
|
|
world.player).connected_region, entrance)
|
|
|
|
for entrance in regions["Time Rift - Mafia of Cooks"].entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Burger"))
|
|
|
|
for entrance in regions["Time Rift - The Owl Express"].entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 2"))
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 3"))
|
|
reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 2",
|
|
world.player).connected_region, entrance)
|
|
reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 3",
|
|
world.player).connected_region, entrance)
|
|
|
|
for entrance in regions["Time Rift - The Moon"].entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 4"))
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 5"))
|
|
reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 4",
|
|
world.player).connected_region, entrance)
|
|
reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 5",
|
|
world.player).connected_region, entrance)
|
|
|
|
for entrance in regions["Time Rift - Dead Bird Studio"].entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Train"))
|
|
|
|
for entrance in regions["Time Rift - Pipe"].entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 2"))
|
|
reg_act_connection(world, world.multiworld.get_entrance("Subcon Forest - Act 2",
|
|
world.player).connected_region, entrance)
|
|
if painting_logic(world):
|
|
add_rule(entrance, lambda state: has_paintings(state, world, 2))
|
|
|
|
for entrance in regions["Time Rift - Village"].entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 4"))
|
|
reg_act_connection(world, world.multiworld.get_entrance("Subcon Forest - Act 4",
|
|
world.player).connected_region, entrance)
|
|
|
|
if painting_logic(world):
|
|
add_rule(entrance, lambda state: has_paintings(state, world, 2))
|
|
|
|
for entrance in regions["Time Rift - Sleepy Subcon"].entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "UFO"))
|
|
if painting_logic(world):
|
|
add_rule(entrance, lambda state: has_paintings(state, world, 3))
|
|
|
|
for entrance in regions["Time Rift - Curly Tail Trail"].entrances:
|
|
add_rule(entrance, lambda state: state.has("Windmill Cleared", world.player))
|
|
|
|
for entrance in regions["Time Rift - The Twilight Bell"].entrances:
|
|
add_rule(entrance, lambda state: state.has("Twilight Bell Cleared", world.player))
|
|
|
|
for entrance in regions["Time Rift - Alpine Skyline"].entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Crayon"))
|
|
if entrance.parent_region.name == "Alpine Free Roam":
|
|
add_rule(entrance,
|
|
lambda state: can_use_hookshot(state, world) and can_hit(state, world, umbrella_only=True))
|
|
|
|
if world.is_dlc1():
|
|
for entrance in regions["Time Rift - Balcony"].entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "The Arctic Cruise - Finale"))
|
|
reg_act_connection(world, world.multiworld.get_entrance("The Arctic Cruise - Finale",
|
|
world.player).connected_region, entrance)
|
|
|
|
for entrance in regions["Time Rift - Deep Sea"].entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Cake"))
|
|
|
|
if world.is_dlc2():
|
|
for entrance in regions["Time Rift - Rumbi Factory"].entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Necklace"))
|
|
|
|
|
|
# Basically the same as above, but without the need of the dict since we are just setting defaults
|
|
# Called if Act Rando is disabled
|
|
def set_default_rift_rules(world: "HatInTimeWorld"):
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Gallery", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_use_hat(state, world, HatType.BREWING)
|
|
and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS]))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - The Lab", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_use_hat(state, world, HatType.DWELLER)
|
|
and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE]))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Sewers", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 4"))
|
|
reg_act_connection(world, "Down with the Mafia!", entrance.name)
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Bazaar", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 6"))
|
|
reg_act_connection(world, "Heating Up Mafia Town", entrance.name)
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Mafia of Cooks", world.player).entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Burger"))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - The Owl Express", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 2"))
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 3"))
|
|
reg_act_connection(world, "Murder on the Owl Express", entrance.name)
|
|
reg_act_connection(world, "Picture Perfect", entrance.name)
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - The Moon", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 4"))
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 5"))
|
|
reg_act_connection(world, "Train Rush", entrance.name)
|
|
reg_act_connection(world, "The Big Parade", entrance.name)
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Dead Bird Studio", world.player).entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Train"))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Pipe", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 2"))
|
|
reg_act_connection(world, "The Subcon Well", entrance.name)
|
|
if painting_logic(world):
|
|
add_rule(entrance, lambda state: has_paintings(state, world, 2))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Village", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 4"))
|
|
reg_act_connection(world, "Queen Vanessa's Manor", entrance.name)
|
|
if painting_logic(world):
|
|
add_rule(entrance, lambda state: has_paintings(state, world, 2))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Sleepy Subcon", world.player).entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "UFO"))
|
|
if painting_logic(world):
|
|
add_rule(entrance, lambda state: has_paintings(state, world, 3))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Curly Tail Trail", world.player).entrances:
|
|
add_rule(entrance, lambda state: state.has("Windmill Cleared", world.player))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - The Twilight Bell", world.player).entrances:
|
|
add_rule(entrance, lambda state: state.has("Twilight Bell Cleared", world.player))
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Alpine Skyline", world.player).entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Crayon"))
|
|
if entrance.parent_region.name == "Alpine Free Roam":
|
|
add_rule(entrance,
|
|
lambda state: can_use_hookshot(state, world) and can_hit(state, world, umbrella_only=True))
|
|
|
|
if world.is_dlc1():
|
|
for entrance in world.multiworld.get_region("Time Rift - Balcony", world.player).entrances:
|
|
add_rule(entrance, lambda state: can_clear_required_act(state, world, "The Arctic Cruise - Finale"))
|
|
reg_act_connection(world, "Rock the Boat", entrance.name)
|
|
|
|
for entrance in world.multiworld.get_region("Time Rift - Deep Sea", world.player).entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Cake"))
|
|
|
|
if world.is_dlc2():
|
|
for entrance in world.multiworld.get_region("Time Rift - Rumbi Factory", world.player).entrances:
|
|
add_rule(entrance, lambda state: has_relic_combo(state, world, "Necklace"))
|
|
|
|
|
|
def set_event_rules(world: "HatInTimeWorld"):
|
|
for (name, data) in event_locs.items():
|
|
if not is_location_valid(world, name):
|
|
continue
|
|
|
|
event: Location = world.multiworld.get_location(name, world.player)
|
|
|
|
if data.act_event:
|
|
add_rule(event, world.multiworld.get_location(f"Act Completion ({data.region})", world.player).access_rule)
|