SoE: implement everything else
This commit is contained in:
parent
5d0d9c2890
commit
655d14ed6e
|
@ -0,0 +1,3 @@
|
||||||
|
dumpy.py
|
||||||
|
pyevermizer
|
||||||
|
.pyevermizer
|
|
@ -1,7 +1,6 @@
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from ..AutoWorld import LogicMixin
|
from ..AutoWorld import LogicMixin
|
||||||
from typing import Set
|
from typing import Set
|
||||||
# TODO: import Options
|
|
||||||
# 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?
|
||||||
|
|
||||||
from . import pyevermizer
|
from . import pyevermizer
|
||||||
|
@ -19,8 +18,8 @@ items = [item for item in filter(lambda item: item.progression, pyevermizer.get_
|
||||||
class SecretOfEvermoreLogic(LogicMixin):
|
class SecretOfEvermoreLogic(LogicMixin):
|
||||||
def _soe_count(self, progress: int, world: MultiWorld, player: int, max_count: int = 0) -> int:
|
def _soe_count(self, progress: int, world: MultiWorld, player: int, max_count: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Returns reached count of one of evermizer's progress steps based on
|
Returns reached count of one of evermizer's progress steps based on collected items.
|
||||||
collected items. i.e. returns 0-3 for P_DE based on items giving CHECK_BOSS,DIAMOND_EYE_DROP
|
i.e. returns 0-3 for P_DE based on items providing CHECK_BOSS,DIAMOND_EYE_DROP
|
||||||
"""
|
"""
|
||||||
n = 0
|
n = 0
|
||||||
for item in items:
|
for item in items:
|
||||||
|
@ -46,7 +45,6 @@ class SecretOfEvermoreLogic(LogicMixin):
|
||||||
|
|
||||||
def _soe_has(self, progress: int, world: MultiWorld, player: int, count: int = 1) -> bool:
|
def _soe_has(self, progress: int, world: MultiWorld, player: int, count: int = 1) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if count of an evermizer progress steps are reached based
|
Returns True if count of one of evermizer's progress steps is reached based on collected items. i.e. 2 * P_DE
|
||||||
on collected items. i.e. 2 * P_DE
|
|
||||||
"""
|
"""
|
||||||
return self._soe_count(progress, world, player, count) >= count
|
return self._soe_count(progress, world, player, count) >= count
|
||||||
|
|
|
@ -1,7 +1,154 @@
|
||||||
import typing
|
import typing
|
||||||
from Options import Option
|
from Options import Option, Range, Choice, Toggle, DefaultOnToggle
|
||||||
|
|
||||||
|
|
||||||
|
class EvermizerFlags:
|
||||||
|
flags: typing.List[str]
|
||||||
|
|
||||||
|
def to_flag(self) -> str:
|
||||||
|
return self.flags[self.value]
|
||||||
|
|
||||||
|
|
||||||
|
class EvermizerFlag:
|
||||||
|
flag: str
|
||||||
|
|
||||||
|
def to_flag(self) -> str:
|
||||||
|
return self.flag if self.value != self.default else ''
|
||||||
|
|
||||||
|
|
||||||
|
class OffOnChaosChoice(Choice):
|
||||||
|
option_off = 0
|
||||||
|
option_on = 1
|
||||||
|
option_chaos = 2
|
||||||
|
alias_false = 0
|
||||||
|
alias_true = 1
|
||||||
|
|
||||||
|
|
||||||
|
class Difficulty(EvermizerFlags, Choice):
|
||||||
|
"""Changes relative spell cost and stuff"""
|
||||||
|
displayname = "Difficulty"
|
||||||
|
option_easy = 0
|
||||||
|
option_normal = 1
|
||||||
|
option_hard = 2
|
||||||
|
option_chaos = 3 # random is reserved pre 0.2
|
||||||
|
default = 1
|
||||||
|
flags = ['e', 'n', 'h', 'x']
|
||||||
|
|
||||||
|
|
||||||
|
class MoneyModifier(Range):
|
||||||
|
"""Money multiplier in %"""
|
||||||
|
displayname = "Money Modifier"
|
||||||
|
range_start = 1
|
||||||
|
range_end = 2500
|
||||||
|
default = 200
|
||||||
|
|
||||||
|
|
||||||
|
class ExpModifier(Range):
|
||||||
|
"""EXP multiplier for Weapons, Characters and Spells in %"""
|
||||||
|
displayname = "Exp Modifier"
|
||||||
|
range_start = 1
|
||||||
|
range_end = 2500
|
||||||
|
default = 200
|
||||||
|
|
||||||
|
|
||||||
|
class FixSequence(EvermizerFlag, DefaultOnToggle):
|
||||||
|
"""Fix some sequence breaks"""
|
||||||
|
displayname = "Fix Sequence"
|
||||||
|
flag = '1'
|
||||||
|
|
||||||
|
|
||||||
|
class FixCheats(EvermizerFlag, DefaultOnToggle):
|
||||||
|
"""Fix cheats left in by the devs (not desert skip)"""
|
||||||
|
displayname = "Fix Cheats"
|
||||||
|
flag = '2'
|
||||||
|
|
||||||
|
|
||||||
|
class FixInfiniteAmmo(EvermizerFlag, Toggle):
|
||||||
|
"""Fix infinite ammo glitch"""
|
||||||
|
displayname = "Fix Infinite Ammo"
|
||||||
|
flag = '5'
|
||||||
|
|
||||||
|
|
||||||
|
class FixAtlasGlitch(EvermizerFlag, Toggle):
|
||||||
|
"""Fix atlas underflowing stats"""
|
||||||
|
displayname = "Fix Atlas Glitch"
|
||||||
|
flag = '6'
|
||||||
|
|
||||||
|
|
||||||
|
class FixWingsGlitch(EvermizerFlag, Toggle):
|
||||||
|
"""Fix wings making you invincible in some areas"""
|
||||||
|
displayname = "Fix Wings Glitch"
|
||||||
|
flag = '7'
|
||||||
|
|
||||||
|
|
||||||
|
class ShorterDialogs(EvermizerFlag, Toggle):
|
||||||
|
"""Cuts some dialogs"""
|
||||||
|
displayname = "Shorter Dialogs"
|
||||||
|
flag = '9'
|
||||||
|
|
||||||
|
|
||||||
|
class ShortBossRush(EvermizerFlag, Toggle):
|
||||||
|
"""Start boss rush at Magmar, cut HP in half"""
|
||||||
|
displayname = "Short Boss Rush"
|
||||||
|
flag = 'f'
|
||||||
|
|
||||||
|
|
||||||
|
class Ingredienizer(EvermizerFlags, OffOnChaosChoice):
|
||||||
|
"""Shuffles or randomizes spell ingredients"""
|
||||||
|
displayname = "Ingredienizer"
|
||||||
|
default = 1
|
||||||
|
flags = ['i', '', 'I']
|
||||||
|
|
||||||
|
|
||||||
|
class Sniffamizer(EvermizerFlags, OffOnChaosChoice):
|
||||||
|
"""Shuffles or randomizes drops in sniff locations"""
|
||||||
|
displayname = "Sniffamizer"
|
||||||
|
default = 1
|
||||||
|
flags = ['s', '', 'S']
|
||||||
|
|
||||||
|
|
||||||
|
class Callbeadamizer(EvermizerFlags, OffOnChaosChoice):
|
||||||
|
"""Shuffles call bead characters or spells"""
|
||||||
|
displayname = "Callbeadamizer"
|
||||||
|
default = 1
|
||||||
|
flags = ['c', '', 'C']
|
||||||
|
|
||||||
|
|
||||||
|
class Musicmizer(EvermizerFlag, Toggle):
|
||||||
|
"""Randomize music for some rooms"""
|
||||||
|
displayname = "Musicmizer"
|
||||||
|
flag = 'm'
|
||||||
|
|
||||||
|
|
||||||
|
class Doggomizer(EvermizerFlags, OffOnChaosChoice):
|
||||||
|
"""On shuffles dog per act, Chaos randomizes dog per screen, Pupdunk gives you Everpupper everywhere"""
|
||||||
|
displayname = "Doggomizer"
|
||||||
|
option_pupdunk = 3
|
||||||
|
default = 0
|
||||||
|
flags = ['', 'd', 'D', 'p']
|
||||||
|
|
||||||
|
|
||||||
|
class TurdoMode(EvermizerFlag, Toggle):
|
||||||
|
"""Replace offensive spells by Turd Balls with varying strength and make weapons weak"""
|
||||||
|
displayname = "Turdo Mode"
|
||||||
|
flag = 't'
|
||||||
|
|
||||||
# TODO: add options
|
|
||||||
|
|
||||||
soe_options: typing.Dict[str, type(Option)] = {
|
soe_options: typing.Dict[str, type(Option)] = {
|
||||||
|
"difficulty": Difficulty,
|
||||||
|
"money_modifier": MoneyModifier,
|
||||||
|
"exp_modifier": ExpModifier,
|
||||||
|
"fix_sequence": FixSequence,
|
||||||
|
"fix_cheats": FixCheats,
|
||||||
|
"fix_infinite_ammo": FixInfiniteAmmo,
|
||||||
|
"fix_atlas_glitch": FixAtlasGlitch,
|
||||||
|
"fix_wings_glitch": FixWingsGlitch,
|
||||||
|
"shorter_dialogs": ShorterDialogs,
|
||||||
|
"short_boss_rush": ShortBossRush,
|
||||||
|
"ingredienizer": Ingredienizer,
|
||||||
|
"sniffamizer": Sniffamizer,
|
||||||
|
"callbeadamizer": Callbeadamizer,
|
||||||
|
"musicmizer": Musicmizer,
|
||||||
|
"doggomizer": Doggomizer,
|
||||||
|
"turdo_mode": TurdoMode,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import bsdiff4
|
||||||
|
import yaml
|
||||||
|
from typing import Optional
|
||||||
|
import Utils
|
||||||
|
|
||||||
|
|
||||||
|
def read_rom(stream, strip_header=True) -> bytes:
|
||||||
|
"""Reads rom into bytearray and optionally strips off any smc header"""
|
||||||
|
data = stream.read()
|
||||||
|
if strip_header and len(data) % 0x400 == 0x200:
|
||||||
|
return data[0x200:]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes:
|
||||||
|
patch = yaml.dump({"meta": metadata,
|
||||||
|
"patch": patch,
|
||||||
|
"game": "Secret of Evermore",
|
||||||
|
# minimum version of patch system expected for patching to be successful
|
||||||
|
"compatible_version": 1,
|
||||||
|
"version": 1})
|
||||||
|
return patch.encode(encoding="utf-8-sig")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_patch(vanilla_file, randomized_file, metadata: Optional[dict] = None) -> bytes:
|
||||||
|
with open(vanilla_file, "rb") as f:
|
||||||
|
vanilla = read_rom(f)
|
||||||
|
with open(randomized_file, "rb") as f:
|
||||||
|
randomized = read_rom(f)
|
||||||
|
if metadata is None:
|
||||||
|
metadata = {}
|
||||||
|
patch = bsdiff4.diff(vanilla, randomized)
|
||||||
|
return generate_yaml(patch, metadata)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import lzma
|
||||||
|
parser = argparse.ArgumentParser(description='Apply patch to Secret of Evermore.')
|
||||||
|
parser.add_argument('patch', type=pathlib.Path, help='path to .absoe file')
|
||||||
|
args = parser.parse_args()
|
||||||
|
with open(args.patch, "rb") as f:
|
||||||
|
data = Utils.parse_yaml(lzma.decompress(f.read()).decode("utf-8-sig"))
|
||||||
|
if data['game'] != 'Secret of Evermore':
|
||||||
|
raise RuntimeError('Patch is not for Secret of Evermore')
|
||||||
|
with open(Utils.get_options()['soe_options']['rom_file'], 'rb') as f:
|
||||||
|
vanilla_data = read_rom(f)
|
||||||
|
patched_data = bsdiff4.patch(vanilla_data, data["patch"])
|
||||||
|
with open(args.patch.parent / (args.patch.stem + '.sfc'), 'wb') as f:
|
||||||
|
f.write(patched_data)
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
from .Options import soe_options
|
|
||||||
from ..AutoWorld import World
|
from ..AutoWorld import World
|
||||||
from ..generic.Rules import set_rule
|
from ..generic.Rules import set_rule, add_item_rule
|
||||||
from BaseClasses import Region, Location, Entrance, Item
|
from BaseClasses import Region, Location, Entrance, Item
|
||||||
|
from Utils import get_options, output_path
|
||||||
import typing
|
import typing
|
||||||
from . import Logic # load logic mixin
|
import lzma
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pyevermizer # from package
|
import pyevermizer # from package
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
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 .Options import soe_options
|
||||||
|
from .Patch import generate_patch
|
||||||
|
|
||||||
"""
|
"""
|
||||||
In evermizer:
|
In evermizer:
|
||||||
|
|
||||||
|
@ -36,99 +44,115 @@ TODO: for balancing we may want to generate Regions (with Entrances) for some
|
||||||
common rules, place the locations in those Regions and shorten the rules.
|
common rules, place the locations in those Regions and shorten the rules.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GAME_NAME = "Secret of Evermore"
|
_id_base = 64000
|
||||||
ID_OFF_BASE = 64000
|
_id_offset: typing.Dict[int, int] = {
|
||||||
ID_OFFS: typing.Dict[int,int] = {
|
pyevermizer.CHECK_ALCHEMY: _id_base + 0, # alchemy 64000..64049
|
||||||
pyevermizer.CHECK_ALCHEMY: ID_OFF_BASE + 0, # alchemy 64000..64049
|
pyevermizer.CHECK_BOSS: _id_base + 50, # bosses 64050..6499
|
||||||
pyevermizer.CHECK_BOSS: ID_OFF_BASE + 50, # bosses 64050..6499
|
pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399
|
||||||
pyevermizer.CHECK_GOURD: ID_OFF_BASE + 100, # gourds 64100..64399
|
pyevermizer.CHECK_NPC: _id_base + 400, # npc 64400..64499
|
||||||
pyevermizer.CHECK_NPC: ID_OFF_BASE + 400, # npc 64400..64499
|
|
||||||
# TODO: sniff 64500..64799
|
# TODO: sniff 64500..64799
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# cache native evermizer items and locations
|
||||||
def _get_locations():
|
_items = pyevermizer.get_items()
|
||||||
locs = pyevermizer.get_locations()
|
_locations = pyevermizer.get_locations()
|
||||||
for loc in locs:
|
# fix up texts for AP
|
||||||
if loc.type == 3: # TODO: CHECK_GOURD
|
for _loc in _locations:
|
||||||
loc.name = f'{loc.name} #{loc.index}'
|
if _loc.type == pyevermizer.CHECK_GOURD:
|
||||||
return locs
|
_loc.name = f'{_loc.name} #{_loc.index}'
|
||||||
|
|
||||||
|
|
||||||
def _get_location_ids():
|
def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Location]]:
|
||||||
m = {}
|
name_to_id = {}
|
||||||
for loc in _get_locations():
|
id_to_raw = {}
|
||||||
m[loc.name] = ID_OFFS[loc.type] + loc.index
|
for loc in _locations:
|
||||||
m['Done'] = None
|
apid = _id_offset[loc.type] + loc.index
|
||||||
return m
|
id_to_raw[apid] = loc
|
||||||
|
name_to_id[loc.name] = apid
|
||||||
|
name_to_id['Done'] = None
|
||||||
|
return name_to_id, id_to_raw
|
||||||
|
|
||||||
|
|
||||||
def _get_items():
|
def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]:
|
||||||
return pyevermizer.get_items()
|
name_to_id = {}
|
||||||
|
id_to_raw = {}
|
||||||
|
for item in _items:
|
||||||
def _get_item_ids():
|
if item.name in name_to_id:
|
||||||
m = {}
|
continue
|
||||||
for item in _get_items():
|
apid = _id_offset[item.type] + item.index
|
||||||
if item.name in m: continue
|
id_to_raw[apid] = item
|
||||||
m[item.name] = ID_OFFS[item.type] + item.index
|
name_to_id[item.name] = apid
|
||||||
m['Victory'] = None
|
name_to_id['Victory'] = None
|
||||||
return m
|
return name_to_id, id_to_raw
|
||||||
|
|
||||||
|
|
||||||
class SoEWorld(World):
|
class SoEWorld(World):
|
||||||
"""
|
"""
|
||||||
TODO: insert game description here
|
Secret of Evermore is a SNES action RPG. You learn alchemy spells, fight bosses and gather rocket parts to visit a
|
||||||
|
space station where the final boss must be defeated.
|
||||||
"""
|
"""
|
||||||
game: str = GAME_NAME
|
game: str = "Secret of Evermore"
|
||||||
# options = soe_options
|
options = soe_options
|
||||||
topology_present: bool = True
|
topology_present: bool = True
|
||||||
|
remote_items: bool = False # True only for testing
|
||||||
|
data_version = 0
|
||||||
|
|
||||||
item_name_to_id = _get_item_ids()
|
item_name_to_id, item_id_to_raw = _get_item_mapping()
|
||||||
location_name_to_id = _get_location_ids()
|
location_name_to_id, location_id_to_raw = _get_location_mapping()
|
||||||
|
|
||||||
remote_items: bool = True # False # True only for testing
|
evermizer_seed: int
|
||||||
|
restrict_item_placement: bool = False # placeholder to force certain item types to certain pools
|
||||||
|
|
||||||
def generate_basic(self):
|
def __init__(self, *args, **kwargs):
|
||||||
print('SoE: generate_basic')
|
self.connect_name_available_event = threading.Event()
|
||||||
itempool = [item for item in map(lambda item: self.create_item(item), _get_items())]
|
super(SoEWorld, self).__init__(*args, **kwargs)
|
||||||
self.world.itempool += itempool
|
|
||||||
self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory'))
|
def create_event(self, event: str) -> Item:
|
||||||
|
progression = True
|
||||||
|
return SoEItem(event, progression, None, self.player)
|
||||||
|
|
||||||
|
def create_item(self, item: typing.Union[pyevermizer.Item, str], force_progression: bool = False) -> Item:
|
||||||
|
if type(item) is str:
|
||||||
|
item = self.item_id_to_raw[self.item_name_to_id[item]]
|
||||||
|
return SoEItem(item.name, force_progression or item.progression, self.item_name_to_id[item.name], self.player)
|
||||||
|
|
||||||
def create_regions(self):
|
def create_regions(self):
|
||||||
# TODO: generate *some* regions from locations' requirements
|
# TODO: generate *some* regions from locations' requirements?
|
||||||
r = Region('Menu', None, 'Menu', self.player, self.world)
|
r = Region('Menu', None, 'Menu', self.player, self.world)
|
||||||
r.exits = [Entrance(self.player, 'New Game', r)]
|
r.exits = [Entrance(self.player, 'New Game', r)]
|
||||||
self.world.regions += [r]
|
self.world.regions += [r]
|
||||||
|
|
||||||
r = Region('Ingame', None, 'Ingame', self.player, self.world)
|
r = Region('Ingame', None, 'Ingame', self.player, self.world)
|
||||||
r.locations = [SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], r)
|
r.locations = [SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], r)
|
||||||
for loc in _get_locations()]
|
for loc in _locations]
|
||||||
r.locations.append(SoELocation(self.player, 'Done', None, r))
|
r.locations.append(SoELocation(self.player, 'Done', None, r))
|
||||||
self.world.regions += [r]
|
self.world.regions += [r]
|
||||||
|
|
||||||
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_event(self, event: str) -> Item:
|
def create_items(self):
|
||||||
progression = True
|
# clear precollected items since we don't support them yet
|
||||||
return SoEItem(event, progression, None, self.player)
|
if type(self.world.precollected_items) is dict:
|
||||||
|
self.world.precollected_items[self.player] = []
|
||||||
def create_item(self, item) -> Item:
|
# add items to the pool
|
||||||
# TODO: if item is string: look up item by name
|
self.world.itempool += [item for item in
|
||||||
return SoEItem(item.name, item.progression, self.item_name_to_id[item.name], self.player)
|
map(lambda item: self.create_item(item, self.restrict_item_placement), _items)]
|
||||||
|
|
||||||
def set_rules(self):
|
def set_rules(self):
|
||||||
print('SoE: set_rules')
|
|
||||||
self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player)
|
self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player)
|
||||||
# set Done from goal option once we have multiple goals
|
# set Done from goal option once we have multiple goals
|
||||||
set_rule(self.world.get_location('Done', self.player),
|
set_rule(self.world.get_location('Done', self.player),
|
||||||
lambda state: state._soe_has(pyevermizer.P_FINAL_BOSS, self.world, self.player))
|
lambda state: state._soe_has(pyevermizer.P_FINAL_BOSS, self.world, self.player))
|
||||||
set_rule(self.world.get_entrance('New Game', self.player), lambda state: True)
|
set_rule(self.world.get_entrance('New Game', self.player), lambda state: True)
|
||||||
for loc in _get_locations():
|
for loc in _locations:
|
||||||
set_rule(self.world.get_location(loc.name, self.player), self.make_rule(loc.requires))
|
location = self.world.get_location(loc.name, self.player)
|
||||||
|
set_rule(location, self.make_rule(loc.requires))
|
||||||
|
# limit location pool by item type
|
||||||
|
if self.restrict_item_placement:
|
||||||
|
add_item_rule(location, self.make_item_type_limit_rule(loc.type))
|
||||||
|
|
||||||
def make_rule(self, requires):
|
def make_rule(self, requires: typing.List[typing.Tuple[int]]) -> typing.Callable[[typing.Any], bool]:
|
||||||
def rule(state):
|
def rule(state) -> bool:
|
||||||
for count, progress in requires:
|
for count, progress in requires:
|
||||||
if not state._soe_has(progress, self.world, self.player, count):
|
if not state._soe_has(progress, self.world, self.player, count):
|
||||||
return False
|
return False
|
||||||
|
@ -136,13 +160,79 @@ class SoEWorld(World):
|
||||||
|
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
|
def make_item_type_limit_rule(self, item_type: int):
|
||||||
|
return lambda item: item.player != self.player or self.item_id_to_raw[item.code].type == item_type
|
||||||
|
|
||||||
|
def generate_basic(self):
|
||||||
|
# place Victory event
|
||||||
|
self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory'))
|
||||||
|
# generate stuff for later
|
||||||
|
self.evermizer_seed = self.world.random.randint(0, 2**16-1) # TODO: make this an option for "full" plando?
|
||||||
|
|
||||||
|
def post_fill(self):
|
||||||
|
# fix up the advancement property of items so they are displayed correctly in other games
|
||||||
|
if self.restrict_item_placement:
|
||||||
|
for location in self.world.get_locations():
|
||||||
|
item = location.item
|
||||||
|
if item.code and item.player == self.player and not self.item_id_to_raw[location.item.code].progression:
|
||||||
|
item.advancement = False
|
||||||
|
|
||||||
|
def generate_output(self, output_directory: str):
|
||||||
|
player_name = self.world.get_player_name(self.player)
|
||||||
|
self.connect_name = player_name[:32]
|
||||||
|
while len(self.connect_name.encode('utf-8')) > 32:
|
||||||
|
self.connect_name = self.connect_name[:-1]
|
||||||
|
self.connect_name_available_event.set()
|
||||||
|
placement_file = None
|
||||||
|
out_file = None
|
||||||
|
try:
|
||||||
|
money = self.world.money_modifier[self.player].value
|
||||||
|
exp = self.world.exp_modifier[self.player].value
|
||||||
|
rom_file = get_options()['soe_options']['rom_file']
|
||||||
|
out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_{player_name}')
|
||||||
|
out_file = out_base + '.sfc'
|
||||||
|
placement_file = out_base + '.txt'
|
||||||
|
patch_file = out_base + '.apsoe'
|
||||||
|
flags = 'l' # spoiler log
|
||||||
|
for option_name in self.options:
|
||||||
|
option = getattr(self.world, option_name)[self.player]
|
||||||
|
if hasattr(option, 'to_flag'):
|
||||||
|
flags += option.to_flag()
|
||||||
|
|
||||||
|
with open(placement_file, "wb") as f: # generate placement file
|
||||||
|
for location in filter(lambda l: l.player == self.player, self.world.get_locations()):
|
||||||
|
item = location.item
|
||||||
|
if item.code is None:
|
||||||
|
continue # skip events
|
||||||
|
loc = self.location_id_to_raw[location.address]
|
||||||
|
if item.player != self.player:
|
||||||
|
line = f'{loc.type},{loc.index}:{pyevermizer.CHECK_NONE},{item.code},{item.player}\n'
|
||||||
|
else:
|
||||||
|
item = self.item_id_to_raw[item.code]
|
||||||
|
line = f'{loc.type},{loc.index}:{item.type},{item.index}\n'
|
||||||
|
f.write(line.encode('utf-8'))
|
||||||
|
|
||||||
|
if (pyevermizer.main(rom_file, out_file, placement_file, self.world.seed_name, self.connect_name, self.evermizer_seed,
|
||||||
|
flags, money, exp)):
|
||||||
|
raise RuntimeError()
|
||||||
|
with lzma.LZMAFile(patch_file, 'wb') as f:
|
||||||
|
f.write(generate_patch(rom_file, out_file))
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.unlink(placement_file)
|
||||||
|
os.unlink(out_file)
|
||||||
|
os.unlink(out_file[:-4]+'_SPOILER.log')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
class SoEItem(Item):
|
class SoEItem(Item):
|
||||||
game: str = GAME_NAME
|
game: str = "Secret of Evermore"
|
||||||
|
|
||||||
|
|
||||||
class SoELocation(Location):
|
class SoELocation(Location):
|
||||||
game: str = GAME_NAME
|
game: str = "Secret of Evermore"
|
||||||
|
|
||||||
def __init__(self, player: int, name: str, address: typing.Optional[int], parent):
|
def __init__(self, player: int, name: str, address: typing.Optional[int], parent):
|
||||||
super().__init__(player, name, address, parent)
|
super().__init__(player, name, address, parent)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-cp38-cp38-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8'
|
||||||
|
#https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||||
|
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-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.39.0/pyevermizer-0.39-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10'
|
||||||
|
bsdiff4>=1.2.1
|
Loading…
Reference in New Issue