Skip to content

Commit

Permalink
SoE: update to pyevermizer v0.48.0 (ArchipelagoMW#3050)
Browse files Browse the repository at this point in the history
  • Loading branch information
black-sliver authored Mar 29, 2024
1 parent c97215e commit 5d9d4ed
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 51 deletions.
52 changes: 42 additions & 10 deletions worlds/soe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from worlds.AutoWorld import WebWorld, World
from worlds.generic.Rules import add_item_rule, set_rule
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

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -64,20 +64,28 @@
pyevermizer.CHECK_BOSS: _id_base + 50, # bosses 64050..6499
pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399
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_TRAP: _id_base + 900, # trap 64900..64999
pyevermizer.CHECK_SNIFF: _id_base + 1000 # sniff 65000..65592
}

# cache native evermizer items and locations
_items = pyevermizer.get_items()
_sniff_items = pyevermizer.get_sniff_items() # optional, not part of the default location pool
_traps = pyevermizer.get_traps()
_extras = pyevermizer.get_extra_items() # items that are not placed by default
_locations = pyevermizer.get_locations()
_sniff_locations = pyevermizer.get_sniff_locations() # optional, not part of the default location pool
# fix up texts for AP
for _loc in _locations:
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
_ingredients = (
'Wax', 'Water', 'Vinegar', 'Root', 'Oil', 'Mushroom', 'Mud Pepper', 'Meteorite', 'Limestone', 'Iron',
Expand All @@ -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]]:
name_to_id = {}
id_to_raw = {}
for loc in _locations:
for loc in itertools.chain(_locations, _sniff_locations):
ap_id = _id_offset[loc.type] + loc.index
id_to_raw[ap_id] = loc
name_to_id[loc.name] = ap_id
Expand All @@ -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]]:
name_to_id = {}
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:
continue
ap_id = _id_offset[item.type] + item.index
Expand Down Expand Up @@ -168,9 +176,9 @@ class SoEWorld(World):
options: SoEOptions
settings: typing.ClassVar[SoESettings]
topology_present = False
data_version = 4
data_version = 5
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()
location_name_to_id, location_id_to_raw = _get_location_mapping()
Expand Down Expand Up @@ -238,16 +246,26 @@ def get_sphere_index(evermizer_loc: pyevermizer.Location) -> int:
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))
# 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
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
1: {pyevermizer.CHECK_GOURD: (70, 90, 90, 90)}, # remove up to 90 gourds from sphere 2
0: {pyevermizer.CHECK_GOURD: (20, 40, 40, 40), # remove up to 40 gourds from sphere 1
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
for trash_sphere, fills in trash_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]
for location in self.random.sample(spheres[trash_sphere][typ], count):
assert location.name != "Energy Core #285", "Error in sphere generation"
Expand Down Expand Up @@ -299,6 +317,15 @@ def create_items(self) -> None:
# remove one pair of wings that will be placed in generate_basic
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:
for ingredient in _ingredients:
if _match_item_name(item, ingredient):
Expand Down Expand Up @@ -345,7 +372,12 @@ def set_rules(self) -> None:
set_rule(self.multiworld.get_location('Done', self.player),
lambda state: self.logic.has(state, pyevermizer.P_FINAL_BOSS))
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)
set_rule(location, self.make_rule(loc.requires))

Expand Down
7 changes: 5 additions & 2 deletions worlds/soe/logic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import typing
from itertools import chain
from typing import Callable, Set

from . import pyevermizer
Expand All @@ -11,10 +12,12 @@

# 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)
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
# NOTE: we are skipping sniff items here because none of them is supposed to provide progression
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]


Expand Down
25 changes: 22 additions & 3 deletions worlds/soe/options.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass, fields
from datetime import datetime
from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol

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


class Sniffamizer(EvermizerFlags, OffOnFullChoice):
"""On Shuffles, Full randomizes drops in sniff locations"""
class Sniffamizer(EvermizerFlags, Choice):
"""
Off: all vanilla items in sniff spots
Shuffle: sniff items shuffled into random sniff spots
"""
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
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):
"""On Shuffles call bead characters, Full shuffles individual spells"""
display_name = "Callbeadamizer"
Expand Down Expand Up @@ -207,7 +225,7 @@ def __new__(mcs, name: str, bases: Tuple[type], attrs: Dict[Any, Any]) -> "ItemC
attrs["display_name"] = f"{attrs['item_name']} Chance"
attrs["range_start"] = 0
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)


Expand Down Expand Up @@ -268,6 +286,7 @@ class SoEOptions(PerGameCommonOptions):
short_boss_rush: ShortBossRush
ingredienizer: Ingredienizer
sniffamizer: Sniffamizer
sniff_ingredients: SniffIngredients
callbeadamizer: Callbeadamizer
musicmizer: Musicmizer
doggomizer: Doggomizer
Expand Down
72 changes: 36 additions & 36 deletions worlds/soe/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
pyevermizer==0.46.1 \
--hash=sha256:9fd71b5e4af26a5dd24a9cbf5320bf0111eef80320613401a1c03011b1515806 \
--hash=sha256:23f553ed0509d9a238b2832f775e0b5abd7741b38ab60d388294ee8a7b96c5fb \
--hash=sha256:7189b67766418a3e7e6c683f09c5e758aa1a5c24316dd9b714984bac099c4b75 \
--hash=sha256:befa930711e63d5d5892f67fd888b2e65e746363e74599c53e71ecefb90ae16a \
--hash=sha256:202933ce21e0f33859537bf3800d9a626c70262a9490962e3f450171758507ca \
--hash=sha256:c20ca69311c696528e1122ebc7d33775ee971f538c0e3e05dd3bfd4de10b82d4 \
--hash=sha256:74dc689a771ae5ffcd5257e763f571ee890e3e87bdb208233b7f451522c00d66 \
--hash=sha256:072296baef464daeb6304cf58827dcbae441ad0803039aee1c0caa10d56e0674 \
--hash=sha256:7921baf20d52d92d6aeb674125963c335b61abb7e1298bde4baf069d11a2d05e \
--hash=sha256:ca098034a84007038c2bff004582e6e6ac2fa9cc8b9251301d25d7e2adcee6da \
--hash=sha256:22ddb29823c19be9b15e1b3627db1babfe08b486aede7d5cc463a0a1ae4c75d8 \
--hash=sha256:bf1c441b49026d9000166be6e2f63fc351a3fda170aa3fdf18d44d5e5d044640 \
--hash=sha256:9710aa7957b4b1f14392006237eb95803acf27897377df3e85395f057f4316b9 \
--hash=sha256:8feb676c198bee17ab991ee015828345ac3f87c27dfdb3061d92d1fe47c184b4 \
--hash=sha256:597026dede72178ff3627a4eb3315de8444461c7f0f856f5773993c3f9790c53 \
--hash=sha256:70f9b964bdfb5191e8f264644c5d1af3041c66fe15261df8a99b3d719dc680d6 \
--hash=sha256:74655c0353ffb6cda30485091d0917ce703b128cd824b612b3110a85c79a93d0 \
--hash=sha256:0e9c74d105d4ec3af12404e85bb8776931c043657add19f798ee69465f92b999 \
--hash=sha256:d3c13446d3d482b9cce61ac73b38effd26fcdcf7f693a405868d3aaaa4d18ca6 \
--hash=sha256:371ac3360640ef439a5920ddfe11a34e9d2e546ed886bb8c9ed312611f9f4655 \
--hash=sha256:6e5cf63b036f24d2ae4375a88df8d0bc93208352939521d1fcac3c829ef2c363 \
--hash=sha256:edf28f5c4d1950d17343adf6d8d40d12c7e982d1e39535d55f7915e122cd8b0e \
--hash=sha256:b5ef6f3b4e04f677c296f60f7f4c320ac22cd5bc09c05574460116c8641c801a \
--hash=sha256:dd651f66720af4abe2ddae29944e299a57ff91e6fca1739e6dc1f8fd7a8c2b39 \
--hash=sha256:4e278f5f72c27f9703bce5514d2fead8c00361caac03e94b0bf9ad8a144f1eeb \
--hash=sha256:38f36ea1f545b835c3ecd6e081685a233ac2e3cf0eec8916adc92e4d791098a6 \
--hash=sha256:0a2e58ed6e7c42f006cc17d32cec1f432f01b3fe490e24d71471b36e0d0d8742 \
--hash=sha256:c1b658db76240596c03571c60635abe953f36fb55b363202971831c2872ea9a0 \
--hash=sha256:deb5a84a6a56325eb6701336cdbf70f72adaaeab33cbe953d0e551ecf2592f20 \
--hash=sha256:b1425c793e0825f58b3726e7afebaf5a296c07cb0d28580d0ee93dbe10dcdf63 \
--hash=sha256:11995fb4dfd14b5c359591baee2a864c5814650ba0084524d4ea0466edfaf029 \
--hash=sha256:5d2120b5c93ae322fe2a85d48e3eab4168a19e974a880908f1ac291c0300940f \
--hash=sha256:254912ea4bfaaffb0abe366e73bd9ecde622677d6afaf2ce8a0c330df99fefd9 \
--hash=sha256:540d8e4525f0b5255c1554b4589089dc58e15df22f343e9545ea00f7012efa07 \
--hash=sha256:f69b8ebded7eed181fabe30deabae89fd10c41964f38abb26b19664bbe55c1ae
pyevermizer==0.48.0 \
--hash=sha256:069ce348e480e04fd6208cfd0f789c600b18d7c34b5272375b95823be191ed57 \
--hash=sha256:58164dddaba2f340b0a8b4f39605e9dac46d8b0ffb16120e2e57bef2bfc1d683 \
--hash=sha256:115dd09d38a10f11d4629b340dfd75e2ba4089a1ff9e9748a11619829e02c876 \
--hash=sha256:b5e79cfe721e75cd7dec306b5eecd6385ce059e31ef7523ba7f677e22161ec6f \
--hash=sha256:382882fa9d641b9969a6c3ed89449a814bdabcb6b17b558872d95008a6cc908b \
--hash=sha256:92f67700e9132064a90858d391dd0b8fb111aff6dfd472befed57772d89ae567 \
--hash=sha256:fe4c453b7dbd5aa834b81f9a7aedb949a605455650b938b8b304d8e5a7edcbf7 \
--hash=sha256:c6bdbc45daf73818f763ed59ad079f16494593395d806f772dd62605c722b3e9 \
--hash=sha256:bb09f45448fdfd28566ae6fcc38c35a6632f4c31a9de2483848f6ce17b2359b5 \
--hash=sha256:00a8b9014744bd1528d0d39c33ede7c0d1713ad797a331cebb33d377a5bc1064 \
--hash=sha256:64ee69edc0a7d3b3caded78f2e46975f9beaff1ff8feaf29b87da44c45f38d7d \
--hash=sha256:9211bdb1313e9f4869ed5bdc61f3831d39679bd08bb4087f1c1e5475d9e3018b \
--hash=sha256:4a57821e422a1d75fe3307931a78db7a65e76955f8e401c4b347db6570390d09 \
--hash=sha256:04670cee0a0b913f24d2b9a1e771781560e2485bda31e6cd372a08421cf85cfa \
--hash=sha256:971fe77d0a20a1db984020ad253b613d0983f5e23ff22cba60ee5ac00d8128de \
--hash=sha256:127265fdb49f718f54706bf15604af1cec23590afd00d423089dea4331dcfc61 \
--hash=sha256:d47576360337c1a23f424cd49944a8d68fc4f3338e00719c9f89972c84604bef \
--hash=sha256:879659603e51130a0de8d9885d815a2fa1df8bd6cebe6d520d1c6002302adfdb \
--hash=sha256:6a91bfc53dd130db6424adf8ac97a1133e97b4157ed00f889d8cbd26a2a4b340 \
--hash=sha256:f3bf35fc5eef4cda49d2de77339fc201dd3206660a3dc15db005625b15bb806c \
--hash=sha256:e7c8d5bf59a3c16db20411bc5d8e9c9087a30b6b4edf1b5ed9f4c013291427e4 \
--hash=sha256:054a4d84ffe75448d41e88e1e0642ef719eb6111be5fe608e71e27a558c59069 \
--hash=sha256:e6f141ca367469c69ba7fbf65836c479ec6672c598cfcb6b39e8098c60d346bc \
--hash=sha256:6e65eb88f0c1ff4acde1c13b24ce649b0fe3d1d3916d02d96836c781a5022571 \
--hash=sha256:e61e8f476b6da809cf38912755ed8bb009665f589e913eb8df877e9fa763024b \
--hash=sha256:7e7c5484c0a2e3da6064de3f73d8d988d6703db58ab0be4730cbbf1a82319237 \
--hash=sha256:9033b954e5f4878fd94af6d2056c78e3316115521fb1c24a4416d5cbf2ad66ad \
--hash=sha256:824c623fff8ae4da176306c458ad63ad16a06a495a16db700665eca3c115924f \
--hash=sha256:8e31031409a8386c6a63b79d480393481badb3ba29f32ff7a0db2b4abed20ac8 \
--hash=sha256:7dbb7bb13e1e94f69f7ccdbcf4d35776424555fce5af1ca29d0256f91fdf087a \
--hash=sha256:3a24e331b259407b6912d6e0738aa8a675831db3b7493fcf54dc17cb0cb80d37 \
--hash=sha256:fdda06662a994271e96633cba100dd92b2fcd524acef8b2f664d1aaa14503cbd \
--hash=sha256:0f0fc81bef3dbb78ba6a7622dd4296f23c59825968a0bb0448beb16eb3397cc2 \
--hash=sha256:e07cbef776a7468669211546887357cc88e9afcf1578b23a4a4f2480517b15d9 \
--hash=sha256:e442212695bdf60e455673b7b9dd83a5d4b830d714376477093d2c9054d92832
2 changes: 2 additions & 0 deletions worlds/soe/test/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from test.bases import WorldTestBase
from typing import Iterable
from .. import SoEWorld


class SoETestBase(WorldTestBase):
game = "Secret of Evermore"
world: SoEWorld

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

0 comments on commit 5d9d4ed

Please sign in to comment.