Files
dockipelago/worlds/kdl3/rules.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

236 lines
11 KiB
Python

from BaseClasses import ItemClassification
from rule_builder.rules import HasAll, Has, CanReachLocation, OptionFilter, Rule, True_
from .items import KDL3Item
from .locations import location_table
from .names import location_name, enemy_abilities, animal_friend_spawns
from .options import GoalSpeed
import typing
if typing.TYPE_CHECKING:
from . import KDL3World
from BaseClasses import CollectionState
def CanReachBoss(level: int, open_world: int, ow_boss_req: int, player_levels: dict[int, list[int]]) -> Rule:
if open_world:
return Has(f"{location_name.level_names_inverse[level]} - Stage Completion", count=ow_boss_req)
else:
return CanReachLocation(location_table[player_levels[level][5]])
CanReachRick = HasAll("Rick", "Rick Spawn")
CanReachKine = HasAll("Kine", "Kine Spawn")
CanReachCoo = HasAll("Coo", "Coo Spawn")
CanReachNago = HasAll("Nago", "Nago Spawn")
CanReachChuChu = HasAll("ChuChu", "ChuChu Spawn")
CanReachPitch = HasAll("Pitch", "Pitch Spawn")
CanReachAnyAnimalOtherThanKine = CanReachRick | CanReachCoo | CanReachNago | CanReachChuChu | CanReachPitch
CanReachBurning = HasAll("Burning", "Burning Ability")
CanReachStone = HasAll("Stone", "Stone Ability")
CanReachIce = HasAll("Ice", "Ice Ability")
CanReachNeedle = HasAll("Needle", "Needle Ability")
CanReachClean = HasAll("Clean", "Clean Ability")
CanReachParasol = HasAll("Parasol", "Parasol Ability")
CanReachSpark = HasAll("Spark", "Spark Ability")
CanReachCutter = HasAll("Cutter", "Cutter Ability")
def can_reach_regions(state: "CollectionState", player: int, regions: typing.List[str]):
for region in regions:
if not state.can_reach_region(region, player):
return False
return True
ability_map: dict[str, Rule] = {
"No Ability": True_,
"Burning Ability": CanReachBurning,
"Stone Ability": CanReachStone,
"Ice Ability": CanReachIce,
"Needle Ability": CanReachNeedle,
"Clean Ability": CanReachClean,
"Parasol Ability": CanReachParasol,
"Spark Ability": CanReachSpark,
"Cutter Ability": CanReachCutter,
}
STATIC_LOCATION_RULES: dict[str, Rule] = {
# Level 1
location_name.grass_land_muchi: CanReachChuChu,
location_name.grass_land_chao: Has("Goku"),
location_name.grass_land_mine: CanReachKine,
# Level 2
# Ripple Field 5 rules are applied on entrances instead
location_name.ripple_field_kamuribana: CanReachPitch & CanReachClean,
location_name.ripple_field_bakasa: CanReachKine & CanReachParasol,
location_name.ripple_field_toad: Has("Little Toad"),
location_name.ripple_field_4_little_toad: CanReachNeedle,
location_name.ripple_field_5_wall: CanReachBurning & CanReachStone,
location_name.ripple_field_mama_pitch: CanReachPitch, # Rest are on entrances/fixup
# Level 3
location_name.sand_canyon_auntie: CanReachClean,
location_name.sand_canyon_nyupun: CanReachChuChu, # Cutter handled on entrance
location_name.sand_canyon_rob: HasAll("ROB Base", "ROB Torso", "ROB Left Arm",
"ROB Right Arm", "ROB Head"),
location_name.sand_canyon_6_rob_left: CanReachKine, # edge case
location_name.sand_canyon_6_rob_right: CanReachCoo, # edge case
# Level 4
location_name.cloudy_park_hibanamodoki: (CanReachRick | CanReachCoo) & CanReachClean,
location_name.cloudy_park_piyokeko: CanReachNeedle,
location_name.cloudy_park_mikarin: Has("Mikarin"),
location_name.cloudy_park_4_mikarin: CanReachCoo,
location_name.cloudy_park_pick: CanReachRick,
# Level 5
location_name.iceberg_kogoesou: CanReachBurning,
location_name.iceberg_samus: CanReachIce,
location_name.iceberg_name: Has("Shell"), # Handled through entrances
location_name.iceberg_shiro: CanReachNago,
location_name.iceberg_angel: Has("Angel Feather", count=8)
}
STATIC_CONSUMABLE_RULES: dict[str, Rule] = {
location_name.grass_land_1_u1: CanReachParasol,
location_name.grass_land_1_m1: CanReachSpark,
location_name.grass_land_2_u1: CanReachNeedle,
location_name.ripple_field_2_u1: CanReachKine,
location_name.ripple_field_2_m1: CanReachKine,
location_name.ripple_field_3_u1: CanReachCutter | CanReachSpark,
location_name.ripple_field_4_u1: CanReachStone,
location_name.ripple_field_4_m2: CanReachStone,
location_name.ripple_field_5_m1: CanReachKine,
location_name.ripple_field_5_u1: CanReachKine, # Wall applied in fixup
location_name.ripple_field_5_m2: CanReachKine,
location_name.sand_canyon_4_u1: CanReachClean,
location_name.sand_canyon_4_m2: CanReachNeedle,
location_name.sand_canyon_5_u2: CanReachIce & CanReachAnyAnimalOtherThanKine,
location_name.sand_canyon_5_u3: CanReachIce & CanReachAnyAnimalOtherThanKine,
location_name.sand_canyon_5_u4: CanReachIce & CanReachAnyAnimalOtherThanKine,
location_name.cloudy_park_6_u1: CanReachCutter,
}
STATIC_STAR_RULES: dict[tuple[str, tuple[int, ...]], Rule] = {
("Grass Land 1", (*range(7, 11),)) : CanReachCutter,
("Grass Land 1", (*range(11, 14),)) : CanReachParasol,
("Grass Land 2", (1, 3, 4, 9, 10)): CanReachStone,
("Grass Land 2", (2,)): CanReachBurning,
("Ripple Field 2", (17,)): CanReachKine,
("Ripple Field 5", (41, 42)): CanReachKine,
("Ripple Field 5", (*range(46, 50),)): CanReachBurning & CanReachStone, # These are the stars in the ability blocks
("Sand Canyon 5", (*range(12, 18),)): CanReachIce & CanReachAnyAnimalOtherThanKine,
("Sand Canyon 5", (21, 22)): CanReachChuChu,
("Sand Canyon 5", (19, 20, *range(23, 31),)): CanReachClean,
("Sand Canyon 5", (*range(31, 41),)): CanReachBurning,
("Cloudy Park 4", (*range(1, 31), *range(44, 51),)): CanReachCoo,
("Cloudy Park 6", (18, *range(20, 25),)): CanReachIce,
("Cloudy Park 6", (19, *range(25, 30),)): CanReachBurning,
("Iceberg 4", (1, 2, 3)): CanReachBurning
}
STATIC_ABILITY_ANIMAL_RULES: dict[str, Rule] = {
enemy_abilities.Ripple_Field_2_E3: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_3_E6: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_4_E5: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_4_E7: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_4_E8: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_5_E1: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_5_E2: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_5_E3: CanReachKine | CanReachChuChu,
enemy_abilities.Ripple_Field_5_E4: CanReachKine | CanReachChuChu,
enemy_abilities.Sand_Canyon_4_E7: CanReachKine | CanReachChuChu,
enemy_abilities.Sand_Canyon_4_E8: CanReachKine | CanReachChuChu,
enemy_abilities.Sand_Canyon_4_E9: CanReachKine | CanReachChuChu,
enemy_abilities.Sand_Canyon_4_E10: CanReachKine | CanReachChuChu,
animal_friend_spawns.iceberg_4_a2: CanReachChuChu & CanReachBurning,
animal_friend_spawns.iceberg_4_a3: CanReachChuChu & CanReachBurning,
}
def set_rules(world: "KDL3World") -> None:
goal = world.options.goal.value
goal_location = world.get_location(location_name.goals[goal])
goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, world.player))
world.multiworld.completion_condition[world.player] = lambda state: state.has("Love-Love Rod", world.player)
for location, rule in STATIC_LOCATION_RULES.items():
world.set_rule(world.get_location(location), rule)
if not world.options.door_shuffle:
angel_requirements = [
world.copy_abilities["Sparky"],
world.copy_abilities["Blocky"],
world.copy_abilities["Jumper Shoot"],
world.copy_abilities["Yuki"],
world.copy_abilities["Sir Kibble"],
world.copy_abilities["Haboki"],
world.copy_abilities["Boboo"],
world.copy_abilities["Captain Stitch"],
]
else:
angel_requirements = [
"Spark Ability",
"Stone Ability",
"Parasol Ability",
"Ice Ability",
"Cutter Ability",
"Clean Ability",
"Burning Ability",
"Needle Ability"
]
for i, req in enumerate(angel_requirements):
angel_rule = ability_map[req]
world.set_rule(world.get_location(f"Iceberg 6 - Feather {i+1}"), angel_rule)
# Consumables
if world.options.consumables:
for location, rule in STATIC_CONSUMABLE_RULES.items():
world.set_rule(world.get_location(location), rule)
if world.options.starsanity:
# ranges are our friend
for loc_info, rule in STATIC_STAR_RULES.items():
level, stars = loc_info
for i in stars:
world.set_rule(world.get_location(f"{level} - Star {i}"), rule)
# copy ability access edge cases
# Kirby cannot eat enemies fully submerged in water. Vast majority of cases, the enemy can be brought to the surface
# and eaten by inhaling while falling on top of them
for location, rule in STATIC_ABILITY_ANIMAL_RULES.items():
world.set_rule(world.get_location(location), rule)
for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified",
"Level 3 Boss - Purified", "Level 4 Boss - Purified",
"Level 5 Boss - Purified"],
[location_name.grass_land_whispy, location_name.ripple_field_acro,
location_name.sand_canyon_poncon, location_name.cloudy_park_ado,
location_name.iceberg_dedede],
range(1, 6)):
rule = Has("Heart Star", count=world.boss_requirements[i - 1]) \
& CanReachBoss(i,
world.options.open_world.value,
world.options.ow_boss_requirement.value,
world.player_levels)
world.set_rule(world.get_location(boss_flag), rule)
world.set_rule(world.get_location(purification), rule)
if world.options.open_world:
for boss_flag, level in zip(["Level 1 Boss - Defeated", "Level 2 Boss - Defeated", "Level 3 Boss - Defeated",
"Level 4 Boss - Defeated", "Level 5 Boss - Defeated"],
location_name.level_names.keys()):
world.set_rule(world.get_location(boss_flag), Has(f"{level} - Stage Completion",
count=world.options.ow_boss_requirement.value))
hyper_zone_rule = Has("Heart Star", count=world.required_heart_stars)
if world.options.goal_speed == GoalSpeed.option_normal:
hyper_zone_rule &= HasAll("Level 1 Boss Purified", "Level 2 Boss Purified", "Level 3 Boss Purified",
"Level 4 Boss Purified", "Level 5 Boss Purified")
world.set_rule(world.get_entrance("To Level 6"), hyper_zone_rule)
for level in range(2, 6):
rule = Has(f"Level {level - 1} Boss Defeated")
if world.options.strict_bosses:
rule &= Has(f"Level {level - 1} Boss Purified")
world.set_rule(world.get_entrance(f"To Level {level}"), rule)