SoE: texts, energy core, fragments, useful (#777)

* fix missing fields in custom prog balancing option
* fix typos and pep8
* update and implement pyevermizer 0.41.3
  * allow randomizing energy core
  * add energy core fragments (turn in at Prof. Ruffleberg)
  * rename some items to avoid confusion
  * differentiate between progression and useful
* remove obsolete 'Bazooka' group
* don't add items to the pool that get removed
This commit is contained in:
black-sliver 2022-07-15 18:01:07 +02:00 committed by GitHub
parent 73fb1b8074
commit ce789d1e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 35 deletions

View File

@ -1,5 +1,6 @@
from BaseClasses import MultiWorld from BaseClasses import MultiWorld
from ..AutoWorld import LogicMixin from ..AutoWorld import LogicMixin
from .Options import EnergyCore
from typing import Set from typing import Set
# TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early? # TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early?
@ -8,9 +9,9 @@ from . import pyevermizer
# 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 = [rule for rule in pyevermizer.get_logic() if len(rule.provides) > 0]
# Logic.items are all items excluding non-progression items and duplicates # Logic.items are all items and extra items excluding non-progression items and duplicates
item_names: Set[str] = set() item_names: Set[str] = set()
items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items()) items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items() + pyevermizer.get_extra_items())
if item.name not in item_names and not item_names.add(item.name)] if item.name not in item_names and not item_names.add(item.name)]
@ -47,4 +48,9 @@ class SecretOfEvermoreLogic(LogicMixin):
""" """
Returns True if count of one of evermizer's progress steps is reached based on collected items. i.e. 2 * P_DE Returns True if count of one of evermizer's progress steps is reached based on collected items. i.e. 2 * P_DE
""" """
if progress == pyevermizer.P_ENERGY_CORE: # logic is shared between worlds, so we override in the call
w = world.worlds[player]
if w.energy_core == EnergyCore.option_fragments:
progress = pyevermizer.P_CORE_FRAGMENT
count = w.required_fragments
return self._soe_count(progress, world, player, count) >= count return self._soe_count(progress, world, player, count) >= count

View File

@ -37,6 +37,32 @@ class Difficulty(EvermizerFlags, Choice):
flags = ['e', 'n', 'h', 'x'] flags = ['e', 'n', 'h', 'x']
class EnergyCore(EvermizerFlags, Choice):
"""How to obtain the Energy Core"""
display_name = "Energy Core"
option_vanilla = 0
option_shuffle = 1
option_fragments = 2
default = 1
flags = ['z', '', 'Z']
class RequiredFragments(Range):
"""Required fragment count for Energy Core = Fragments"""
display_name = "Required Fragments"
range_start = 1
range_end = 99
default = 10
class AvailableFragments(Range):
"""Placed fragment count for Energy Core = Fragments"""
display_name = "Available Fragments"
range_start = 1
range_end = 99
default = 11
class MoneyModifier(Range): class MoneyModifier(Range):
"""Money multiplier in %""" """Money multiplier in %"""
display_name = "Money Modifier" display_name = "Money Modifier"
@ -186,10 +212,15 @@ class TrapChanceOHKO(TrapChance):
class SoEProgressionBalancing(ProgressionBalancing): class SoEProgressionBalancing(ProgressionBalancing):
default = 30 default = 30
__doc__ = ProgressionBalancing.__doc__.replace(f"default {ProgressionBalancing.default}", f"default {default}")
special_range_names = {**ProgressionBalancing.special_range_names, "normal": default}
soe_options: typing.Dict[str, type(Option)] = { soe_options: typing.Dict[str, type(Option)] = {
"difficulty": Difficulty, "difficulty": Difficulty,
"energy_core": EnergyCore,
"required_fragments": RequiredFragments,
"available_fragments": AvailableFragments,
"money_modifier": MoneyModifier, "money_modifier": MoneyModifier,
"exp_modifier": ExpModifier, "exp_modifier": ExpModifier,
"fix_sequence": FixSequence, "fix_sequence": FixSequence,

View File

@ -16,7 +16,7 @@ except ImportError:
from . import pyevermizer # as part of the source tree from . import pyevermizer # as part of the source tree
from . import Logic # load logic mixin from . import Logic # load logic mixin
from .Options import soe_options from .Options import soe_options, EnergyCore, RequiredFragments, AvailableFragments
from .Patch import SoEDeltaPatch, get_base_rom_path from .Patch import SoEDeltaPatch, get_base_rom_path
""" """
@ -52,7 +52,6 @@ Item grouping currently supports
* Ingredients - Matches all ingredient drops * Ingredients - Matches all ingredient drops
* Alchemy - Matches all alchemy formulas * Alchemy - Matches all alchemy formulas
* Weapons - Matches all weapons but Bazooka, Bone Crusher, Neutron Blade * Weapons - Matches all weapons but Bazooka, Bone Crusher, Neutron Blade
* Bazooka - Matches all bazookas (currently only one)
* Traps - Matches all traps * Traps - Matches all traps
""" """
@ -63,12 +62,14 @@ _id_offset: typing.Dict[int, int] = {
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 # TODO: sniff 64500..64799
pyevermizer.CHECK_TRAP: _id_base + 900, # npc 64900..64999 pyevermizer.CHECK_EXTRA: _id_base + 800, # extra items 64800..64899
pyevermizer.CHECK_TRAP: _id_base + 900, # trap 64900..64999
} }
# cache native evermizer items and locations # cache native evermizer items and locations
_items = pyevermizer.get_items() _items = pyevermizer.get_items()
_traps = pyevermizer.get_traps() _traps = pyevermizer.get_traps()
_extras = pyevermizer.get_extra_items() # items that are not placed by default
_locations = pyevermizer.get_locations() _locations = pyevermizer.get_locations()
# fix up texts for AP # fix up texts for AP
for _loc in _locations: for _loc in _locations:
@ -104,7 +105,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, _traps): for item in itertools.chain(_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
@ -127,7 +128,6 @@ def _get_item_grouping() -> typing.Dict[str, typing.Set[str]]:
groups['Alchemy'] = set(item.name for item in _items if item.type == pyevermizer.CHECK_ALCHEMY) groups['Alchemy'] = set(item.name for item in _items if item.type == pyevermizer.CHECK_ALCHEMY)
groups['Weapons'] = {'Spider Claw', 'Horn Spear', 'Gladiator Sword', 'Bronze Axe', 'Bronze Spear', 'Crusader Sword', groups['Weapons'] = {'Spider Claw', 'Horn Spear', 'Gladiator Sword', 'Bronze Axe', 'Bronze Spear', 'Crusader Sword',
'Lance (Weapon)', 'Knight Basher', 'Atom Smasher', 'Laser Lance'} 'Lance (Weapon)', 'Knight Basher', 'Atom Smasher', 'Laser Lance'}
groups['Bazooka'] = {'Bazooka+Shells / Shining Armor / 5k Gold'}
groups['Traps'] = {trap.name for trap in _traps} groups['Traps'] = {trap.name for trap in _traps}
return groups return groups
@ -136,7 +136,8 @@ class SoEWebWorld(WebWorld):
theme = 'jungle' theme = 'jungle'
tutorials = [Tutorial( tutorials = [Tutorial(
"Multiworld Setup Guide", "Multiworld Setup Guide",
"A guide to playing Secret of Evermore randomizer. This guide covers single-player, multiworld and related software.", "A guide to playing Secret of Evermore randomizer. This guide covers single-player, multiworld and related"
" software.",
"English", "English",
"multiworld_en.md", "multiworld_en.md",
"multiworld/en", "multiworld/en",
@ -153,9 +154,9 @@ class SoEWorld(World):
options = soe_options options = soe_options
topology_present = False topology_present = False
remote_items = False remote_items = False
data_version = 2 data_version = 3
web = SoEWebWorld() web = SoEWebWorld()
required_client_version = (0, 2, 6) required_client_version = (0, 3, 3)
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()
@ -165,6 +166,9 @@ class SoEWorld(World):
evermizer_seed: int evermizer_seed: int
connect_name: str connect_name: str
energy_core: int
available_fragments: int
required_fragments: int
_halls_ne_chest_names: typing.List[str] = [loc.name for loc in _locations if 'Halls NE' in loc.name] _halls_ne_chest_names: typing.List[str] = [loc.name for loc in _locations if 'Halls NE' in loc.name]
@ -172,6 +176,14 @@ class SoEWorld(World):
self.connect_name_available_event = threading.Event() self.connect_name_available_event = threading.Event()
super(SoEWorld, self).__init__(*args, **kwargs) super(SoEWorld, self).__init__(*args, **kwargs)
def generate_early(self) -> None:
# store option values that change logic
self.energy_core = self.world.energy_core[self.player].value
self.required_fragments = self.world.required_fragments[self.player].value
if self.required_fragments > self.world.available_fragments[self.player].value:
self.world.available_fragments[self.player].value = self.required_fragments
self.available_fragments = self.world.available_fragments[self.player].value
def create_event(self, event: str) -> Item: def create_event(self, event: str) -> Item:
return SoEItem(event, ItemClassification.progression, None, self.player) return SoEItem(event, ItemClassification.progression, None, self.player)
@ -182,6 +194,8 @@ class SoEWorld(World):
classification = ItemClassification.trap classification = ItemClassification.trap
elif item.progression: elif item.progression:
classification = ItemClassification.progression classification = ItemClassification.progression
elif item.useful:
classification = ItemClassification.useful
else: else:
classification = ItemClassification.filler classification = ItemClassification.filler
@ -208,9 +222,33 @@ class SoEWorld(World):
self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player)) self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player))
def create_items(self): def create_items(self):
# add items to the pool # add regular items to the pool
items = list(map(lambda item: self.create_item(item), _items)) exclusions: typing.List[str] = []
if self.energy_core != EnergyCore.option_shuffle:
exclusions.append("Energy Core") # will be placed in generate_basic or replaced by a fragment below
items = list(map(lambda item: self.create_item(item), (item for item in _items if item.name not in exclusions)))
# remove one pair of wings that will be placed in generate_basic
items.remove(self.create_item("Wings"))
def is_ingredient(item):
for ingredient in _ingredients:
if _match_item_name(item, ingredient):
return True
return False
# add energy core fragments to the pool
ingredients = [n for n, item in enumerate(items) if is_ingredient(item)]
if self.energy_core == EnergyCore.option_fragments:
items.append(self.create_item("Energy Core Fragment")) # replaces the vanilla energy core
for _ in range(self.available_fragments - 1):
if len(ingredients) < 1:
break # out of ingredients to replace
r = self.world.random.choice(ingredients)
ingredients.remove(r)
items[r] = self.create_item("Energy Core Fragment")
# add traps to the pool
trap_count = self.world.trap_count[self.player].value trap_count = self.world.trap_count[self.player].value
trap_chances = {} trap_chances = {}
trap_names = {} trap_names = {}
@ -232,13 +270,12 @@ class SoEWorld(World):
return self.create_item(trap_names[t]) return self.create_item(trap_names[t])
v -= c v -= c
while trap_count > 0: for _ in range(trap_count):
r = self.world.random.randrange(len(items)) if len(ingredients) < 1:
for ingredient in _ingredients: break # out of ingredients to replace
if _match_item_name(items[r], ingredient): r = self.world.random.choice(ingredients)
items[r] = create_trap() ingredients.remove(r)
trap_count -= 1 items[r] = create_trap()
break
self.world.itempool += items self.world.itempool += items
@ -271,7 +308,10 @@ class SoEWorld(World):
wings_location = self.world.random.choice(self._halls_ne_chest_names) wings_location = self.world.random.choice(self._halls_ne_chest_names)
wings_item = self.create_item('Wings') wings_item = self.create_item('Wings')
self.world.get_location(wings_location, self.player).place_locked_item(wings_item) self.world.get_location(wings_location, self.player).place_locked_item(wings_item)
self.world.itempool.remove(wings_item) # place energy core at vanilla location for vanilla mode
if self.energy_core == EnergyCore.option_vanilla:
energy_core = self.create_item('Energy Core')
self.world.get_location('Energy Core #285', self.player).place_locked_item(energy_core)
# generate stuff for later # generate stuff for later
self.evermizer_seed = self.world.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando? self.evermizer_seed = self.world.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando?
@ -286,9 +326,12 @@ class SoEWorld(World):
try: try:
money = self.world.money_modifier[self.player].value money = self.world.money_modifier[self.player].value
exp = self.world.exp_modifier[self.player].value exp = self.world.exp_modifier[self.player].value
switches = [] switches: typing.List[str] = []
if self.world.death_link[self.player].value: if self.world.death_link[self.player].value:
switches.append("--death-link") switches.append("--death-link")
if self.energy_core == EnergyCore.option_fragments:
switches.extend(('--available-fragments', str(self.available_fragments),
'--required-fragments', str(self.required_fragments)))
rom_file = get_base_rom_path() rom_file = get_base_rom_path()
out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_' out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_'
f'{self.world.get_file_safe_player_name(self.player)}') f'{self.world.get_file_safe_player_name(self.player)}')

View File

@ -1,14 +1,14 @@
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8'
#https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' #https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10' https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10'
#https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2.tar.gz#egg=pyevermizer; python_version == '3.11' #https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3.tar.gz#egg=pyevermizer; python_version == '3.11'