2021-11-11 00:06:51 +01:00
|
|
|
import unittest
|
2025-04-24 14:23:51 -05:00
|
|
|
from argparse import Namespace
|
|
|
|
from typing import Type
|
2024-01-13 19:15:35 -06:00
|
|
|
|
2025-04-24 14:23:51 -05:00
|
|
|
from BaseClasses import CollectionState, MultiWorld
|
|
|
|
from Fill import distribute_items_restrictive
|
|
|
|
from Options import ItemLinks
|
|
|
|
from worlds.AutoWorld import AutoWorldRegister, World, call_all
|
2023-02-15 15:46:10 -06:00
|
|
|
from . import setup_solo_multiworld
|
2021-11-11 00:06:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
class TestBase(unittest.TestCase):
|
2023-10-22 06:00:27 -05:00
|
|
|
def test_create_item(self):
|
|
|
|
"""Test that a world can successfully create all items in its datapackage"""
|
2021-11-11 00:06:51 +01:00
|
|
|
for game_name, world_type in AutoWorldRegister.world_types.items():
|
2025-03-05 23:48:03 +01:00
|
|
|
multiworld = setup_solo_multiworld(world_type, steps=("generate_early", "create_regions", "create_items"))
|
|
|
|
proxy_world = multiworld.worlds[1]
|
2021-11-11 00:06:51 +01:00
|
|
|
for item_name in world_type.item_name_to_id:
|
2025-03-05 23:48:03 +01:00
|
|
|
test_state = CollectionState(multiworld)
|
2021-11-11 00:06:51 +01:00
|
|
|
with self.subTest("Create Item", item_name=item_name, game_name=game_name):
|
|
|
|
item = proxy_world.create_item(item_name)
|
2025-03-05 23:48:03 +01:00
|
|
|
|
|
|
|
with self.subTest("Item Name", item_name=item_name, game_name=game_name):
|
2021-11-11 00:06:51 +01:00
|
|
|
self.assertEqual(item.name, item_name)
|
2022-06-01 17:25:40 +02:00
|
|
|
|
2025-03-05 23:48:03 +01:00
|
|
|
if item.advancement:
|
|
|
|
with self.subTest("Item State Collect", item_name=item_name, game_name=game_name):
|
|
|
|
test_state.collect(item, True)
|
|
|
|
|
|
|
|
with self.subTest("Item State Remove", item_name=item_name, game_name=game_name):
|
|
|
|
test_state.remove(item)
|
|
|
|
|
|
|
|
self.assertEqual(test_state.prog_items, multiworld.state.prog_items,
|
|
|
|
"Item Collect -> Remove should restore empty state.")
|
|
|
|
else:
|
|
|
|
with self.subTest("Item State Collect No Change", item_name=item_name, game_name=game_name):
|
|
|
|
# Non-Advancement should not modify state.
|
|
|
|
test_state.collect(item)
|
|
|
|
self.assertEqual(test_state.prog_items, multiworld.state.prog_items)
|
|
|
|
|
2023-10-22 06:00:27 -05:00
|
|
|
def test_item_name_group_has_valid_item(self):
|
2022-06-01 17:25:40 +02:00
|
|
|
"""Test that all item name groups contain valid items. """
|
|
|
|
# This cannot test for Event names that you may have declared for logic, only sendable Items.
|
|
|
|
# In such a case, you can add your entries to this Exclusion dict. Game Name -> Group Names
|
|
|
|
exclusion_dict = {
|
|
|
|
"A Link to the Past":
|
|
|
|
{"Pendants", "Crystals"},
|
2022-11-09 15:07:14 -06:00
|
|
|
"Ocarina of Time":
|
|
|
|
{"medallions", "stones", "rewards", "logic_bottles"},
|
2024-03-15 17:33:03 +01:00
|
|
|
"Starcraft 2":
|
|
|
|
{"Missions", "WoL Missions"},
|
2024-05-17 19:23:05 +02:00
|
|
|
"Yu-Gi-Oh! 2006":
|
|
|
|
{"Campaign Boss Beaten"}
|
2022-06-01 17:25:40 +02:00
|
|
|
}
|
|
|
|
for game_name, world_type in AutoWorldRegister.world_types.items():
|
|
|
|
with self.subTest(game_name, game_name=game_name):
|
|
|
|
exclusions = exclusion_dict.get(game_name, frozenset())
|
|
|
|
for group_name, items in world_type.item_name_groups.items():
|
|
|
|
if group_name not in exclusions:
|
|
|
|
with self.subTest(group_name, group_name=group_name):
|
|
|
|
for item in items:
|
|
|
|
self.assertIn(item, world_type.item_name_to_id)
|
2022-09-05 01:02:40 -07:00
|
|
|
|
2023-10-22 06:00:27 -05:00
|
|
|
def test_item_name_group_conflict(self):
|
2023-01-18 15:45:48 +01:00
|
|
|
"""Test that all item name groups aren't also item names."""
|
|
|
|
for game_name, world_type in AutoWorldRegister.world_types.items():
|
|
|
|
with self.subTest(game_name, game_name=game_name):
|
|
|
|
for group_name in world_type.item_name_groups:
|
|
|
|
with self.subTest(group_name, group_name=group_name):
|
|
|
|
self.assertNotIn(group_name, world_type.item_name_to_id)
|
|
|
|
|
2024-01-30 08:00:47 +00:00
|
|
|
def test_item_count_equal_locations(self):
|
|
|
|
"""Test that by the pre_fill step under default settings, each game submits items == locations"""
|
2022-09-05 01:02:40 -07:00
|
|
|
for game_name, world_type in AutoWorldRegister.world_types.items():
|
|
|
|
with self.subTest("Game", game=game_name):
|
2023-02-15 15:46:10 -06:00
|
|
|
multiworld = setup_solo_multiworld(world_type)
|
2024-01-30 08:00:47 +00:00
|
|
|
self.assertEqual(
|
2023-02-15 15:46:10 -06:00
|
|
|
len(multiworld.itempool),
|
|
|
|
len(multiworld.get_unfilled_locations()),
|
2024-01-30 08:00:47 +00:00
|
|
|
f"{game_name} Item count MUST match the number of locations",
|
2022-09-05 01:02:40 -07:00
|
|
|
)
|
2023-02-15 15:46:10 -06:00
|
|
|
|
2024-01-13 19:15:35 -06:00
|
|
|
def test_items_in_datapackage(self):
|
2023-02-15 15:46:10 -06:00
|
|
|
"""Test that any created items in the itempool are in the datapackage"""
|
|
|
|
for game_name, world_type in AutoWorldRegister.world_types.items():
|
|
|
|
with self.subTest("Game", game=game_name):
|
|
|
|
multiworld = setup_solo_multiworld(world_type)
|
|
|
|
for item in multiworld.itempool:
|
|
|
|
self.assertIn(item.name, world_type.item_name_to_id)
|
2025-04-24 14:23:51 -05:00
|
|
|
|
|
|
|
def test_item_links(self) -> None:
|
|
|
|
"""
|
|
|
|
Tests item link creation by creating a multiworld of 2 worlds for every game and linking their items together.
|
|
|
|
"""
|
|
|
|
def setup_link_multiworld(world: Type[World], link_replace: bool) -> None:
|
|
|
|
multiworld = MultiWorld(2)
|
|
|
|
multiworld.game = {1: world.game, 2: world.game}
|
|
|
|
multiworld.player_name = {1: "Linker 1", 2: "Linker 2"}
|
|
|
|
multiworld.set_seed()
|
|
|
|
item_link_group = [{
|
|
|
|
"name": "ItemLinkTest",
|
|
|
|
"item_pool": ["Everything"],
|
|
|
|
"link_replacement": link_replace,
|
|
|
|
"replacement_item": None,
|
|
|
|
}]
|
|
|
|
args = Namespace()
|
|
|
|
for name, option in world.options_dataclass.type_hints.items():
|
|
|
|
setattr(args, name, {1: option.from_any(option.default), 2: option.from_any(option.default)})
|
|
|
|
setattr(args, "item_links",
|
|
|
|
{1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)})
|
|
|
|
multiworld.set_options(args)
|
|
|
|
multiworld.set_item_links()
|
|
|
|
# groups get added to state during its constructor so this has to be after item links are set
|
|
|
|
multiworld.state = CollectionState(multiworld)
|
|
|
|
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "connect_entrances", "generate_basic")
|
|
|
|
for step in gen_steps:
|
|
|
|
call_all(multiworld, step)
|
|
|
|
# link the items together and attempt to fill
|
|
|
|
multiworld.link_items()
|
|
|
|
multiworld._all_state = None
|
|
|
|
call_all(multiworld, "pre_fill")
|
|
|
|
distribute_items_restrictive(multiworld)
|
|
|
|
call_all(multiworld, "post_fill")
|
|
|
|
self.assertTrue(multiworld.can_beat_game(CollectionState(multiworld)), f"seed = {multiworld.seed}")
|
|
|
|
|
|
|
|
for game_name, world_type in AutoWorldRegister.world_types.items():
|
|
|
|
with self.subTest("Can generate with link replacement", game=game_name):
|
|
|
|
setup_link_multiworld(world_type, True)
|
|
|
|
with self.subTest("Can generate without link replacement", game=game_name):
|
|
|
|
setup_link_multiworld(world_type, False)
|
2023-11-15 07:26:10 +01:00
|
|
|
|
2024-01-13 19:15:35 -06:00
|
|
|
def test_itempool_not_modified(self):
|
|
|
|
"""Test that worlds don't modify the itempool after `create_items`"""
|
|
|
|
gen_steps = ("generate_early", "create_regions", "create_items")
|
2025-01-20 16:07:15 +01:00
|
|
|
additional_steps = ("set_rules", "connect_entrances", "generate_basic", "pre_fill")
|
2024-01-13 19:15:35 -06:00
|
|
|
excluded_games = ("Links Awakening DX", "Ocarina of Time", "SMZ3")
|
|
|
|
worlds_to_test = {game: world
|
|
|
|
for game, world in AutoWorldRegister.world_types.items() if game not in excluded_games}
|
|
|
|
for game_name, world_type in worlds_to_test.items():
|
|
|
|
with self.subTest("Game", game=game_name):
|
|
|
|
multiworld = setup_solo_multiworld(world_type, gen_steps)
|
|
|
|
created_items = multiworld.itempool.copy()
|
|
|
|
for step in additional_steps:
|
|
|
|
with self.subTest("step", step=step):
|
|
|
|
call_all(multiworld, step)
|
|
|
|
self.assertEqual(created_items, multiworld.itempool,
|
|
|
|
f"{game_name} modified the itempool during {step}")
|
2024-11-29 16:57:35 -05:00
|
|
|
|
|
|
|
def test_locality_not_modified(self):
|
|
|
|
"""Test that worlds don't modify the locality of items after duplicates are resolved"""
|
2025-07-26 16:30:55 -04:00
|
|
|
gen_steps = ("generate_early",)
|
|
|
|
additional_steps = ("create_regions", "create_items", "set_rules", "connect_entrances", "generate_basic", "pre_fill")
|
2024-11-29 16:57:35 -05:00
|
|
|
worlds_to_test = {game: world for game, world in AutoWorldRegister.world_types.items()}
|
|
|
|
for game_name, world_type in worlds_to_test.items():
|
|
|
|
with self.subTest("Game", game=game_name):
|
|
|
|
multiworld = setup_solo_multiworld(world_type, gen_steps)
|
|
|
|
local_items = multiworld.worlds[1].options.local_items.value.copy()
|
|
|
|
non_local_items = multiworld.worlds[1].options.non_local_items.value.copy()
|
|
|
|
for step in additional_steps:
|
|
|
|
with self.subTest("step", step=step):
|
|
|
|
call_all(multiworld, step)
|
|
|
|
self.assertEqual(local_items, multiworld.worlds[1].options.local_items.value,
|
|
|
|
f"{game_name} modified local_items during {step}")
|
|
|
|
self.assertEqual(non_local_items, multiworld.worlds[1].options.non_local_items.value,
|
|
|
|
f"{game_name} modified non_local_items during {step}")
|