diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index 2a8206b0cb..796a076217 100644 --- a/worlds/sm64ex/Regions.py +++ b/worlds/sm64ex/Regions.py @@ -79,6 +79,14 @@ sm64_secrets_to_level = {secret: level for (level,secret) in sm64_level_to_secre sm64_entrances_to_level = {**sm64_paintings_to_level, **sm64_secrets_to_level } sm64_level_to_entrances = {**sm64_level_to_paintings, **sm64_level_to_secrets } +# Levels with at least one star without a movement rule +# Currently excluding WF, HMC, WDW, TTM, THI, TTC, and RR +valid_move_randomizer_start_courses = [ + "Bob-omb Battlefield", "Jolly Roger Bay", "Cool, Cool Mountain", + "Big Boo's Haunt", "Lethal Lava Land", "Shifting Sand Land", + "Dire, Dire Docks", "Snowman's Land" +] + def create_regions(multiworld: MultiWorld, options: SM64Options, player: int): regSS = Region("Menu", player, multiworld, "Castle Area") create_default_locs(regSS, locSS_table) diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py index 92ee61bda4..e97c2aacd0 100644 --- a/worlds/sm64ex/Rules.py +++ b/worlds/sm64ex/Rules.py @@ -5,7 +5,8 @@ from ..generic.Rules import add_rule, set_rule from .Locations import location_table from .Options import SM64Options from .Regions import connect_regions, SM64Levels, sm64_level_to_paintings, sm64_paintings_to_level,\ -sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances +sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances,\ +valid_move_randomizer_start_courses from .Items import action_item_data_table def shuffle_dict_keys(multiworld: MultiWorld, dictionary: dict) -> dict: @@ -28,40 +29,33 @@ def fix_reg(entrance_map: Dict[SM64Levels, str], entrance: SM64Levels, invalid_r def set_rules(multiworld: MultiWorld, options: SM64Options, player: int, area_connections: dict, star_costs: dict, move_rando_bitvec: int): randomized_level_to_paintings = sm64_level_to_paintings.copy() randomized_level_to_secrets = sm64_level_to_secrets.copy() - valid_move_randomizer_start_courses = [ - "Bob-omb Battlefield", "Jolly Roger Bay", "Cool, Cool Mountain", - "Big Boo's Haunt", "Lethal Lava Land", "Shifting Sand Land", - "Dire, Dire Docks", "Snowman's Land" - ] # Excluding WF, HMC, WDW, TTM, THI, TTC, and RR - if options.area_rando >= 1: # Some randomization is happening, randomize Courses - randomized_level_to_paintings = shuffle_dict_keys(multiworld,sm64_level_to_paintings) - # If not shuffling later, ensure a valid start course on move randomizer - if options.area_rando < 3 and move_rando_bitvec > 0: - swapdict = randomized_level_to_paintings.copy() - invalid_start_courses = {course for course in randomized_level_to_paintings.values() if course not in valid_move_randomizer_start_courses} - fix_reg(randomized_level_to_paintings, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, multiworld) - fix_reg(randomized_level_to_paintings, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, multiworld) - if options.area_rando == 2: # Randomize Secrets as well + if options.area_rando > options.area_rando.option_Off: # Some randomization is happening, randomize Courses + randomized_level_to_paintings = shuffle_dict_keys(multiworld, sm64_level_to_paintings) + + if options.area_rando == options.area_rando.option_Courses_and_Secrets_Separate: # Randomize Secrets as well randomized_level_to_secrets = shuffle_dict_keys(multiworld, sm64_level_to_secrets) - randomized_entrances = {**randomized_level_to_paintings, **randomized_level_to_secrets} - if options.area_rando == 3: # Randomize Courses and Secrets in one pool + + randomized_entrances = {**randomized_level_to_paintings, **randomized_level_to_secrets} # Concatenate courses and secrets for rest + + if options.area_rando == options.area_rando.option_Courses_and_Secrets: # Randomize Courses and Secrets in one pool randomized_entrances = shuffle_dict_keys(multiworld, randomized_entrances) - # Guarantee first entrance is a course - swapdict = randomized_entrances.copy() - if move_rando_bitvec == 0: - fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, sm64_secrets_to_level.keys(), swapdict, multiworld) - else: - invalid_start_courses = {course for course in randomized_entrances.values() if course not in valid_move_randomizer_start_courses} - fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, multiworld) - fix_reg(randomized_entrances, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, multiworld) - # Guarantee BITFS is not mapped to DDD - fix_reg(randomized_entrances, SM64Levels.BOWSER_IN_THE_FIRE_SEA, {"Dire, Dire Docks"}, swapdict, multiworld) - # Guarantee COTMC is not mapped to HMC, cuz thats impossible. If BitFS -> HMC, also no COTMC -> DDD. - if randomized_entrances[SM64Levels.BOWSER_IN_THE_FIRE_SEA] == "Hazy Maze Cave": - fix_reg(randomized_entrances, SM64Levels.CAVERN_OF_THE_METAL_CAP, {"Hazy Maze Cave", "Dire, Dire Docks"}, swapdict, multiworld) - else: - fix_reg(randomized_entrances, SM64Levels.CAVERN_OF_THE_METAL_CAP, {"Hazy Maze Cave"}, swapdict, multiworld) + + # Now, fix assignment if necessary + swapdict = randomized_entrances.copy() + if move_rando_bitvec == 0: + fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, sm64_secrets_to_level.keys(), swapdict, multiworld) + else: + invalid_start_courses = {course for course in randomized_entrances.values() if course not in valid_move_randomizer_start_courses} + fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, multiworld) + fix_reg(randomized_entrances, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, multiworld) + # Guarantee BITFS is not mapped to DDD + fix_reg(randomized_entrances, SM64Levels.BOWSER_IN_THE_FIRE_SEA, {"Dire, Dire Docks"}, swapdict, multiworld) + # Guarantee COTMC is not mapped to HMC, cuz thats impossible. If BitFS -> HMC, also no COTMC -> DDD. + if randomized_entrances[SM64Levels.BOWSER_IN_THE_FIRE_SEA] == "Hazy Maze Cave": + fix_reg(randomized_entrances, SM64Levels.CAVERN_OF_THE_METAL_CAP, {"Hazy Maze Cave", "Dire, Dire Docks"}, swapdict, multiworld) + else: + fix_reg(randomized_entrances, SM64Levels.CAVERN_OF_THE_METAL_CAP, {"Hazy Maze Cave"}, swapdict, multiworld) # Destination Format: LVL | AREA with LVL = LEVEL_x, AREA = Area as used in sm64 code # Cast to int to not rely on availability of SM64Levels enum. Will cause crash in MultiServer otherwise diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index f1208d2059..138425e43d 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -139,7 +139,7 @@ class SM64World(World): if self.move_rando_bitvec & (1 << itemdata.code - double_jump_bitvec_offset)] def generate_basic(self): - if not (self.options.buddy_checks): + if not self.options.buddy_checks: self.multiworld.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB")) self.multiworld.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF")) self.multiworld.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB")) diff --git a/worlds/sm64ex/test/__init__.py b/worlds/sm64ex/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/worlds/sm64ex/test/bases.py b/worlds/sm64ex/test/bases.py new file mode 100644 index 0000000000..b19a5c0f25 --- /dev/null +++ b/worlds/sm64ex/test/bases.py @@ -0,0 +1,8 @@ +from test.bases import WorldTestBase + +from .. import SM64World + + +class SM64TestBase(WorldTestBase): + game = "Super Mario 64" + world: SM64World diff --git a/worlds/sm64ex/test/test_access.py b/worlds/sm64ex/test/test_access.py new file mode 100644 index 0000000000..32695da259 --- /dev/null +++ b/worlds/sm64ex/test/test_access.py @@ -0,0 +1,81 @@ +from .bases import SM64TestBase +from .. import Options +from ..Regions import sm64_entrances_to_level, sm64_level_to_entrances + +# Access to Locations/Areas/Entrances by Power Star count in star_cost +class StarCostAccessTestBase(SM64TestBase): + run_default_tests = False + options = { + "progressive_keys": Options.ProgressiveKeys.option_false, + "enable_locked_paintings": Options.EnableLockedPaintings.option_false, + # Test for access would mean access to entrance/painting, + # not level itself for the sake of entrance rando. + "area_rando": Options.AreaRandomizer.option_Courses_and_Secrets, + } + + def test_BoB_entrance_access(self): + # Always accessible, no stars needed. + bob_level_id = sm64_entrances_to_level["Bob-omb Battlefield"] + bob_entrance_id = self.world.area_connections[bob_level_id] + bob_entrance = sm64_level_to_entrances[bob_entrance_id] + self.assertTrue(self.can_reach_region(bob_entrance)) + + def test_MIPS1_access(self): + # Requires Basement Key and Power Stars calculated in star_costs["MIPS1Cost"] + self.assertFalse(self.can_reach_location("MIPS 1")) + self.collect([self.get_item_by_name("Basement Key")]) + self.assertFalse(self.can_reach_location("MIPS 1")) + mips1_cost = self.world.star_costs["MIPS1Cost"] + self.collect([self.get_item_by_name("Power Star")] * mips1_cost) + self.assertTrue(self.can_reach_location("MIPS 1")) + + def test_MIPS2_access(self): + # Requires Basement Key and Power Stars calculated in star_costs["MIPS2Cost"] + self.assertFalse(self.can_reach_location("MIPS 2")) + self.collect([self.get_item_by_name("Basement Key")]) + self.assertFalse(self.can_reach_location("MIPS 2")) + mips2_cost = self.world.star_costs["MIPS2Cost"] + self.collect([self.get_item_by_name("Power Star")] * mips2_cost) + self.assertTrue(self.can_reach_location("MIPS 2")) + + def test_BitDW_entrance_access(self): + # Requires Power Stars calculated in star_costs["FirstBowserDoorCost"] + bitdw_level_id = sm64_entrances_to_level["Bowser in the Dark World"] + bitdw_entrance_id = self.world.area_connections[bitdw_level_id] + bitdw_entrance = sm64_level_to_entrances[bitdw_entrance_id] + self.assertFalse(self.can_reach_region(bitdw_entrance)) + bitdw_cost = self.world.star_costs["FirstBowserDoorCost"] + self.collect([self.get_item_by_name("Power Star")] * bitdw_cost) + self.assertTrue(self.can_reach_region(bitdw_entrance)) + + # Since BitFS is locked behind "DDD: Board Bowser's Sub", we just need to test DDD. + def test_DDD_entrance_access(self): + # Requires Basement Key and Power Stars calculated in star_costs["BasementDoorCost"] + ddd_level_id = sm64_entrances_to_level["Dire, Dire Docks"] + ddd_entrance_id = self.world.area_connections[ddd_level_id] + ddd_entrance = sm64_level_to_entrances[ddd_entrance_id] + self.assertFalse(self.can_reach_region(ddd_entrance)) + self.collect([self.get_item_by_name("Basement Key")]) + self.assertFalse(self.can_reach_region(ddd_entrance)) + bitfs_cost = self.world.star_costs["BasementDoorCost"] + self.collect([self.get_item_by_name("Power Star")] * bitfs_cost) + self.assertTrue(self.can_reach_region(ddd_entrance)) + + def test_Floor3_access(self): + # Requires Second Floor Key and Power Stars calculated in star_costs["SecondFloorDoorCost"] + self.assertFalse(self.can_reach_region("Third Floor")) + self.collect([self.get_item_by_name("Second Floor Key")]) + self.assertFalse(self.can_reach_region("Third Floor")) + floor3_cost = self.world.star_costs["StarsToFinish"] + self.collect([self.get_item_by_name("Power Star")] * floor3_cost) + self.assertTrue(self.can_reach_region("Third Floor")) + + def test_BitS_entrance_access(self): + # Requires Second Floor Key and Power Stars calculated in star_costs["StarsToFinish"] + self.assertFalse(self.can_reach_region("Bowser in the Sky")) + self.collect([self.get_item_by_name("Second Floor Key")]) + self.assertFalse(self.can_reach_region("Bowser in the Sky")) + bits_cost = self.world.star_costs["StarsToFinish"] + self.collect([self.get_item_by_name("Power Star")] * bits_cost) + self.assertTrue(self.can_reach_region("Third Floor")) + self.assertTrue(self.can_reach_region("Bowser in the Sky")) diff --git a/worlds/sm64ex/test/test_options.py b/worlds/sm64ex/test/test_options.py new file mode 100644 index 0000000000..2a55b31723 --- /dev/null +++ b/worlds/sm64ex/test/test_options.py @@ -0,0 +1,278 @@ +from .bases import SM64TestBase +from .. import Options +from ..Locations import loc100Coin_table, location_table +from ..Regions import sm64_entrances_to_level, sm64_level_to_paintings, sm64_level_to_secrets, \ + valid_move_randomizer_start_courses + +valid_move_randomizer_start_entrances = { + level: entrance + for (level, entrance) in sm64_entrances_to_level.items() + if level in valid_move_randomizer_start_courses +} + + +# Coin Star Logic +class EnableCoinStarsTestBase(SM64TestBase): + options = { + "enable_coin_stars": Options.EnableCoinStars.option_on + } + + # Ensure Coin Star locations are created + def test_coin_star_locations(self): + possible_locations = self.world.location_names + for loc in loc100Coin_table: + # Use subtest to force all locations to be tested + with self.subTest("Location created", location=loc): + self.assertIn(loc, possible_locations) + + +class DisableCoinStarsTestBase(SM64TestBase): + options = { + "enable_coin_stars": Options.EnableCoinStars.option_off + } + + # Ensure Coin Star locations are not created + def test_coin_star_locations(self): + possible_locations = self.world.get_locations() + for loc in loc100Coin_table: + # Use subtest to force all locations to be tested + with self.subTest("Location not created", location=loc): + self.assertNotIn(loc, possible_locations) + + +class VanillaCoinStarsTestBase(SM64TestBase): + options = { + "enable_coin_stars": Options.EnableCoinStars.option_vanilla + } + + # Ensure Coin Star locations are created + def test_coin_star_locations(self): + possible_locations = self.world.location_names + for loc in loc100Coin_table: + # Use subtest to force all locations to be tested + with self.subTest("Location created", location=loc): + self.assertIn(loc, possible_locations) + + # Vanilla Coin Stars should give the player their own Power Stars + def test_items_in_coin_star_locations(self): + for loc in loc100Coin_table: + # Use subtest to force all locations to be tested + with self.subTest("Location created", location=loc): + item_in_loc = self.world.get_location(loc).item + self.assertEqual(item_in_loc.name, "Power Star") + # By default, these test bases are single player multiworld. + # In any other case, we should test that they belong to their respective worlds. + + +# Exclamation Boxes +class ExclamationBoxesOnTestBase(SM64TestBase): + options = { + "exclamation_boxes": Options.ExclamationBoxes.option_true, + } + + +class ExclamationBoxesOffTestBase(SM64TestBase): + options = { + "exclamation_boxes": Options.ExclamationBoxes.option_false, + } + + # Should populate the boxes with the players own 1Up Mushrooms + def test_items_in_exclamation_box_locations(self): + # Get 1Up Block locations + loc1ups_table = {name for name in location_table.keys() if "1Up Block" in name} + for loc in loc1ups_table: + # Use subtest to force all locations to be tested + with self.subTest("Location has own 1Up Mushroom.", location=loc): + item_in_loc = self.world.get_location(loc).item + self.assertEqual(item_in_loc.name, "1Up Mushroom") + # By default, these test bases are single player multiworld. + # In any other case, we should test that they belong to their respective worlds. + + +# Entrance Randomizer +class EntranceRandoOffTestBase(SM64TestBase): + options = { + "area_rando": Options.AreaRandomizer.option_Off + } + + # Ensure entrance rando disabled + def test_BoB_entrance(self): + bob_level_id = sm64_entrances_to_level["Bob-omb Battlefield"] + self.assertEqual(self.world.area_connections[bob_level_id], bob_level_id) + + def test_BitFS_entrance(self): + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + self.assertEqual(self.world.area_connections[bitfs_level_id], bitfs_level_id) + + +class EntranceRandoCourseTestBase(SM64TestBase): + options = { + "area_rando": Options.AreaRandomizer.option_Courses_Only + } + + def test_BoB_entrance(self): + bob_level_id = sm64_entrances_to_level["Bob-omb Battlefield"] + # BoB goes to a painting, not a secret + self.assertNotIn(self.world.area_connections[bob_level_id], sm64_level_to_secrets.keys()) + self.assertIn(self.world.area_connections[bob_level_id], sm64_level_to_paintings.keys()) + + def test_BitFS_entrance(self): + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + # BitFS is a secret (aka not a course), unaffected by Course Only entrance rando. + self.assertEqual(self.world.area_connections[bitfs_level_id], bitfs_level_id) + + +class EntranceRandoSeparateTestBase(SM64TestBase): + options = { + "area_rando": Options.AreaRandomizer.option_Courses_and_Secrets_Separate + } + + def test_BoB_entrance(self): + bob_level_id = sm64_entrances_to_level["Bob-omb Battlefield"] + # BoB goes to a painting, not a secret + self.assertNotIn(self.world.area_connections[bob_level_id], sm64_level_to_secrets.keys()) + self.assertIn(self.world.area_connections[bob_level_id], sm64_level_to_paintings.keys()) + + def test_BitFS_entrance(self): + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + # BitFS goes to a secret, not a painting + self.assertIn(self.world.area_connections[bitfs_level_id], sm64_level_to_secrets.keys()) + self.assertNotIn(self.world.area_connections[bitfs_level_id], sm64_level_to_paintings.keys()) + # BitFS does not go to DDD + self.assertIsNot(self.world.area_connections[bitfs_level_id], sm64_entrances_to_level["Dire, Dire Docks"]) + + +class EntranceRandoAllTestBase(SM64TestBase): + options = { + "area_rando": Options.AreaRandomizer.option_Courses_and_Secrets + } + + def test_BitFS_entrance(self): + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + # BitFS does not go to DDD + self.assertIsNot(self.world.area_connections[bitfs_level_id], sm64_entrances_to_level["Dire, Dire Docks"]) + + +# Completion Type +class CompletionLastBowserTestBase(SM64TestBase): + options = { + "completion_type": Options.CompletionType.option_Last_Bowser_Stage + } + + +class CompletionAllBowserTestBase(SM64TestBase): + options = { + "completion_type": Options.CompletionType.option_All_Bowser_Stages + } + + +# Option Combos + + +# Smallest Power Star count possible +class MinimumStarsPossibleTestBase(SM64TestBase): + options = { + "amount_of_stars": Options.AmountOfStars.range_start, + "enable_move_rando": Options.EnableMoveRandomizer.option_true, + "exclamation_boxes": Options.ExclamationBoxes.option_false, + "enable_coin_stars": Options.EnableCoinStars.option_off + } + + # There will be less Power Stars than filler with this low of a star count + def test_stars_vs_filler(self): + filler_count = len(self.get_items_by_name("1Up Mushroom")) + star_count = len(self.get_items_by_name("Power Star")) + self.assertGreater(filler_count, star_count) + + +# Entrance + Move Randos +class CourseEntrancesMoveTestBase(SM64TestBase): + options = { + "enable_move_rando": Options.EnableMoveRandomizer.option_true, + "area_rando": Options.AreaRandomizer.option_Courses_Only + } + + def test_BoB_entrance(self): + bob_level_id = sm64_entrances_to_level["Bob-omb Battlefield"] + # BoB goes to a course, not a secret. + self.assertNotIn(self.world.area_connections[bob_level_id], sm64_level_to_secrets.keys()) + self.assertIn(self.world.area_connections[bob_level_id], sm64_level_to_paintings.keys()) + # BoB goes to level with at least one star without a movement rule. + self.assertIn(self.world.area_connections[bob_level_id], valid_move_randomizer_start_entrances.values()) + + def test_BitFS_entrance(self): + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + # BitFS is a secret (aka not a course), unaffected by Course Only entrance rando. + self.assertEqual(self.world.area_connections[bitfs_level_id], bitfs_level_id) + + def test_WF_entrance(self): + wf_level_id = sm64_entrances_to_level["Whomp's Fortress"] + # WF goes to level with at least one star without a movement rule. + self.assertIn(self.world.area_connections[wf_level_id], valid_move_randomizer_start_entrances.values()) + + +class SeparateEntrancesMoveTestBase(SM64TestBase): + options = { + "enable_move_rando": Options.EnableMoveRandomizer.option_true, + "area_rando": Options.AreaRandomizer.option_Courses_and_Secrets_Separate + } + + def test_BoB_entrance(self): + bob_level_id = sm64_entrances_to_level["Bob-omb Battlefield"] + # BoB goes to a course, not a secret. + self.assertNotIn(self.world.area_connections[bob_level_id], sm64_level_to_secrets.keys()) + self.assertIn(self.world.area_connections[bob_level_id], sm64_level_to_paintings.keys()) + # BoB goes to level with at least one star without a movement rule. + self.assertIn(self.world.area_connections[bob_level_id], valid_move_randomizer_start_entrances.values()) + + def test_BitFS_entrance(self): + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + # BitFS does not go to DDD. + self.assertIsNot(self.world.area_connections[bitfs_level_id], sm64_entrances_to_level["Dire, Dire Docks"]) + + def test_WF_entrance(self): + wf_level_id = sm64_entrances_to_level["Whomp's Fortress"] + # WF goes to level with at least one star without a movement rule. + self.assertIn(self.world.area_connections[wf_level_id], valid_move_randomizer_start_entrances.values()) + + +class AllEntrancesMoveTestBase(SM64TestBase): + options = { + "enable_move_rando": Options.EnableMoveRandomizer.option_true, + "area_rando": Options.AreaRandomizer.option_Courses_and_Secrets + } + + def test_BoB_entrance(self): + bob_level_id = sm64_entrances_to_level["Bob-omb Battlefield"] + # BoB goes to level with at least one star without a movement rule. + self.assertIn(self.world.area_connections[bob_level_id], valid_move_randomizer_start_entrances.values()) + + def test_BitFS_entrance(self): + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + # BitFS does not go to DDD. + self.assertIsNot(self.world.area_connections[bitfs_level_id], sm64_entrances_to_level["Dire, Dire Docks"]) + + def test_WF_entrance(self): + wf_level_id = sm64_entrances_to_level["Whomp's Fortress"] + # WF goes to level with at least one star without a movement rule. + self.assertIn(self.world.area_connections[wf_level_id], valid_move_randomizer_start_entrances.values()) + + def test_CotMC_entrance(self): + cotmc_level_id = sm64_entrances_to_level["Cavern of the Metal Cap"] + # CotMC does not go to HMC. + self.assertIsNot(self.world.area_connections[cotmc_level_id], sm64_entrances_to_level["Hazy Maze Cave"]) + # If BitFS -> HMC, CotMC does not go to DDD. + bitfs_level_id = sm64_entrances_to_level["Bowser in the Fire Sea"] + if self.world.area_connections[bitfs_level_id] == sm64_entrances_to_level["Hazy Maze Cave"]: + self.assertIsNot(self.world.area_connections[cotmc_level_id], sm64_entrances_to_level["Dire, Dire Docks"]) + + +# No Strict Requirements +class NoStrictRequirementsTestBase(SM64TestBase): + options = { + "enable_move_rando": Options.EnableMoveRandomizer.option_true, + "buddy_checks": Options.BuddyChecks.option_true, + "strict_move_requirements": Options.StrictMoveRequirements.option_false, + "strict_cap_requirements": Options.StrictCapRequirements.option_false, + "strict_cannon_requirements": Options.StrictCannonRequirements.option_false, + }