SoE: implement logic
This commit is contained in:
parent
666e3b5333
commit
f10163e7d2
|
@ -0,0 +1,52 @@
|
|||
from BaseClasses import MultiWorld
|
||||
from ..AutoWorld import LogicMixin
|
||||
from typing import Set
|
||||
# TODO: import Options
|
||||
# TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early?
|
||||
|
||||
from . import pyevermizer
|
||||
|
||||
# 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]
|
||||
# Logic.items are all items excluding non-progression items and duplicates
|
||||
item_names: Set[str] = set()
|
||||
items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items())
|
||||
if item.name not in item_names and not item_names.add(item.name)]
|
||||
|
||||
|
||||
# when this module is loaded, this mixin will extend BaseClasses.CollectionState
|
||||
class SecretOfEvermoreLogic(LogicMixin):
|
||||
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
|
||||
collected items. i.e. returns 0-3 for P_DE based on items giving CHECK_BOSS,DIAMOND_EYE_DROP
|
||||
"""
|
||||
n = 0
|
||||
for item in items:
|
||||
for pvd in item.provides:
|
||||
if pvd[1] == progress:
|
||||
if self.has(item.name, player):
|
||||
n += self.item_count(item.name, player) * pvd[0]
|
||||
if n >= max_count > 0:
|
||||
return n
|
||||
for rule in rules:
|
||||
for pvd in rule.provides:
|
||||
if pvd[1] == progress and pvd[0] > 0:
|
||||
has = True
|
||||
for req in rule.requires:
|
||||
if not self._soe_has(req[1], world, player, req[0]):
|
||||
has = False
|
||||
break
|
||||
if has:
|
||||
n += pvd[0]
|
||||
if n >= max_count > 0:
|
||||
return n
|
||||
return n
|
||||
|
||||
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
|
||||
on collected items. i.e. 2 * P_DE
|
||||
"""
|
||||
return self._soe_count(progress, world, player, count) >= count
|
|
@ -0,0 +1,7 @@
|
|||
import typing
|
||||
from Options import Option
|
||||
|
||||
# TODO: add options
|
||||
|
||||
soe_options: typing.Dict[str, type(Option)] = {
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
from .Options import soe_options
|
||||
from ..AutoWorld import World
|
||||
from ..generic.Rules import set_rule
|
||||
from BaseClasses import Region, Location, Entrance, Item
|
||||
import typing
|
||||
from . import Logic # load logic mixin
|
||||
|
||||
try:
|
||||
import pyevermizer # from package
|
||||
except ImportError:
|
||||
from . import pyevermizer # as part of the source tree
|
||||
|
||||
"""
|
||||
In evermizer:
|
||||
|
||||
Items are uniquely defined by a pair of (type, id).
|
||||
For most items this is their vanilla location (i.e. CHECK_GOURD, number).
|
||||
|
||||
Items have `provides`, which give the actual progression
|
||||
instead of providing multiple events per item, we iterate through them in Logic.py
|
||||
e.g. Found any weapon
|
||||
|
||||
Locations have `requires` and `provides`.
|
||||
Requirements have to be converted to (access) rules for AP
|
||||
e.g. Chest locked behind having a weapon
|
||||
Provides could be events, but instead we iterate through the entire logic in Logic.py
|
||||
e.g. NPC available after fighting a Boss
|
||||
|
||||
Rules are special locations that don't have a physical location
|
||||
instead of implementing virtual locations and virtual items, we simply use them in Logic.py
|
||||
e.g. 2DEs+Wheel+Gauge = Rocket
|
||||
|
||||
Rules and Locations live on the same logic tree returned by pyevermizer.get_logic()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
GAME_NAME = "Secret of Evermore"
|
||||
ID_OFF_BASE = 64000
|
||||
ID_OFFS: typing.Dict[int,int] = {
|
||||
pyevermizer.CHECK_ALCHEMY: ID_OFF_BASE + 0, # alchemy 64000..64049
|
||||
pyevermizer.CHECK_BOSS: ID_OFF_BASE + 50, # bosses 64050..6499
|
||||
pyevermizer.CHECK_GOURD: ID_OFF_BASE + 100, # gourds 64100..64399
|
||||
pyevermizer.CHECK_NPC: ID_OFF_BASE + 400, # npc 64400..64499
|
||||
# TODO: sniff 64500..64799
|
||||
}
|
||||
|
||||
|
||||
def _get_locations():
|
||||
locs = pyevermizer.get_locations()
|
||||
for loc in locs:
|
||||
if loc.type == 3: # TODO: CHECK_GOURD
|
||||
loc.name = f'{loc.name} #{loc.index}'
|
||||
return locs
|
||||
|
||||
|
||||
def _get_location_ids():
|
||||
m = {}
|
||||
for loc in _get_locations():
|
||||
m[loc.name] = ID_OFFS[loc.type] + loc.index
|
||||
m['Done'] = None
|
||||
return m
|
||||
|
||||
|
||||
def _get_items():
|
||||
return pyevermizer.get_items()
|
||||
|
||||
|
||||
def _get_item_ids():
|
||||
m = {}
|
||||
for item in _get_items():
|
||||
if item.name in m: continue
|
||||
m[item.name] = ID_OFFS[item.type] + item.index
|
||||
m['Victory'] = None
|
||||
return m
|
||||
|
||||
|
||||
class SoEWorld(World):
|
||||
"""
|
||||
TODO: insert game description here
|
||||
"""
|
||||
game: str = GAME_NAME
|
||||
# options = soe_options
|
||||
topology_present: bool = True
|
||||
|
||||
item_name_to_id = _get_item_ids()
|
||||
location_name_to_id = _get_location_ids()
|
||||
|
||||
remote_items: bool = True # False # True only for testing
|
||||
|
||||
def generate_basic(self):
|
||||
print('SoE: generate_basic')
|
||||
itempool = [item for item in map(lambda item: self.create_item(item), _get_items())]
|
||||
self.world.itempool += itempool
|
||||
self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory'))
|
||||
|
||||
def create_regions(self):
|
||||
# TODO: generate *some* regions from locations' requirements
|
||||
r = Region('Menu', None, 'Menu', self.player, self.world)
|
||||
r.exits = [Entrance(self.player, 'New Game', r)]
|
||||
self.world.regions += [r]
|
||||
|
||||
r = Region('Ingame', None, 'Ingame', self.player, self.world)
|
||||
r.locations = [SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], r)
|
||||
for loc in _get_locations()]
|
||||
r.locations.append(SoELocation(self.player, 'Done', None, r))
|
||||
self.world.regions += [r]
|
||||
|
||||
self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player))
|
||||
|
||||
def create_event(self, event: str) -> Item:
|
||||
progression = True
|
||||
return SoEItem(event, progression, None, self.player)
|
||||
|
||||
def create_item(self, item) -> Item:
|
||||
# TODO: if item is string: look up item by name
|
||||
return SoEItem(item.name, item.progression, self.item_name_to_id[item.name], self.player)
|
||||
|
||||
def set_rules(self):
|
||||
print('SoE: set_rules')
|
||||
self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player)
|
||||
# set Done from goal option once we have multiple goals
|
||||
set_rule(self.world.get_location('Done', 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)
|
||||
for loc in _get_locations():
|
||||
set_rule(self.world.get_location(loc.name, self.player), self.make_rule(loc.requires))
|
||||
|
||||
def make_rule(self, requires):
|
||||
def rule(state):
|
||||
for count, progress in requires:
|
||||
if not state._soe_has(progress, self.world, self.player, count):
|
||||
return False
|
||||
return True
|
||||
|
||||
return rule
|
||||
|
||||
|
||||
class SoEItem(Item):
|
||||
game: str = GAME_NAME
|
||||
|
||||
|
||||
class SoELocation(Location):
|
||||
game: str = GAME_NAME
|
||||
|
||||
def __init__(self, player: int, name: str, address: typing.Optional[int], parent):
|
||||
super().__init__(player, name, address, parent)
|
||||
self.event = not address
|
Loading…
Reference in New Issue