diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index c7f41092..f8f71f33 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -39,7 +39,7 @@ class SMCollectionState(metaclass=AutoLogicRegister): # for unit tests where MultiWorld is instantiated before worlds if hasattr(parent, "state"): self.smbm = {player: SMBoolManager(player, parent.state.smbm[player].maxDiff, - parent.state.smbm[player].onlyBossLeft) for player in + parent.state.smbm[player].onlyBossLeft, parent.state.smbm[player].lastAP) for player in parent.get_game_players("Super Metroid")} for player, group in parent.groups.items(): if (group["game"] == "Super Metroid"): @@ -116,7 +116,7 @@ class SMWorld(World): Logic.factory('vanilla') self.variaRando = VariaRandomizer(self.multiworld, get_base_rom_path(), self.player) - self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty) + self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty, lastAP = self.variaRando.args.startLocation) # keeps Nothing items local so no player will ever pickup Nothing # doing so reduces contribution of this world to the Multiworld the more Nothing there is though @@ -628,6 +628,11 @@ class SMWorld(World): def collect(self, state: CollectionState, item: Item) -> bool: state.smbm[self.player].addItem(item.type) + if (item.location != None and item.location.player == self.player): + for entrance in self.multiworld.get_region(item.location.parent_region.name, self.player).entrances: + if (entrance.parent_region.can_reach(state)): + state.smbm[self.player].lastAP = entrance.parent_region.name + break return super(SMWorld, self).collect(state, item) def remove(self, state: CollectionState, item: Item) -> bool: @@ -736,18 +741,34 @@ class SMLocation(Location): 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) and self.can_comeback(state, item)))) + 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: + # self.access_rule computes faster on average, so placing it first for faster abort + assert self.parent_region, "Can't reach location without region" + return self.access_rule(state) and self.parent_region.can_reach(state) and self.can_comeback(state) + + def can_comeback(self, state: CollectionState): + # some specific early/late game checks + if self.name == 'Bomb' or self.name == 'Mother Brain': + return True - def can_comeback(self, state: CollectionState, item: Item): randoExec = state.multiworld.worlds[self.player].variaRando.randoExec + n = 2 if GraphUtils.isStandardStart(randoExec.graphSettings.startAP) else 3 + # is early game + if (len([loc for loc in state.locations_checked if loc.player == self.player]) <= n): + return True + for key in locationsDict[self.name].AccessFrom.keys(): - if (randoExec.areaGraph.canAccessList( state.smbm[self.player], - key, - [randoExec.graphSettings.startAP, 'Landing Site'] if not GraphUtils.isStandardStart(randoExec.graphSettings.startAP) else ['Landing Site'], - state.smbm[self.player].maxDiff)): + smbm = state.smbm[self.player] + if (randoExec.areaGraph.canAccess( smbm, + smbm.lastAP, + key, + smbm.maxDiff, + None)): return True return False - + class SMItem(Item): game = "Super Metroid" diff --git a/worlds/sm/variaRandomizer/graph/graph.py b/worlds/sm/variaRandomizer/graph/graph.py index 6ca7465a..bcbf1381 100644 --- a/worlds/sm/variaRandomizer/graph/graph.py +++ b/worlds/sm/variaRandomizer/graph/graph.py @@ -367,22 +367,6 @@ class AccessGraph(object): #print("canAccess: {}".format(can)) return can - # test access from an access point to a list of others, given an optional item - def canAccessList(self, smbm, srcAccessPointName, destAccessPointNameList, maxDiff, item=None): - if item is not None: - smbm.addItem(item) - #print("canAccess: item: {}, src: {}, dest: {}".format(item, srcAccessPointName, destAccessPointName)) - destAccessPointList = [self.accessPoints[destAccessPointName] for destAccessPointName in destAccessPointNameList] - srcAccessPoint = self.accessPoints[srcAccessPointName] - availAccessPoints = self.getAvailableAccessPoints(srcAccessPoint, smbm, maxDiff, item) - can = any(ap in availAccessPoints for ap in destAccessPointList) - # if not can: - # self.log.debug("canAccess KO: avail = {}".format([ap.Name for ap in availAccessPoints.keys()])) - if item is not None: - smbm.removeItem(item) - #print("canAccess: {}".format(can)) - return can - # returns a list of AccessPoint instances from srcAccessPointName to destAccessPointName # (not including source ap) # or None if no possible path diff --git a/worlds/sm/variaRandomizer/logic/smboolmanager.py b/worlds/sm/variaRandomizer/logic/smboolmanager.py index 342faed8..c09455fd 100644 --- a/worlds/sm/variaRandomizer/logic/smboolmanager.py +++ b/worlds/sm/variaRandomizer/logic/smboolmanager.py @@ -13,7 +13,7 @@ class SMBoolManager(object): items = ['ETank', 'Missile', 'Super', 'PowerBomb', 'Bomb', 'Charge', 'Ice', 'HiJump', 'SpeedBooster', 'Wave', 'Spazer', 'SpringBall', 'Varia', 'Plasma', 'Grapple', 'Morph', 'Reserve', 'Gravity', 'XRayScope', 'SpaceJump', 'ScrewAttack', 'Nothing', 'NoEnergy', 'MotherBrain', 'Hyper'] + Bosses.Golden4() countItems = ['Missile', 'Super', 'PowerBomb', 'ETank', 'Reserve'] - def __init__(self, player=0, maxDiff=sys.maxsize, onlyBossLeft = False): + def __init__(self, player=0, maxDiff=sys.maxsize, onlyBossLeft = False, lastAP = 'Landing Site'): self._items = { } self._counts = { } @@ -21,6 +21,8 @@ class SMBoolManager(object): self.maxDiff = maxDiff self.onlyBossLeft = onlyBossLeft + self.lastAP = lastAP + # cache related self.cacheKey = 0 self.computeItemsPositions()