Kirby's Dream Land 3: Implement New Game (#2119)
Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com> Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> Co-authored-by: Doug Hoskisson <beauxq@yahoo.com> Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
This commit is contained in:
37
worlds/kdl3/test/__init__.py
Normal file
37
worlds/kdl3/test/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import typing
|
||||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld, PlandoOptions, CollectionState
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.general import gen_steps
|
||||
from worlds import AutoWorld
|
||||
from worlds.AutoWorld import call_all
|
||||
|
||||
|
||||
class KDL3TestBase(WorldTestBase):
|
||||
game = "Kirby's Dream Land 3"
|
||||
|
||||
def world_setup(self, seed: typing.Optional[int] = None) -> None:
|
||||
if type(self) is WorldTestBase or \
|
||||
(hasattr(WorldTestBase, self._testMethodName)
|
||||
and not self.run_default_tests and
|
||||
getattr(self, self._testMethodName).__code__ is
|
||||
getattr(WorldTestBase, self._testMethodName, None).__code__):
|
||||
return # setUp gets called for tests defined in the base class. We skip world_setup here.
|
||||
if not hasattr(self, "game"):
|
||||
raise NotImplementedError("didn't define game name")
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.game[1] = self.game
|
||||
self.multiworld.player_name = {1: "Tester"}
|
||||
self.multiworld.set_seed(seed)
|
||||
self.multiworld.state = CollectionState(self.multiworld)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items():
|
||||
setattr(args, name, {
|
||||
1: option.from_any(self.options.get(name, getattr(option, "default")))
|
||||
})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.plando_options = PlandoOptions.connections
|
||||
self.multiworld.plando_connections = self.options["plando_connections"] if "plando_connections" in self.options.keys() else []
|
||||
for step in gen_steps:
|
||||
call_all(self.multiworld, step)
|
||||
64
worlds/kdl3/test/test_goal.py
Normal file
64
worlds/kdl3/test/test_goal.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from . import KDL3TestBase
|
||||
|
||||
|
||||
class TestFastGoal(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "fast",
|
||||
"total_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
}
|
||||
|
||||
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(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
|
||||
self.collect(heart_stars[15:])
|
||||
self.assertBeatable(True)
|
||||
|
||||
|
||||
class TestNormalGoal(KDL3TestBase):
|
||||
# TODO: open world tests
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "normal",
|
||||
"total_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
def test_kine(self):
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_cutter(self):
|
||||
self.collect_by_name(["Kine", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_burning(self):
|
||||
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
68
worlds/kdl3/test/test_locations.py
Normal file
68
worlds/kdl3/test/test_locations.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from . import KDL3TestBase
|
||||
from worlds.generic import PlandoConnection
|
||||
from ..Names import LocationName
|
||||
import typing
|
||||
|
||||
|
||||
class TestLocations(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": True,
|
||||
"ow_boss_requirement": 1,
|
||||
"strict_bosses": False
|
||||
# these ensure we can always reach all stages physically
|
||||
}
|
||||
|
||||
def test_simple_heart_stars(self):
|
||||
self.run_location_test(LocationName.grass_land_muchi, ["ChuChu"])
|
||||
self.run_location_test(LocationName.grass_land_chao, ["Stone"])
|
||||
self.run_location_test(LocationName.grass_land_mine, ["Kine"])
|
||||
self.run_location_test(LocationName.ripple_field_kamuribana, ["Pitch", "Clean"])
|
||||
self.run_location_test(LocationName.ripple_field_bakasa, ["Kine", "Parasol"])
|
||||
self.run_location_test(LocationName.ripple_field_toad, ["Needle"])
|
||||
self.run_location_test(LocationName.ripple_field_mama_pitch, ["Pitch", "Kine", "Burning", "Stone"])
|
||||
self.run_location_test(LocationName.sand_canyon_auntie, ["Clean"])
|
||||
self.run_location_test(LocationName.sand_canyon_nyupun, ["ChuChu", "Cutter"])
|
||||
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"])
|
||||
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]),
|
||||
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]),
|
||||
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]),
|
||||
self.run_location_test(LocationName.cloudy_park_hibanamodoki, ["Coo", "Clean"])
|
||||
self.run_location_test(LocationName.cloudy_park_piyokeko, ["Needle"])
|
||||
self.run_location_test(LocationName.cloudy_park_mikarin, ["Coo"])
|
||||
self.run_location_test(LocationName.cloudy_park_pick, ["Rick"])
|
||||
self.run_location_test(LocationName.iceberg_kogoesou, ["Burning"])
|
||||
self.run_location_test(LocationName.iceberg_samus, ["Ice"])
|
||||
self.run_location_test(LocationName.iceberg_name, ["Burning", "Coo", "ChuChu"])
|
||||
self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", "Stone", "Ice"])
|
||||
|
||||
def run_location_test(self, location: str, itempool: typing.List[str]):
|
||||
items = itempool.copy()
|
||||
while len(itempool) > 0:
|
||||
self.assertFalse(self.can_reach_location(location), str(self.multiworld.seed))
|
||||
self.collect_by_name(itempool.pop())
|
||||
self.assertTrue(self.can_reach_location(location), str(self.multiworld.seed))
|
||||
self.remove(self.get_items_by_name(items))
|
||||
|
||||
|
||||
class TestShiro(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"plando_connections": [
|
||||
[],
|
||||
[
|
||||
PlandoConnection("Grass Land 1", "Iceberg 5", "both"),
|
||||
PlandoConnection("Grass Land 2", "Ripple Field 5", "both"),
|
||||
PlandoConnection("Grass Land 3", "Grass Land 1", "both")
|
||||
]],
|
||||
"stage_shuffle": "shuffled",
|
||||
"plando_options": "connections"
|
||||
}
|
||||
|
||||
def test_shiro(self):
|
||||
self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed))
|
||||
self.collect_by_name("Nago")
|
||||
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")
|
||||
self.assertTrue(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed))
|
||||
245
worlds/kdl3/test/test_shuffles.py
Normal file
245
worlds/kdl3/test/test_shuffles.py
Normal file
@@ -0,0 +1,245 @@
|
||||
from typing import List, Tuple
|
||||
from . import KDL3TestBase
|
||||
from ..Room import KDL3Room
|
||||
|
||||
|
||||
class TestCopyAbilityShuffle(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "normal",
|
||||
"total_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
"copy_ability_randomization": "enabled",
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
def test_kine(self):
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_cutter(self):
|
||||
self.collect_by_name(["Kine", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_burning(self):
|
||||
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_cutter_and_burning_reachable(self):
|
||||
rooms = self.multiworld.worlds[1].rooms
|
||||
copy_abilities = self.multiworld.worlds[1].copy_abilities
|
||||
sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1)
|
||||
assert isinstance(sand_canyon_5, KDL3Room)
|
||||
valid_rooms = [room for room in rooms if (room.level < sand_canyon_5.level)
|
||||
or (room.level == sand_canyon_5.level and room.stage < sand_canyon_5.stage)]
|
||||
for room in valid_rooms:
|
||||
if any(copy_abilities[enemy] == "Cutter Ability" for enemy in room.enemies):
|
||||
break
|
||||
else:
|
||||
self.fail("Could not reach Cutter Ability before Sand Canyon 5!")
|
||||
iceberg_4 = self.multiworld.get_region("Iceberg 4 - 7", 1)
|
||||
assert isinstance(iceberg_4, KDL3Room)
|
||||
valid_rooms = [room for room in rooms if (room.level < iceberg_4.level)
|
||||
or (room.level == iceberg_4.level and room.stage < iceberg_4.stage)]
|
||||
for room in valid_rooms:
|
||||
if any(copy_abilities[enemy] == "Burning Ability" for enemy in room.enemies):
|
||||
break
|
||||
else:
|
||||
self.fail("Could not reach Burning Ability before Iceberg 4!")
|
||||
|
||||
def test_valid_abilities_for_ROB(self):
|
||||
# there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings
|
||||
self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach
|
||||
# first we need to identify our bukiset requirements
|
||||
groups = [
|
||||
({"Parasol Ability", "Cutter Ability"}, {'Bukiset (Parasol)', 'Bukiset (Cutter)'}),
|
||||
({"Spark Ability", "Clean Ability"}, {'Bukiset (Spark)', 'Bukiset (Clean)'}),
|
||||
({"Ice Ability", "Needle Ability"}, {'Bukiset (Ice)', 'Bukiset (Needle)'}),
|
||||
({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}),
|
||||
]
|
||||
copy_abilities = self.multiworld.worlds[1].copy_abilities
|
||||
required_abilities: List[Tuple[str]] = []
|
||||
for abilities, bukisets in groups:
|
||||
potential_abilities: List[str] = list()
|
||||
for bukiset in bukisets:
|
||||
if copy_abilities[bukiset] in abilities:
|
||||
potential_abilities.append(copy_abilities[bukiset])
|
||||
required_abilities.append(tuple(potential_abilities))
|
||||
collected_abilities = list()
|
||||
for group in required_abilities:
|
||||
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:
|
||||
# required for non-Bukiset related portions
|
||||
self.collect_by_name(["Parasol", "Stone"])
|
||||
|
||||
if "Cutter Ability" not in collected_abilities:
|
||||
# we can't actually reach 3-6 without Cutter
|
||||
self.assertFalse(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed))
|
||||
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))
|
||||
|
||||
|
||||
class TestAnimalShuffle(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "normal",
|
||||
"total_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
"animal_randomization": "full",
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
def test_kine(self):
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_cutter(self):
|
||||
self.collect_by_name(["Kine", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_burning(self):
|
||||
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
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"})
|
||||
|
||||
|
||||
class TestAllShuffle(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "normal",
|
||||
"total_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
"animal_randomization": "full",
|
||||
"copy_ability_randomization": "enabled",
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
def test_kine(self):
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_cutter(self):
|
||||
self.collect_by_name(["Kine", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_burning(self):
|
||||
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
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"})
|
||||
|
||||
def test_cutter_and_burning_reachable(self):
|
||||
rooms = self.multiworld.worlds[1].rooms
|
||||
copy_abilities = self.multiworld.worlds[1].copy_abilities
|
||||
sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1)
|
||||
assert isinstance(sand_canyon_5, KDL3Room)
|
||||
valid_rooms = [room for room in rooms if (room.level < sand_canyon_5.level)
|
||||
or (room.level == sand_canyon_5.level and room.stage < sand_canyon_5.stage)]
|
||||
for room in valid_rooms:
|
||||
if any(copy_abilities[enemy] == "Cutter Ability" for enemy in room.enemies):
|
||||
break
|
||||
else:
|
||||
self.fail("Could not reach Cutter Ability before Sand Canyon 5!")
|
||||
iceberg_4 = self.multiworld.get_region("Iceberg 4 - 7", 1)
|
||||
assert isinstance(iceberg_4, KDL3Room)
|
||||
valid_rooms = [room for room in rooms if (room.level < iceberg_4.level)
|
||||
or (room.level == iceberg_4.level and room.stage < iceberg_4.stage)]
|
||||
for room in valid_rooms:
|
||||
if any(copy_abilities[enemy] == "Burning Ability" for enemy in room.enemies):
|
||||
break
|
||||
else:
|
||||
self.fail("Could not reach Burning Ability before Iceberg 4!")
|
||||
|
||||
def test_valid_abilities_for_ROB(self):
|
||||
# there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings
|
||||
self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach
|
||||
# first we need to identify our bukiset requirements
|
||||
groups = [
|
||||
({"Parasol Ability", "Cutter Ability"}, {'Bukiset (Parasol)', 'Bukiset (Cutter)'}),
|
||||
({"Spark Ability", "Clean Ability"}, {'Bukiset (Spark)', 'Bukiset (Clean)'}),
|
||||
({"Ice Ability", "Needle Ability"}, {'Bukiset (Ice)', 'Bukiset (Needle)'}),
|
||||
({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}),
|
||||
]
|
||||
copy_abilities = self.multiworld.worlds[1].copy_abilities
|
||||
required_abilities: List[Tuple[str]] = []
|
||||
for abilities, bukisets in groups:
|
||||
potential_abilities: List[str] = list()
|
||||
for bukiset in bukisets:
|
||||
if copy_abilities[bukiset] in abilities:
|
||||
potential_abilities.append(copy_abilities[bukiset])
|
||||
required_abilities.append(tuple(potential_abilities))
|
||||
collected_abilities = list()
|
||||
for group in required_abilities:
|
||||
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:
|
||||
# required for non-Bukiset related portions
|
||||
self.collect_by_name(["Parasol", "Stone"])
|
||||
|
||||
if "Cutter Ability" not in collected_abilities:
|
||||
# we can't actually reach 3-6 without Cutter
|
||||
self.assertFalse(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed))
|
||||
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))
|
||||
Reference in New Issue
Block a user