forked from mirror/Archipelago
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
236 lines
11 KiB
Python
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)
|