Files
dockipelago/worlds/tloz_oos/data/logic/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

1389 lines
37 KiB
Python

from rule_builder.rules import And, Or, CanReachRegion
from .Rulebuilder import *
from ..Constants import *
from ...Options import OracleOfSeasonsLogicDifficulty, OracleOfSeasonsDefaultSeedType, OracleOfSeasonsMasterKeys, OracleOfSeasonsDungeonShuffle, \
OracleOfSeasonsRemoveD0AltEntrance, OracleOfSeasonsRemoveD2AltEntrance, OracleOfSeasonsAnimalCompanion, OracleOfSeasonsLostWoodsItemSequence, \
OracleOfSeasonsLostWoodsMainSequence, OracleOfSeasonsHoronSeason
# Items predicates ############################################################
def oos_has_sword(accept_biggoron: bool = True) -> Rule:
return Or(
Has("Progressive Sword"),
And(
from_bool(accept_biggoron),
Has("Biggoron's Sword")
)
)
def oos_has_noble_sword() -> Rule:
return Has("Progressive Sword", 2)
def oos_has_shield() -> Rule:
return Has("Progressive Shield")
def oos_has_fools_ore() -> Rule:
return Has("Fool's Ore")
def oos_has_feather() -> Rule:
return Has("Progressive Feather")
def oos_has_cape() -> Rule:
return Has("Progressive Feather", 2)
def oos_has_satchel(level: int = 1) -> Rule:
return Has("Seed Satchel", level)
def oos_has_slingshot() -> Rule:
return Has("Progressive Slingshot")
def oos_has_hyper_slingshot() -> Rule:
return Has("Progressive Slingshot", 2)
def oos_has_boomerang() -> Rule:
return Has("Progressive Boomerang")
def oos_has_magic_boomerang() -> Rule:
return Has("Progressive Boomerang", 2)
def oos_has_bracelet() -> Rule:
return Has("Power Bracelet")
def oos_has_shovel() -> Rule:
return Has("Shovel")
def oos_has_flippers() -> Rule:
return Has("Flippers")
# Cross items
def oos_has_cane() -> Rule:
return Has("Cane of Somaria")
def oos_has_switch_hook(level: int = 1) -> Rule:
return Has("Switch Hook", level)
def oos_has_tight_switch_hook() -> Rule:
return Or(
oos_has_switch_hook(2),
And(
oos_option_medium_logic(),
oos_has_switch_hook()
)
)
def oos_has_shooter() -> Rule:
return Has("Seed Shooter")
def oos_has_seed_thrower() -> Rule:
return Or(
oos_has_slingshot(),
oos_has_shooter()
)
def oos_has_season(season: int) -> Rule:
return Has(SEASON_ITEMS[season])
def oos_has_summer() -> Rule:
return Has(SEASON_ITEMS[SEASON_SUMMER])
def oos_has_spring() -> Rule:
return Has(SEASON_ITEMS[SEASON_SPRING])
def oos_has_winter() -> Rule:
return Has(SEASON_ITEMS[SEASON_WINTER])
def oos_has_autumn() -> Rule:
return Has(SEASON_ITEMS[SEASON_AUTUMN])
def oos_has_magnet_gloves() -> Rule:
return Has("Magnetic Gloves")
def oos_has_ember_seeds() -> Rule:
return Or(
Has("Ember Seeds"),
from_option(OracleOfSeasonsDefaultSeedType, OracleOfSeasonsDefaultSeedType.option_ember),
And(
Has("_wild_ember_seeds"),
oos_option_medium_logic()
)
)
def oos_has_scent_seeds() -> Rule:
return Or(
Has("Scent Seeds"),
from_option(OracleOfSeasonsDefaultSeedType, OracleOfSeasonsDefaultSeedType.option_scent),
)
def oos_has_pegasus_seeds() -> Rule:
return Or(
Has("Pegasus Seeds"),
from_option(OracleOfSeasonsDefaultSeedType, OracleOfSeasonsDefaultSeedType.option_pegasus)
)
def oos_has_mystery_seeds() -> Rule:
return Or(
Has("Mystery Seeds"),
from_option(OracleOfSeasonsDefaultSeedType, OracleOfSeasonsDefaultSeedType.option_mystery),
And(
Has("_wild_mystery_seeds"),
oos_option_medium_logic()
)
)
def oos_has_gale_seeds() -> Rule:
return Or(
Has("Gale Seeds"),
from_option(OracleOfSeasonsDefaultSeedType, OracleOfSeasonsDefaultSeedType.option_gale)
)
def oos_has_small_keys(dungeon_id: int, amount: int = 1) -> Rule:
return Or(
Has(f"Small Key ({DUNGEON_NAMES[dungeon_id]})", amount,
options=[OptionFilter(OracleOfSeasonsMasterKeys, OracleOfSeasonsMasterKeys.option_disabled)]),
Has(f"Master Key ({DUNGEON_NAMES[dungeon_id]})",
options=[OptionFilter(OracleOfSeasonsMasterKeys, OracleOfSeasonsMasterKeys.option_disabled, "ne")]),
)
def oos_has_boss_key(dungeon_id: int) -> Rule:
return Or(
Has(f"Boss Key ({DUNGEON_NAMES[dungeon_id]})",
options=[OptionFilter(OracleOfSeasonsMasterKeys, OracleOfSeasonsMasterKeys.option_all_dungeon_keys, "ne")]),
Has(f"Master Key ({DUNGEON_NAMES[dungeon_id]})",
options=[OptionFilter(OracleOfSeasonsMasterKeys, OracleOfSeasonsMasterKeys.option_all_dungeon_keys)]),
)
# Options and generation predicates ###########################################
def oos_option_medium_logic() -> Rule:
return from_option(OracleOfSeasonsLogicDifficulty, OracleOfSeasonsLogicDifficulty.option_medium, "ge")
def oos_option_hard_logic() -> Rule:
return from_option(OracleOfSeasonsLogicDifficulty, OracleOfSeasonsLogicDifficulty.option_hard, "ge")
def oos_option_hell_logic() -> Rule:
return from_option(OracleOfSeasonsLogicDifficulty, OracleOfSeasonsLogicDifficulty.option_hell, "ge")
def oos_option_shuffled_dungeons() -> Rule:
return from_option(OracleOfSeasonsDungeonShuffle, OracleOfSeasonsDungeonShuffle.option_true)
def oos_option_no_d0_alt_entrance() -> Rule:
return from_option(OracleOfSeasonsRemoveD0AltEntrance, OracleOfSeasonsRemoveD0AltEntrance.option_true)
def oos_option_no_d2_alt_entrance() -> Rule:
return from_option(OracleOfSeasonsRemoveD2AltEntrance, OracleOfSeasonsRemoveD2AltEntrance.option_true)
def oos_is_companion_ricky() -> Rule:
return from_option(OracleOfSeasonsAnimalCompanion, OracleOfSeasonsAnimalCompanion.option_ricky)
def oos_is_companion_moosh() -> Rule:
return from_option(OracleOfSeasonsAnimalCompanion, OracleOfSeasonsAnimalCompanion.option_moosh)
def oos_is_companion_dimitri() -> Rule:
return from_option(OracleOfSeasonsAnimalCompanion, OracleOfSeasonsAnimalCompanion.option_dimitri)
def oos_is_default_season(area_name: str, season: int, is_season: bool = True) -> Rule:
return Season(area_name, season, is_season)
def oos_can_remove_season(season: int) -> Rule:
# Test if player has any other season than the one we want to remove
return Or(
*[Has(item_name) for season_name, item_name in SEASON_ITEMS.items() if season_name != season]
)
def oos_has_essences(target_count: int) -> Rule:
return HasGroup("Essences", target_count)
def oos_has_essences_for_maku_seed() -> Rule:
return HasGroupOption("Essences", "required_essences")
def oos_has_essences_for_treehouse() -> Rule:
return HasGroupOption("Essences", "treehouse_old_man_requirement")
def oos_has_required_jewels() -> Rule:
return HasGroupOption("Jewels", "tarm_gate_required_jewels")
def oos_can_reach_lost_woods_pedestal(allow_default: bool = False) -> Rule:
return And(
LostWoods([], False, allow_default),
Or(
CanReachRegion("lost woods phonograph"),
And(
# if sequence is vanilla, medium+ players are expected to know it
oos_option_medium_logic(),
from_option(OracleOfSeasonsLostWoodsItemSequence, OracleOfSeasonsLostWoodsItemSequence.option_false)
)
)
)
def oos_can_complete_lost_woods_main_sequence(allow_default: bool = False) -> Rule:
return And(
LostWoods([], True, allow_default),
Or(
CanReachRegion("lost woods deku"),
And(
# if sequence is vanilla, medium+ players are expected to know it
oos_option_medium_logic(),
from_option(OracleOfSeasonsLostWoodsMainSequence, OracleOfSeasonsLostWoodsMainSequence.option_false)
)
)
)
def oos_can_beat_required_golden_beasts() -> Rule:
return HasFromListOption("_beat_golden_darknut", "_beat_golden_lynel", "_beat_golden_moblin", "_beat_golden_octorok",
option_name="golden_beasts_requirement")
def oos_can_complete_d11_puzzle() -> Rule:
return Or(
from_option(OracleOfSeasonsDungeonShuffle, OracleOfSeasonsDungeonShuffle.option_false),
CanReachNumRegions([f"enter d{i}" for i in range(1, 9)], 7) # And then deduce the last
)
# Various item predicates ###########################################
def oos_has_rupees_for_shop(shop_name: str) -> Rule:
return Or(
And(
oos_option_hard_logic(),
oos_has_shovel()
),
HasRupeesForShop(shop_name)
)
def oos_can_farm_rupees() -> Rule:
# Having a weapon to get or a shovel is enough to guarantee that we can reach a significant amount of rupees
return Or(
oos_can_kill_normal_enemy(False, False),
oos_has_shovel()
)
def oos_can_buy_market() -> Rule:
return HasOresForShop()
def oos_can_farm_ore_chunks() -> Rule:
return Or(
oos_has_shovel(),
And(
oos_option_medium_logic(),
Or(
oos_has_magic_boomerang(),
oos_has_sword()
)
),
And(
oos_option_hard_logic(),
Or(
CanReachRegion("subrosian dance hall"),
oos_has_bracelet(),
oos_has_switch_hook()
)
)
)
def oos_can_date_rosa() -> Rule:
return And(
CanReachRegion("subrosia market sector"),
Has("Ribbon")
)
def oos_can_trigger_far_switch() -> Rule:
return Or(
oos_has_boomerang(),
oos_has_bombs(),
oos_has_seed_thrower(),
oos_shoot_beams(),
oos_has_switch_hook()
)
def oos_shoot_beams() -> Rule:
return Or(
And(
oos_option_medium_logic(),
oos_has_sword(False),
Has("Energy Ring"),
),
And(
oos_option_medium_logic(),
oos_has_noble_sword(),
Or(
Has("Heart Ring L-2"),
And(
oos_option_hard_logic(),
Has("Heart Ring L-1"),
)
)
)
)
def oos_has_rod() -> Rule:
return Or(
oos_has_winter(),
oos_has_summer(),
oos_has_spring(),
oos_has_autumn()
)
def oos_has_bombs(amount: int = 1) -> Rule:
return Or(
Has("Bombs", amount),
And(
# With medium logic is expected to know they can get free bombs
# from D2 moblin room even if they never had bombs before
from_bool(amount == 1),
oos_option_medium_logic(),
Has("_wild_bombs"),
)
)
def oos_has_bombs_to_fight() -> Rule:
return oos_has_bombs(4)
def oos_has_bombs_for_tiles() -> Rule:
return oos_has_bombs(2)
def oos_has_bombs_for_bombjump() -> Rule:
return oos_has_bombs(2)
def oos_has_bombchus(amount: int = 1) -> Rule:
return Has("Bombchus", amount)
def oos_has_bombchus_to_fight() -> Rule:
return oos_has_bombchus(2)
def oos_has_bombchus_for_tiles() -> Rule:
return oos_has_bombchus(4)
def oos_has_flute() -> Rule:
return Or(
oos_can_summon_ricky(),
oos_can_summon_moosh(),
oos_can_summon_dimitri()
)
def oos_can_summon_ricky() -> Rule:
return Has("Ricky's Flute")
def oos_can_summon_moosh() -> Rule:
return Has("Moosh's Flute")
def oos_can_summon_dimitri() -> Rule:
return Has("Dimitri's Flute")
# Jump-related predicates ###########################################
def oos_can_jump_1_wide_liquid(can_summon_companion: bool) -> Rule:
return Or(
oos_has_feather(),
And(
oos_option_medium_logic(),
from_bool(can_summon_companion),
oos_can_summon_ricky()
)
)
def oos_can_jump_2_wide_liquid() -> Rule:
return Or(
oos_has_cape(),
And(
oos_has_feather(),
oos_can_use_pegasus_seeds()
),
And(
# Hard logic expects bomb jumps over 2-wide liquids
oos_option_hard_logic(),
oos_has_feather(),
oos_has_bombs()
)
)
def oos_can_jump_3_wide_liquid() -> Rule:
return Or(
oos_has_cape(),
And(
oos_option_hard_logic(),
oos_has_feather(),
oos_can_use_pegasus_seeds(),
oos_has_bombs(),
)
)
def oos_can_jump_4_wide_liquid() -> Rule:
return And(
oos_has_cape(),
Or(
oos_can_use_pegasus_seeds(),
And(
# Hard logic expects player to be able to cape bomb-jump above 4-wide liquids
oos_option_hard_logic(),
oos_has_bombs()
)
)
)
def oos_can_jump_5_wide_liquid() -> Rule:
return And(
oos_has_cape(),
oos_can_use_pegasus_seeds(),
)
def oos_can_jump_1_wide_pit(can_summon_companion: bool) -> Rule:
return Or(
oos_has_feather(),
And(
from_bool(can_summon_companion),
Or(
oos_can_summon_moosh(),
oos_can_summon_ricky()
)
)
)
def oos_can_jump_2_wide_pit() -> Rule:
return Or(
oos_has_cape(),
And(
oos_has_feather(),
Or(
# Medium logic expects player to be able to jump above 2-wide pits without pegasus seeds
oos_option_medium_logic(),
oos_can_use_pegasus_seeds()
)
)
)
def oos_can_jump_3_wide_pit() -> Rule:
return Or(
oos_has_cape(),
And(
oos_option_medium_logic(),
oos_has_feather(),
oos_can_use_pegasus_seeds(),
)
)
def oos_can_jump_4_wide_pit() -> Rule:
return And(
oos_has_cape(),
Or(
oos_option_medium_logic(),
oos_can_use_pegasus_seeds(),
)
)
def oos_can_jump_5_wide_pit() -> Rule:
return And(
oos_has_cape(),
oos_can_use_pegasus_seeds(),
)
def oos_can_jump_6_wide_pit() -> Rule:
return And(
oos_option_medium_logic(),
oos_has_cape(),
oos_can_use_pegasus_seeds(),
)
# Seed-related predicates ###########################################
def oos_can_use_seeds() -> Rule:
return Or(
oos_has_slingshot(),
oos_has_shooter(),
oos_has_satchel()
)
def oos_can_use_ember_seeds(accept_mystery_seeds: bool) -> Rule:
return And(
oos_can_use_seeds(),
Or(
oos_has_ember_seeds(),
And(
# Medium logic expects the player to know they can use mystery seeds
# to randomly get the ember effect in some cases
from_bool(accept_mystery_seeds),
oos_option_medium_logic(),
oos_has_mystery_seeds(),
)
)
)
def oos_can_use_scent_seeds() -> Rule:
return And(
oos_can_use_seeds(),
oos_has_scent_seeds()
)
def oos_can_use_pegasus_seeds() -> Rule:
return And(
# Unlike other seeds, pegasus only have an interesting effect with the satchel
oos_has_satchel(),
oos_has_pegasus_seeds()
)
def oos_can_use_gale_seeds_offensively() -> Rule:
return And(
oos_has_satchel(2),
oos_option_medium_logic(),
Or(
oos_has_gale_seeds(),
oos_has_mystery_seeds()
),
Or(
oos_has_seed_thrower(),
And(
oos_has_satchel(),
Or(
oos_option_hard_logic(),
oos_has_feather()
),
)
)
)
def oos_can_use_mystery_seeds() -> Rule:
return And(
oos_can_use_seeds(),
oos_has_mystery_seeds()
)
# Break / kill predicates ###########################################
def oos_can_break_bush(can_summon_companion: bool = False, allow_bombchus: bool = False) -> Rule:
return Or(
oos_can_break_flowers(can_summon_companion, allow_bombchus),
oos_has_bracelet()
)
def oos_can_harvest_regrowing_bush() -> Rule:
return Or(
oos_has_sword(),
oos_has_fools_ore(),
oos_has_bombs_for_tiles(),
And(
oos_option_medium_logic(),
oos_has_bombchus_for_tiles()
)
)
def oos_can_break_mushroom(can_use_companion: bool) -> Rule:
return Or(
oos_has_bracelet(),
And(
oos_option_medium_logic(),
Or(
oos_has_magic_boomerang(),
And(
from_bool(can_use_companion),
oos_can_summon_dimitri()
)
)
),
)
def oos_can_break_pot() -> Rule:
return Or(
oos_has_bracelet(),
oos_has_noble_sword(),
Has("Biggoron's Sword"),
oos_has_switch_hook()
)
def oos_can_break_flowers(can_summon_companion: bool = False, allow_bombchus: bool = False) -> Rule:
return Or(
oos_has_sword(),
oos_has_magic_boomerang(),
oos_has_switch_hook(),
And(
from_bool(can_summon_companion),
oos_has_flute()
),
And(
# Consumables need at least medium logic, since they need a good knowledge of the game
# not to be frustrating
oos_option_medium_logic(),
Or(
oos_has_bombs_for_tiles(),
oos_can_use_ember_seeds(False),
And(
oos_has_seed_thrower(),
oos_has_gale_seeds()
),
And(
from_bool(allow_bombchus),
oos_has_bombchus_for_tiles()
)
)
),
)
def oos_can_break_crystal() -> Rule:
return Or(
oos_has_sword(),
oos_has_bombs_for_tiles(),
oos_has_bracelet(),
And(
oos_option_medium_logic(),
Has("Expert's Ring")
),
And(
oos_option_medium_logic(),
oos_has_bombchus_for_tiles()
),
)
def oos_can_break_sign() -> Rule:
return Or(
oos_has_noble_sword(),
Has("Biggoron's Sword"),
oos_has_bracelet(),
oos_can_use_ember_seeds(False),
oos_has_magic_boomerang(),
oos_has_switch_hook()
)
def oos_can_harvest_tree(can_use_companion: bool) -> Rule:
return And(
oos_can_use_seeds(),
Or(
oos_has_sword(),
oos_has_fools_ore(),
oos_has_rod(),
oos_can_punch(),
And(
from_bool(can_use_companion),
oos_option_medium_logic(),
oos_can_summon_dimitri()
)
)
)
def oos_can_harvest_gasha(count: int) -> Rule:
return And(
HasFromList(*[f"_reached_{region_name}" for region_name in GASHA_SPOT_REGIONS], count=count), # Enough soils are reachable
Has("Gasha Seed", count), # Enough seeds to plant
Or(
# Can actually harvest the nut, and get kills
oos_has_sword(),
oos_has_fools_ore()
)
)
def oos_can_push_enemy() -> Rule:
return Or(
oos_has_rod(),
oos_has_shield(),
And(
oos_option_medium_logic(),
oos_has_shovel()
)
)
def oos_can_kill_normal_enemy(pit_available: bool = False,
allow_gale_seeds: bool = True) -> Rule:
return Or(
oos_can_kill_normal_enemy_no_cane(pit_available, allow_gale_seeds),
(oos_option_medium_logic() & oos_has_cane())
)
def oos_can_kill_normal_enemy_no_cane(pit_available: bool = False,
allow_gale_seeds: bool = True) -> Rule:
return Or(
And(
# If a pit is avaiable nearby, it can be used to put the enemies inside using
# items that are usually non-lethal
from_bool(pit_available),
oos_can_push_enemy()
),
oos_has_sword(),
oos_has_fools_ore(),
oos_can_kill_normal_using_satchel(allow_gale_seeds),
oos_can_kill_normal_using_slingshot(allow_gale_seeds),
And(
oos_option_medium_logic(),
oos_has_bombs_to_fight()
),
oos_has_bombchus_to_fight(),
oos_can_punch()
)
def oos_can_kill_normal_using_satchel(allow_gale_seeds: bool = True) -> Rule:
# Expect a 50+ seed satchel to ensure we can chain dungeon rooms to some extent if that's our only kill option
return oos_has_satchel(2) & Or(
# Casual logic => only ember
oos_has_ember_seeds(),
And(
# Medium logic => allow scent or gale+feather
oos_option_medium_logic(),
Or(
oos_has_scent_seeds(),
oos_has_mystery_seeds(),
And(
from_bool(allow_gale_seeds),
oos_has_gale_seeds(),
oos_has_feather()
)
)
),
And(
# Hard logic => allow gale without feather
from_bool(allow_gale_seeds),
oos_option_hard_logic(),
oos_has_gale_seeds()
)
)
def oos_can_kill_normal_using_slingshot(allow_gale_seeds: bool = True) -> Rule:
return And(
# Expect a 50+ seed satchel to ensure we can chain dungeon rooms to some extent if that's our only kill option
oos_has_satchel(2),
oos_has_seed_thrower(),
Or(
oos_has_ember_seeds(),
oos_has_scent_seeds(),
And(
oos_option_medium_logic(),
Or(
And(
from_bool(allow_gale_seeds),
oos_has_gale_seeds(),
),
oos_has_mystery_seeds(),
)
)
)
)
def oos_can_kill_armored_enemy(allow_cane: bool, allow_bombchus: bool) -> Rule:
return Or(
oos_has_sword(),
oos_has_fools_ore(),
And(
oos_option_medium_logic(),
oos_has_bombs_to_fight()
),
And(
from_bool(allow_bombchus),
oos_has_bombchus_to_fight()
),
And(
oos_has_satchel(2), # Expect a 50+ seeds satchel to be able to chain rooms in dungeons
oos_has_scent_seeds(),
Or(
oos_has_seed_thrower(),
oos_option_medium_logic()
)
),
And(
from_bool(allow_cane),
oos_option_medium_logic(),
oos_has_cane()
),
oos_can_punch()
)
def oos_can_kill_stalfos() -> Rule:
return Or(
oos_can_kill_normal_enemy(),
And(
oos_option_medium_logic(),
oos_has_rod()
)
)
def oos_can_kill_moldorm(pit_available: bool = False) -> Rule:
return Or(
oos_can_kill_armored_enemy(True, True),
oos_has_switch_hook(),
And(
from_bool(pit_available),
Or(
oos_has_shield(),
And(
oos_option_medium_logic(),
oos_has_shovel()
)
)
)
)
def oos_can_kill_facade() -> Rule:
return Or(
oos_has_bombs_to_fight(),
oos_has_bombchus_to_fight()
)
def oos_can_punch() -> Rule:
return And(
oos_option_medium_logic(),
Or(
Has("Fist Ring"),
Has("Expert's Ring")
)
)
def oos_can_trigger_lever() -> Rule:
return Or(
oos_can_trigger_lever_from_minecart(),
oos_has_switch_hook(),
And(
oos_option_medium_logic(),
oos_has_shovel()
)
)
def oos_can_trigger_lever_from_minecart() -> Rule:
return Or(
oos_can_punch(),
oos_has_sword(),
oos_has_fools_ore(),
oos_has_boomerang(),
oos_has_rod(),
oos_can_use_scent_seeds(),
oos_can_use_mystery_seeds(),
oos_can_use_ember_seeds(False),
oos_has_seed_thrower(), # any seed works using slingshot
)
def oos_can_kill_d2_hardhat() -> Rule:
return Or(
oos_has_sword(),
oos_has_fools_ore(),
oos_has_boomerang(),
oos_can_push_enemy(),
oos_has_switch_hook(), # Also push the hardhat
And(
oos_option_medium_logic(),
oos_has_satchel(2),
Or(
oos_has_seed_thrower(),
And(
oos_option_hard_logic(),
oos_has_satchel(),
)
),
Or(
oos_has_scent_seeds(),
oos_has_gale_seeds(),
oos_has_mystery_seeds()
)
),
And(
oos_option_medium_logic(),
Or(
oos_has_bombchus_to_fight(),
oos_has_bombs_to_fight()
)
)
)
def oos_can_kill_d2_far_moblin() -> Rule:
return Or(
oos_can_kill_normal_using_slingshot(),
And(
Or(
oos_has_feather(),
And(
# Switch with a moblin, kill the other, jump in the pit, kill the first
oos_option_medium_logic(),
oos_has_switch_hook()
)
),
oos_can_kill_normal_enemy(True),
),
And(
oos_option_hard_logic(),
Or(
oos_can_use_ember_seeds(False),
oos_can_punch(),
oos_has_cane()
)
)
)
def oos_can_flip_spiked_beetle() -> Rule:
return Or(
oos_has_shield(),
And(
oos_option_medium_logic(),
oos_has_shovel()
)
)
def oos_can_kill_spiked_beetle() -> Rule:
return Or(
And( # Regular flip + kill
oos_can_flip_spiked_beetle(),
Or(
oos_has_sword(),
oos_has_fools_ore(),
oos_can_kill_normal_using_satchel(),
oos_can_kill_normal_using_slingshot(),
oos_has_switch_hook()
)
),
# Instant kill using Gale Seeds
oos_can_use_gale_seeds_offensively()
)
def oos_can_kill_magunesu() -> Rule:
return Or(
oos_has_sword(),
oos_has_fools_ore(),
# Has("expert's ring")
)
# Action predicates ###########################################
def oos_can_remove_snow(can_summon_companion: bool) -> Rule:
return Or(
oos_has_shovel(),
And(
from_bool(can_summon_companion),
oos_has_flute()
)
)
def oos_can_swim(can_summon_companion: bool) -> Rule:
return Or(
oos_has_flippers(),
And(
from_bool(can_summon_companion),
oos_can_summon_dimitri()
)
)
def oos_can_remove_rockslide(can_summon_companion: bool) -> Rule:
return Or(
oos_has_bombs(),
And(
oos_option_medium_logic(),
oos_has_bombchus_for_tiles()
),
And(
from_bool(can_summon_companion),
oos_can_summon_ricky()
)
)
def oos_can_meet_maple() -> Rule:
return oos_can_kill_normal_enemy(False, False)
def oos_can_dimitri_clip() -> Rule:
return And(
oos_option_hell_logic(),
oos_can_summon_dimitri(),
oos_has_bracelet(),
oos_has_gale_seeds(),
oos_has_satchel()
)
# Season in region predicates ##########################################
def oos_season_in_spool_swamp(season: int) -> Rule:
return Or(
oos_is_default_season("SPOOL_SWAMP", season),
And(
oos_has_season(season),
CanReachRegion("spool stump")
)
)
def oos_season_in_eyeglass_lake(season: int) -> Rule:
return Or(
oos_is_default_season("EYEGLASS_LAKE", season),
And(
oos_has_season(season),
Or(
CanReachRegion("d1 stump"),
CanReachRegion("d5 stump"),
)
)
)
def oos_season_in_temple_remains(season: int) -> Rule:
return Or(
oos_is_default_season("TEMPLE_REMAINS", season),
And(
oos_has_season(season),
Or(
CanReachRegion("temple remains lower stump"),
CanReachRegion("temple remains upper stump"),
)
)
)
def oos_season_in_holodrum_plain(season: int) -> Rule:
return Or(
oos_is_default_season("HOLODRUM_PLAIN", season),
And(
oos_has_season(season),
CanReachRegion("ghastly stump")
)
)
def oos_season_in_western_coast(season: int) -> Rule:
return Or(
oos_is_default_season("WESTERN_COAST", season),
And(
oos_has_season(season),
CanReachRegion("coast stump"),
)
)
def oos_season_in_eastern_suburbs(season: int) -> Rule:
return Or(
oos_is_default_season("EASTERN_SUBURBS", season),
oos_has_season(season)
)
def oos_not_season_in_eastern_suburbs(season: int) -> Rule:
return Or(
oos_is_default_season("EASTERN_SUBURBS", season, False),
oos_can_remove_season(season)
)
def oos_season_in_sunken_city(season: int) -> Rule:
return Or(
oos_is_default_season("SUNKEN_CITY", season),
And(
oos_has_season(season),
Or(
oos_is_default_season("SUNKEN_CITY", SEASON_WINTER),
oos_can_swim(True),
CanReachRegion("sunken city dimitri")
)
)
)
def oos_season_in_woods_of_winter(season: int) -> Rule:
return Or(
oos_is_default_season("WOODS_OF_WINTER", season),
oos_has_season(season)
)
def oos_season_in_central_woods_of_winter(season: int) -> Rule:
return Or(
oos_is_default_season("WOODS_OF_WINTER", season),
And(
oos_has_season(season),
CanReachRegion("d2 stump")
)
)
def oos_season_in_mt_cucco(season: int) -> Rule:
return Or(
oos_is_default_season("SUNKEN_CITY", season),
oos_has_season(season)
)
def oos_season_in_lost_woods(season: int) -> Rule:
return Or(
oos_is_default_season("LOST_WOODS", season),
oos_has_season(season)
)
def oos_season_in_tarm_ruins(season: int) -> Rule:
return Or(
oos_is_default_season("TARM_RUINS", season),
oos_has_season(season)
)
def oos_season_in_horon_village(season: int) -> Rule:
# With vanilla behavior, you can randomly have any season inside Horon, making any season virtually accessible
return Or(
from_option(OracleOfSeasonsHoronSeason, OracleOfSeasonsHoronSeason.option_false),
oos_is_default_season("HORON_VILLAGE", season),
oos_has_season(season)
)
# Self-locking items helper predicates ##########################################
def oos_self_locking_item(location_name: str, item_name: str) -> Rule:
return ItemInLocation(location_name, item_name)
def oos_self_locking_small_key(region_name: str, dungeon: int) -> Rule:
item_name = f"Small Key ({DUNGEON_NAMES[dungeon]})"
return oos_self_locking_item(region_name, item_name)
# Rooster adventure logic ######################################################
POSITION_BOTH = 0
POSITION_TOP = 1
POSITION_BOTTOM = 2
def oos_roosters(region: str, any_amount: int = 0, top_amount: int = 0, bottom_amount: int = 0, visited_regions=None) -> Rule:
# Avoid loops
if visited_regions is None:
visited_regions = set()
visited_regions.add(region)
if any_amount > top_amount + bottom_amount:
return (oos_roosters(region, any_amount, top_amount + 1, bottom_amount, set(visited_regions)) |
oos_roosters(region, any_amount, top_amount, bottom_amount + 1, set(visited_regions)))
elif region == "cucco mountain":
rule = oos_can_reach_rooster_adventure()
if bottom_amount > 2:
raise NotImplementedError()
elif bottom_amount > 0:
rule &= And(
oos_season_in_mt_cucco(SEASON_SPRING),
oos_can_break_flowers() | Has("Spring Banana")
)
if top_amount > 3:
raise NotImplementedError()
elif top_amount == 3:
rule &= And(
oos_has_shovel(),
oos_has_boomerang()
)
elif top_amount == 2:
rule &= (oos_has_shovel() |
(oos_has_boomerang() & oos_can_use_pegasus_seeds()))
# sign + season gives 2 tops, one of which being sacrificed,
# which is included in oos_can_reach_rooster_adventure()
return rule
elif region == "horon":
return And(
(oos_can_jump_3_wide_pit() | oos_can_swim(True)), # Swim through Natzu
oos_roosters("cucco mountain", any_amount, top_amount, bottom_amount)
)
elif region == "sunken":
rule = Or(
And(
oos_roosters("horon", any_amount, top_amount, bottom_amount, set(visited_regions)),
Or(
# Go through Natzu
oos_has_flute(),
oos_is_companion_moosh() & oos_can_jump_4_wide_liquid()
)
),
And(
# Go through moblin fortress using a top cucco
oos_roosters("horon", any_amount + 1, top_amount + 1, bottom_amount, set(visited_regions)),
oos_is_companion_moosh() & oos_can_jump_3_wide_pit()
),
And(
oos_is_companion_ricky(),
oos_can_swim(False),
oos_can_break_flowers() | oos_roosters("cucco mountain", any_amount + 1, top_amount, bottom_amount, set(visited_regions)),
oos_roosters("cucco mountain", any_amount, top_amount, bottom_amount, set(visited_regions))
)
)
if "suburbs" not in visited_regions:
rule |= And(
# Suburbs flower
oos_season_in_eastern_suburbs(SEASON_SPRING),
oos_roosters("suburbs", any_amount, top_amount, bottom_amount, set(visited_regions))
)
return rule
elif region == "suburbs":
return Or(
oos_roosters("sunken", any_amount, top_amount, bottom_amount, set(visited_regions)),
And(
oos_roosters("horon", any_amount, top_amount, bottom_amount, set(visited_regions)),
oos_can_use_ember_seeds(False)
),
And(
# Use portal screen in suburbs
oos_roosters("horon", any_amount + 1, top_amount, bottom_amount, set(visited_regions)),
oos_season_in_eyeglass_lake(SEASON_WINTER),
Or(
Season("EYEGLASS_LAKE", SEASON_SUMMER, True),
oos_can_remove_season(SEASON_SUMMER)
),
oos_can_swim(True)
)
)
elif region == "moblin road":
return Or(
And(
oos_season_in_eastern_suburbs(SEASON_WINTER),
oos_roosters("suburbs", any_amount, top_amount, bottom_amount, set(visited_regions))
),
# Use a top rooster from top of the flower
oos_roosters("sunken", any_amount + 1, top_amount + 1, bottom_amount, set(visited_regions))
)
elif region == "swamp":
return Or(
And(
Or(
oos_season_in_holodrum_plain(SEASON_SUMMER),
oos_can_jump_4_wide_pit(),
oos_can_summon_ricky(),
oos_can_summon_moosh()
),
oos_roosters("horon", any_amount, top_amount, bottom_amount, set(visited_regions))
),
# Use bottom cucco to climb the vines
oos_roosters("horon", any_amount + 1, top_amount, bottom_amount + 1, set(visited_regions))
)
elif region == "d6":
return And(
oos_has_required_jewels(),
Or(
oos_season_in_lost_woods(SEASON_SUMMER),
And(
oos_season_in_lost_woods(SEASON_AUTUMN),
oos_option_medium_logic(),
oos_has_magic_boomerang(),
Or(
oos_can_jump_1_wide_pit(False),
oos_option_hard_logic()
)
)
),
oos_season_in_lost_woods(SEASON_WINTER),
oos_can_remove_season(SEASON_WINTER),
oos_has_autumn(),
oos_can_break_mushroom(False),
Or(
oos_can_complete_lost_woods_main_sequence(False),
And(
oos_can_complete_lost_woods_main_sequence(True),
oos_can_reach_lost_woods_pedestal(False)
)
)
)
else:
raise NotImplementedError()
def oos_can_reach_rooster_adventure() -> Rule:
return oos_option_hell_logic() & CanReachRegion("rooster adventure")