TUNIC: Logic Rules Redux (#3544)

* Clean these functions up, get the hell out of here 5 parameter function

* Clean up a bunch of rules that no longer need to be multi-lined since the functions are shorter

* Clean up some range functions

* Update to use world instead of player like Vi recommended

* Fix merge conflict

* Create new options

* Slightly revise ls rule

* Update options.py

* Update options.py

* Add tedious option for ls

* Update laurels zips description

* Create new options

* Slightly revise ls rule

* Update options.py

* Update options.py

* Add tedious option for ls

* Update laurels zips description

* Creating structures to redo ladder storage rules

* Put together overworld ladder groups, remove tedious

* Write up the rules for the regular rules

* Update slot data and UT stuff

* Put new ice grapple stuff in er rules

* Ice grapple hard to get to fountain cross room

* More ladder data

* Wrote majority of overworld ladder rules

* Finish the ladder storage rules

* Update notes

* Add note

* Add well rail to the rules

* More rules

* Comment out logically irrelevant entrances

* Update with laurels_zip helper

* Add parameter to has_ice_grapple_logic for difficulty

* Add new parameter to has_ice_grapple_logic

* Move ice grapple chest to lower forest in ER/ladders

* Fix rule

* Finishing out hooking the new rules into the code

* Fix bugs

* Add more hard ice grapples

* Fix more bugs

* Shops my beloved

* Change victory condition back

* Remove debug stuff

* Update plando connections description

* Fix extremely rare bug

* Add well front -> back hard ladder storages

* Note in ls rules about knocking yourself down with bombs being out of logic

* Add atoll fuse with wand + hard ls

* Add some nonsense that boils down to activating the fuse in overworld

* Further update LS description

* Fix missing logic on bridge switch chest in upper zig

* Revise upper zig rule change to account for ER

* Fix merge conflict

* Fix formatting, fix rule for heir access after merge

* Add the shop sword logic stuff in

* Remove todo that was already done

* Fill out a to-do with some cursed nonsense

* Fix event in wrong region

* Fix missing cathedral -> elevator connection

* Fix missing cathedral -> elevator connection

* Add ER exception to cathedral -> elevator

* Fix secret gathering place issue

* Fix incorrect ls rule

* Move 3 locations to Quarry Back since they're easily accessible from the back

* Also update non-er region

* Remove redundant parentheses

* Add new test for a weird edge case in ER

* Slight option description updates

* Use has_ladder in spots where it wasn't used for some reason, add a comment

* Fix unit test for ER

* Update per exempt's suggestion

* Add back LogicRules as an invisible option, to not break old yamls

* Remove unused elevation from portal class

* Update ladder storage without items description

* Remove shop_scene stuff since it's no longer relevant in the mod by the time this version comes out

* Remove shop scene stuff from game info since it's no longer relevant in the mod by the time this comes out

* Update portal list to match main

* god I love github merging things

* Remove note

* Add ice grapple hard path from upper overworld to temple rafters entrance

* Actually that should be medium

* Remove outdated note

* Add ice grapple hard for swamp mid to the ledge

* Add missing laurels zip in swamp

* Some fixes to the ladder storage data while reviewing it

* Add unit test for weird edge case

* Backport outlet region system to fix ls bug

* Fix incorrect ls, add todo

* Add missing swamp ladder storage connections

* Add swamp zip to er data

* Add swamp zip to er rules

* Add hard ice grapple for forest grave path main to upper

* Add ice grapple logic for all bomb walls except the east quarry one

* Add ice grapple logic for frog stairs eye to mouth without the ladder

* Add hard ice grapple for overworld to the stairs to west garden

* Add the ice grapple boss quick kills to medium ice grappling

* Add the reverse connection for the ice grapple kill on Garden Knight

* Add atoll house ice grapple push, and add west garden ice grapple entry to the regular rules
This commit is contained in:
Scipio Wright
2024-09-08 08:42:59 -04:00
committed by GitHub
parent a652108472
commit dad228cd4a
12 changed files with 1042 additions and 698 deletions

View File

@@ -3,7 +3,7 @@ from typing import Dict, TYPE_CHECKING
from worlds.generic.Rules import set_rule, forbid_item, add_rule
from BaseClasses import CollectionState
from .options import TunicOptions
from .options import TunicOptions, LadderStorage, IceGrappling
if TYPE_CHECKING:
from . import TunicWorld
@@ -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]:
@@ -64,32 +64,33 @@ def has_sword(state: CollectionState, player: int) -> bool:
return state.has("Sword", player) or state.has("Sword Upgrade", player, 2)
def has_ice_grapple_logic(long_range: bool, state: CollectionState, world: "TunicWorld") -> bool:
player = world.player
if not world.options.logic_rules:
def laurels_zip(state: CollectionState, world: "TunicWorld") -> bool:
return world.options.laurels_zips and state.has(laurels, world.player)
def has_ice_grapple_logic(long_range: bool, difficulty: IceGrappling, state: CollectionState, world: "TunicWorld") -> bool:
if world.options.ice_grappling < difficulty:
return False
if not long_range:
return state.has_all({ice_dagger, grapple}, player)
return state.has_all({ice_dagger, grapple}, world.player)
else:
return state.has_all({ice_dagger, fire_wand, grapple}, player) and has_ability(icebolt, state, world)
return state.has_all({ice_dagger, fire_wand, grapple}, world.player) and has_ability(icebolt, state, world)
def can_ladder_storage(state: CollectionState, world: "TunicWorld") -> bool:
return world.options.logic_rules == "unrestricted" and has_stick(state, world.player)
if not world.options.ladder_storage:
return False
if world.options.ladder_storage_without_items:
return True
return has_stick(state, world.player) or state.has(grapple, world.player)
def has_mask(state: CollectionState, world: "TunicWorld") -> bool:
if world.options.maskless:
return True
else:
return state.has(mask, world.player)
return world.options.maskless or state.has(mask, world.player)
def has_lantern(state: CollectionState, world: "TunicWorld") -> bool:
if world.options.lanternless:
return True
else:
return state.has(lantern, world.player)
return world.options.lanternless or state.has(lantern, world.player)
def set_region_rules(world: "TunicWorld") -> None:
@@ -102,12 +103,14 @@ def set_region_rules(world: "TunicWorld") -> None:
lambda state: has_stick(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, state, world) \
or has_ice_grapple_logic(True, IceGrappling.option_easy, state, world) \
or can_ladder_storage(state, world)
# using laurels or ls to get in is covered by the -> Eastern Vault Fortress rules
world.get_entrance("Overworld -> Beneath the Vault").access_rule = \
@@ -124,8 +127,8 @@ def set_region_rules(world: "TunicWorld") -> None:
world.get_entrance("Lower Quarry -> Rooted Ziggurat").access_rule = \
lambda state: state.has(grapple, player) and has_ability(prayer, state, world)
world.get_entrance("Swamp -> Cathedral").access_rule = \
lambda state: state.has(laurels, player) and has_ability(prayer, state, world) \
or has_ice_grapple_logic(False, state, world)
lambda state: (state.has(laurels, player) and has_ability(prayer, state, world)) \
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
world.get_entrance("Overworld -> Spirit Arena").access_rule = \
lambda state: ((state.has(gold_hexagon, player, options.hexagon_goal.value) if options.hexagon_quest.value
else state.has_all({red_hexagon, green_hexagon, blue_hexagon}, player)
@@ -133,10 +136,18 @@ def set_region_rules(world: "TunicWorld") -> None:
and has_ability(prayer, state, world) and has_sword(state, player)
and state.has_any({lantern, laurels}, player))
world.get_region("Quarry").connect(world.get_region("Rooted Ziggurat"),
rule=lambda state: has_ice_grapple_logic(True, IceGrappling.option_hard, state, world)
and has_ability(prayer, state, world))
if options.ladder_storage >= LadderStorage.option_medium:
# ls at any ladder in a safe spot in quarry to get to the monastery rope entrance
world.get_region("Quarry Back").connect(world.get_region("Monastery"),
rule=lambda state: can_ladder_storage(state, world))
def set_location_rules(world: "TunicWorld") -> None:
player = world.player
options = world.options
forbid_item(world.get_location("Secret Gathering Place - 20 Fairy Reward"), fairies, player)
@@ -147,11 +158,13 @@ def set_location_rules(world: "TunicWorld") -> None:
lambda state: has_ability(prayer, state, world)
or state.has(laurels, player)
or can_ladder_storage(state, world)
or (has_ice_grapple_logic(True, state, world) and has_lantern(state, world)))
or (has_ice_grapple_logic(True, IceGrappling.option_easy, state, world)
and has_lantern(state, world)))
set_rule(world.get_location("Fortress Courtyard - Page Near Cave"),
lambda state: has_ability(prayer, state, world) or state.has(laurels, player)
or can_ladder_storage(state, world)
or (has_ice_grapple_logic(True, state, world) and has_lantern(state, world)))
or (has_ice_grapple_logic(True, IceGrappling.option_easy, state, world)
and has_lantern(state, world)))
set_rule(world.get_location("East Forest - Dancing Fox Spirit Holy Cross"),
lambda state: has_ability(holy_cross, state, world))
set_rule(world.get_location("Forest Grave Path - Holy Cross Code by Grave"),
@@ -186,17 +199,17 @@ def set_location_rules(world: "TunicWorld") -> None:
lambda state: state.has(laurels, player))
set_rule(world.get_location("Old House - Normal Chest"),
lambda state: state.has(house_key, player)
or has_ice_grapple_logic(False, state, world)
or (state.has(laurels, player) and options.logic_rules))
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
or laurels_zip(state, world))
set_rule(world.get_location("Old House - Holy Cross Chest"),
lambda state: has_ability(holy_cross, state, world) and (
state.has(house_key, player)
or has_ice_grapple_logic(False, state, world)
or (state.has(laurels, player) and options.logic_rules)))
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
or laurels_zip(state, world)))
set_rule(world.get_location("Old House - Shield Pickup"),
lambda state: state.has(house_key, player)
or has_ice_grapple_logic(False, state, world)
or (state.has(laurels, player) and options.logic_rules))
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
or laurels_zip(state, world))
set_rule(world.get_location("Overworld - [Northwest] Page on Pillar by Dark Tomb"),
lambda state: state.has(laurels, player))
set_rule(world.get_location("Overworld - [Southwest] From West Garden"),
@@ -206,7 +219,7 @@ def set_location_rules(world: "TunicWorld") -> None:
or (has_lantern(state, world) and has_sword(state, player))
or can_ladder_storage(state, world))
set_rule(world.get_location("Overworld - [Northwest] Chest Beneath Quarry Gate"),
lambda state: state.has_any({grapple, laurels}, player) or options.logic_rules)
lambda state: state.has_any({grapple, laurels}, player))
set_rule(world.get_location("Overworld - [East] Grapple Chest"),
lambda state: state.has(grapple, player))
set_rule(world.get_location("Special Shop - Secret Page Pickup"),
@@ -215,11 +228,11 @@ def set_location_rules(world: "TunicWorld") -> None:
lambda state: has_ability(holy_cross, state, world)
and (state.has(laurels, player) or (has_lantern(state, world) and (has_sword(state, player)
or state.has(fire_wand, player)))
or has_ice_grapple_logic(False, state, world)))
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)))
set_rule(world.get_location("Sealed Temple - Page Pickup"),
lambda state: state.has(laurels, player)
or (has_lantern(state, world) and (has_sword(state, player) or state.has(fire_wand, player)))
or has_ice_grapple_logic(False, state, world))
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
set_rule(world.get_location("West Furnace - Lantern Pickup"),
lambda state: has_stick(state, player) or state.has_any({fire_wand, laurels}, player))
@@ -254,7 +267,7 @@ def set_location_rules(world: "TunicWorld") -> None:
lambda state: state.has(laurels, player) and has_ability(holy_cross, state, world))
set_rule(world.get_location("West Garden - [East Lowlands] Page Behind Ice Dagger House"),
lambda state: (state.has(laurels, player) and has_ability(prayer, state, world))
or has_ice_grapple_logic(True, state, world))
or has_ice_grapple_logic(True, IceGrappling.option_easy, state, world))
set_rule(world.get_location("West Garden - [Central Lowlands] Below Left Walkway"),
lambda state: state.has(laurels, player))
set_rule(world.get_location("West Garden - [Central Highlands] After Garden Knight"),
@@ -265,12 +278,15 @@ 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) or options.logic_rules)
lambda state: has_sword(state, player))
# Frog's Domain
set_rule(world.get_location("Frog's Domain - Side Room Grapple Secret"),
@@ -285,10 +301,12 @@ def set_location_rules(world: "TunicWorld") -> None:
lambda state: state.has(laurels, player))
set_rule(world.get_location("Fortress Arena - Siege Engine/Vault Key Pickup"),
lambda state: has_sword(state, player)
and (has_ability(prayer, state, world) or has_ice_grapple_logic(False, state, world)))
and (has_ability(prayer, state, world)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)))
set_rule(world.get_location("Fortress Arena - Hexagon Red"),
lambda state: state.has(vault_key, player)
and (has_ability(prayer, state, world) or has_ice_grapple_logic(False, state, world)))
and (has_ability(prayer, state, world)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)))
# Beneath the Vault
set_rule(world.get_location("Beneath the Fortress - Bridge"),
@@ -301,14 +319,14 @@ def set_location_rules(world: "TunicWorld") -> None:
lambda state: state.has(laurels, player))
set_rule(world.get_location("Rooted Ziggurat Upper - Near Bridge Switch"),
lambda state: has_sword(state, player) or state.has_all({fire_wand, laurels}, player))
# nmg - kill boss scav with orb + firecracker, or similar
set_rule(world.get_location("Rooted Ziggurat Lower - Hexagon Blue"),
lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules))
lambda state: has_sword(state, player))
# Swamp
set_rule(world.get_location("Cathedral Gauntlet - Gauntlet Reward"),
lambda state: (state.has(fire_wand, player) and has_sword(state, player))
and (state.has(laurels, player) or has_ice_grapple_logic(False, state, world)))
and (state.has(laurels, player)
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)))
set_rule(world.get_location("Swamp - [Entrance] Above Entryway"),
lambda state: state.has(laurels, player))
set_rule(world.get_location("Swamp - [South Graveyard] Upper Walkway Dash Chest"),
@@ -335,8 +353,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