diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py index a2eff1e51e..77fc3dee95 100644 --- a/worlds/kdl3/__init__.py +++ b/worlds/kdl3/__init__.py @@ -1,7 +1,7 @@ import logging import typing -from BaseClasses import Tutorial, ItemClassification, MultiWorld +from BaseClasses import Tutorial, ItemClassification, MultiWorld, CollectionState from Fill import fill_restrictive from Options import PerGameCommonOptions from worlds.AutoWorld import World, WebWorld @@ -201,9 +201,23 @@ class KDL3World(World): animal_pool.append("Coo Spawn") else: animal_pool.append("Kine Spawn") + if not self.options.open_world or self.options.ow_boss_requirement.value == 6: + # Need to guarantee Kine before Ripple Field 5 + ripple_field_5 = self.get_region("Ripple Field 5 - 4") + # this is primarily for typing, but if this ever hits it's fine to crash + assert isinstance(ripple_field_5, KDL3Room) + valid_rooms = [room for room in self.rooms if (room.level < ripple_field_5.level) + or (room.level == ripple_field_5.stage and room.stage < ripple_field_5.stage)] + animal_spawns = [location for room in valid_rooms for location in room.locations + if "Animal" in location.name] + spawn = self.random.choice(animal_spawns) + animal_pool.remove("Kine Spawn") # should always have at least one in the pool + spawn.place_locked_item(self.create_item("Kine Spawn")) locations = [self.multiworld.get_location(spawn, self.player) for spawn in spawns] items = [self.create_item(animal) for animal in animal_pool] - allstate = self.multiworld.get_all_state(False) + allstate = CollectionState(self.multiworld) + for item in [*copy_ability_table, *animal_friend_table, *["Heart Star" for _ in range(50)]]: + self.collect(allstate, self.create_item(item)) self.random.shuffle(locations) self.random.shuffle(items) fill_restrictive(self.multiworld, allstate, locations, items, True, True) diff --git a/worlds/kdl3/test/test_shuffles.py b/worlds/kdl3/test/test_shuffles.py index d676b641b0..2074a3f7ea 100644 --- a/worlds/kdl3/test/test_shuffles.py +++ b/worlds/kdl3/test/test_shuffles.py @@ -1,4 +1,4 @@ -from typing import List, Tuple +from typing import List, Tuple, Optional from . import KDL3TestBase from ..Room import KDL3Room @@ -14,32 +14,45 @@ class TestCopyAbilityShuffle(KDL3TestBase): } def test_goal(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, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect(heart_stars[14: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, str(self.multiworld.seed)) - self.assertBeatable(True) + try: + 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, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14: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, str(self.multiworld.seed)) + self.assertBeatable(True) + except AssertionError as ex: + # if assert beatable fails, this will catch and print the seed + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_kine(self): - self.collect_by_name(["Cutter", "Burning", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_cutter(self): - self.collect_by_name(["Kine", "Burning", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_burning(self): - self.collect_by_name(["Cutter", "Kine", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_cutter_and_burning_reachable(self): rooms = self.multiworld.worlds[1].rooms @@ -110,37 +123,53 @@ class TestAnimalShuffle(KDL3TestBase): } def test_goal(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, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect(heart_stars[14: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, str(self.multiworld.seed)) - self.assertBeatable(True) + try: + 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, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14: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, str(self.multiworld.seed)) + self.assertBeatable(True) + except AssertionError as ex: + # if assert beatable fails, this will catch and print the seed + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_kine(self): - self.collect_by_name(["Cutter", "Burning", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_cutter(self): - self.collect_by_name(["Kine", "Burning", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_burning(self): - self.collect_by_name(["Cutter", "Kine", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_locked_animals(self): - 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"}) + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn", + f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn", + f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in + {"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}") class TestAllShuffle(KDL3TestBase): @@ -154,38 +183,57 @@ class TestAllShuffle(KDL3TestBase): "copy_ability_randomization": "enabled", } + def world_setup(self, seed: Optional[int] = None) -> None: + super().world_setup(97344459114886422393) + def test_goal(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, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect(heart_stars[14: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, str(self.multiworld.seed)) - self.assertBeatable(True) + try: + 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, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14: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, str(self.multiworld.seed)) + self.assertBeatable(True) + except AssertionError as ex: + # if assert beatable fails, this will catch and print the seed + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_kine(self): - self.collect_by_name(["Cutter", "Burning", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_cutter(self): - self.collect_by_name(["Kine", "Burning", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_burning(self): - self.collect_by_name(["Cutter", "Kine", "Heart Star"]) - self.assertBeatable(False) + try: + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex def test_locked_animals(self): - 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"}) + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn", + f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn", + f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in + {"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}") def test_cutter_and_burning_reachable(self): rooms = self.multiworld.worlds[1].rooms @@ -242,4 +290,4 @@ class TestAllShuffle(KDL3TestBase): self.collect_by_name(["Cutter"]) self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), - ''.join(str(self.multiworld.seed)).join(collected_abilities)) + f"Seed: {self.multiworld.seed}, Collected: {collected_abilities}")