Skip to content

Commit 5d9d4ed

Browse files
authored
SoE: update to pyevermizer v0.48.0 (#3050)
1 parent c97215e commit 5d9d4ed

File tree

6 files changed

+237
-51
lines changed

6 files changed

+237
-51
lines changed

worlds/soe/__init__.py

+42-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from worlds.AutoWorld import WebWorld, World
1414
from worlds.generic.Rules import add_item_rule, set_rule
1515
from .logic import SoEPlayerLogic
16-
from .options import Difficulty, EnergyCore, SoEOptions
16+
from .options import Difficulty, EnergyCore, Sniffamizer, SniffIngredients, SoEOptions
1717
from .patch import SoEDeltaPatch, get_base_rom_path
1818

1919
if typing.TYPE_CHECKING:
@@ -64,20 +64,28 @@
6464
pyevermizer.CHECK_BOSS: _id_base + 50, # bosses 64050..6499
6565
pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399
6666
pyevermizer.CHECK_NPC: _id_base + 400, # npc 64400..64499
67-
# TODO: sniff 64500..64799
67+
# blank 64500..64799
6868
pyevermizer.CHECK_EXTRA: _id_base + 800, # extra items 64800..64899
6969
pyevermizer.CHECK_TRAP: _id_base + 900, # trap 64900..64999
70+
pyevermizer.CHECK_SNIFF: _id_base + 1000 # sniff 65000..65592
7071
}
7172

7273
# cache native evermizer items and locations
7374
_items = pyevermizer.get_items()
75+
_sniff_items = pyevermizer.get_sniff_items() # optional, not part of the default location pool
7476
_traps = pyevermizer.get_traps()
7577
_extras = pyevermizer.get_extra_items() # items that are not placed by default
7678
_locations = pyevermizer.get_locations()
79+
_sniff_locations = pyevermizer.get_sniff_locations() # optional, not part of the default location pool
7780
# fix up texts for AP
7881
for _loc in _locations:
7982
if _loc.type == pyevermizer.CHECK_GOURD:
80-
_loc.name = f'{_loc.name} #{_loc.index}'
83+
_loc.name = f"{_loc.name} #{_loc.index}"
84+
for _loc in _sniff_locations:
85+
if _loc.type == pyevermizer.CHECK_SNIFF:
86+
_loc.name = f"{_loc.name} Sniff #{_loc.index}"
87+
del _loc
88+
8189
# item helpers
8290
_ingredients = (
8391
'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:
97105
def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Location]]:
98106
name_to_id = {}
99107
id_to_raw = {}
100-
for loc in _locations:
108+
for loc in itertools.chain(_locations, _sniff_locations):
101109
ap_id = _id_offset[loc.type] + loc.index
102110
id_to_raw[ap_id] = loc
103111
name_to_id[loc.name] = ap_id
@@ -108,7 +116,7 @@ def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[i
108116
def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]:
109117
name_to_id = {}
110118
id_to_raw = {}
111-
for item in itertools.chain(_items, _extras, _traps):
119+
for item in itertools.chain(_items, _sniff_items, _extras, _traps):
112120
if item.name in name_to_id:
113121
continue
114122
ap_id = _id_offset[item.type] + item.index
@@ -168,9 +176,9 @@ class SoEWorld(World):
168176
options: SoEOptions
169177
settings: typing.ClassVar[SoESettings]
170178
topology_present = False
171-
data_version = 4
179+
data_version = 5
172180
web = SoEWebWorld()
173-
required_client_version = (0, 3, 5)
181+
required_client_version = (0, 4, 4)
174182

175183
item_name_to_id, item_id_to_raw = _get_item_mapping()
176184
location_name_to_id, location_id_to_raw = _get_location_mapping()
@@ -238,16 +246,26 @@ def get_sphere_index(evermizer_loc: pyevermizer.Location) -> int:
238246
spheres.setdefault(get_sphere_index(loc), {}).setdefault(loc.type, []).append(
239247
SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], ingame,
240248
loc.difficulty > max_difficulty))
249+
# extend pool if feature and setting enabled
250+
if hasattr(Sniffamizer, "option_everywhere") and self.options.sniffamizer == Sniffamizer.option_everywhere:
251+
for loc in _sniff_locations:
252+
spheres.setdefault(get_sphere_index(loc), {}).setdefault(loc.type, []).append(
253+
SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], ingame,
254+
loc.difficulty > max_difficulty))
241255

242256
# location balancing data
243257
trash_fills: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int, int]]] = {
244-
0: {pyevermizer.CHECK_GOURD: (20, 40, 40, 40)}, # remove up to 40 gourds from sphere 1
245-
1: {pyevermizer.CHECK_GOURD: (70, 90, 90, 90)}, # remove up to 90 gourds from sphere 2
258+
0: {pyevermizer.CHECK_GOURD: (20, 40, 40, 40), # remove up to 40 gourds from sphere 1
259+
pyevermizer.CHECK_SNIFF: (100, 130, 130, 130)}, # remove up to 130 sniff spots from sphere 1
260+
1: {pyevermizer.CHECK_GOURD: (70, 90, 90, 90), # remove up to 90 gourds from sphere 2
261+
pyevermizer.CHECK_SNIFF: (160, 200, 200, 200)}, # remove up to 200 sniff spots from sphere 2
246262
}
247263

248264
# mark some as excluded based on numbers above
249265
for trash_sphere, fills in trash_fills.items():
250266
for typ, counts in fills.items():
267+
if typ not in spheres[trash_sphere]:
268+
continue # e.g. player does not have sniff locations
251269
count = counts[self.options.difficulty.value]
252270
for location in self.random.sample(spheres[trash_sphere][typ], count):
253271
assert location.name != "Energy Core #285", "Error in sphere generation"
@@ -299,6 +317,15 @@ def create_items(self) -> None:
299317
# remove one pair of wings that will be placed in generate_basic
300318
items.remove(self.create_item("Wings"))
301319

320+
# extend pool if feature and setting enabled
321+
if hasattr(Sniffamizer, "option_everywhere") and self.options.sniffamizer == Sniffamizer.option_everywhere:
322+
if self.options.sniff_ingredients == SniffIngredients.option_vanilla_ingredients:
323+
# vanilla ingredients
324+
items += list(map(lambda item: self.create_item(item), _sniff_items))
325+
else:
326+
# random ingredients
327+
items += [self.create_item(self.get_filler_item_name()) for _ in _sniff_items]
328+
302329
def is_ingredient(item: pyevermizer.Item) -> bool:
303330
for ingredient in _ingredients:
304331
if _match_item_name(item, ingredient):
@@ -345,7 +372,12 @@ def set_rules(self) -> None:
345372
set_rule(self.multiworld.get_location('Done', self.player),
346373
lambda state: self.logic.has(state, pyevermizer.P_FINAL_BOSS))
347374
set_rule(self.multiworld.get_entrance('New Game', self.player), lambda state: True)
348-
for loc in _locations:
375+
locations: typing.Iterable[pyevermizer.Location]
376+
if hasattr(Sniffamizer, "option_everywhere") and self.options.sniffamizer == Sniffamizer.option_everywhere:
377+
locations = itertools.chain(_locations, _sniff_locations)
378+
else:
379+
locations = _locations
380+
for loc in locations:
349381
location = self.multiworld.get_location(loc.name, self.player)
350382
set_rule(location, self.make_rule(loc.requires))
351383

worlds/soe/logic.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import typing
2+
from itertools import chain
23
from typing import Callable, Set
34

45
from . import pyevermizer
@@ -11,10 +12,12 @@
1112

1213
# TODO: resolve/flatten/expand rules to get rid of recursion below where possible
1314
# Logic.rules are all rules including locations, excluding those with no progress (i.e. locations that only drop items)
14-
rules = [rule for rule in pyevermizer.get_logic() if len(rule.provides) > 0]
15+
rules = pyevermizer.get_logic()
1516
# Logic.items are all items and extra items excluding non-progression items and duplicates
17+
# NOTE: we are skipping sniff items here because none of them is supposed to provide progression
1618
item_names: Set[str] = set()
17-
items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items() + pyevermizer.get_extra_items())
19+
items = [item for item in filter(lambda item: item.progression, # type: ignore[arg-type]
20+
chain(pyevermizer.get_items(), pyevermizer.get_extra_items()))
1821
if item.name not in item_names and not item_names.add(item.name)] # type: ignore[func-returns-value]
1922

2023

worlds/soe/options.py

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import dataclass, fields
2+
from datetime import datetime
23
from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol
34

45
from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \
@@ -158,13 +159,30 @@ class Ingredienizer(EvermizerFlags, OffOnFullChoice):
158159
flags = ['i', '', 'I']
159160

160161

161-
class Sniffamizer(EvermizerFlags, OffOnFullChoice):
162-
"""On Shuffles, Full randomizes drops in sniff locations"""
162+
class Sniffamizer(EvermizerFlags, Choice):
163+
"""
164+
Off: all vanilla items in sniff spots
165+
Shuffle: sniff items shuffled into random sniff spots
166+
"""
163167
display_name = "Sniffamizer"
168+
option_off = 0
169+
option_shuffle = 1
170+
if datetime.today().year > 2024 or datetime.today().month > 3:
171+
option_everywhere = 2
172+
__doc__ = __doc__ + " Everywhere: add sniff spots to multiworld pool"
173+
alias_true = 1
164174
default = 1
165175
flags = ['s', '', 'S']
166176

167177

178+
class SniffIngredients(EvermizerFlag, Choice):
179+
"""Select which items should be used as sniff items"""
180+
display_name = "Sniff Ingredients"
181+
option_vanilla_ingredients = 0
182+
option_random_ingredients = 1
183+
flag = 'v'
184+
185+
168186
class Callbeadamizer(EvermizerFlags, OffOnFullChoice):
169187
"""On Shuffles call bead characters, Full shuffles individual spells"""
170188
display_name = "Callbeadamizer"
@@ -207,7 +225,7 @@ def __new__(mcs, name: str, bases: Tuple[type], attrs: Dict[Any, Any]) -> "ItemC
207225
attrs["display_name"] = f"{attrs['item_name']} Chance"
208226
attrs["range_start"] = 0
209227
attrs["range_end"] = 100
210-
cls = super(ItemChanceMeta, mcs).__new__(mcs, name, bases, attrs)
228+
cls = super(ItemChanceMeta, mcs).__new__(mcs, name, bases, attrs) # type: ignore[no-untyped-call]
211229
return cast(ItemChanceMeta, cls)
212230

213231

@@ -268,6 +286,7 @@ class SoEOptions(PerGameCommonOptions):
268286
short_boss_rush: ShortBossRush
269287
ingredienizer: Ingredienizer
270288
sniffamizer: Sniffamizer
289+
sniff_ingredients: SniffIngredients
271290
callbeadamizer: Callbeadamizer
272291
musicmizer: Musicmizer
273292
doggomizer: Doggomizer

worlds/soe/requirements.txt

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

worlds/soe/test/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from test.bases import WorldTestBase
22
from typing import Iterable
3+
from .. import SoEWorld
34

45

56
class SoETestBase(WorldTestBase):
67
game = "Secret of Evermore"
8+
world: SoEWorld
79

810
def assertLocationReachability(self, reachable: Iterable[str] = (), unreachable: Iterable[str] = (),
911
satisfied: bool = True) -> None:

0 commit comments

Comments
 (0)