SM: comeback fix6 and some refactor (#1756)

refactored and cleaned a bit SMWorld class for best practices:
- moved content of Regions.py and Rules.py in SMWorld
- moved appropiate code to their dedicated World core functions
- moved some Entrances being created in generate_basic to create_regions
more comeback check fixes:
- fixed setting progression door openers items local if doors_colors_rando is used
- enable comeback check only for filling stage as later stages (progression balancing, accessibility and spoiler playthrough) are prone to fail with it
This commit is contained in:
lordlou 2023-04-23 16:16:01 -04:00 committed by GitHub
parent ab5cb7adad
commit 67c3076572
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 209 additions and 237 deletions

View File

@ -1,42 +0,0 @@
def create_regions(self, world, player: int):
from . import create_region
from BaseClasses import Entrance
from .variaRandomizer.logic.logic import Logic
from .variaRandomizer.graph.vanilla.graph_locations import locationsDict
regions = []
for accessPoint in Logic.accessPoints:
if not accessPoint.Escape:
regions.append(create_region(self,
world,
player,
accessPoint.Name,
None,
[accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()]))
world.regions += regions
# create a region for each location and link each to what the location has access
# we make them one way so that the filler (and spoiler log) doesnt try to use those region as intermediary path
# this is required in AP because a location cant have multiple parent regions
locationRegions = []
for locationName, value in locationsDict.items():
locationRegions.append(create_region( self,
world,
player,
locationName,
[locationName]))
for key in value.AccessFrom.keys():
currentRegion =world.get_region(key, player)
currentRegion.exits.append(Entrance(player, key + "->"+ locationName, currentRegion))
world.regions += locationRegions
#create entrances
regionConcat = regions + locationRegions
for region in regionConcat:
for exit in region.exits:
exit.connect(world.get_region(exit.name[exit.name.find("->") + 2:], player))
world.regions += [
create_region(self, world, player, 'Menu', None, ['StartAP'])
]

View File

@ -1,38 +0,0 @@
from worlds.generic.Rules import set_rule, add_rule
from .variaRandomizer.graph.vanilla.graph_locations import locationsDict
from .variaRandomizer.logic.logic import Logic
def evalSMBool(smbool, maxDiff):
return smbool.bool == True and smbool.difficulty <= maxDiff
def add_accessFrom_rule(location, player, accessFrom):
add_rule(location, lambda state: any((state.can_reach(accessName, player=player) and evalSMBool(rule(state.smbm[player]), state.smbm[player].maxDiff)) for accessName, rule in accessFrom.items()))
def add_postAvailable_rule(location, player, func):
add_rule(location, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
def set_available_rule(location, player, func):
set_rule(location, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
def set_entrance_rule(entrance, player, func):
set_rule(entrance, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
def add_entrance_rule(entrance, player, func):
add_rule(entrance, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
def set_rules(world, player):
world.completion_condition[player] = lambda state: state.has('Mother Brain', player)
for key, value in locationsDict.items():
location = world.get_location(key, player)
set_available_rule(location, player, value.Available)
if value.AccessFrom is not None:
add_accessFrom_rule(location, player, value.AccessFrom)
if value.PostAvailable is not None:
add_postAvailable_rule(location, player, value.PostAvailable)
for accessPoint in Logic.accessPoints:
if not accessPoint.Escape:
for key, value1 in accessPoint.intraTransitions.items():
set_entrance_rule(world.get_entrance(accessPoint.Name + "->" + key, player), player, value1)

View File

@ -10,11 +10,10 @@ from typing import Any, Dict, Iterable, List, Set, TextIO, TypedDict
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, CollectionState, Tutorial from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, CollectionState, Tutorial
from Fill import fill_restrictive from Fill import fill_restrictive
from worlds.AutoWorld import World, AutoLogicRegister, WebWorld from worlds.AutoWorld import World, AutoLogicRegister, WebWorld
from worlds.generic.Rules import set_rule, add_rule, add_item_rule
logger = logging.getLogger("Super Metroid") logger = logging.getLogger("Super Metroid")
from .Regions import create_regions
from .Rules import set_rules, add_entrance_rule
from .Options import sm_options from .Options import sm_options
from .Client import SMSNIClient from .Client import SMSNIClient
from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols
@ -106,6 +105,7 @@ class SMWorld(World):
def __init__(self, world: MultiWorld, player: int): def __init__(self, world: MultiWorld, player: int):
self.rom_name_available_event = threading.Event() self.rom_name_available_event = threading.Event()
self.locations = {} self.locations = {}
self.need_comeback_check = True
super().__init__(world, player) super().__init__(world, player)
@classmethod @classmethod
@ -134,7 +134,7 @@ class SMWorld(World):
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal") self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
logger.warning(f"accessibility forced to 'minimal' for player {self.multiworld.get_player_name(self.player)} because of 'fun' settings") logger.warning(f"accessibility forced to 'minimal' for player {self.multiworld.get_player_name(self.player)} because of 'fun' settings")
def generate_basic(self): def create_items(self):
itemPool = self.variaRando.container.itemPool itemPool = self.variaRando.container.itemPool
self.startItems = [variaItem for item in self.multiworld.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name] self.startItems = [variaItem for item in self.multiworld.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name]
if self.multiworld.start_inventory_removes_from_pool[self.player]: if self.multiworld.start_inventory_removes_from_pool[self.player]:
@ -150,7 +150,6 @@ class SMWorld(World):
pool = [] pool = []
self.locked_items = {} self.locked_items = {}
self.NothingPool = [] self.NothingPool = []
self.prefilled_locked_items = []
weaponCount = [0, 0, 0] weaponCount = [0, 0, 0]
for item in itemPool: for item in itemPool:
isAdvancement = True isAdvancement = True
@ -180,12 +179,9 @@ class SMWorld(World):
player=self.player) player=self.player)
beamItems = ['Spazer', 'Ice', 'Wave' ,'Plasma'] beamItems = ['Spazer', 'Ice', 'Wave' ,'Plasma']
self.ammoItems = ['Missile', 'Super', 'PowerBomb']
if self.multiworld.doors_colors_rando[self.player].value != 0: if self.multiworld.doors_colors_rando[self.player].value != 0:
if item.Type in beamItems: if item.Type in beamItems:
self.multiworld.local_items[self.player].value.add(item.Name) self.multiworld.local_items[self.player].value.add(item.Name)
elif item.Type in self.ammoItems and isAdvancement:
self.prefilled_locked_items.append(smitem)
if itemClass == 'Boss': if itemClass == 'Boss':
self.locked_items[item.Name] = smitem self.locked_items[item.Name] = smitem
@ -200,8 +196,95 @@ class SMWorld(World):
self.multiworld.get_location(location, self.player).place_locked_item(item) self.multiworld.get_location(location, self.player).place_locked_item(item)
self.multiworld.get_location(location, self.player).address = None self.multiworld.get_location(location, self.player).address = None
startAP = self.multiworld.get_entrance('StartAP', self.player) def evalSMBool(self, smbool, maxDiff):
startAP.connect(self.multiworld.get_region(self.variaRando.args.startLocation, self.player)) return smbool.bool == True and smbool.difficulty <= maxDiff
def add_entrance_rule(self, entrance, player, func):
add_rule(entrance, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
def set_rules(self):
def add_accessFrom_rule(location, player, accessFrom):
add_rule(location, lambda state: any((state.can_reach(accessName, player=player) and self.evalSMBool(rule(state.smbm[player]), state.smbm[player].maxDiff)) for accessName, rule in accessFrom.items()))
def add_postAvailable_rule(location, player, func):
add_rule(location, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
def set_available_rule(location, player, func):
set_rule(location, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
def set_entrance_rule(entrance, player, func):
set_rule(entrance, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
self.multiworld.completion_condition[self.player] = lambda state: state.has('Mother Brain', self.player)
ammoItems = ['Missile', 'Super', 'PowerBomb']
for key, value in locationsDict.items():
location = self.multiworld.get_location(key, self.player)
set_available_rule(location, self.player, value.Available)
if value.AccessFrom is not None:
add_accessFrom_rule(location, self.player, value.AccessFrom)
if value.PostAvailable is not None:
add_postAvailable_rule(location, self.player, value.PostAvailable)
if self.multiworld.doors_colors_rando[self.player].value != 0:
add_item_rule(location, lambda item: item.type not in ammoItems or
(item.type in ammoItems and \
(not item.advancement or (item.advancement and item.player == self.player))))
for accessPoint in Logic.accessPoints:
if not accessPoint.Escape:
for key, value1 in accessPoint.intraTransitions.items():
set_entrance_rule(self.multiworld.get_entrance(accessPoint.Name + "->" + key, self.player), self.player, value1)
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
ret = Region(name, player, world)
if locations:
for loc in locations:
location = self.locations[loc]
location.parent_region = ret
ret.locations.append(location)
if exits:
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
return ret
def create_regions(self):
# create locations
for name in locationsDict:
self.locations[name] = SMLocation(self.player, name, self.location_name_to_id.get(name, None))
# create regions
regions = []
for accessPoint in Logic.accessPoints:
if not accessPoint.Escape:
regions.append(self.create_region( self.multiworld,
self.player,
accessPoint.Name,
None,
[accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()]))
self.multiworld.regions += regions
# create a region for each location and link each to what the location has access
# we make them one way so that the filler (and spoiler log) doesnt try to use those region as intermediary path
# this is required in AP because a location cant have multiple parent regions
locationRegions = []
for locationName, value in locationsDict.items():
locationRegions.append(self.create_region( self.multiworld,
self.player,
locationName,
[locationName]))
for key in value.AccessFrom.keys():
currentRegion = self.multiworld.get_region(key, self.player)
currentRegion.exits.append(Entrance(self.player, key + "->"+ locationName, currentRegion))
self.multiworld.regions += locationRegions
#create entrances
regionConcat = regions + locationRegions
for region in regionConcat:
for exit in region.exits:
exit.connect(self.multiworld.get_region(exit.name[exit.name.find("->") + 2:], self.player))
for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions: for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions:
src_region = self.multiworld.get_region(src.Name, self.player) src_region = self.multiworld.get_region(src.Name, self.player)
@ -210,26 +293,125 @@ class SMWorld(World):
src_region.exits.append(Entrance(self.player, src.Name + "->" + dest.Name, src_region)) src_region.exits.append(Entrance(self.player, src.Name + "->" + dest.Name, src_region))
srcDestEntrance = self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player) srcDestEntrance = self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player)
srcDestEntrance.connect(dest_region) srcDestEntrance.connect(dest_region)
add_entrance_rule(self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse) self.add_entrance_rule(self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse)
def set_rules(self): self.multiworld.regions += [
set_rules(self.multiworld, self.player) self.create_region(self.multiworld, self.player, 'Menu', None, ['StartAP'])
]
def create_regions(self): startAP = self.multiworld.get_entrance('StartAP', self.player)
create_locations(self, self.player) startAP.connect(self.multiworld.get_region(self.variaRando.args.startLocation, self.player))
create_regions(self, self.multiworld, self.player)
def collect(self, state: CollectionState, item: Item) -> bool:
state.smbm[self.player].addItem(item.type)
if item.location != None and item.location.game == self.game:
for entrance in self.multiworld.get_region(item.location.parent_region.name, item.location.player).entrances:
if (entrance.parent_region.can_reach(state)):
state.smbm[item.location.player].lastAP = entrance.parent_region.name
break
return super(SMWorld, self).collect(state, item)
def remove(self, state: CollectionState, item: Item) -> bool:
state.smbm[self.player].removeItem(item.type)
return super(SMWorld, self).remove(state, item)
def create_item(self, name: str) -> Item:
item = next(x for x in ItemManager.Items.values() if x.Name == name)
return SMItem(item.Name, ItemClassification.progression if item.Class != 'Minor' else ItemClassification.filler, item.Type, self.item_name_to_id[item.Name],
player=self.player)
def get_filler_item_name(self) -> str:
if self.multiworld.random.randint(0, 100) < self.multiworld.minor_qty[self.player].value:
power_bombs = self.multiworld.power_bomb_qty[self.player].value
missiles = self.multiworld.missile_qty[self.player].value
super_missiles = self.multiworld.super_qty[self.player].value
roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles)
if roll <= power_bombs:
return "Power Bomb"
elif roll <= power_bombs + missiles:
return "Missile"
else:
return "Super Missile"
else:
return "Nothing"
def pre_fill(self): def pre_fill(self):
from Fill import fill_restrictive if len(self.NothingPool) > 0:
if len(self.prefilled_locked_items) > 0: nonChozoLoc = []
locations = [loc for loc in self.locations.values() if loc.item is None] chozoLoc = []
self.multiworld.random.shuffle(locations)
all_state = self.multiworld.get_all_state(False)
for item in self.ammoItems:
while (all_state.has(item.name, self.player, 1)):
all_state.remove(item)
fill_restrictive(self.multiworld, all_state, locations, self.prefilled_locked_items, True, True) for loc in self.locations.values():
if loc.item is None:
if locationsDict[loc.name].isChozo():
chozoLoc.append(loc)
else:
nonChozoLoc.append(loc)
self.multiworld.random.shuffle(nonChozoLoc)
self.multiworld.random.shuffle(chozoLoc)
missingCount = len(self.NothingPool) - len(nonChozoLoc)
locations = nonChozoLoc
if (missingCount > 0):
locations += chozoLoc[:missingCount]
locations = locations[:len(self.NothingPool)]
for item, loc in zip(self.NothingPool, locations):
loc.place_locked_item(item)
loc.address = loc.item.code = None
def post_fill(self):
self.itemLocs = [
ItemLocation(ItemManager.Items[itemLoc.item.type
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
'ArchipelagoItem'],
locationsDict[itemLoc.name], itemLoc.item.player, True)
for itemLoc in self.multiworld.get_locations(self.player)
]
self.progItemLocs = [
ItemLocation(ItemManager.Items[itemLoc.item.type
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
'ArchipelagoItem'],
locationsDict[itemLoc.name], itemLoc.item.player, True)
for itemLoc in self.multiworld.get_locations(self.player) if itemLoc.item.advancement
]
for itemLoc in self.itemLocs:
if itemLoc.Item.Class == "Boss":
itemLoc.Item.Class = "Minor"
for itemLoc in self.progItemLocs:
if itemLoc.Item.Class == "Boss":
itemLoc.Item.Class = "Minor"
localItemLocs = [il for il in self.itemLocs if il.player == self.player]
localprogItemLocs = [il for il in self.progItemLocs if il.player == self.player]
escapeTrigger = (localItemLocs, localprogItemLocs, 'Full') if self.variaRando.randoExec.randoSettings.restrictions["EscapeTrigger"] else None
escapeOk = self.variaRando.randoExec.graphBuilder.escapeGraph(self.variaRando.container, self.variaRando.randoExec.areaGraph, self.variaRando.randoExec.randoSettings.maxDiff, escapeTrigger)
assert escapeOk, "Could not find a solution for escape"
self.variaRando.doors = GraphUtils.getDoorConnections(self.variaRando.randoExec.areaGraph,
self.variaRando.args.area, self.variaRando.args.bosses,
self.variaRando.args.escapeRando)
self.variaRando.randoExec.postProcessItemLocs(self.itemLocs, self.variaRando.args.hideItems)
self.need_comeback_check = False
@classmethod
def stage_post_fill(cls, world):
new_state = CollectionState(world)
progitempool = []
for item in world.itempool:
if item.game == "Super Metroid" and item.advancement:
progitempool.append(item)
for item in progitempool:
new_state.collect(item, True)
bossesLoc = ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain']
for player in world.get_game_players("Super Metroid"):
for bossLoc in bossesLoc:
if not world.get_location(bossLoc, player).can_reach(new_state):
world.state.smbm[player].onlyBossLeft = True
break
def getWordArray(self, w: int) -> List[int]: def getWordArray(self, w: int) -> List[int]:
""" little-endian convert a 16-bit number to an array of numbers <= 255 each """ """ little-endian convert a 16-bit number to an array of numbers <= 255 each """
@ -669,115 +851,6 @@ class SMWorld(World):
return slot_data return slot_data
def collect(self, state: CollectionState, item: Item) -> bool:
state.smbm[self.player].addItem(item.type)
if item.location != None and item.location.game == self.game:
for entrance in self.multiworld.get_region(item.location.parent_region.name, item.location.player).entrances:
if (entrance.parent_region.can_reach(state)):
state.smbm[item.location.player].lastAP = entrance.parent_region.name
break
return super(SMWorld, self).collect(state, item)
def remove(self, state: CollectionState, item: Item) -> bool:
state.smbm[self.player].removeItem(item.type)
return super(SMWorld, self).remove(state, item)
def create_item(self, name: str) -> Item:
item = next(x for x in ItemManager.Items.values() if x.Name == name)
return SMItem(item.Name, ItemClassification.progression if item.Class != 'Minor' else ItemClassification.filler, item.Type, self.item_name_to_id[item.Name],
player=self.player)
def get_filler_item_name(self) -> str:
if self.multiworld.random.randint(0, 100) < self.multiworld.minor_qty[self.player].value:
power_bombs = self.multiworld.power_bomb_qty[self.player].value
missiles = self.multiworld.missile_qty[self.player].value
super_missiles = self.multiworld.super_qty[self.player].value
roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles)
if roll <= power_bombs:
return "Power Bomb"
elif roll <= power_bombs + missiles:
return "Missile"
else:
return "Super Missile"
else:
return "Nothing"
def pre_fill(self):
if len(self.NothingPool) > 0:
nonChozoLoc = []
chozoLoc = []
for loc in self.locations.values():
if loc.item is None:
if locationsDict[loc.name].isChozo():
chozoLoc.append(loc)
else:
nonChozoLoc.append(loc)
self.multiworld.random.shuffle(nonChozoLoc)
self.multiworld.random.shuffle(chozoLoc)
missingCount = len(self.NothingPool) - len(nonChozoLoc)
locations = nonChozoLoc
if (missingCount > 0):
locations += chozoLoc[:missingCount]
locations = locations[:len(self.NothingPool)]
for item, loc in zip(self.NothingPool, locations):
loc.place_locked_item(item)
loc.address = loc.item.code = None
def post_fill(self):
self.itemLocs = [
ItemLocation(ItemManager.Items[itemLoc.item.type
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
'ArchipelagoItem'],
locationsDict[itemLoc.name], itemLoc.item.player, True)
for itemLoc in self.multiworld.get_locations(self.player)
]
self.progItemLocs = [
ItemLocation(ItemManager.Items[itemLoc.item.type
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
'ArchipelagoItem'],
locationsDict[itemLoc.name], itemLoc.item.player, True)
for itemLoc in self.multiworld.get_locations(self.player) if itemLoc.item.advancement
]
for itemLoc in self.itemLocs:
if itemLoc.Item.Class == "Boss":
itemLoc.Item.Class = "Minor"
for itemLoc in self.progItemLocs:
if itemLoc.Item.Class == "Boss":
itemLoc.Item.Class = "Minor"
localItemLocs = [il for il in self.itemLocs if il.player == self.player]
localprogItemLocs = [il for il in self.progItemLocs if il.player == self.player]
escapeTrigger = (localItemLocs, localprogItemLocs, 'Full') if self.variaRando.randoExec.randoSettings.restrictions["EscapeTrigger"] else None
escapeOk = self.variaRando.randoExec.graphBuilder.escapeGraph(self.variaRando.container, self.variaRando.randoExec.areaGraph, self.variaRando.randoExec.randoSettings.maxDiff, escapeTrigger)
assert escapeOk, "Could not find a solution for escape"
self.variaRando.doors = GraphUtils.getDoorConnections(self.variaRando.randoExec.areaGraph,
self.variaRando.args.area, self.variaRando.args.bosses,
self.variaRando.args.escapeRando)
self.variaRando.randoExec.postProcessItemLocs(self.itemLocs, self.variaRando.args.hideItems)
@classmethod
def stage_post_fill(cls, world):
new_state = CollectionState(world)
progitempool = []
for item in world.itempool:
if item.game == "Super Metroid" and item.advancement:
progitempool.append(item)
for item in progitempool:
new_state.collect(item, True)
bossesLoc = ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain']
for player in world.get_game_players("Super Metroid"):
for bossLoc in bossesLoc:
if not world.get_location(bossLoc, player).can_reach(new_state):
world.state.smbm[player].onlyBossLeft = True
break
def write_spoiler(self, spoiler_handle: TextIO): def write_spoiler(self, spoiler_handle: TextIO):
if self.multiworld.area_randomization[self.player].value != 0: if self.multiworld.area_randomization[self.player].value != 0:
spoiler_handle.write('\n\nArea Transitions:\n\n') spoiler_handle.write('\n\nArea Transitions:\n\n')
@ -793,39 +866,18 @@ class SMWorld(World):
'<=>', '<=>',
dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss])) dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss]))
def create_locations(self, player: int):
for name in locationsDict:
self.locations[name] = SMLocation(player, name, self.location_name_to_id.get(name, None))
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
ret = Region(name, player, world)
if locations:
for loc in locations:
location = self.locations[loc]
location.parent_region = ret
ret.locations.append(location)
if exits:
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
return ret
class SMLocation(Location): class SMLocation(Location):
game: str = "Super Metroid" game: str = "Super Metroid"
def __init__(self, player: int, name: str, address=None, parent=None): def __init__(self, player: int, name: str, address=None, parent=None):
super(SMLocation, self).__init__(player, name, address, parent) super(SMLocation, self).__init__(player, name, address, parent)
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
return self.always_allow(state, item) or (self.item_rule(item) and (not check_access or self.can_reach(state)))
def can_reach(self, state: CollectionState) -> bool: def can_reach(self, state: CollectionState) -> bool:
# self.access_rule computes faster on average, so placing it first for faster abort # self.access_rule computes faster on average, so placing it first for faster abort
assert self.parent_region, "Can't reach location without region" assert self.parent_region, "Can't reach location without region"
return self.access_rule(state) and \ return super(SMLocation, self).can_reach(state) and \
self.parent_region.can_reach(state) and \ (not state.multiworld.worlds[self.player].need_comeback_check or \
self.can_comeback(state, self.item) self.can_comeback(state, self.item))
def can_comeback(self, state: CollectionState, item): def can_comeback(self, state: CollectionState, item):
randoExec = state.multiworld.worlds[self.player].variaRando.randoExec randoExec = state.multiworld.worlds[self.player].variaRando.randoExec