AHiT: Rework Subcon Forest Boss Arena, Boss Firewall and YCHE logic (#4494)

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
This commit is contained in:
Mysteryem
2025-03-10 15:34:10 +00:00
committed by GitHub
parent 7c30c4a169
commit e267714d44
3 changed files with 84 additions and 23 deletions

View File

@@ -206,7 +206,7 @@ ahit_locations = {
"Subcon Village - Graveyard Ice Cube": LocData(2000325077, "Subcon Forest Area"),
"Subcon Village - House Top": LocData(2000325471, "Subcon Forest Area"),
"Subcon Village - Ice Cube House": LocData(2000325469, "Subcon Forest Area"),
"Subcon Village - Snatcher Statue Chest": LocData(2000323730, "Subcon Forest Area", paintings=1),
"Subcon Village - Snatcher Statue Chest": LocData(2000323730, "Subcon Forest Behind Boss Firewall"),
"Subcon Village - Stump Platform Chest": LocData(2000323729, "Subcon Forest Area"),
"Subcon Forest - Giant Tree Climb": LocData(2000325470, "Subcon Forest Area"),
@@ -233,7 +233,7 @@ ahit_locations = {
"Subcon Forest - Long Tree Climb Chest": LocData(2000323734, "Subcon Forest Area",
required_hats=[HatType.DWELLER], paintings=2),
"Subcon Forest - Boss Arena Chest": LocData(2000323735, "Subcon Forest Area"),
"Subcon Forest - Boss Arena Chest": LocData(2000323735, "Subcon Forest Boss Arena"),
"Subcon Forest - Manor Rooftop": LocData(2000325466, "Subcon Forest Area",
hit_type=HitType.dweller_bell, paintings=1),
@@ -411,7 +411,7 @@ act_completions = {
"Act Completion (Mail Delivery Service)": LocData(2000312032, "Mail Delivery Service",
required_hats=[HatType.SPRINT]),
"Act Completion (Your Contract has Expired)": LocData(2000311390, "Your Contract has Expired",
"Act Completion (Your Contract has Expired)": LocData(2000311390, "Your Contract has Expired - Post Fight",
hit_type=HitType.umbrella),
"Act Completion (Time Rift - Pipe)": LocData(2000313069, "Time Rift - Pipe", hookshot=True),
@@ -976,7 +976,6 @@ event_locs = {
**snatcher_coins,
"HUMT Access": LocData(0, "Heating Up Mafia Town"),
"TOD Access": LocData(0, "Toilet of Doom"),
"YCHE Access": LocData(0, "Your Contract has Expired"),
"AFR Access": LocData(0, "Alpine Free Roam"),
"TIHS Access": LocData(0, "The Illness has Spread"),

View File

@@ -347,7 +347,7 @@ def create_regions(world: "HatInTimeWorld"):
sf_act3 = create_region_and_connect(world, "Toilet of Doom", "Subcon Forest - Act 3", subcon_forest)
sf_act4 = create_region_and_connect(world, "Queen Vanessa's Manor", "Subcon Forest - Act 4", subcon_forest)
sf_act5 = create_region_and_connect(world, "Mail Delivery Service", "Subcon Forest - Act 5", subcon_forest)
create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
sf_finale = create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
# ------------------------------------------- ALPINE SKYLINE ------------------------------------------ #
alpine_skyline = create_region_and_connect(world, "Alpine Skyline", "Telescope -> Alpine Skyline", spaceship)
@@ -386,11 +386,24 @@ def create_regions(world: "HatInTimeWorld"):
create_rift_connections(world, create_region(world, "Time Rift - Bazaar"))
sf_area: Region = create_region(world, "Subcon Forest Area")
sf_behind_boss_firewall: Region = create_region(world, "Subcon Forest Behind Boss Firewall")
sf_boss_arena: Region = create_region(world, "Subcon Forest Boss Arena")
sf_area.connect(sf_behind_boss_firewall, "SF Area -> SF Behind Boss Firewall")
sf_behind_boss_firewall.connect(sf_boss_arena, "SF Behind Boss Firewall -> SF Boss Arena")
sf_act1.connect(sf_area, "Subcon Forest Entrance CO")
sf_act2.connect(sf_area, "Subcon Forest Entrance SW")
sf_act3.connect(sf_area, "Subcon Forest Entrance TOD")
sf_act4.connect(sf_area, "Subcon Forest Entrance QVM")
sf_act5.connect(sf_area, "Subcon Forest Entrance MDS")
# YCHE puts the player directly in the boss arena, with no access to the rest of Subcon Forest by default.
sf_finale.connect(sf_boss_arena, "Subcon Forest Entrance YCHE")
# To support the Snatcher Hover expert logic for Act Completion (Your Contract has Expired), the act completion has
# to go in a separate region because the Snatcher Hover gives direct access to the Act Completion, but does not
# give access to the act itself.
sf_finale_post_fight: Region = create_region(world, "Your Contract has Expired - Post Fight")
# This connection must never have any rules placed on it because they will not be inherited when setting up act
# connections, only the rules for the entrances to the act and the rules for the Act Completion are inherited.
sf_finale.connect(sf_finale_post_fight, "YCHE -> YCHE - Post Fight")
create_rift_connections(world, create_region(world, "Time Rift - Sleepy Subcon"))
create_rift_connections(world, create_region(world, "Time Rift - Pipe"))
@@ -947,6 +960,16 @@ def get_shuffled_region(world: "HatInTimeWorld", region: str) -> str:
return name
def get_region_shuffled_to(world: "HatInTimeWorld", region: str) -> str:
if world.options.ActRandomizer:
original_ci: str = chapter_act_info[region]
shuffled_ci = world.act_connections[original_ci]
return next(act_name for act_name, ci in chapter_act_info.items()
if ci == shuffled_ci)
else:
return region
def get_region_location_count(world: "HatInTimeWorld", region_name: str, included_only: bool = True) -> int:
count = 0
region = world.multiworld.get_region(region_name, world.player)

View File

@@ -481,9 +481,8 @@ def set_hard_rules(world: "HatInTimeWorld"):
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 (painting still expected)
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
lambda state: has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player))
# 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))
@@ -566,27 +565,61 @@ def set_expert_rules(world: "HatInTimeWorld"):
lambda state: True)
# Expert: Cherry Hovering
subcon_area = world.multiworld.get_region("Subcon Forest Area", world.player)
yche = world.multiworld.get_region("Your Contract has Expired", world.player)
entrance = yche.connect(subcon_area, "Subcon Forest Entrance YCHE")
# 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")
if world.options.NoPaintingSkips:
add_rule(entrance, lambda state: has_paintings(state, world, 1))
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 - Boss Arena Chest", world.player),
lambda state: has_paintings(state, world, 1, True))
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
subcon_area.connect(yche, "Snatcher Hover")
set_rule(world.multiworld.get_location("Act Completion (Your Contract has Expired)", world.player),
lambda state: True)
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
@@ -681,12 +714,18 @@ def set_subcon_rules(world: "HatInTimeWorld"):
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, so these two need to be set this way
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
lambda state: state.has("TOD Access", world.player) and can_use_hookshot(state, world)
and has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player))
# 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 painting wall can't be skipped without cherry hover, which is Expert
# 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))