diff --git a/worlds/kdl3/Regions.py b/worlds/kdl3/Regions.py index 66153b62d953..f6bf73e55af1 100644 --- a/worlds/kdl3/Regions.py +++ b/worlds/kdl3/Regions.py @@ -5,11 +5,14 @@ from BaseClasses import Region from worlds.AutoWorld import World +from worlds.generic.Rules import add_item_rule from .Locations import KDL3Location from .Names import LocationName from .Options import BossShuffle from .Room import Room -from ..generic.Rules import add_item_rule + +if typing.TYPE_CHECKING: + from . import KDL3World default_levels = { 1: [0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770006, 0x770200], @@ -34,7 +37,7 @@ def generate_valid_level(level, stage, possible_stages, slot_random): return new_stage -def generate_rooms(world: World, door_shuffle: bool, level_regions: typing.Dict[int, Region]): +def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing.Dict[int, Region]): level_names = {LocationName.level_names[level]: level for level in LocationName.level_names} room_data = json.loads(get_data(__name__, os.path.join("data", "Rooms.json"))) rooms: typing.Dict[str, Room] = dict() @@ -44,7 +47,8 @@ def generate_rooms(world: World, door_shuffle: bool, level_regions: typing.Dict[ room_entry["animal_pointers"], room_entry["enemies"], room_entry["entity_load"], room_entry["consumables"], room_entry["consumables_pointer"]) room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else - None for location in room_entry["locations"] if not any([x in location for x in ["1-Up", "Maxim"]]) or + None for location in room_entry["locations"] + if not any([x in location for x in ["1-Up", "Maxim"]]) or world.multiworld.consumables[world.player]}, KDL3Location) rooms[room.name] = room for location in room.locations: @@ -86,8 +90,9 @@ def generate_rooms(world: World, door_shuffle: bool, level_regions: typing.Dict[ proper_stage = world.player_levels[level][stage] level_regions[level].add_exits([first_rooms[proper_stage].name], {first_rooms[proper_stage].name: - (lambda state: True) if world.multiworld.open_world[world.player] or - stage == 0 else lambda state, level=level, stage=stage: state.has( + (lambda state: True) if world.multiworld.open_world[world.player] or + stage == 0 else lambda state, level=level, + stage=stage: state.has( f"{LocationName.level_names_inverse[level]} " f"{f'{stage}'}" f" - Stage Completion", world.player)}) @@ -95,7 +100,7 @@ def generate_rooms(world: World, door_shuffle: bool, level_regions: typing.Dict[ level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name]) -def generate_valid_levels(world: World, enforce_world: bool, enforce_pattern: bool) -> dict: +def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_pattern: bool) -> dict: levels: typing.Dict[int, typing.List[typing.Optional[int]]] = { 1: [None for _ in range(7)], 2: [None for _ in range(7)], @@ -188,7 +193,7 @@ def generate_valid_levels(world: World, enforce_world: bool, enforce_pattern: bo return levels -def create_levels(world: World) -> None: +def create_levels(world: "KDL3World") -> None: menu = Region("Menu", world.player, world.multiworld) level1 = Region("Grass Land", world.player, world.multiworld) level2 = Region("Ripple Field", world.player, world.multiworld) diff --git a/worlds/kdl3/Rom.py b/worlds/kdl3/Rom.py index f868f2394358..5a5760aae56b 100644 --- a/worlds/kdl3/Rom.py +++ b/worlds/kdl3/Rom.py @@ -377,7 +377,7 @@ def patch(self, target: str): def patch_rom(multiworld: MultiWorld, player: int, rom: RomData, heart_stars_required: int, - boss_requirements: Dict[int, int], shuffled_levels: Dict[int, List[int]], bb_boss_enabled: Dict[int, int], + boss_requirements: Dict[int, int], shuffled_levels: Dict[int, List[int]], bb_boss_enabled: List[bool], copy_abilities: Dict[str, str], slot_random: Random): # increase BWRAM by 0x8000 rom.write_byte(0x7FD8, 0x06) diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/Rules.py index e52d147ded5f..9c170e908ff7 100644 --- a/worlds/kdl3/Rules.py +++ b/worlds/kdl3/Rules.py @@ -89,7 +89,7 @@ def can_reach_cutter(state: "CollectionState", player: int) -> bool: 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): + 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) diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py index e93dca581b83..57781c15991f 100644 --- a/worlds/kdl3/__init__.py +++ b/worlds/kdl3/__init__.py @@ -110,6 +110,32 @@ def get_trap_item_name(self) -> str: self.multiworld.ability_trap_weight[self.player]])[0] def pre_fill(self) -> None: + if self.multiworld.copy_ability_randomization[self.player]: + # randomize copy abilities + valid_abilities = list(copy_ability_access_table.keys()) + enemies_to_set = list(self.copy_abilities.keys()) + # now for the edge cases + for abilities, enemies in enemy_restrictive: + available_enemies = list() + for enemy in enemies: + if enemy not in enemies_to_set: + if self.copy_abilities[enemy] in abilities: + break + else: + available_enemies.append(enemy) + else: + chosen_enemy = self.random.choice(available_enemies) + chosen_ability = self.random.choice(tuple(abilities)) + self.copy_abilities[chosen_enemy] = chosen_ability + enemies_to_set.remove(chosen_enemy) + # place remaining + for enemy in enemies_to_set: + self.copy_abilities[enemy] = self.random \ + .choice(valid_abilities) + + for enemy in enemy_mapping: + self.multiworld.get_location(enemy, self.player) \ + .place_locked_item(self.create_item(self.copy_abilities[enemy_mapping[enemy]])) # fill animals if self.multiworld.animal_randomization[self.player] != 0: spawns = [animal for animal in animal_friend_spawns.keys() if @@ -146,32 +172,6 @@ def pre_fill(self) -> None: self.multiworld.get_location(animal, self.player) \ .place_locked_item(self.create_item(animal_friends[animal])) - if self.multiworld.copy_ability_randomization[self.player]: - # randomize copy abilities - valid_abilities = list(copy_ability_access_table.keys()) - enemies_to_set = list(self.copy_abilities.keys()) - # now for the edge cases - for abilities, enemies in enemy_restrictive: - available_enemies = list() - for enemy in enemies: - if enemy not in enemies_to_set: - if self.copy_abilities[enemy] in abilities: - break - else: - available_enemies.append(enemy) - else: - chosen_enemy = self.random.choice(available_enemies) - chosen_ability = self.random.choice(tuple(abilities)) - self.copy_abilities[chosen_enemy] = chosen_ability - enemies_to_set.remove(chosen_enemy) - # place remaining - for enemy in enemies_to_set: - self.copy_abilities[enemy] = self.random \ - .choice(valid_abilities) - - for enemy in enemy_mapping: - self.multiworld.get_location(enemy, self.player) \ - .place_locked_item(self.create_item(self.copy_abilities[enemy_mapping[enemy]])) def create_items(self) -> None: itempool = [] diff --git a/worlds/kdl3/test/TestGoal.py b/worlds/kdl3/test/TestGoal.py index 2bf9e01e78da..af5bc6bd1a1b 100644 --- a/worlds/kdl3/test/TestGoal.py +++ b/worlds/kdl3/test/TestGoal.py @@ -14,10 +14,10 @@ def testGoal(self): self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) self.assertBeatable(False) self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) self.assertBeatable(True) self.remove([self.get_item_by_name("Love-Love Rod")]) self.collect_by_name("Kine") # Ensure a little more progress, but leave out cutter and burning @@ -39,16 +39,16 @@ def testGoal(self): self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) self.assertBeatable(False) self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) self.assertBeatable(False) self.collect_by_name(["Burning", "Cutter", "Kine"]) self.assertBeatable(True) self.remove([self.get_item_by_name("Love-Love Rod")]) self.collect(heart_stars) - self.assertEqual(self.count("Heart Star"), 30) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) self.assertBeatable(True) def testKine(self): diff --git a/worlds/kdl3/test/TestLocations.py b/worlds/kdl3/test/TestLocations.py index 6eaaf9ced393..90a7f34e6eea 100644 --- a/worlds/kdl3/test/TestLocations.py +++ b/worlds/kdl3/test/TestLocations.py @@ -38,9 +38,9 @@ def testSimpleHeartStars(self): def run_location_test(self, location: str, itempool: typing.List[str]): items = itempool.copy() while len(itempool) > 0: - assert not self.can_reach_location(location) + self.assertFalse(self.can_reach_location(location), str(self.multiworld.seed)) self.collect_by_name(itempool.pop()) - assert self.can_reach_location(location) + self.assertTrue(self.can_reach_location(location), str(self.multiworld.seed)) self.remove(self.get_items_by_name(items)) @@ -59,11 +59,10 @@ class TestShiro(KDL3TestBase): } def testShiro(self): - assert not self.can_reach_location("Iceberg 5 - Shiro") + self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) self.collect_by_name("Nago") - assert not self.can_reach_location("Iceberg 5 - Shiro") + self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) # despite Shiro only requiring Nago for logic, it cannot be in logic because our two accessible stages # do not actually give the player access to Nago, thus we need Kine to pass 2-5 self.collect_by_name("Kine") - assert self.can_reach_location("Iceberg 5 - Shiro") - + self.assertTrue(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) diff --git a/worlds/kdl3/test/TestShuffles.py b/worlds/kdl3/test/TestShuffles.py index 1b12a59ec998..a875b9e34241 100644 --- a/worlds/kdl3/test/TestShuffles.py +++ b/worlds/kdl3/test/TestShuffles.py @@ -17,16 +17,16 @@ def testGoal(self): self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) self.assertBeatable(False) self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) self.assertBeatable(False) self.collect_by_name(["Burning", "Cutter", "Kine"]) self.assertBeatable(True) self.remove([self.get_item_by_name("Love-Love Rod")]) self.collect(heart_stars) - self.assertEqual(self.count("Heart Star"), 30) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) self.assertBeatable(True) def testKine(self): @@ -61,7 +61,7 @@ def testValidAbilitiesForROB(self): required_abilities.append(tuple(potential_abilities)) collected_abilities = list() for group in required_abilities: - self.assertFalse(len(group) == 0) + self.assertFalse(len(group) == 0, str(self.multiworld.seed)) collected_abilities.append(group[0]) self.collect_by_name([ability.replace(" Ability", "") for ability in collected_abilities]) if "Parasol Ability" not in collected_abilities or "Stone Ability" not in collected_abilities: @@ -70,10 +70,10 @@ def testValidAbilitiesForROB(self): if "Cutter Ability" not in collected_abilities: # we can't actually reach 3-6 without Cutter - assert not self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B") + self.assertFalse(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed)) self.collect_by_name(["Cutter"]) - assert self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B") + self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed)) class TestAnimalShuffle(KDL3TestBase): @@ -90,16 +90,16 @@ def testGoal(self): self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) self.assertBeatable(False) self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) self.assertBeatable(False) self.collect_by_name(["Burning", "Cutter", "Kine"]) self.assertBeatable(True) self.remove([self.get_item_by_name("Love-Love Rod")]) self.collect(heart_stars) - self.assertEqual(self.count("Heart Star"), 30) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) self.assertBeatable(True) def testKine(self): @@ -115,9 +115,9 @@ def testBurning(self): self.assertBeatable(False) def testLockedAnimals(self): - self.assertTrue(self, self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") - self.assertTrue(self, self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") - self.assertTrue(self, self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) class TestAllShuffle(KDL3TestBase): @@ -135,16 +135,16 @@ def testGoal(self): self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) self.assertBeatable(False) self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) self.assertBeatable(False) self.collect_by_name(["Burning", "Cutter", "Kine"]) self.assertBeatable(True) self.remove([self.get_item_by_name("Love-Love Rod")]) self.collect(heart_stars) - self.assertEqual(self.count("Heart Star"), 30) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) self.assertBeatable(True) def testKine(self): @@ -160,6 +160,6 @@ def testBurning(self): self.assertBeatable(False) def testLockedAnimals(self): - self.assertTrue(self, self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") - self.assertTrue(self, self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") - self.assertTrue(self, self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) \ No newline at end of file + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) diff --git a/worlds/kdl3/test/__init__.py b/worlds/kdl3/test/__init__.py index 64a2ec44f070..d962b7efa97e 100644 --- a/worlds/kdl3/test/__init__.py +++ b/worlds/kdl3/test/__init__.py @@ -4,8 +4,9 @@ from BaseClasses import MultiWorld, PlandoOptions from test.TestBase import WorldTestBase from test.general import gen_steps -from ... import AutoWorld -from ...AutoWorld import call_all +from worlds import AutoWorld +from worlds.AutoWorld import call_all + class KDL3TestBase(WorldTestBase): game = "Kirby's Dream Land 3"