Files
dockipelago/worlds/tloz_st/data/LogicPredicates.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

574 lines
21 KiB
Python

from BaseClasses import CollectionState
from Options import Accessibility
from .Constants import *
from .Items import ITEMS
# =========== Item States =============
def st_has_stamp_book(state, player):
return state.has("Stamp Book", player)
def st_has_spirit_flute(state, player):
return state.has("Spirit Flute", player)
def st_has_sword(state: CollectionState, player: int):
return state.has("Sword (Progressive)", player) or state.has("Sword", player)
def st_has_shield(state: CollectionState, player: int):
# Shield can be bought from shop
return state.has("Shield", player)
def st_has_bow(state: CollectionState, player: int):
return state.has("Bow (Progressive)", player)
def st_has_bombs(state: CollectionState, player: int):
return state.has("Bombs (Progressive)", player)
def st_has_whirlwind(state: CollectionState, player: int):
return state.has("Whirlwind", player)
def st_has_whip(state: CollectionState, player: int):
return state.has("Whip", player)
def st_has_boomerang(state: CollectionState, player: int):
return state.has("Boomerang", player)
def st_has_sand_wand(state: CollectionState, player: int):
return state.has("Sand Wand", player)
def st_has_sword_beam_scroll(state: CollectionState, player: int):
return state.has("Sword Beam Scroll", player)
def st_has_regal_necklace(state: CollectionState, player: int):
return state.has("Regal Necklace", player)
def st_has_wood_heart(state: CollectionState, player: int):
return state.has("Wood Heart", player)
def st_has_net(state: CollectionState, player: int):
return state.has("Rabbit Net", player) and st_has_cannon(state, player)
def st_has_compass_of_light(state, player):
required_shards = state.multiworld.worlds[player].options.compass_shard_count.value
return state.has("Compass of Light", player) or state.has("Compass of Light Shard", player, required_shards)
def st_has_wagon(state, player):
return state.has("Wagon", player)
def st_has_cargo(state, player, cargo, event):
return st_has_wagon(state, player) and (
state.has(f"Cargo: {cargo}", player)
or state.has(event, player))
## ========== Rabbits ===========
def st_has_rabbit_items(state, player, realm, count=10):
rabbit_count = state.count(f"{realm} Rabbit", player)
return rabbit_count >= count
def st_has_total_rabbits(state: CollectionState, player: int, count):
rabbit_total = sum([state.count(f"{realm} Rabbit", player) for realm in rabbit_realms])
return rabbit_total >= count
def st_caught_rabbits(state, player, realm, count):
return state.has(f"_caught_{realm.lower()}_rabbits", player, count)
def st_all_types_rabbits(state, player, count):
return all([st_has_rabbit_items(state, player, r, count) for r in rabbit_realms])
## ========= Rail Items =============
def st_has_glyph(state: CollectionState, player: int, realm: str):
return state.has_group(f"Tracks: {realm} Glyph", player)
def st_has_cannon(state: CollectionState, player: int):
return state.has("Cannon", player)
def st_train_access(state, player):
return any([
not st_option_train_requires_forest_glyph(state, player),
st_has_glyph(state, player, "Forest")
])
def st_has_source(state: CollectionState, player: int, realm: str):
return state.has_group(f"Tracks: {realm} Source", player)
def st_has_temple_tracks(state, player, temple):
return state.has_group(f"Tracks: {temple} Temple Tracks", player)
def st_has_misc_tracks(state: "CollectionState", player, tracks):
return state.has_group(f"Tracks: {tracks}", player)
def st_has_portal(state, player, portal, forward):
if state.multiworld.worlds[player].options.portal_behavior.value == 1:
return True
if state.multiworld.worlds[player].options.portal_behavior.value == 0:
return forward and st_has_cannon(state, player)
return state.has(f"Portal Unlock: {portal}", player) and (not forward or st_has_cannon(state, player))
def st_soft_cannon(state, player):
return any([
st_has_cannon(state, player),
state.has("_UT_Glitched_Logic", player),
state.multiworld.worlds[player].options.cannon_logic == "no_logic"])
# ============== Songs =======================
def st_has_awakening_song(state: CollectionState, player: int):
return state.has("Song of Awakening", player) and st_has_spirit_flute(state, player)
def st_has_healing_song(state: CollectionState, player: int):
return state.has("Song of Healing", player) and st_has_spirit_flute(state, player)
def st_has_birds_song(state: CollectionState, player: int):
return state.has("Song of Birds", player) and st_has_spirit_flute(state, player)
def st_has_light_song(state: CollectionState, player: int):
return state.has("Song of Light", player) and st_has_spirit_flute(state, player)
def st_has_discovery_song(state: CollectionState, player: int):
return state.has("Song of Discovery", player) and st_has_spirit_flute(state, player)
def st_has_tears(state: CollectionState, player: int, section: int):
options = state.multiworld.worlds[player].options
section_count = min(state.multiworld.worlds[player].sections_included, 5)
if options.shuffle_tos_sections and options.tear_sections == "progressive":
section = state.multiworld.worlds[player].tower_section_lookup[section]
# print(f"Section lookup {state.multiworld.worlds[player].tower_section_lookup}")
return any([
state.has(f"Tear of Light (ToS {section})", player, 3),
state.has(f"Big Tear of Light (ToS {section})", player),
state.has(f"Tear of Light (Progressive)", player, section*3),
state.has(f"Tear of Light (Progressive)", player, section_count*3 + 1),
state.has(f"Big Tear of Light (Progressive)", player, section),
state.has(f"Tear of Light (All Sections)", player, 3),
state.has(f"Big Tear of Light (All Sections)", player),
])
def st_has_bow_of_light(state, player):
section_count = min(state.multiworld.worlds[player].sections_included, 5)
return any([
state.has("Bow of Light", player) and st_has_bow(state, player),
state.has(f"Tear of Light (Progressive)", player, section_count*3 + 1),
state.has(f"Big Tear of Light (Progressive)", player, section_count+1),
state.has(f"Tear of Light (All Sections)", player, 4),
state.has(f"Big Tear of Light (All Sections)", player, 2),
])
def st_can_possess_phantoms(state: CollectionState, player: int, floor: int):
return any([
state.has("Sword (Progressive)", player, 2),
st_has_bow_of_light(state, player),
st_has_sword(state, player) and st_has_tears(state, player, floor)
])
def st_vanilla_tears(state: CollectionState, player: int):
return st_has_sword(state, player) and state.multiworld.worlds[player].options.randomize_tears.value == -1
# =========== Combined item states ================
def st_has_good_damage(state: CollectionState, player: int):
return any([
state.has("Sword (Progressive)", player),
state.has("Bombs (Progressive)", player),
state.has("Bow (Progressive)", player),
])
def st_has_damage(state: CollectionState, player: int):
return any([
st_has_good_damage(state, player),
state.has("Whip", player),
])
def st_can_kill_bat(state: CollectionState, player: int):
return any([
st_has_damage(state, player),
st_has_boomerang(state, player)
])
def st_can_kill_bubble(state: CollectionState, player: int):
return any([
st_has_bombs(state, player),
st_has_bow(state, player),
st_has_whip(state, player),
all([
st_has_sword(state, player), any([
st_has_boomerang(state, player),
st_has_whirlwind(state, player),
])
])
])
def st_has_range(state: CollectionState, player: int):
return state.has_any(["Boomerang", "Bow (Progressive)"], player)
def st_has_range_objects(state, player):
return state.has_any(["Boomerang", "Bow (Progressive)", "Whirlwind"], player)
def st_has_short_range(state: CollectionState, player: int):
return any([st_has_mid_range(state, player),
st_clever_bombs(state, player), ])
def st_can_rotate_repeater(state, player):
return any([
st_has_sword(state, player),
st_has_boomerang(state, player),
st_has_whip(state, player)
])
def st_has_mid_range(state: CollectionState, player: int):
return any([st_has_range(state, player), st_has_whip(state, player),
st_has_beam_sword(state, player)])
def st_has_beam_sword(state: CollectionState, player: int):
return all([
st_has_sword(state, player),
st_has_sword_beam_scroll(state, player)
])
def st_can_ring_bell(state: CollectionState, player: int):
return any([st_has_sword(state, player), st_has_boomerang(state, player)])
def st_can_kill_freezards(state, player):
return all([
any([
st_has_shield(state, player),
st_has_bow_of_light(state, player),
st_option_hard_logic(state, player)
]),
st_has_damage(state, player)
])
def st_can_kill_freezards_torch(state, player):
return all([
any([
st_has_boomerang(state, player),
st_has_shield(state, player),
st_has_bow_of_light(state, player),
st_option_hard_logic(state, player),
]),
st_has_damage(state, player)
])
# ================ Rupee States ==================
def st_has_rupees(state: CollectionState, player: int, cost: int):
# If has a farmable minigame and the means to sell, expensive things are in logic.
options = state.multiworld.worlds[player].options
treasure_count = state.count("Treasure", player) - 2500 if state.has("_can_sell_treasure", player) else 0
return any([
state.has("_UT_Glitched_Logic", player),
all([
state.has("_rupee_farming_spot", player),
options.excess_random_treasure.value == 2,
options.rupee_farming_logic.value == 1
]),
all([
state.has("_rupee_farming_spot", player),
state.has("_can_sell_treasure", player),
options.excess_random_treasure.value == 1,
options.rupee_farming_logic.value == 1
]),
state.count("Rupees", player) + treasure_count > cost
])
# ============ Option states =============
def st_option_glitched_logic(state: CollectionState, player: int):
return state.multiworld.worlds[player].options.logic == "glitched" or state.has("_UT_Glitched_Logic", player)
def st_option_normal_logic(state: CollectionState, player: int):
return state.multiworld.worlds[player].options.logic == "normal"
def st_option_hard_logic(state: CollectionState, player: int):
return state.multiworld.worlds[player].options.logic in ["hard", "glitched"] or state.has("_UT_Glitched_Logic", player)
def st_option_not_glitched_logic(state: CollectionState, player: int):
return state.multiworld.worlds[player].options.logic in ["hard", "normal"]
def st_option_keysanity(state: CollectionState, player: int):
return state.multiworld.worlds[player].options.keysanity == "anywhere"
def st_option_keys_vanilla(state: CollectionState, player: int):
return state.multiworld.worlds[player].options.keysanity == "vanilla"
def st_option_keys_in_own_dungeon(state: CollectionState, player: int):
return state.multiworld.worlds[player].options.keysanity in ["in_own_dungeon", "vanilla"]
# def st_option_stantoms_hard(state: CollectionState, player: int):
# return all([state.multiworld.worlds[player].options.stantom_combat_difficulty in ["require_weapon"],
# st_has_whip(state, player)])
# def st_option_stantoms_med(state: CollectionState, player: int):
# return all([state.multiworld.worlds[player].options.stantom_combat_difficulty in ["require_weapon", "require_stun"],
# any([
# st_has_bow(state, player),
# st_has_hammer(state, player),
# st_has_fire_sword(state, player)
# ])])
#
#
# def st_option_stantoms_easy(state: CollectionState, player: int):
# return (state.multiworld.worlds[player].options.stantom_combat_difficulty in
# ["require_weapon", "require_stun", "require_traps"])
#
# def st_option_stantoms_sword_only(state: CollectionState, player: int):
# return all([state.multiworld.worlds[player].options.stantom_combat_difficulty in
# ["require_weapon", "require_stun", "require_traps", "require_stantom_sword"],
# st_has_stantom_sword(state, player)])
def st_clever_pots(state: CollectionState, player: int):
return st_option_hard_logic(state, player)
def st_can_hit_switches(state: CollectionState, player: int):
return any([st_option_hard_logic(state, player), st_can_kill_bat(state, player)])
def st_clever_bombs(state: CollectionState, player: int):
return all([st_has_bombs(state, player),
st_option_hard_logic(state, player)])
#
# def st_option_randomize_frogs(state: CollectionState, player: int):
# return state.multiworld.worlds[player].options.randomize_frogs == "randomize"
#
#
# def st_option_start_with_frogs(state: CollectionState, player: int):
# return state.multiworld.worlds[player].options.randomize_frogs == "start_with"
# def st_beat_required_dungeons(state: CollectionState, player: int):
# # print(f"Dungeons required: {state.count_group('Current Metals', player)}/{state.multiworld.worlds[player].options.dungeons_required} {state.has_group("Current Metals", player,
# # state.multiworld.worlds[player].options.dungeons_required)}")
# return state.has_group("Current Metals", player,
# state.multiworld.worlds[player].options.dungeons_required)
def st_option_train_requires_forest_glyph(state: CollectionState, player: int):
#state.multiworld.worlds[player].options.train_requires_forest_glyph
return True
# For doing sneaky stuff with universal tracker UT
def st_is_ut(state: CollectionState, player: int):
return getattr(state.multiworld, "generation_is_fake", False)
# ============= Key logic ==============
def st_has_small_keys(state: CollectionState, player: int, dung_name: str, amount: int = 1, ool: int = 4):
return any([
state.has(f"Small Key ({dung_name})", player, amount),
state.has(f"Keyring ({dung_name})", player),
( state.has("_UT_Glitched_Logic", player)
and state.has(f"Small Key ({dung_name})", player, ool)
)
])
def st_has_boss_key(state: CollectionState, player: int, dung_name: str):
return state.has(f"Boss Key ({dung_name})", player) or (
state.has(f"Keyring ({dung_name})", player) and state.multiworld.worlds[player].options.big_keyrings)
#def st_has_boss_key_simple(state: CollectionState, player: int, dung_name: str):
# return any([
# st_has_boss_key(state, player, dung_name),
# st_is_ut(state, player)
# ])
def st_ut_small_key_vanilla_location(state, player):
return all([
st_is_ut(state, player),
st_option_keys_vanilla(state, player)
])
def st_ut_small_key_own_dungeon(state, player):
return all([
st_is_ut(state, player),
st_option_keys_in_own_dungeon(state, player)
])
# ======== Harder Logic ===========
def st_can_hit_tricky_switches(state: CollectionState, player: int):
return any([
st_clever_pots(state, player),
st_has_short_range(state, player)
])
def st_can_boomerang_return(state: CollectionState, player: int):
return all([
st_has_boomerang(state, player),
st_option_glitched_logic(state, player)
])
def st_can_arrow_despawn(state: CollectionState, player: int):
return all([
st_has_bow(state, player),
st_option_glitched_logic(state, player)
])
def st_can_sword_glitch(state, player):
return all([
st_has_sword(state, player),
st_option_glitched_logic(state, player)
])
def st_can_sword_scroll_clip(state, player):
return all([
st_has_sword(state, player),
state.has("Swordsman's Scroll", player),
st_option_glitched_logic(state, player)
])
def st_has_train(state, player):
return all([st_has_glyph(state, player, "Forest"),
any([
st_has_cannon(state, player),
state.multiworld.worlds[player].options.cannon_logic.value > 1,
state.multiworld.worlds[player].options.cannon_logic.value > 0 and state.has("_UT_Glitched_Logic", player)
]),
])
# ====== Specific locations =============
# Overworld
def st_castle_town_cuccos(state, player):
return st_has_birds_song(state, player) or (st_has_whirlwind(state, player) and st_option_hard_logic(state, player))
def st_has_dungeon_rewards(state, player):
if state.multiworld.worlds[player].options.dark_realm_access not in ["dungeons", "both"]:
return True
dungeon_count = state.multiworld.worlds[player].options.dungeons_required.value
return state.has("_dungeon_reward", player, dungeon_count)
def st_can_fight_malladus(state, player):
return st_has_sword(state, player) and st_has_bow_of_light(state, player)
def st_can_enter_tos(state, player):
options = state.multiworld.worlds[player].options
return any([
options.tos_unlock_base_item.value == 0,
options.tos_unlock_base_item.value == 1 and (
state.has("Tower of Spirits Base", player) or
state.has("Progressive ToS Section", player)
)
])
def st_can_enter_tos_section(state, player, section):
sources = [None, "Forest", "Snow", "Ocean", "Fire"]
# print(f"Section: {section}")
if section == 1:
return st_can_enter_tos(state, player)
options = state.multiworld.worlds[player].options
# print(f"Open Tower: {options.tos_section_unlocks.value == 0}\n"
# f"Has Source {sources[section-1]} {st_has_source(state, player, sources[section-1])} and {options.tos_section_unlocks.value == 1}\n"
# f"Progressive: {options.tos_section_unlocks.value == 2} and:\n"
# f"\tProgressive Sections {state.has('Progressive ToS Section', player, section) and options.tos_unlock_base_item.value == 1}\n"
# f"\tNo Base {state.has('Progressive ToS Section', player, section) and options.tos_unlock_base_item.value == 1}\n"
# f"""Total: {any([
# options.tos_section_unlocks.value == 0,
# all([st_has_source(state, player, sources[section-1]), options.tos_section_unlocks.value == 1]),
# options.tos_section_unlocks.value == 2 and any([
# state.has("Progressive ToS Section", player, section) and options.tos_unlock_base_item.value == 1,
# state.has("Progressive ToS Section", player, section-1) and options.tos_unlock_base_item.value == 0,
# ])
# ])}""")
return any([
options.tos_section_unlocks.value == 0, # Open tower
all([st_has_source(state, player, sources[section-1]), options.tos_section_unlocks.value == 1]),
options.tos_section_unlocks.value == 2 and any([
state.has("Progressive ToS Section", player, section) and options.tos_unlock_base_item.value == 1,
state.has("Progressive ToS Section", player, section-1) and options.tos_unlock_base_item.value == 0,
])
])
def st_desert_temple_keys(state, player):
return st_has_small_keys(state, player, "Desert Temple", 2, 1)
def st_mtt_center(state, player):
return any([
all([
st_has_small_keys(state, player, "Mountain Temple", 2),
any([
st_has_boomerang(state, player),
st_has_bombs(state, player),
all([
st_option_hard_logic(state, player),
st_has_bow(state, player) or st_has_beam_sword(state, player)
])
])
]),
all([
st_option_glitched_logic(state, player),
any([
all([
st_has_small_keys(state, player, "Mountain Temple", 2, 1),
st_has_boomerang(state, player)
]),
st_has_bombs(state, player) # New rta method
])
])
])
def st_tos_14f_glitched(state, player):
return any([
st_has_small_keys(state, player, "ToS 4", 2, 1),
all([
st_option_glitched_logic(state, player),
st_has_small_keys(state, player, "ToS 4", 0),
st_has_boomerang(state, player) or st_has_whip(state, player)
])
])
def st_tos_15f_glitched(state, player):
return any([
all([
st_has_range(state, player) or st_has_beam_sword(state, player),
st_has_small_keys(state, player, "ToS 4", 3, 2)
]),
all([
st_option_glitched_logic(state, player),
st_has_small_keys(state, player, "ToS 4", 3, 1),
any([
st_has_range(state, player),
st_has_beam_sword(state, player),
st_has_bombs(state, player) and st_has_whirlwind(state, player)
])
])
])
def st_hard_birds(state, player):
return all([
st_has_whip(state, player),
st_has_birds_song(state, player) or st_option_hard_logic(state, player)
])
def st_has_passenger(state, player, passenger, event):
return state.has(f"Passenger: {passenger}", player) or state.has(event, player)