| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | from typing import List, Union | 
					
						
							|  |  |  | import unittest | 
					
						
							|  |  |  | import random | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from BaseClasses import MultiWorld | 
					
						
							| 
									
										
										
										
											2023-10-28 00:18:33 +02:00
										 |  |  | from ...mods.mod_data import all_mods | 
					
						
							|  |  |  | from .. import setup_solo_multiworld, SVTestBase, SVTestCase, allsanity_options_without_mods | 
					
						
							|  |  |  | from ..TestOptions import basic_checks | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  | from ... import items, Group, ItemClassification | 
					
						
							|  |  |  | from ...regions import RandomizationFlag, create_final_connections, randomize_connections, create_final_regions | 
					
						
							|  |  |  | from ...items import item_table, items_by_group | 
					
						
							|  |  |  | from ...locations import location_table | 
					
						
							|  |  |  | from ...options import Mods, EntranceRandomization, Friendsanity, SeasonRandomization, SpecialOrderLocations, ExcludeGingerIsland, TrapItems | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 00:18:33 +02:00
										 |  |  | def check_stray_mod_items(chosen_mods: Union[List[str], str], tester: unittest.TestCase, multiworld: MultiWorld): | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |     if isinstance(chosen_mods, str): | 
					
						
							|  |  |  |         chosen_mods = [chosen_mods] | 
					
						
							|  |  |  |     for multiworld_item in multiworld.get_items(): | 
					
						
							|  |  |  |         item = item_table[multiworld_item.name] | 
					
						
							|  |  |  |         tester.assertTrue(item.mod_name is None or item.mod_name in chosen_mods) | 
					
						
							|  |  |  |     for multiworld_location in multiworld.get_locations(): | 
					
						
							|  |  |  |         if multiworld_location.event: | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         location = location_table[multiworld_location.name] | 
					
						
							|  |  |  |         tester.assertTrue(location.mod_name is None or location.mod_name in chosen_mods) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 00:18:33 +02:00
										 |  |  | class TestGenerateModsOptions(SVTestCase): | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_given_single_mods_when_generate_then_basic_checks(self): | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         for mod in all_mods: | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |             with self.subTest(f"Mod: {mod}"): | 
					
						
							|  |  |  |                 multi_world = setup_solo_multiworld({Mods: mod}) | 
					
						
							|  |  |  |                 basic_checks(self, multi_world) | 
					
						
							|  |  |  |                 check_stray_mod_items(mod, self, multi_world) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_given_mod_names_when_generate_paired_with_entrance_randomizer_then_basic_checks(self): | 
					
						
							|  |  |  |         for option in EntranceRandomization.options: | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |             for mod in all_mods: | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |                 with self.subTest(f"entrance_randomization: {option}, Mod: {mod}"): | 
					
						
							|  |  |  |                     multiworld = setup_solo_multiworld({EntranceRandomization.internal_name: option, Mods: mod}) | 
					
						
							|  |  |  |                     basic_checks(self, multiworld) | 
					
						
							|  |  |  |                     check_stray_mod_items(mod, self, multiworld) | 
					
						
							| 
									
										
										
										
											2023-10-28 00:18:33 +02:00
										 |  |  |                     if self.skip_extra_tests: | 
					
						
							|  |  |  |                         return  # assume the rest will work as well | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TestBaseItemGeneration(SVTestBase): | 
					
						
							|  |  |  |     options = { | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         Friendsanity.internal_name: Friendsanity.option_all_with_marriage, | 
					
						
							|  |  |  |         SeasonRandomization.internal_name: SeasonRandomization.option_progressive, | 
					
						
							|  |  |  |         SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi, | 
					
						
							|  |  |  |         Mods.internal_name: all_mods | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_all_progression_items_are_added_to_the_pool(self): | 
					
						
							|  |  |  |         all_created_items = [item.name for item in self.multiworld.itempool] | 
					
						
							|  |  |  |         # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression | 
					
						
							|  |  |  |         items_to_ignore = [event.name for event in items.events] | 
					
						
							|  |  |  |         items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) | 
					
						
							|  |  |  |         items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) | 
					
						
							|  |  |  |         items_to_ignore.extend(footwear.name for footwear in items.items_by_group[Group.FOOTWEAR]) | 
					
						
							|  |  |  |         items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) | 
					
						
							|  |  |  |         items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) | 
					
						
							|  |  |  |         progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression | 
					
						
							|  |  |  |                              and item.name not in items_to_ignore] | 
					
						
							|  |  |  |         for progression_item in progression_items: | 
					
						
							|  |  |  |             with self.subTest(f"{progression_item.name}"): | 
					
						
							|  |  |  |                 self.assertIn(progression_item.name, all_created_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TestNoGingerIslandModItemGeneration(SVTestBase): | 
					
						
							|  |  |  |     options = { | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         Friendsanity.internal_name: Friendsanity.option_all_with_marriage, | 
					
						
							|  |  |  |         SeasonRandomization.internal_name: SeasonRandomization.option_progressive, | 
					
						
							|  |  |  |         ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, | 
					
						
							|  |  |  |         Mods.internal_name: all_mods | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_all_progression_items_except_island_are_added_to_the_pool(self): | 
					
						
							|  |  |  |         all_created_items = [item.name for item in self.multiworld.itempool] | 
					
						
							|  |  |  |         # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression | 
					
						
							|  |  |  |         items_to_ignore = [event.name for event in items.events] | 
					
						
							|  |  |  |         items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) | 
					
						
							|  |  |  |         items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) | 
					
						
							|  |  |  |         items_to_ignore.extend(footwear.name for footwear in items.items_by_group[Group.FOOTWEAR]) | 
					
						
							|  |  |  |         items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) | 
					
						
							|  |  |  |         items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) | 
					
						
							|  |  |  |         progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression | 
					
						
							|  |  |  |                              and item.name not in items_to_ignore] | 
					
						
							|  |  |  |         for progression_item in progression_items: | 
					
						
							|  |  |  |             with self.subTest(f"{progression_item.name}"): | 
					
						
							|  |  |  |                 if Group.GINGER_ISLAND in progression_item.groups: | 
					
						
							|  |  |  |                     self.assertNotIn(progression_item.name, all_created_items) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     self.assertIn(progression_item.name, all_created_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 00:18:33 +02:00
										 |  |  | class TestModEntranceRando(SVTestCase): | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_mod_entrance_randomization(self): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         for option, flag in [(EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), | 
					
						
							|  |  |  |                              (EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION), | 
					
						
							|  |  |  |                              (EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]: | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |             with self.subTest(option=option, flag=flag): | 
					
						
							|  |  |  |                 seed = random.randrange(sys.maxsize) | 
					
						
							|  |  |  |                 rand = random.Random(seed) | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |                 world_options = {EntranceRandomization.internal_name: option, | 
					
						
							|  |  |  |                                  ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false, | 
					
						
							|  |  |  |                                  Mods.internal_name: all_mods} | 
					
						
							|  |  |  |                 multiworld = setup_solo_multiworld(world_options) | 
					
						
							|  |  |  |                 world = multiworld.worlds[1] | 
					
						
							|  |  |  |                 final_regions = create_final_regions(world.options) | 
					
						
							|  |  |  |                 final_connections = create_final_connections(world.options) | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 regions_by_name = {region.name: region for region in final_regions} | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |                 _, randomized_connections = randomize_connections(rand, world.options, regions_by_name) | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 for connection in final_connections: | 
					
						
							|  |  |  |                     if flag in connection.flag: | 
					
						
							|  |  |  |                         connection_in_randomized = connection.name in randomized_connections | 
					
						
							|  |  |  |                         reverse_in_randomized = connection.reverse in randomized_connections | 
					
						
							|  |  |  |                         self.assertTrue(connection_in_randomized, | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |                                         f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}") | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |                         self.assertTrue(reverse_in_randomized, | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |                                         f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}") | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()), | 
					
						
							|  |  |  |                                  f"Connections are duplicated in randomization. Seed = {seed}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 00:18:33 +02:00
										 |  |  | class TestModTraps(SVTestCase): | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |     def test_given_traps_when_generate_then_all_traps_in_pool(self): | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         for value in TrapItems.options: | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |             if value == "no_traps": | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2023-10-28 00:18:33 +02:00
										 |  |  |             world_options = allsanity_options_without_mods() | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |             world_options.update({TrapItems.internal_name: TrapItems.options[value], Mods: "Magic"}) | 
					
						
							| 
									
										
										
										
											2023-07-19 14:26:38 -04:00
										 |  |  |             multi_world = setup_solo_multiworld(world_options) | 
					
						
							|  |  |  |             trap_items = [item_data.name for item_data in items_by_group[Group.TRAP] if Group.DEPRECATED not in item_data.groups] | 
					
						
							|  |  |  |             multiworld_items = [item.name for item in multi_world.get_items()] | 
					
						
							|  |  |  |             for item in trap_items: | 
					
						
							|  |  |  |                 with self.subTest(f"Option: {value}, Item: {item}"): | 
					
						
							|  |  |  |                     self.assertIn(item, multiworld_items) |