# Conflicts:

#	worlds/tunic/__init__.py
#	worlds/tunic/er_data.py
#	worlds/tunic/er_rules.py
#	worlds/tunic/er_scripts.py
#	worlds/tunic/rules.py
#	worlds/tunic/test/test_access.py
This commit is contained in:
Scipio Wright
2024-08-30 21:55:20 -04:00
parent cbbd395012
commit 2c264cd3d8
7 changed files with 128 additions and 49 deletions

View File

@@ -164,7 +164,7 @@ def check_combat_reqs(area_name: str, state: CollectionState, player: int, alt_d
data.hp_level, data.sp_level, data.mp_level + extra_mp_needed, data.potion_count)
if not has_required_stats(modified_stats, state, player):
# we may need to check if you would have the required stats if you were missing a weapon
# if someone has a better way of doing this, please tell me lmao
# it's kinda janky, but these only get hit in less than once per 100 generations, so whatever
if sword_bool and "Sword" in data.equipment and "Magic" in data.equipment:
# we need to check if you would have the required stats if you didn't have melee
equip_list = [item for item in data.equipment if item != "Sword"]

View File

@@ -83,8 +83,6 @@ Notes:
- The `direction` field is not supported. Connections are always coupled.
- For a list of entrance names, check `er_data.py` in the TUNIC world folder or generate a game with the Entrance Randomizer option enabled and check the spoiler log.
- There is no limit to the number of Shops you can plando.
- If you have more than one shop in a scene, you may be wrong warped when exiting a shop.
- If you have a shop in every scene, and you have an odd number of shops, it will error out.
See the [Archipelago Plando Guide](../../../tutorial/Archipelago/plando/en) for more information on Plando and Connection Plando.

View File

@@ -833,6 +833,9 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
[],
"Cube Cave Entrance Region":
[],
# drop a rudeling, icebolt or ice bomb
"Overworld to West Garden from Furnace":
[["IG3"]],
},
"East Overworld": {
"Above Ruined Passage":
@@ -1080,7 +1083,7 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
"Forest Grave Path Main": {
"Forest Grave Path Upper":
[["Hyperdash"], ["LS2"]],
[["Hyperdash"], ["LS2"], ["IG3"]],
"Forest Grave Path by Grave":
[],
},
@@ -1090,7 +1093,7 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
},
"Forest Grave Path by Grave": {
"Forest Hero's Grave":
[],
[],
"Forest Grave Path Main":
[["IG1"]],
},
@@ -1373,7 +1376,7 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
"Fortress Exterior from East Forest": {
"Fortress Exterior from Overworld":
[],
[],
"Fortress Courtyard Upper":
[["LS2"]],
"Fortress Courtyard":
@@ -1381,9 +1384,9 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
},
"Fortress Exterior from Overworld": {
"Fortress Exterior from East Forest":
[["Hyperdash"]],
[["Hyperdash"]],
"Fortress Exterior near cave":
[],
[],
"Fortress Courtyard":
[["Hyperdash"], ["IG1"], ["LS1"]],
},
@@ -1642,6 +1645,9 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
"Swamp Front": {
"Swamp Mid":
[],
# get one pillar from the gate, then dash onto the gate, very tricky
"Back of Swamp Laurels Area":
[["Hyperdash", "Zip"]],
},
"Swamp Mid": {
"Swamp Front":
@@ -1672,12 +1678,21 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
[["Hyperdash"], ["LS2"]],
"Swamp Hero's Grave Region":
[],
"Swamp Mid":
[["LS2"]],
"Swamp Front":
[["LS1"]],
"Swamp to Cathedral Main Entrance Region":
[["LS3"]],
"Swamp to Cathedral Treasure Room":
[["LS3"]]
},
"Back of Swamp Laurels Area": {
"Back of Swamp":
[["Hyperdash"]],
# get one pillar from the gate, then dash onto the gate, very tricky
"Swamp Mid":
[["IG1", "Hyperdash"]],
[["IG1", "Hyperdash"], ["Hyperdash", "Zip"]],
},
"Swamp Hero's Grave Region": {
"Back of Swamp":

View File

@@ -151,10 +151,13 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["Overworld"],
rule=lambda state: has_ladder("Ladders near Weathervane", state, world))
# for the hard ice grapple, get to the chest after the bomb wall, grab a slime, and grapple push down
# you can ice grapple through the bomb wall, so no need for shop logic checking
regions["Overworld"].connect(
connecting_region=regions["Above Ruined Passage"],
rule=lambda state: has_ladder("Ladders near Weathervane", state, world)
or state.has(laurels, player))
or state.has(laurels, player)
or has_ice_grapple_logic(False, IceGrappling.option_hard, state, world))
regions["Above Ruined Passage"].connect(
connecting_region=regions["Overworld"],
rule=lambda state: has_ladder("Ladders near Weathervane", state, world)
@@ -238,9 +241,11 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["Upper Overworld"],
rule=lambda state: state.has_any({grapple, laurels}, player))
# ice grapple push guard captain down the ledge
regions["Upper Overworld"].connect(
connecting_region=regions["Overworld after Temple Rafters"],
rule=lambda state: has_ladder("Ladder near Temple Rafters", state, world))
rule=lambda state: has_ladder("Ladder near Temple Rafters", state, world)
or has_ice_grapple_logic(True, IceGrappling.option_medium, state, world))
regions["Overworld after Temple Rafters"].connect(
connecting_region=regions["Upper Overworld"],
rule=lambda state: has_ladder("Ladder near Temple Rafters", state, world)
@@ -327,7 +332,6 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["Overworld"],
rule=lambda state: has_ability(holy_cross, state, world))
# not including ice grapple through this because we're not including it on the other door
regions["Overworld"].connect(
connecting_region=regions["Overworld Fountain Cross Door"],
rule=lambda state: has_ability(holy_cross, state, world)
@@ -381,6 +385,11 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
regions["Cube Cave Entrance Region"].connect(
connecting_region=regions["Overworld"])
# drop a rudeling down, icebolt or ice bomb
regions["Overworld"].connect(
connecting_region=regions["Overworld to West Garden from Furnace"],
rule=lambda state: has_ice_grapple_logic(True, IceGrappling.option_hard, state, world))
# Overworld side areas
regions["Old House Front"].connect(
connecting_region=regions["Old House Back"])
@@ -428,7 +437,7 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["Forest Belltower Lower"],
rule=lambda state: has_ladder("Ladder to East Forest", state, world))
# nmg: ice grapple up to dance fox spot, and vice versa
# ice grapple up to dance fox spot, and vice versa
regions["East Forest"].connect(
connecting_region=regions["East Forest Dance Fox Spot"],
rule=lambda state: state.has(laurels, player)
@@ -465,18 +474,20 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["Guard House 2 Upper"],
rule=lambda state: has_ladder("Ladders to Lower Forest", state, world))
# nmg: ice grapple from upper grave path exit to the rest of it
# ice grapple from upper grave path exit to the rest of it
regions["Forest Grave Path Upper"].connect(
connecting_region=regions["Forest Grave Path Main"],
rule=lambda state: state.has(laurels, player)
or has_ice_grapple_logic(True, IceGrappling.option_easy, state, world))
# for the ice grapple, lure a rudeling up top, then grapple push it across
regions["Forest Grave Path Main"].connect(
connecting_region=regions["Forest Grave Path Upper"],
rule=lambda state: state.has(laurels, player))
rule=lambda state: state.has(laurels, player)
or has_ice_grapple_logic(False, IceGrappling.option_hard, state, world))
regions["Forest Grave Path Main"].connect(
connecting_region=regions["Forest Grave Path by Grave"])
# nmg: ice grapple or laurels through the gate
# ice grapple or laurels through the gate
regions["Forest Grave Path by Grave"].connect(
connecting_region=regions["Forest Grave Path Main"],
rule=lambda state: has_ice_grapple_logic(False, IceGrappling.option_easy, state, world)
@@ -565,12 +576,18 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["West Garden Laurels Exit Region"],
rule=lambda state: state.has(laurels, player))
regions["West Garden after Boss"].connect(
# laurels past, or ice grapple it off, or ice grapple to it then fight
after_gk_to_wg = regions["West Garden after Boss"].connect(
connecting_region=regions["West Garden before Boss"],
rule=lambda state: state.has(laurels, player))
rule=lambda state: state.has(laurels, player)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
or (has_ice_grapple_logic(False, IceGrappling.option_easy, state, world)
and has_sword(state, player)))
# ice grapple push Garden Knight off the side
wg_to_after_gk = regions["West Garden before Boss"].connect(
connecting_region=regions["West Garden after Boss"],
rule=lambda state: state.has(laurels, player) or has_sword(state, player))
rule=lambda state: state.has(laurels, player) or has_sword(state, player)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
regions["West Garden before Terry"].connect(
connecting_region=regions["West Garden Hero's Grave Region"],
@@ -600,7 +617,7 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
rule=lambda state: has_ice_grapple_logic(True, IceGrappling.option_medium, state, world))
# Atoll and Frog's Domain
# nmg: ice grapple the bird below the portal
# ice grapple the bird below the portal
regions["Ruined Atoll"].connect(
connecting_region=regions["Ruined Atoll Lower Entry Area"],
rule=lambda state: state.has(laurels, player)
@@ -645,7 +662,8 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
regions["Frog Stairs Eye Exit"].connect(
connecting_region=regions["Frog Stairs Upper"],
rule=lambda state: has_ladder("Ladders to Frog's Domain", state, world))
rule=lambda state: has_ladder("Ladders to Frog's Domain", state, world)
or has_ice_grapple_logic(True, IceGrappling.option_easy, state, world))
regions["Frog Stairs Upper"].connect(
connecting_region=regions["Frog Stairs Eye Exit"],
rule=lambda state: has_ladder("Ladders to Frog's Domain", state, world))
@@ -1038,9 +1056,11 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["Swamp Mid"],
rule=lambda state: has_ice_grapple_logic(False, IceGrappling.option_easy, state, world))
# grapple push the enemy by the door down, then grapple to it. Really jank
regions["Swamp Mid"].connect(
connecting_region=regions["Swamp Ledge under Cathedral Door"],
rule=lambda state: has_ladder("Ladders in Swamp", state, world))
rule=lambda state: has_ladder("Ladders in Swamp", state, world)
or has_ice_grapple_logic(True, IceGrappling.option_hard, state, world))
# ice grapple enemy standing at the door
regions["Swamp Ledge under Cathedral Door"].connect(
connecting_region=regions["Swamp Mid"],
@@ -1060,11 +1080,17 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions["Back of Swamp"],
rule=lambda state: state.has(laurels, player))
# nmg: can ice grapple down while you're on the pillars
# ice grapple down from the pillar, or do that really annoying laurels zip
# the zip goes to front or mid, just doing mid since mid -> front can be done with laurels alone
regions["Back of Swamp Laurels Area"].connect(
connecting_region=regions["Swamp Mid"],
rule=lambda state: state.has(laurels, player)
and has_ice_grapple_logic(True, IceGrappling.option_easy, state, world))
rule=lambda state: laurels_zip(state, world)
or (state.has(laurels, player)
and has_ice_grapple_logic(True, IceGrappling.option_easy, state, world)))
# get one pillar from the gate, then dash onto the gate, very tricky
regions["Swamp Front"].connect(
connecting_region=regions["Back of Swamp Laurels Area"],
rule=lambda state: laurels_zip(state, world))
regions["Back of Swamp"].connect(
connecting_region=regions["Swamp Hero's Grave Region"],
@@ -1238,9 +1264,10 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions[dest_region],
name=portal_name + " (LS) " + ls_info.origin,
rule=lambda state: can_ladder_storage(state, world)
and (state.has("Ladders in South Atoll", player)
or not options.shuffle_ladders # if ladder shuffle is off, don't gotta worry about ladders
and (has_ladder("Ladders in South Atoll", state, world)
or state.has(key, player, 2) # can do it from the rope
# ice grapple push a crab into the door
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
or options.ladder_storage >= LadderStorage.option_medium)) # use the little ladder
# holy cross mid-ls to get in here
elif ls_info.destination == "Swamp Redux 2, Cathedral Redux_secret":
@@ -1249,7 +1276,7 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
connecting_region=regions[dest_region],
name=portal_name + " (LS) " + ls_info.origin,
rule=lambda state: can_ladder_storage(state, world) and has_ability(holy_cross, state, world)
and (state.has("Ladders in Swamp", player) or not options.shuffle_ladders))
and has_ladder("Ladders in Swamp", state, world))
else:
regions[ls_info.origin].connect(
connecting_region=regions[dest_region],
@@ -1275,7 +1302,15 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_
if world.options.combat_logic >= CombatLogic.option_bosses_only:
set_rule(wg_to_after_gk,
lambda state: state.has(laurels, player)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
or has_combat_reqs("Garden Knight", state, player))
# laurels past, or ice grapple it off, or ice grapple to it and fight
set_rule(after_gk_to_wg,
lambda state: state.has(laurels, player)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
or (has_ice_grapple_logic(False, IceGrappling.option_easy, state, world)
and has_combat_reqs("Garden Knight", state, player)))
if not world.options.hexagon_quest:
add_rule(heir_fight,
lambda state: has_combat_reqs("The Heir", state, player))
@@ -1565,10 +1600,13 @@ def set_er_location_rules(world: "TunicWorld") -> None:
# Ruined Atoll
set_rule(world.get_location("Ruined Atoll - [West] Near Kevin Block"),
lambda state: state.has(laurels, player))
# ice grapple push a crab through the door
set_rule(world.get_location("Ruined Atoll - [East] Locked Room Lower Chest"),
lambda state: state.has(laurels, player) or state.has(key, player, 2))
lambda state: state.has(laurels, player) or state.has(key, player, 2)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
set_rule(world.get_location("Ruined Atoll - [East] Locked Room Upper Chest"),
lambda state: state.has(laurels, player) or state.has(key, player, 2))
lambda state: state.has(laurels, player) or state.has(key, player, 2)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
# Frog's Domain
set_rule(world.get_location("Frog's Domain - Side Room Grapple Secret"),
@@ -1605,8 +1643,11 @@ def set_er_location_rules(world: "TunicWorld") -> None:
set_rule(world.get_location("Librarian - Hexagon Green"),
lambda state: has_sword(state, player)
and has_ladder("Ladders in Library", state, world))
# can ice grapple boss scav off the side
# the grapple from the other side of the bridge isn't in logic 'cause we don't have a misc tricks option
set_rule(world.get_location("Rooted Ziggurat Lower - Hexagon Blue"),
lambda state: has_sword(state, player))
lambda state: has_sword(state, player)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
# Swamp
set_rule(world.get_location("Cathedral Gauntlet - Gauntlet Reward"),
@@ -1666,7 +1707,13 @@ def set_er_location_rules(world: "TunicWorld") -> None:
# Bombable Walls
for location_name in bomb_walls:
set_rule(world.get_location(location_name), lambda state: state.has(gun, player) or can_shop(state, world))
set_rule(world.get_location(location_name),
lambda state: state.has(gun, player)
or can_shop(state, world)
or has_ice_grapple_logic(False, IceGrappling.option_hard, state, world))
# not enough space to ice grapple into here
set_rule(world.get_location("Quarry - [East] Bombable Wall"),
lambda state: state.has(gun, player) or can_shop(state, world))
# Shop
set_rule(world.get_location("Shop - Potion 1"),
@@ -1701,6 +1748,9 @@ def set_er_location_rules(world: "TunicWorld") -> None:
rule=lambda state: has_combat_reqs("The Librarian", state, player)
and has_ladder("Ladders in Library", state, world))
combat_logic_to_loc("Rooted Ziggurat Lower - Hexagon Blue", "Boss Scavenger", set_instead=True)
if world.options.ice_grappling >= IceGrappling.option_medium:
add_rule(world.get_location("Rooted Ziggurat Lower - Hexagon Blue"),
lambda state: has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
combat_logic_to_loc("Cathedral Gauntlet - Gauntlet Reward", "Gauntlet", set_instead=True)
if world.options.combat_logic == CombatLogic.option_on:

View File

@@ -81,6 +81,8 @@ easy_ls: List[LadderInfo] = [
LadderInfo("West Garden before Boss", "Archipelagos Redux, Overworld Redux_upper"),
# West Garden laurels exit
LadderInfo("West Garden after Terry", "Archipelagos Redux, Overworld Redux_lowest"),
# Magic dagger house, only relevant with combat logic on
LadderInfo("West Garden after Terry", "Archipelagos Redux, archipelagos_house_"),
# Atoll, use the little ladder you fix at the beginning
LadderInfo("Ruined Atoll", "Atoll Redux, Overworld Redux_lower"),
@@ -100,6 +102,9 @@ easy_ls: List[LadderInfo] = [
# Fortress main entry and grave path lower entry, ls at the ladder by the telescope
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress Main_Big Door"),
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress Reliquary_Lower"),
# Use the top of the ladder by the telescope
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress Reliquary_Upper"),
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress East_"),
# same as above, except from the east side of the area
LadderInfo("Fortress Exterior from East Forest", "Fortress Courtyard, Overworld Redux_"),
@@ -126,9 +131,7 @@ easy_ls: List[LadderInfo] = [
medium_ls: List[LadderInfo] = [
# region-destination versions of easy ls spots
LadderInfo("East Forest", "East Forest Dance Fox Spot", dest_is_region=True),
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard", dest_is_region=True),
LadderInfo("Fortress Exterior from East Forest", "Fortress Courtyard", dest_is_region=True),
LadderInfo("Fortress Exterior near cave", "Fortress Courtyard", "Ladder to Beneath the Vault", dest_is_region=True),
# fortress courtyard knockdowns are never logically relevant, the fuse requires upper
LadderInfo("Back of Swamp", "Swamp Mid", dest_is_region=True),
LadderInfo("Back of Swamp", "Swamp Front", dest_is_region=True),
@@ -139,8 +142,6 @@ medium_ls: List[LadderInfo] = [
LadderInfo("Forest Grave Path Main", "Sword Access, East Forest Redux_upper"),
# Upper exits from the courtyard. Use the ramp in the courtyard, then the blocks north of the first fuse
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress Reliquary_Upper"),
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress East_"),
LadderInfo("Fortress Exterior from Overworld", "Fortress Courtyard Upper", dest_is_region=True),
LadderInfo("Fortress Exterior from East Forest", "Fortress Courtyard, Fortress Reliquary_Upper"),
LadderInfo("Fortress Exterior from East Forest", "Fortress Courtyard, Fortress East_"),
@@ -175,8 +176,10 @@ hard_ls: List[LadderInfo] = [
LadderInfo("Beneath the Well Front", "Beneath the Well Back", "Ladders in Well", dest_is_region=True),
# go through the hexagon engraving above the vault door
LadderInfo("Frog's Domain Front", "frog cave main, Frog Stairs_Exit", "Ladders to Frog's Domain"),
# maybe a little egregious -- ice grapple a spider off, then use it to push into the portal room
LadderInfo("Rooted Ziggurat Lower Back", "ziggurat2020_3, ziggurat2020_FTRoom_"),
# the turret at the end here is not affected by enemy rando
LadderInfo("Frog's Domain Front", "Frog's Domain Back", "Ladders to Frog's Domain", dest_is_region=True),
# todo: see if we can use that new laurels strat here
# LadderInfo("Rooted Ziggurat Lower Back", "ziggurat2020_3, ziggurat2020_FTRoom_"),
# go behind the cathedral to reach the door, pretty easily doable
LadderInfo("Swamp Mid", "Swamp Redux 2, Cathedral Redux_main", "Ladders in Swamp"),
LadderInfo("Back of Swamp", "Swamp Redux 2, Cathedral Redux_main"),

View File

@@ -235,8 +235,8 @@ class IceGrappling(Choice):
Easy includes ice grappling enemies that are in range without luring them. May include clips through terrain.
Medium includes using ice grapples to push enemies through doors or off ledges without luring them. Also includes bringing an enemy over to the Temple Door to grapple through it.
Hard includes luring or grappling enemies to get to where you want to go.
The Medium and Hard options will give the player the Torch to return to the Overworld checkpoint to avoid softlocks.
Note: You will still be expected to ice grapple to the slime in East Forest even with this option off.
The Medium and Hard options will give the player the Torch to return to the Overworld checkpoint to avoid softlocks. Using the Torch is considered in logic.
Note: You will still be expected to ice grapple to the slime in East Forest from below with this option off.
"""
internal_name = "ice_grappling"
display_name = "Ice Grapple Logic"
@@ -272,7 +272,7 @@ class LadderStorageWithoutItems(Toggle):
If enabled, you will be expected to perform Ladder Storage without progression items.
This can be done with the plushie code, a Golden Coin, Prayer, and many other options.
This option has no effect if you do not have Ladder Storage Logic enabled
This option has no effect if you do not have Ladder Storage Logic enabled.
"""
internal_name = "ladder_storage_without_items"
display_name = "Ladder Storage without Items"

View File

@@ -27,10 +27,10 @@ green_hexagon = "Green Questagon"
blue_hexagon = "Blue Questagon"
gold_hexagon = "Gold Questagon"
# "Quarry - [East] Bombable Wall" is excluded from this list since it has slightly different rules
bomb_walls = ["East Forest - Bombable Wall", "Eastern Vault Fortress - [East Wing] Bombable Wall",
"Overworld - [Central] Bombable Wall", "Overworld - [Southwest] Bombable Wall Near Fountain",
"Quarry - [West] Upper Area Bombable Wall", "Quarry - [East] Bombable Wall",
"Ruined Atoll - [Northwest] Bombable Wall"]
"Quarry - [West] Upper Area Bombable Wall", "Ruined Atoll - [Northwest] Bombable Wall"]
def randomize_ability_unlocks(random: Random, options: TunicOptions) -> Dict[str, int]:
@@ -102,9 +102,11 @@ def set_region_rules(world: "TunicWorld") -> None:
lambda state: has_melee(state, player) or state.has(fire_wand, player)
world.get_entrance("Overworld -> Dark Tomb").access_rule = \
lambda state: has_lantern(state, world)
# laurels in, ladder storage in through the furnace, or ice grapple down the belltower
world.get_entrance("Overworld -> West Garden").access_rule = \
lambda state: state.has(laurels, player) \
or can_ladder_storage(state, world)
lambda state: (state.has(laurels, player)
or can_ladder_storage(state, world)
or has_ice_grapple_logic(False, IceGrappling.option_hard, state, world))
world.get_entrance("Overworld -> Eastern Vault Fortress").access_rule = \
lambda state: state.has(laurels, player) \
or has_ice_grapple_logic(True, IceGrappling.option_easy, state, world) \
@@ -275,10 +277,13 @@ def set_location_rules(world: "TunicWorld") -> None:
# Ruined Atoll
set_rule(world.get_location("Ruined Atoll - [West] Near Kevin Block"),
lambda state: state.has(laurels, player))
# ice grapple push a crab through the door
set_rule(world.get_location("Ruined Atoll - [East] Locked Room Lower Chest"),
lambda state: state.has(laurels, player) or state.has(key, player, 2))
lambda state: state.has(laurels, player) or state.has(key, player, 2)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
set_rule(world.get_location("Ruined Atoll - [East] Locked Room Upper Chest"),
lambda state: state.has(laurels, player) or state.has(key, player, 2))
lambda state: state.has(laurels, player) or state.has(key, player, 2)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
set_rule(world.get_location("Librarian - Hexagon Green"),
lambda state: has_sword(state, player))
@@ -347,8 +352,16 @@ def set_location_rules(world: "TunicWorld") -> None:
# Bombable Walls
for location_name in bomb_walls:
# has_sword is there because you can buy bombs in the shop
set_rule(world.get_location(location_name), lambda state: state.has(gun, player) or has_sword(state, player))
set_rule(world.get_location(location_name),
lambda state: state.has(gun, player)
or has_sword(state, player)
or has_ice_grapple_logic(False, IceGrappling.option_hard, state, world))
add_rule(world.get_location("Cube Cave - Holy Cross Chest"),
lambda state: state.has(gun, player)
or has_sword(state, player)
or has_ice_grapple_logic(False, IceGrappling.option_hard, state, world))
# can't ice grapple to this one, not enough space
set_rule(world.get_location("Quarry - [East] Bombable Wall"),
lambda state: state.has(gun, player) or has_sword(state, player))
# Shop