SoE: update to pyevermizer v0.48.0 (#3050)

This commit is contained in:
black-sliver 2024-03-29 01:01:31 +01:00 committed by GitHub
parent c97215e0e7
commit 5d9d4ed9f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 237 additions and 51 deletions

View File

@ -13,7 +13,7 @@ from Utils import output_path
from worlds.AutoWorld import WebWorld, World from worlds.AutoWorld import WebWorld, World
from worlds.generic.Rules import add_item_rule, set_rule from worlds.generic.Rules import add_item_rule, set_rule
from .logic import SoEPlayerLogic from .logic import SoEPlayerLogic
from .options import Difficulty, EnergyCore, SoEOptions from .options import Difficulty, EnergyCore, Sniffamizer, SniffIngredients, SoEOptions
from .patch import SoEDeltaPatch, get_base_rom_path from .patch import SoEDeltaPatch, get_base_rom_path
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
@ -64,20 +64,28 @@ _id_offset: typing.Dict[int, int] = {
pyevermizer.CHECK_BOSS: _id_base + 50, # bosses 64050..6499 pyevermizer.CHECK_BOSS: _id_base + 50, # bosses 64050..6499
pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399 pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399
pyevermizer.CHECK_NPC: _id_base + 400, # npc 64400..64499 pyevermizer.CHECK_NPC: _id_base + 400, # npc 64400..64499
# TODO: sniff 64500..64799 # blank 64500..64799
pyevermizer.CHECK_EXTRA: _id_base + 800, # extra items 64800..64899 pyevermizer.CHECK_EXTRA: _id_base + 800, # extra items 64800..64899
pyevermizer.CHECK_TRAP: _id_base + 900, # trap 64900..64999 pyevermizer.CHECK_TRAP: _id_base + 900, # trap 64900..64999
pyevermizer.CHECK_SNIFF: _id_base + 1000 # sniff 65000..65592
} }
# cache native evermizer items and locations # cache native evermizer items and locations
_items = pyevermizer.get_items() _items = pyevermizer.get_items()
_sniff_items = pyevermizer.get_sniff_items() # optional, not part of the default location pool
_traps = pyevermizer.get_traps() _traps = pyevermizer.get_traps()
_extras = pyevermizer.get_extra_items() # items that are not placed by default _extras = pyevermizer.get_extra_items() # items that are not placed by default
_locations = pyevermizer.get_locations() _locations = pyevermizer.get_locations()
_sniff_locations = pyevermizer.get_sniff_locations() # optional, not part of the default location pool
# fix up texts for AP # fix up texts for AP
for _loc in _locations: for _loc in _locations:
if _loc.type == pyevermizer.CHECK_GOURD: if _loc.type == pyevermizer.CHECK_GOURD:
_loc.name = f'{_loc.name} #{_loc.index}' _loc.name = f"{_loc.name} #{_loc.index}"
for _loc in _sniff_locations:
if _loc.type == pyevermizer.CHECK_SNIFF:
_loc.name = f"{_loc.name} Sniff #{_loc.index}"
del _loc
# item helpers # item helpers
_ingredients = ( _ingredients = (
'Wax', 'Water', 'Vinegar', 'Root', 'Oil', 'Mushroom', 'Mud Pepper', 'Meteorite', 'Limestone', 'Iron', 'Wax', 'Water', 'Vinegar', 'Root', 'Oil', 'Mushroom', 'Mud Pepper', 'Meteorite', 'Limestone', 'Iron',
@ -97,7 +105,7 @@ def _match_item_name(item: pyevermizer.Item, substr: str) -> bool:
def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Location]]: def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Location]]:
name_to_id = {} name_to_id = {}
id_to_raw = {} id_to_raw = {}
for loc in _locations: for loc in itertools.chain(_locations, _sniff_locations):
ap_id = _id_offset[loc.type] + loc.index ap_id = _id_offset[loc.type] + loc.index
id_to_raw[ap_id] = loc id_to_raw[ap_id] = loc
name_to_id[loc.name] = ap_id name_to_id[loc.name] = ap_id
@ -108,7 +116,7 @@ def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[i
def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]: def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]:
name_to_id = {} name_to_id = {}
id_to_raw = {} id_to_raw = {}
for item in itertools.chain(_items, _extras, _traps): for item in itertools.chain(_items, _sniff_items, _extras, _traps):
if item.name in name_to_id: if item.name in name_to_id:
continue continue
ap_id = _id_offset[item.type] + item.index ap_id = _id_offset[item.type] + item.index
@ -168,9 +176,9 @@ class SoEWorld(World):
options: SoEOptions options: SoEOptions
settings: typing.ClassVar[SoESettings] settings: typing.ClassVar[SoESettings]
topology_present = False topology_present = False
data_version = 4 data_version = 5
web = SoEWebWorld() web = SoEWebWorld()
required_client_version = (0, 3, 5) required_client_version = (0, 4, 4)
item_name_to_id, item_id_to_raw = _get_item_mapping() item_name_to_id, item_id_to_raw = _get_item_mapping()
location_name_to_id, location_id_to_raw = _get_location_mapping() location_name_to_id, location_id_to_raw = _get_location_mapping()
@ -238,16 +246,26 @@ class SoEWorld(World):
spheres.setdefault(get_sphere_index(loc), {}).setdefault(loc.type, []).append( spheres.setdefault(get_sphere_index(loc), {}).setdefault(loc.type, []).append(
SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], ingame, SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], ingame,
loc.difficulty > max_difficulty)) loc.difficulty > max_difficulty))
# extend pool if feature and setting enabled
if hasattr(Sniffamizer, "option_everywhere") and self.options.sniffamizer == Sniffamizer.option_everywhere:
for loc in _sniff_locations:
spheres.setdefault(get_sphere_index(loc), {}).setdefault(loc.type, []).append(
SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], ingame,
loc.difficulty > max_difficulty))
# location balancing data # location balancing data
trash_fills: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int, int]]] = { trash_fills: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int, int]]] = {
0: {pyevermizer.CHECK_GOURD: (20, 40, 40, 40)}, # remove up to 40 gourds from sphere 1 0: {pyevermizer.CHECK_GOURD: (20, 40, 40, 40), # remove up to 40 gourds from sphere 1
1: {pyevermizer.CHECK_GOURD: (70, 90, 90, 90)}, # remove up to 90 gourds from sphere 2 pyevermizer.CHECK_SNIFF: (100, 130, 130, 130)}, # remove up to 130 sniff spots from sphere 1
1: {pyevermizer.CHECK_GOURD: (70, 90, 90, 90), # remove up to 90 gourds from sphere 2
pyevermizer.CHECK_SNIFF: (160, 200, 200, 200)}, # remove up to 200 sniff spots from sphere 2
} }
# mark some as excluded based on numbers above # mark some as excluded based on numbers above
for trash_sphere, fills in trash_fills.items(): for trash_sphere, fills in trash_fills.items():
for typ, counts in fills.items(): for typ, counts in fills.items():
if typ not in spheres[trash_sphere]:
continue # e.g. player does not have sniff locations
count = counts[self.options.difficulty.value] count = counts[self.options.difficulty.value]
for location in self.random.sample(spheres[trash_sphere][typ], count): for location in self.random.sample(spheres[trash_sphere][typ], count):
assert location.name != "Energy Core #285", "Error in sphere generation" assert location.name != "Energy Core #285", "Error in sphere generation"
@ -299,6 +317,15 @@ class SoEWorld(World):
# remove one pair of wings that will be placed in generate_basic # remove one pair of wings that will be placed in generate_basic
items.remove(self.create_item("Wings")) items.remove(self.create_item("Wings"))
# extend pool if feature and setting enabled
if hasattr(Sniffamizer, "option_everywhere") and self.options.sniffamizer == Sniffamizer.option_everywhere:
if self.options.sniff_ingredients == SniffIngredients.option_vanilla_ingredients:
# vanilla ingredients
items += list(map(lambda item: self.create_item(item), _sniff_items))
else:
# random ingredients
items += [self.create_item(self.get_filler_item_name()) for _ in _sniff_items]
def is_ingredient(item: pyevermizer.Item) -> bool: def is_ingredient(item: pyevermizer.Item) -> bool:
for ingredient in _ingredients: for ingredient in _ingredients:
if _match_item_name(item, ingredient): if _match_item_name(item, ingredient):
@ -345,7 +372,12 @@ class SoEWorld(World):
set_rule(self.multiworld.get_location('Done', self.player), set_rule(self.multiworld.get_location('Done', self.player),
lambda state: self.logic.has(state, pyevermizer.P_FINAL_BOSS)) lambda state: self.logic.has(state, pyevermizer.P_FINAL_BOSS))
set_rule(self.multiworld.get_entrance('New Game', self.player), lambda state: True) set_rule(self.multiworld.get_entrance('New Game', self.player), lambda state: True)
for loc in _locations: locations: typing.Iterable[pyevermizer.Location]
if hasattr(Sniffamizer, "option_everywhere") and self.options.sniffamizer == Sniffamizer.option_everywhere:
locations = itertools.chain(_locations, _sniff_locations)
else:
locations = _locations
for loc in locations:
location = self.multiworld.get_location(loc.name, self.player) location = self.multiworld.get_location(loc.name, self.player)
set_rule(location, self.make_rule(loc.requires)) set_rule(location, self.make_rule(loc.requires))

View File

@ -1,4 +1,5 @@
import typing import typing
from itertools import chain
from typing import Callable, Set from typing import Callable, Set
from . import pyevermizer from . import pyevermizer
@ -11,10 +12,12 @@ if typing.TYPE_CHECKING:
# TODO: resolve/flatten/expand rules to get rid of recursion below where possible # TODO: resolve/flatten/expand rules to get rid of recursion below where possible
# Logic.rules are all rules including locations, excluding those with no progress (i.e. locations that only drop items) # Logic.rules are all rules including locations, excluding those with no progress (i.e. locations that only drop items)
rules = [rule for rule in pyevermizer.get_logic() if len(rule.provides) > 0] rules = pyevermizer.get_logic()
# Logic.items are all items and extra items excluding non-progression items and duplicates # Logic.items are all items and extra items excluding non-progression items and duplicates
# NOTE: we are skipping sniff items here because none of them is supposed to provide progression
item_names: Set[str] = set() item_names: Set[str] = set()
items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items() + pyevermizer.get_extra_items()) items = [item for item in filter(lambda item: item.progression, # type: ignore[arg-type]
chain(pyevermizer.get_items(), pyevermizer.get_extra_items()))
if item.name not in item_names and not item_names.add(item.name)] # type: ignore[func-returns-value] if item.name not in item_names and not item_names.add(item.name)] # type: ignore[func-returns-value]

View File

@ -1,4 +1,5 @@
from dataclasses import dataclass, fields from dataclasses import dataclass, fields
from datetime import datetime
from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol
from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \ from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \
@ -158,13 +159,30 @@ class Ingredienizer(EvermizerFlags, OffOnFullChoice):
flags = ['i', '', 'I'] flags = ['i', '', 'I']
class Sniffamizer(EvermizerFlags, OffOnFullChoice): class Sniffamizer(EvermizerFlags, Choice):
"""On Shuffles, Full randomizes drops in sniff locations""" """
Off: all vanilla items in sniff spots
Shuffle: sniff items shuffled into random sniff spots
"""
display_name = "Sniffamizer" display_name = "Sniffamizer"
option_off = 0
option_shuffle = 1
if datetime.today().year > 2024 or datetime.today().month > 3:
option_everywhere = 2
__doc__ = __doc__ + " Everywhere: add sniff spots to multiworld pool"
alias_true = 1
default = 1 default = 1
flags = ['s', '', 'S'] flags = ['s', '', 'S']
class SniffIngredients(EvermizerFlag, Choice):
"""Select which items should be used as sniff items"""
display_name = "Sniff Ingredients"
option_vanilla_ingredients = 0
option_random_ingredients = 1
flag = 'v'
class Callbeadamizer(EvermizerFlags, OffOnFullChoice): class Callbeadamizer(EvermizerFlags, OffOnFullChoice):
"""On Shuffles call bead characters, Full shuffles individual spells""" """On Shuffles call bead characters, Full shuffles individual spells"""
display_name = "Callbeadamizer" display_name = "Callbeadamizer"
@ -207,7 +225,7 @@ class ItemChanceMeta(AssembleOptions):
attrs["display_name"] = f"{attrs['item_name']} Chance" attrs["display_name"] = f"{attrs['item_name']} Chance"
attrs["range_start"] = 0 attrs["range_start"] = 0
attrs["range_end"] = 100 attrs["range_end"] = 100
cls = super(ItemChanceMeta, mcs).__new__(mcs, name, bases, attrs) cls = super(ItemChanceMeta, mcs).__new__(mcs, name, bases, attrs) # type: ignore[no-untyped-call]
return cast(ItemChanceMeta, cls) return cast(ItemChanceMeta, cls)
@ -268,6 +286,7 @@ class SoEOptions(PerGameCommonOptions):
short_boss_rush: ShortBossRush short_boss_rush: ShortBossRush
ingredienizer: Ingredienizer ingredienizer: Ingredienizer
sniffamizer: Sniffamizer sniffamizer: Sniffamizer
sniff_ingredients: SniffIngredients
callbeadamizer: Callbeadamizer callbeadamizer: Callbeadamizer
musicmizer: Musicmizer musicmizer: Musicmizer
doggomizer: Doggomizer doggomizer: Doggomizer

View File

@ -1,36 +1,36 @@
pyevermizer==0.46.1 \ pyevermizer==0.48.0 \
--hash=sha256:9fd71b5e4af26a5dd24a9cbf5320bf0111eef80320613401a1c03011b1515806 \ --hash=sha256:069ce348e480e04fd6208cfd0f789c600b18d7c34b5272375b95823be191ed57 \
--hash=sha256:23f553ed0509d9a238b2832f775e0b5abd7741b38ab60d388294ee8a7b96c5fb \ --hash=sha256:58164dddaba2f340b0a8b4f39605e9dac46d8b0ffb16120e2e57bef2bfc1d683 \
--hash=sha256:7189b67766418a3e7e6c683f09c5e758aa1a5c24316dd9b714984bac099c4b75 \ --hash=sha256:115dd09d38a10f11d4629b340dfd75e2ba4089a1ff9e9748a11619829e02c876 \
--hash=sha256:befa930711e63d5d5892f67fd888b2e65e746363e74599c53e71ecefb90ae16a \ --hash=sha256:b5e79cfe721e75cd7dec306b5eecd6385ce059e31ef7523ba7f677e22161ec6f \
--hash=sha256:202933ce21e0f33859537bf3800d9a626c70262a9490962e3f450171758507ca \ --hash=sha256:382882fa9d641b9969a6c3ed89449a814bdabcb6b17b558872d95008a6cc908b \
--hash=sha256:c20ca69311c696528e1122ebc7d33775ee971f538c0e3e05dd3bfd4de10b82d4 \ --hash=sha256:92f67700e9132064a90858d391dd0b8fb111aff6dfd472befed57772d89ae567 \
--hash=sha256:74dc689a771ae5ffcd5257e763f571ee890e3e87bdb208233b7f451522c00d66 \ --hash=sha256:fe4c453b7dbd5aa834b81f9a7aedb949a605455650b938b8b304d8e5a7edcbf7 \
--hash=sha256:072296baef464daeb6304cf58827dcbae441ad0803039aee1c0caa10d56e0674 \ --hash=sha256:c6bdbc45daf73818f763ed59ad079f16494593395d806f772dd62605c722b3e9 \
--hash=sha256:7921baf20d52d92d6aeb674125963c335b61abb7e1298bde4baf069d11a2d05e \ --hash=sha256:bb09f45448fdfd28566ae6fcc38c35a6632f4c31a9de2483848f6ce17b2359b5 \
--hash=sha256:ca098034a84007038c2bff004582e6e6ac2fa9cc8b9251301d25d7e2adcee6da \ --hash=sha256:00a8b9014744bd1528d0d39c33ede7c0d1713ad797a331cebb33d377a5bc1064 \
--hash=sha256:22ddb29823c19be9b15e1b3627db1babfe08b486aede7d5cc463a0a1ae4c75d8 \ --hash=sha256:64ee69edc0a7d3b3caded78f2e46975f9beaff1ff8feaf29b87da44c45f38d7d \
--hash=sha256:bf1c441b49026d9000166be6e2f63fc351a3fda170aa3fdf18d44d5e5d044640 \ --hash=sha256:9211bdb1313e9f4869ed5bdc61f3831d39679bd08bb4087f1c1e5475d9e3018b \
--hash=sha256:9710aa7957b4b1f14392006237eb95803acf27897377df3e85395f057f4316b9 \ --hash=sha256:4a57821e422a1d75fe3307931a78db7a65e76955f8e401c4b347db6570390d09 \
--hash=sha256:8feb676c198bee17ab991ee015828345ac3f87c27dfdb3061d92d1fe47c184b4 \ --hash=sha256:04670cee0a0b913f24d2b9a1e771781560e2485bda31e6cd372a08421cf85cfa \
--hash=sha256:597026dede72178ff3627a4eb3315de8444461c7f0f856f5773993c3f9790c53 \ --hash=sha256:971fe77d0a20a1db984020ad253b613d0983f5e23ff22cba60ee5ac00d8128de \
--hash=sha256:70f9b964bdfb5191e8f264644c5d1af3041c66fe15261df8a99b3d719dc680d6 \ --hash=sha256:127265fdb49f718f54706bf15604af1cec23590afd00d423089dea4331dcfc61 \
--hash=sha256:74655c0353ffb6cda30485091d0917ce703b128cd824b612b3110a85c79a93d0 \ --hash=sha256:d47576360337c1a23f424cd49944a8d68fc4f3338e00719c9f89972c84604bef \
--hash=sha256:0e9c74d105d4ec3af12404e85bb8776931c043657add19f798ee69465f92b999 \ --hash=sha256:879659603e51130a0de8d9885d815a2fa1df8bd6cebe6d520d1c6002302adfdb \
--hash=sha256:d3c13446d3d482b9cce61ac73b38effd26fcdcf7f693a405868d3aaaa4d18ca6 \ --hash=sha256:6a91bfc53dd130db6424adf8ac97a1133e97b4157ed00f889d8cbd26a2a4b340 \
--hash=sha256:371ac3360640ef439a5920ddfe11a34e9d2e546ed886bb8c9ed312611f9f4655 \ --hash=sha256:f3bf35fc5eef4cda49d2de77339fc201dd3206660a3dc15db005625b15bb806c \
--hash=sha256:6e5cf63b036f24d2ae4375a88df8d0bc93208352939521d1fcac3c829ef2c363 \ --hash=sha256:e7c8d5bf59a3c16db20411bc5d8e9c9087a30b6b4edf1b5ed9f4c013291427e4 \
--hash=sha256:edf28f5c4d1950d17343adf6d8d40d12c7e982d1e39535d55f7915e122cd8b0e \ --hash=sha256:054a4d84ffe75448d41e88e1e0642ef719eb6111be5fe608e71e27a558c59069 \
--hash=sha256:b5ef6f3b4e04f677c296f60f7f4c320ac22cd5bc09c05574460116c8641c801a \ --hash=sha256:e6f141ca367469c69ba7fbf65836c479ec6672c598cfcb6b39e8098c60d346bc \
--hash=sha256:dd651f66720af4abe2ddae29944e299a57ff91e6fca1739e6dc1f8fd7a8c2b39 \ --hash=sha256:6e65eb88f0c1ff4acde1c13b24ce649b0fe3d1d3916d02d96836c781a5022571 \
--hash=sha256:4e278f5f72c27f9703bce5514d2fead8c00361caac03e94b0bf9ad8a144f1eeb \ --hash=sha256:e61e8f476b6da809cf38912755ed8bb009665f589e913eb8df877e9fa763024b \
--hash=sha256:38f36ea1f545b835c3ecd6e081685a233ac2e3cf0eec8916adc92e4d791098a6 \ --hash=sha256:7e7c5484c0a2e3da6064de3f73d8d988d6703db58ab0be4730cbbf1a82319237 \
--hash=sha256:0a2e58ed6e7c42f006cc17d32cec1f432f01b3fe490e24d71471b36e0d0d8742 \ --hash=sha256:9033b954e5f4878fd94af6d2056c78e3316115521fb1c24a4416d5cbf2ad66ad \
--hash=sha256:c1b658db76240596c03571c60635abe953f36fb55b363202971831c2872ea9a0 \ --hash=sha256:824c623fff8ae4da176306c458ad63ad16a06a495a16db700665eca3c115924f \
--hash=sha256:deb5a84a6a56325eb6701336cdbf70f72adaaeab33cbe953d0e551ecf2592f20 \ --hash=sha256:8e31031409a8386c6a63b79d480393481badb3ba29f32ff7a0db2b4abed20ac8 \
--hash=sha256:b1425c793e0825f58b3726e7afebaf5a296c07cb0d28580d0ee93dbe10dcdf63 \ --hash=sha256:7dbb7bb13e1e94f69f7ccdbcf4d35776424555fce5af1ca29d0256f91fdf087a \
--hash=sha256:11995fb4dfd14b5c359591baee2a864c5814650ba0084524d4ea0466edfaf029 \ --hash=sha256:3a24e331b259407b6912d6e0738aa8a675831db3b7493fcf54dc17cb0cb80d37 \
--hash=sha256:5d2120b5c93ae322fe2a85d48e3eab4168a19e974a880908f1ac291c0300940f \ --hash=sha256:fdda06662a994271e96633cba100dd92b2fcd524acef8b2f664d1aaa14503cbd \
--hash=sha256:254912ea4bfaaffb0abe366e73bd9ecde622677d6afaf2ce8a0c330df99fefd9 \ --hash=sha256:0f0fc81bef3dbb78ba6a7622dd4296f23c59825968a0bb0448beb16eb3397cc2 \
--hash=sha256:540d8e4525f0b5255c1554b4589089dc58e15df22f343e9545ea00f7012efa07 \ --hash=sha256:e07cbef776a7468669211546887357cc88e9afcf1578b23a4a4f2480517b15d9 \
--hash=sha256:f69b8ebded7eed181fabe30deabae89fd10c41964f38abb26b19664bbe55c1ae --hash=sha256:e442212695bdf60e455673b7b9dd83a5d4b830d714376477093d2c9054d92832

View File

@ -1,9 +1,11 @@
from test.bases import WorldTestBase from test.bases import WorldTestBase
from typing import Iterable from typing import Iterable
from .. import SoEWorld
class SoETestBase(WorldTestBase): class SoETestBase(WorldTestBase):
game = "Secret of Evermore" game = "Secret of Evermore"
world: SoEWorld
def assertLocationReachability(self, reachable: Iterable[str] = (), unreachable: Iterable[str] = (), def assertLocationReachability(self, reachable: Iterable[str] = (), unreachable: Iterable[str] = (),
satisfied: bool = True) -> None: satisfied: bool = True) -> None:

View File

@ -0,0 +1,130 @@
import typing
from unittest import TestCase, skipUnless
from . import SoETestBase
from .. import pyevermizer
from ..options import Sniffamizer
class TestCount(TestCase):
"""
Test that counts line up for sniff spots
"""
def test_compare_counts(self) -> None:
self.assertEqual(len(pyevermizer.get_sniff_locations()), len(pyevermizer.get_sniff_items()),
"Sniff locations and sniff items don't line up")
class Bases:
# class in class to avoid running tests for helper class
class TestSniffamizerLocal(SoETestBase):
"""
Test that provided options do not add sniff items or locations
"""
def test_no_sniff_items(self) -> None:
self.assertLess(len(self.multiworld.itempool), 500,
"Unexpected number of items")
for item in self.multiworld.itempool:
if item.code is not None:
self.assertLess(item.code, 65000,
"Unexpected item type")
def test_no_sniff_locations(self) -> None:
location_count = sum(1 for location in self.multiworld.get_locations(self.player) if location.item is None)
self.assertLess(location_count, 500,
"Unexpected number of locations")
for location in self.multiworld.get_locations(self.player):
if location.address is not None:
self.assertLess(location.address, 65000,
"Unexpected location type")
self.assertEqual(location_count, len(self.multiworld.itempool),
"Locations and item counts do not line up")
class TestSniffamizerPool(SoETestBase):
"""
Test that provided options add sniff items and locations
"""
def test_sniff_items(self) -> None:
self.assertGreater(len(self.multiworld.itempool), 500,
"Unexpected number of items")
def test_sniff_locations(self) -> None:
location_count = sum(1 for location in self.multiworld.get_locations(self.player) if location.item is None)
self.assertGreater(location_count, 500,
"Unexpected number of locations")
self.assertTrue(any(location.address is not None and location.address >= 65000
for location in self.multiworld.get_locations(self.player)),
"No sniff locations")
self.assertEqual(location_count, len(self.multiworld.itempool),
"Locations and item counts do not line up")
class TestSniffamizerShuffle(Bases.TestSniffamizerLocal):
"""
Test that shuffle does not add extra items or locations
"""
options: typing.Dict[str, typing.Any] = {
"sniffamizer": "shuffle"
}
def test_flags(self) -> None:
# default -> no flags
flags = self.world.options.flags
self.assertNotIn("s", flags)
self.assertNotIn("S", flags)
self.assertNotIn("v", flags)
@skipUnless(hasattr(Sniffamizer, "option_everywhere"), "Feature disabled")
class TestSniffamizerEverywhereVanilla(Bases.TestSniffamizerPool):
"""
Test that everywhere + vanilla ingredients does add extra items and locations
"""
options: typing.Dict[str, typing.Any] = {
"sniffamizer": "everywhere",
"sniff_ingredients": "vanilla_ingredients",
}
def test_flags(self) -> None:
flags = self.world.options.flags
self.assertIn("S", flags)
self.assertNotIn("v", flags)
@skipUnless(hasattr(Sniffamizer, "option_everywhere"), "Feature disabled")
class TestSniffamizerEverywhereRandom(Bases.TestSniffamizerPool):
"""
Test that everywhere + random ingredients also adds extra items and locations
"""
options: typing.Dict[str, typing.Any] = {
"sniffamizer": "everywhere",
"sniff_ingredients": "random_ingredients",
}
def test_flags(self) -> None:
flags = self.world.options.flags
self.assertIn("S", flags)
self.assertIn("v", flags)
@skipUnless(hasattr(Sniffamizer, "option_everywhere"), "Feature disabled")
class EverywhereAccessTest(SoETestBase):
"""
Test that everywhere has certain rules
"""
options: typing.Dict[str, typing.Any] = {
"sniffamizer": "everywhere",
}
@staticmethod
def _resolve_numbers(spots: typing.Mapping[str, typing.Iterable[int]]) -> typing.List[str]:
return [f"{name} #{number}" for name, numbers in spots.items() for number in numbers]
def test_knight_basher(self) -> None:
locations = ["Mungola", "Lightning Storm"] + self._resolve_numbers({
"Gomi's Tower Sniff": range(473, 491),
"Gomi's Tower": range(195, 199),
})
items = [["Knight Basher"]]
self.assertAccessDependency(locations, items)