333 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
| from worlds.generic.Rules import set_rule, add_rule
 | |
| from .Names import LocationName, EnemyAbilities
 | |
| from .Locations import location_table
 | |
| from .Options import GoalSpeed
 | |
| import typing
 | |
| 
 | |
| if typing.TYPE_CHECKING:
 | |
|     from . import KDL3World
 | |
|     from BaseClasses import CollectionState
 | |
| 
 | |
| 
 | |
| def can_reach_boss(state: "CollectionState", player: int, level: int, open_world: int,
 | |
|                    ow_boss_req: int, player_levels: typing.Dict[int, typing.Dict[int, int]]):
 | |
|     if open_world:
 | |
|         return state.has(f"{LocationName.level_names_inverse[level]} - Stage Completion", player, ow_boss_req)
 | |
|     else:
 | |
|         return state.can_reach(location_table[player_levels[level][5]], "Location", player)
 | |
| 
 | |
| 
 | |
| def can_reach_rick(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Rick", player) and state.has("Rick Spawn", player)
 | |
| 
 | |
| 
 | |
| def can_reach_kine(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Kine", player) and state.has("Kine Spawn", player)
 | |
| 
 | |
| 
 | |
| def can_reach_coo(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Coo", player) and state.has("Coo Spawn", player)
 | |
| 
 | |
| 
 | |
| def can_reach_nago(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Nago", player) and state.has("Nago Spawn", player)
 | |
| 
 | |
| 
 | |
| def can_reach_chuchu(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("ChuChu", player) and state.has("ChuChu Spawn", player)
 | |
| 
 | |
| 
 | |
| def can_reach_pitch(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Pitch", player) and state.has("Pitch Spawn", player)
 | |
| 
 | |
| 
 | |
| def can_reach_burning(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Burning", player) and state.has("Burning Ability", player)
 | |
| 
 | |
| 
 | |
| def can_reach_stone(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Stone", player) and state.has("Stone Ability", player)
 | |
| 
 | |
| 
 | |
| def can_reach_ice(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Ice", player) and state.has("Ice Ability", player)
 | |
| 
 | |
| 
 | |
| def can_reach_needle(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Needle", player) and state.has("Needle Ability", player)
 | |
| 
 | |
| 
 | |
| def can_reach_clean(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Clean", player) and state.has("Clean Ability", player)
 | |
| 
 | |
| 
 | |
| def can_reach_parasol(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Parasol", player) and state.has("Parasol Ability", player)
 | |
| 
 | |
| 
 | |
| def can_reach_spark(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Spark", player) and state.has("Spark Ability", player)
 | |
| 
 | |
| 
 | |
| def can_reach_cutter(state: "CollectionState", player: int) -> bool:
 | |
|     return state.has("Cutter", player) and state.has("Cutter Ability", player)
 | |
| 
 | |
| 
 | |
| ability_map: typing.Dict[str, typing.Callable[["CollectionState", int], bool]] = {
 | |
|     "No Ability": lambda state, player: True,
 | |
|     "Burning Ability": can_reach_burning,
 | |
|     "Stone Ability": can_reach_stone,
 | |
|     "Ice Ability": can_reach_ice,
 | |
|     "Needle Ability": can_reach_needle,
 | |
|     "Clean Ability": can_reach_clean,
 | |
|     "Parasol Ability": can_reach_parasol,
 | |
|     "Spark Ability": can_reach_spark,
 | |
|     "Cutter Ability": can_reach_cutter,
 | |
| }
 | |
| 
 | |
| 
 | |
| def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]):
 | |
|     # check animal requirements
 | |
|     if not (can_reach_coo(state, player) and can_reach_kine(state, player)):
 | |
|         return False
 | |
|     for abilities, bukisets in EnemyAbilities.enemy_restrictive[1:5]:
 | |
|         iterator = iter(x for x in bukisets if copy_abilities[x] in abilities)
 | |
|         target_bukiset = next(iterator, None)
 | |
|         can_reach = False
 | |
|         while target_bukiset is not None:
 | |
|             can_reach = can_reach | ability_map[copy_abilities[target_bukiset]](state, player)
 | |
|             target_bukiset = next(iterator, None)
 | |
|         if not can_reach:
 | |
|             return False
 | |
|     # now the known needed abilities
 | |
|     return can_reach_parasol(state, player) and can_reach_stone(state, player)
 | |
| 
 | |
| 
 | |
| def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]):
 | |
|     can_reach = True
 | |
|     for enemy in {"Sparky", "Blocky", "Jumper Shoot", "Yuki", "Sir Kibble", "Haboki", "Boboo", "Captain Stitch"}:
 | |
|         can_reach = can_reach & ability_map[copy_abilities[enemy]](state, player)
 | |
|     return can_reach
 | |
| 
 | |
| 
 | |
| def set_rules(world: "KDL3World") -> None:
 | |
|     # Level 1
 | |
|     set_rule(world.multiworld.get_location(LocationName.grass_land_muchi, world.player),
 | |
|              lambda state: can_reach_chuchu(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.grass_land_chao, world.player),
 | |
|              lambda state: can_reach_stone(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.grass_land_mine, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
| 
 | |
|     # Level 2
 | |
|     set_rule(world.multiworld.get_location(LocationName.ripple_field_5, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.ripple_field_kamuribana, world.player),
 | |
|              lambda state: can_reach_pitch(state, world.player) and can_reach_clean(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.ripple_field_bakasa, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player) and can_reach_parasol(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.ripple_field_toad, world.player),
 | |
|              lambda state: can_reach_needle(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.ripple_field_mama_pitch, world.player),
 | |
|              lambda state: (can_reach_pitch(state, world.player) and
 | |
|                             can_reach_kine(state, world.player) and
 | |
|                             can_reach_burning(state, world.player) and
 | |
|                             can_reach_stone(state, world.player)))
 | |
| 
 | |
|     # Level 3
 | |
|     set_rule(world.multiworld.get_location(LocationName.sand_canyon_5, world.player),
 | |
|              lambda state: can_reach_cutter(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.sand_canyon_auntie, world.player),
 | |
|              lambda state: can_reach_clean(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.sand_canyon_nyupun, world.player),
 | |
|              lambda state: can_reach_chuchu(state, world.player) and can_reach_cutter(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.sand_canyon_rob, world.player),
 | |
|              lambda state: can_assemble_rob(state, world.player, world.copy_abilities)
 | |
|              )
 | |
| 
 | |
|     # Level 4
 | |
|     set_rule(world.multiworld.get_location(LocationName.cloudy_park_hibanamodoki, world.player),
 | |
|              lambda state: can_reach_coo(state, world.player) and can_reach_clean(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.cloudy_park_piyokeko, world.player),
 | |
|              lambda state: can_reach_needle(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.cloudy_park_mikarin, world.player),
 | |
|              lambda state: can_reach_coo(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.cloudy_park_pick, world.player),
 | |
|              lambda state: can_reach_rick(state, world.player))
 | |
| 
 | |
|     # Level 5
 | |
|     set_rule(world.multiworld.get_location(LocationName.iceberg_4, world.player),
 | |
|              lambda state: can_reach_burning(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.iceberg_kogoesou, world.player),
 | |
|              lambda state: can_reach_burning(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.iceberg_samus, world.player),
 | |
|              lambda state: can_reach_ice(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.iceberg_name, world.player),
 | |
|              lambda state: (can_reach_coo(state, world.player) and
 | |
|                             can_reach_burning(state, world.player) and
 | |
|                             can_reach_chuchu(state, world.player)))
 | |
|     # ChuChu is guaranteed here, but we use this for consistency
 | |
|     set_rule(world.multiworld.get_location(LocationName.iceberg_shiro, world.player),
 | |
|              lambda state: can_reach_nago(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(LocationName.iceberg_angel, world.player),
 | |
|              lambda state: can_fix_angel_wings(state, world.player, world.copy_abilities))
 | |
| 
 | |
|     # Consumables
 | |
|     if world.options.consumables:
 | |
|         set_rule(world.multiworld.get_location(LocationName.grass_land_1_u1, world.player),
 | |
|                  lambda state: can_reach_parasol(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.grass_land_1_m1, world.player),
 | |
|                  lambda state: can_reach_spark(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.grass_land_2_u1, world.player),
 | |
|                  lambda state: can_reach_needle(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_2_u1, world.player),
 | |
|                  lambda state: can_reach_kine(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_2_m1, world.player),
 | |
|                  lambda state: can_reach_kine(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_3_u1, world.player),
 | |
|                  lambda state: can_reach_cutter(state, world.player) or can_reach_spark(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_4_u1, world.player),
 | |
|                  lambda state: can_reach_stone(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_4_m2, world.player),
 | |
|                  lambda state: can_reach_stone(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m1, world.player),
 | |
|                  lambda state: can_reach_kine(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_5_u1, world.player),
 | |
|                  lambda state: (can_reach_kine(state, world.player) and
 | |
|                                 can_reach_burning(state, world.player) and
 | |
|                                 can_reach_stone(state, world.player)))
 | |
|         set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m2, world.player),
 | |
|                  lambda state: (can_reach_kine(state, world.player) and
 | |
|                                 can_reach_burning(state, world.player) and
 | |
|                                 can_reach_stone(state, world.player)))
 | |
|         set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_u1, world.player),
 | |
|                  lambda state: can_reach_clean(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_m2, world.player),
 | |
|                  lambda state: can_reach_needle(state, world.player))
 | |
|         set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player),
 | |
|                  lambda state: can_reach_ice(state, world.player) and
 | |
|                  (can_reach_rick(state, world.player) or can_reach_coo(state, world.player)))
 | |
|         set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u3, world.player),
 | |
|                  lambda state: can_reach_ice(state, world.player) and
 | |
|                  (can_reach_rick(state, world.player) or can_reach_coo(state, world.player)))
 | |
|         set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u4, world.player),
 | |
|                  lambda state: can_reach_ice(state, world.player) and
 | |
|                  (can_reach_rick(state, world.player) or can_reach_coo(state, world.player)))
 | |
|         set_rule(world.multiworld.get_location(LocationName.cloudy_park_6_u1, world.player),
 | |
|                  lambda state: can_reach_cutter(state, world.player))
 | |
| 
 | |
|     if world.options.starsanity:
 | |
|         # ranges are our friend
 | |
|         for i in range(7, 11):
 | |
|             set_rule(world.multiworld.get_location(f"Grass Land 1 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_cutter(state, world.player))
 | |
|         for i in range(11, 14):
 | |
|             set_rule(world.multiworld.get_location(f"Grass Land 1 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_parasol(state, world.player))
 | |
|         for i in [1, 3, 4, 9, 10]:
 | |
|             set_rule(world.multiworld.get_location(f"Grass Land 2 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_stone(state, world.player))
 | |
|         set_rule(world.multiworld.get_location("Grass Land 2 - Star 2", world.player),
 | |
|                  lambda state: can_reach_burning(state, world.player))
 | |
|         set_rule(world.multiworld.get_location("Ripple Field 2 - Star 17", world.player),
 | |
|                  lambda state: can_reach_kine(state, world.player))
 | |
|         for i in range(41, 43):
 | |
|             # any star past this point also needs kine, but so does the exit
 | |
|             set_rule(world.multiworld.get_location(f"Ripple Field 5 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_kine(state, world.player))
 | |
|         for i in range(46, 49):
 | |
|             # also requires kine, but only for access from the prior room
 | |
|             set_rule(world.multiworld.get_location(f"Ripple Field 5 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_burning(state, world.player) and can_reach_stone(state, world.player))
 | |
|         for i in range(12, 18):
 | |
|             set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_ice(state, world.player) and
 | |
|                      (can_reach_rick(state, world.player) or can_reach_coo(state, world.player)))
 | |
|         for i in range(21, 23):
 | |
|             set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_chuchu(state, world.player))
 | |
|         for r in [range(19, 21), range(23, 31)]:
 | |
|             for i in r:
 | |
|                 set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player),
 | |
|                          lambda state: can_reach_clean(state, world.player))
 | |
|         for i in range(31, 41):
 | |
|             set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_burning(state, world.player))
 | |
|         for r in [range(1, 31), range(44, 51)]:
 | |
|             for i in r:
 | |
|                 set_rule(world.multiworld.get_location(f"Cloudy Park 4 - Star {i}", world.player),
 | |
|                          lambda state: can_reach_clean(state, world.player))
 | |
|         for i in [18, *list(range(20, 25))]:
 | |
|             set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_ice(state, world.player))
 | |
|         for i in [19, *list(range(25, 30))]:
 | |
|             set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player),
 | |
|                      lambda state: can_reach_ice(state, world.player))
 | |
|     # 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
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_2_E3, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_3_E6, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     # Ripple Field 4 E5, E7, and E8 are doable, but too strict to leave in logic
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E5, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E7, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E8, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E1, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E2, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E3, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E4, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E7, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E8, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E9, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
|     set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E10, world.player),
 | |
|              lambda state: can_reach_kine(state, world.player))
 | |
| 
 | |
|     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"],
 | |
|                                           [LocationName.grass_land_whispy, LocationName.ripple_field_acro,
 | |
|                                            LocationName.sand_canyon_poncon, LocationName.cloudy_park_ado,
 | |
|                                            LocationName.iceberg_dedede],
 | |
|                                           range(1, 6)):
 | |
|         set_rule(world.multiworld.get_location(boss_flag, world.player),
 | |
|                  lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1])
 | |
|                                      and can_reach_boss(state, world.player, i,
 | |
|                                                         world.options.open_world.value,
 | |
|                                                         world.options.ow_boss_requirement.value,
 | |
|                                                         world.player_levels)))
 | |
|         set_rule(world.multiworld.get_location(purification, world.player),
 | |
|                  lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1])
 | |
|                                      and can_reach_boss(state, world.player, i,
 | |
|                                                         world.options.open_world.value,
 | |
|                                                         world.options.ow_boss_requirement.value,
 | |
|                                                         world.player_levels)))
 | |
| 
 | |
|     set_rule(world.multiworld.get_entrance("To Level 6", world.player),
 | |
|              lambda state: state.has("Heart Star", world.player, world.required_heart_stars))
 | |
| 
 | |
|     for level in range(2, 6):
 | |
|         set_rule(world.multiworld.get_entrance(f"To Level {level}", world.player),
 | |
|                  lambda state, i=level: state.has(f"Level {i - 1} Boss Defeated", world.player))
 | |
| 
 | |
|     if world.options.strict_bosses:
 | |
|         for level in range(2, 6):
 | |
|             add_rule(world.multiworld.get_entrance(f"To Level {level}", world.player),
 | |
|                      lambda state, i=level: state.has(f"Level {i - 1} Boss Purified", world.player))
 | |
| 
 | |
|     if world.options.goal_speed == GoalSpeed.option_normal:
 | |
|         add_rule(world.multiworld.get_entrance("To Level 6", world.player),
 | |
|                  lambda state: state.has_all(["Level 1 Boss Purified", "Level 2 Boss Purified", "Level 3 Boss Purified",
 | |
|                                               "Level 4 Boss Purified", "Level 5 Boss Purified"], world.player))
 |