def getAccessibleAccessPoints(self, rootNode='Landing Site'): rootAp = self.accessPoints[rootNode] inBossChk = lambda ap: ap.Boss and ap.Name.endswith("In") allAreas = {dst.GraphArea for (src, dst) in self.InterAreaTransitions if not inBossChk(dst) and not dst.isLoop()} self.log.debug("allAreas="+str(allAreas)) nonBossAPs = [ap for ap in self.getAvailableAccessPoints(rootAp, None, 0) if ap.GraphArea in allAreas] bossesAPs = [self.accessPoints[boss+'RoomIn'] for boss in Bosses.Golden4()] + [self.accessPoints['Draygon Room Bottom']] return nonBossAPs + bossesAPs
def getG4EscapeAccessPoints(n): return (n, [Bosses.accessPoints[boss] for boss in Bosses.Golden4()])
def checkPool(self, forbidden=None): self.log.debug("checkPool. forbidden=" + str(forbidden) + ", self.forbiddenItems=" + str(self.forbiddenItems)) if not self.graphSettings.isMinimizer( ) and not self.settings.isPlandoRando() and len( self.allLocations) > len(self.locations): # invalid graph with looped areas self.log.debug( "checkPool: not all areas are connected, but minimizer param is off / not a plando rando" ) return False ret = True if forbidden is not None: pool = self.getItemPool(forbidden) else: pool = self.getItemPool() # get restricted locs totalAvailLocs = [] comeBack = {} try: container = ItemLocContainer(self.sm, pool, self.locations) except AssertionError as e: # invalid graph altogether self.log.debug( "checkPool: AssertionError when creating ItemLocContainer: {}". format(e)) return False # restrict item pool in chozo: game should be finishable with chozo items only contPool = [] if self.restrictions.isChozo(): container.restrictItemPool(isChozoItem) missile = container.getNextItemInPool('Missile') if missile is not None: # add missile (if zeb skip not known) contPool.append(missile) contPool += [item for item in pool if item in container.itemPool] # give us everything and beat every boss to see what we can access self.disableBossChecks() self.sm.resetItems() self.sm.addItems([item.Type for item in contPool]) # will add bosses as well poolDict = container.getPoolDict() self.log.debug('pool={}'.format( sorted([(t, len(poolDict[t])) for t in poolDict]))) locs = self.services.currentLocations(self.startAP, container, post=True) self.areaGraph.useCache(True) for loc in locs: ap = loc.accessPoint if ap not in comeBack: # we chose Golden Four because it is always there. # Start APs might not have comeback transitions # possible start AP issues are handled in checkStart comeBack[ap] = self.areaGraph.canAccess( self.sm, ap, 'Golden Four', self.settings.maxDiff) if comeBack[ap]: totalAvailLocs.append(loc) self.areaGraph.useCache(False) self.lastRestricted = [ loc for loc in self.locations if loc not in totalAvailLocs ] self.log.debug("restricted=" + str([loc.Name for loc in self.lastRestricted])) # check if all inter-area APs can reach each other interAPs = [ ap for ap in self.areaGraph.getAccessibleAccessPoints(self.startAP) if not ap.isInternal() and not ap.isLoop() ] for startAp in interAPs: availAccessPoints = self.areaGraph.getAvailableAccessPoints( startAp, self.sm, self.settings.maxDiff) for ap in interAPs: if not ap in availAccessPoints: self.log.debug( "checkPool: ap {} non accessible from {}".format( ap.Name, startAp.Name)) ret = False if not ret: self.log.debug("checkPool. inter-area APs check failed") # cleanup self.sm.resetItems() self.restoreBossChecks() # check if we can reach/beat all bosses if ret: for loc in self.lastRestricted: if loc.Name in self.bossesLocs: ret = False self.log.debug("unavail Boss: " + loc.Name) if ret: # revive bosses self.sm.addItems([ item.Type for item in contPool if item.Category != 'Boss' ]) maxDiff = self.settings.maxDiff # see if phantoon doesn't block himself, and if we can reach draygon if she's alive ret = self.areaGraph.canAccess(self.sm, self.startAP, 'PhantoonRoomIn', maxDiff)\ and self.areaGraph.canAccess(self.sm, self.startAP, 'DraygonRoomIn', maxDiff) if ret: # see if we can beat bosses with this equipment (infinity as max diff for a "onlyBossesLeft" type check beatableBosses = sorted([ loc.Name for loc in self.services.currentLocations( self.startAP, container, diff=infinity) if loc.isBoss() ]) self.log.debug("checkPool. beatableBosses=" + str(beatableBosses)) ret = beatableBosses == Bosses.Golden4() if ret: # check that we can then kill mother brain self.sm.addItems(Bosses.Golden4()) beatableMotherBrain = [ loc.Name for loc in self.services.currentLocations( self.startAP, container, diff=infinity) if loc.Name == 'Mother Brain' ] ret = len(beatableMotherBrain) > 0 self.log.debug( "checkPool. beatable Mother Brain={}".format(ret)) else: self.log.debug('checkPool. locked by Phantoon or Draygon') self.log.debug('checkPool. boss access sanity check: ' + str(ret)) if self.restrictions.isChozo(): # last check for chozo locations: don't put more restricted chozo locations than removed chozo items # (we cannot rely on removing ammo/energy in fillRestrictedLocations since it is already the bare minimum in chozo pool) # FIXME something to do there for ultra sparse, it gives us up to 3 more spots for nothing items restrictedLocs = self.restrictedLocs + [ loc for loc in self.lastRestricted if loc not in self.restrictedLocs ] nRestrictedChozo = sum(1 for loc in restrictedLocs if loc.isChozo()) nNothingChozo = sum( 1 for item in pool if 'Chozo' in item.Class and item.Category == 'Nothing') ret &= nRestrictedChozo <= nNothingChozo self.log.debug('checkPool. nRestrictedChozo=' + str(nRestrictedChozo) + ', nNothingChozo=' + str(nNothingChozo)) self.log.debug('checkPool. result: ' + str(ret)) return ret
def computeDifficulty(self): # loop on the available locations depending on the collected items. # before getting a new item, loop on all of them and get their difficulty, # the next collected item is the one with the smallest difficulty, # if equality between major and minor, take major first. # remove mother brain location (there items pickup conditions on top of going to mother brain location) mbLoc = self.getLoc('Mother Brain') self.locations.remove(mbLoc) if self.majorsSplit == 'Major': self.majorLocations = [ loc for loc in self.locations if loc.isMajor() or loc.isBoss() ] self.minorLocations = [ loc for loc in self.locations if loc.isMinor() ] elif self.majorsSplit == 'Chozo': self.majorLocations = [ loc for loc in self.locations if loc.isChozo() or loc.isBoss() ] self.minorLocations = [ loc for loc in self.locations if not loc.isChozo() and not loc.isBoss() ] else: # Full self.majorLocations = self.locations[:] # copy self.minorLocations = self.majorLocations self.visitedLocations = [] self.collectedItems = [] self.log.debug( "{}: available major: {}, available minor: {}, visited: {}".format( Conf.itemsPickup, len(self.majorLocations), len(self.minorLocations), len(self.visitedLocations))) isEndPossible = False endDifficulty = mania diffThreshold = self.getDiffThreshold() while True: # actual while condition hasEnoughMinors = self.pickup.enoughMinors(self.smbm, self.minorLocations) hasEnoughMajors = self.pickup.enoughMajors(self.smbm, self.majorLocations) hasEnoughItems = hasEnoughMajors and hasEnoughMinors canEndGame = self.canEndGame() (isEndPossible, endDifficulty) = (canEndGame.bool, canEndGame.difficulty) if isEndPossible and hasEnoughItems and endDifficulty <= diffThreshold: if self.checkMB(mbLoc): self.log.debug("END") break else: self.log.debug("canEnd but MB loc not accessible") # check time limit if self.runtimeLimit_s > 0: if time.process_time() - self.startTime > self.runtimeLimit_s: self.log.debug("time limit exceeded ({})".format( self.runtimeLimit_s)) return (-1, False) self.log.debug("Current AP/Area: {}/{}".format( self.lastAP, self.lastArea)) # compute the difficulty of all the locations self.computeLocationsDifficulty(self.majorLocations) if self.majorsSplit != 'Full': self.computeLocationsDifficulty(self.minorLocations, phase="minor") # keep only the available locations majorsAvailable = [ loc for loc in self.majorLocations if loc.difficulty is not None and loc.difficulty.bool == True ] minorsAvailable = [ loc for loc in self.minorLocations if loc.difficulty is not None and loc.difficulty.bool == True ] if self.majorsSplit == 'Full': locs = majorsAvailable else: locs = majorsAvailable + minorsAvailable self.nbAvailLocs.append(len(locs)) # check if we're stuck if len(majorsAvailable) == 0 and len(minorsAvailable) == 0: if not isEndPossible: self.log.debug("STUCK MAJORS and MINORS") if self.comeBack.rewind(len(self.collectedItems)) == True: continue else: # we're really stucked self.log.debug("STUCK CAN'T REWIND") break else: self.log.debug("HARD END 2") self.checkMB(mbLoc) break # handle no comeback locations rewindRequired = self.comeBack.handleNoComeBack( locs, len(self.collectedItems)) if rewindRequired == True: if self.comeBack.rewind(len(self.collectedItems)) == True: continue else: # we're really stucked self.log.debug("STUCK CAN'T REWIND") break # sort them on difficulty and proximity self.log.debug("getAvailableItemsList majors") majorsAvailable = self.getAvailableItemsList( majorsAvailable, diffThreshold) if self.majorsSplit == 'Full': minorsAvailable = majorsAvailable else: self.log.debug("getAvailableItemsList minors") minorsAvailable = self.getAvailableItemsList( minorsAvailable, diffThreshold) # choose one to pick up self.nextDecision(majorsAvailable, minorsAvailable, hasEnoughMinors, diffThreshold) self.comeBack.cleanNoComeBack(locs) # compute difficulty value (difficulty, itemsOk) = self.computeDifficultyValue() if self.log.getEffectiveLevel() == logging.DEBUG: self.log.debug("difficulty={}".format(difficulty)) self.log.debug("itemsOk={}".format(itemsOk)) self.log.debug( "{}: remaining major: {}, remaining minor: {}, visited: {}". format(Conf.itemsPickup, len(self.majorLocations), len(self.minorLocations), len(self.visitedLocations))) self.log.debug("remaining majors:") for loc in self.majorLocations: self.log.debug("{} ({})".format(loc.Name, loc.itemName)) self.log.debug("bosses: {}".format([ (boss, Bosses.bossDead(self.smbm, boss)) for boss in Bosses.Golden4() ])) return (difficulty, itemsOk)
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): self._items = {} self._counts = {} # cache related self.cacheKey = 0 self.computeItemsPositions() Cache.reset() self.helpers = Logic.HelpersGraph(self) self.doorsManager = DoorsManager() self.createFacadeFunctions() self.createKnowsFunctions() self.resetItems() def computeItemsPositions(self): # compute index in cache key for each items self.itemsPositions = {} maxBitsForCountItem = 7 # 128 values with 7 bits for (i, item) in enumerate(self.countItems): pos = i * maxBitsForCountItem bitMask = (2 << (maxBitsForCountItem - 1)) - 1 bitMask = bitMask << pos self.itemsPositions[item] = (pos, bitMask) for (i, item) in enumerate(self.items, (i + 1) * maxBitsForCountItem + 1): if item in self.countItems: continue self.itemsPositions[item] = (i, 1 << i) def computeNewCacheKey(self, item, value): # generate an unique integer for each items combinations which is use as key in the cache. if item in ['Nothing', 'NoEnergy']: return (pos, bitMask) = self.itemsPositions[item] # print("--------------------- {} {} ----------------------------".format(item, value)) # print("old: "+format(self.cacheKey, '#067b')) self.cacheKey = (self.cacheKey & (~bitMask)) | (value << pos) # print("new: "+format(self.cacheKey, '#067b')) # self.printItemsInKey(self.cacheKey) def printItemsInKey(self, key): # for debug purpose print("key: " + format(key, '#067b')) msg = "" for (item, (pos, bitMask)) in self.itemsPositions.items(): value = (key & bitMask) >> pos if value != 0: msg += " {}: {}".format(item, value) print("items:{}".format(msg)) def isEmpty(self): for item in self.items: if self.haveItem(item): return False for item in self.countItems: if self.itemCount(item) > 0: return False return True def getItems(self): # get a dict of collected items and how many (to be displayed on the solver spoiler) itemsDict = {} for item in self.items: itemsDict[item] = 1 if self._items[item] == True else 0 for item in self.countItems: itemsDict[item] = self._counts[item] return itemsDict def withItem(self, item, func): self.addItem(item) ret = func(self) self.removeItem(item) return ret def resetItems(self): self._items = {item: smboolFalse for item in self.items} self._counts = {item: 0 for item in self.countItems} self.cacheKey = 0 Cache.update(self.cacheKey) def addItem(self, item): # a new item is available self._items[item] = SMBool(True, items=[item]) if self.isCountItem(item): count = self._counts[item] + 1 self._counts[item] = count self.computeNewCacheKey(item, count) else: self.computeNewCacheKey(item, 1) Cache.update(self.cacheKey) def addItems(self, items): if len(items) == 0: return for item in items: self._items[item] = SMBool(True, items=[item]) if self.isCountItem(item): count = self._counts[item] + 1 self._counts[item] = count self.computeNewCacheKey(item, count) else: self.computeNewCacheKey(item, 1) Cache.update(self.cacheKey) def removeItem(self, item): # randomizer removed an item (or the item was added to test a post available) if self.isCountItem(item): count = self._counts[item] - 1 self._counts[item] = count if count == 0: self._items[item] = smboolFalse self.computeNewCacheKey(item, count) else: self._items[item] = smboolFalse self.computeNewCacheKey(item, 0) Cache.update(self.cacheKey) def createFacadeFunctions(self): for fun in dir(self.helpers): if fun != 'smbm' and fun[0:2] != '__': setattr(self, fun, getattr(self.helpers, fun)) def traverse(self, doorName): return self.doorsManager.traverse(self, doorName) def createKnowsFunctions(self): # for each knows we have a function knowsKnows (ex: knowsAlcatrazEscape()) which # take no parameter for knows in Knows.__dict__: if isKnows(knows): setattr(self, 'knows' + knows, lambda knows=knows: SMBool(Knows.__dict__[knows].bool, Knows.__dict__[knows]. difficulty, knows=[knows])) def isCountItem(self, item): return item in self.countItems def itemCount(self, item): # return integer return self._counts[item] def haveItem(self, item): return self._items[item] wand = staticmethod(SMBool.wand) wandmax = staticmethod(SMBool.wandmax) wor = staticmethod(SMBool.wor) wnot = staticmethod(SMBool.wnot) def itemCountOk(self, item, count, difficulty=0): if self.itemCount(item) >= count: if item in ['ETank', 'Reserve']: item = str(count) + '-' + item return SMBool(True, difficulty, items=[item]) else: return smboolFalse def energyReserveCountOk(self, count, difficulty=0): if self.energyReserveCount() >= count: nEtank = self.itemCount('ETank') if nEtank > count: nEtank = int(count) items = str(nEtank) + '-ETank' nReserve = self.itemCount('Reserve') if nEtank < count: nReserve = int(count) - nEtank items += ' - ' + str(nReserve) + '-Reserve' return SMBool(True, difficulty, items=[items]) else: return smboolFalse
def computeDifficulty(self): # loop on the available locations depending on the collected items. # before getting a new item, loop on all of them and get their difficulty, # the next collected item is the one with the smallest difficulty, # if equality between major and minor, take major first. # remove mother brain location (there items pickup conditions on top of going to mother brain location) mbLoc = self.getLoc('Mother Brain') self.locations.remove(mbLoc) if self.majorsSplit == 'Major': self.majorLocations = [ loc for loc in self.locations if loc.isMajor() or loc.isBoss() ] self.minorLocations = [ loc for loc in self.locations if loc.isMinor() ] elif self.majorsSplit == 'Chozo': self.majorLocations = [ loc for loc in self.locations if loc.isChozo() or loc.isBoss() ] self.minorLocations = [ loc for loc in self.locations if not loc.isChozo() and not loc.isBoss() ] elif self.majorsSplit == 'Scavenger': self.majorLocations = [ loc for loc in self.locations if loc.isScavenger() or loc.isBoss() ] self.minorLocations = [ loc for loc in self.locations if not loc.isScavenger() and not loc.isBoss() ] else: # Full self.majorLocations = self.locations[:] # copy self.minorLocations = self.majorLocations self.visitedLocations = [] self.collectedItems = [] self.log.debug( "{}: available major: {}, available minor: {}, visited: {}".format( Conf.itemsPickup, len(self.majorLocations), len(self.minorLocations), len(self.visitedLocations))) isEndPossible = False endDifficulty = mania diffThreshold = self.getDiffThreshold() self.motherBrainKilled = False self.motherBrainCouldBeKilled = False while True: # actual while condition hasEnoughMinors = self.pickup.enoughMinors(self.smbm, self.minorLocations) hasEnoughMajors = self.pickup.enoughMajors(self.smbm, self.majorLocations) hasEnoughItems = hasEnoughMajors and hasEnoughMinors canEndGame = self.canEndGame() (isEndPossible, endDifficulty) = (canEndGame.bool, canEndGame.difficulty) if isEndPossible and hasEnoughItems: if not self.objectives.tourianRequired: if self.checkEscape(): self.log.debug( "checkMB: disabled, can escape to gunship, END") break else: self.log.debug( "checkMB: disabled, can't escape to gunship") else: if endDifficulty <= diffThreshold: if self.checkMB(mbLoc): self.log.debug( "checkMB: all end game checks are ok, END") break else: self.log.debug( "checkMB: canEnd but MB loc not accessible") else: if not self.motherBrainCouldBeKilled: self.motherBrainCouldBeKilled = self.checkMB( mbLoc, justCheck=True) self.log.debug( "checkMB: end checks ok except MB difficulty, MB could be killed: {}" .format(self.motherBrainCouldBeKilled)) # check time limit if self.runtimeLimit_s > 0: if time.process_time() - self.startTime > self.runtimeLimit_s: self.log.debug("time limit exceeded ({})".format( self.runtimeLimit_s)) return (-1, False) self.log.debug("Current AP/Area: {}/{}".format( self.lastAP, self.lastArea)) # compute the difficulty of all the locations self.computeLocationsDifficulty(self.majorLocations) if self.majorsSplit != 'Full': self.computeLocationsDifficulty(self.minorLocations, phase="minor") # keep only the available locations majorsAvailable = [ loc for loc in self.majorLocations if loc.difficulty is not None and loc.difficulty.bool == True ] minorsAvailable = [ loc for loc in self.minorLocations if loc.difficulty is not None and loc.difficulty.bool == True ] self.nbAvailLocs.append( len(self.getAllLocs(majorsAvailable, minorsAvailable))) # remove next scavenger locs before checking if we're stuck if self.majorsSplit == 'Scavenger': majorsAvailable = self.filterScavengerLocs(majorsAvailable) # check if we're stuck if len(majorsAvailable) == 0 and len(minorsAvailable) == 0: if not isEndPossible: self.log.debug("STUCK MAJORS and MINORS") if self.comeBack.rewind(len(self.collectedItems)) == True: continue else: # we're really stucked self.log.debug("STUCK CAN'T REWIND") break else: self.log.debug("HARD END 2") if self.checkMB(mbLoc): self.log.debug("all end game checks are ok, END") break else: self.log.debug( "We're stucked somewhere and can't reach mother brain" ) # check if we were able to access MB and kill it. # we do it before rollbacks to avoid endless rollbacks. if self.motherBrainCouldBeKilled: self.log.debug( "we're stucked but we could have killed MB before" ) # add MB loc for the spoiler log, remove its path as it's not the correct one # from when the loc was accessible mbLoc.path = [getAccessPoint('Golden Four')] self.visitedLocations.append(mbLoc) self.motherBrainKilled = True break else: # we're really stucked, try to rollback if self.comeBack.rewind(len( self.collectedItems)) == True: continue else: self.log.debug( "We could end but we're STUCK CAN'T REWIND" ) return (-1, False) # handle no comeback locations rewindRequired = self.comeBack.handleNoComeBack( self.getAllLocs(majorsAvailable, minorsAvailable), len(self.collectedItems)) if rewindRequired == True: if self.comeBack.rewind(len(self.collectedItems)) == True: continue else: # we're really stucked self.log.debug("STUCK CAN'T REWIND") break # sort them on difficulty and proximity self.log.debug("getAvailableItemsList majors") majorsAvailable = self.getAvailableItemsList( majorsAvailable, diffThreshold) if self.majorsSplit == 'Full': minorsAvailable = majorsAvailable else: self.log.debug("getAvailableItemsList minors") minorsAvailable = self.getAvailableItemsList( minorsAvailable, diffThreshold) # choose one to pick up self.nextDecision(majorsAvailable, minorsAvailable, hasEnoughMinors, diffThreshold) self.comeBack.cleanNoComeBack( self.getAllLocs(self.majorLocations, self.minorLocations)) # compute difficulty value (difficulty, itemsOk) = self.computeDifficultyValue() if self.log.getEffectiveLevel() == logging.DEBUG: self.log.debug("difficulty={}".format(difficulty)) self.log.debug("itemsOk={}".format(itemsOk)) self.log.debug( "{}: remaining major: {}, remaining minor: {}, visited: {}". format(Conf.itemsPickup, len(self.majorLocations), len(self.minorLocations), len(self.visitedLocations))) self.log.debug("remaining majors:") for loc in self.majorLocations: self.log.debug("{} ({})".format(loc.Name, loc.itemName)) self.log.debug("bosses: {}".format([ (boss, Bosses.bossDead(self.smbm, boss)) for boss in Bosses.Golden4() ])) return (difficulty, itemsOk)
def checkPool(self, forbidden=None): self.log.debug("checkPool. forbidden=" + str(forbidden) + ", self.forbiddenItems=" + str(self.forbiddenItems)) if not self.graphSettings.isMinimizer() and not self.settings.isPlandoRando() and len(self.allLocations) > len(self.locations): # invalid graph with looped areas self.log.debug("checkPool: not all areas are connected, but minimizer param is off / not a plando rando") return False ret = True if forbidden is not None: pool = self.getItemPool(forbidden) else: pool = self.getItemPool() # get restricted locs totalAvailLocs = [] comeBack = {} try: container = ItemLocContainer(self.sm, pool, self.locations) except AssertionError as e: # invalid graph altogether self.log.debug("checkPool: AssertionError when creating ItemLocContainer: {}".format(e)) return False # restrict item pool in chozo: game should be finishable with chozo items only contPool = [] if self.restrictions.isChozo(): container.restrictItemPool(isChozoItem) missile = container.getNextItemInPool('Missile') if missile is not None: # add missile (if zeb skip not known) contPool.append(missile) contPool += [item for item in pool if item in container.itemPool] # give us everything and beat every boss to see what we can access self.disableBossChecks() self.sm.resetItems() self.sm.addItems([item.Type for item in contPool]) # will add bosses as well self.log.debug('pool={}'.format(getItemListStr(container.itemPool))) locs = self.services.currentLocations(self.startAP, container, post=True) self.areaGraph.useCache(True) for loc in locs: ap = loc.accessPoint if ap not in comeBack: # we chose Golden Four because it is always there. # Start APs might not have comeback transitions # possible start AP issues are handled in checkStart comeBack[ap] = self.areaGraph.canAccess(self.sm, ap, 'Golden Four', self.settings.maxDiff) if comeBack[ap]: totalAvailLocs.append(loc) self.areaGraph.useCache(False) self.lastRestricted = [loc for loc in self.locations if loc not in totalAvailLocs] self.log.debug("restricted=" + str([loc.Name for loc in self.lastRestricted])) # check if objectives are compatible with accessible APs startAP = self.areaGraph.accessPoints[self.startAP] availAPs = [ap.Name for ap in self.areaGraph.getAvailableAccessPoints(startAP, self.sm, self.settings.maxDiff)] self.log.debug("availAPs="+str(availAPs)) for goal in Objectives.activeGoals: n, aps = goal.escapeAccessPoints if len(aps) == 0: continue escAPs = [ap for ap in aps if ap in availAPs] self.log.debug("escAPs="+str(escAPs)) if len(escAPs) < n: self.log.debug("checkPool. goal '"+goal.name+"' impossible to complete due to area layout") ret = False continue for ap in escAPs: if not self.areaGraph.canAccess(self.sm, ap, "Golden Four", self.settings.maxDiff): self.log.debug("checkPool. goal '"+goal.name+"' impossible to complete due to area layout") ret = False break # check if all inter-area APs can reach each other if ret: interAPs = [ap for ap in self.areaGraph.getAccessibleAccessPoints(self.startAP) if not ap.isInternal() and not ap.isLoop()] for startAp in interAPs: availAccessPoints = self.areaGraph.getAvailableAccessPoints(startAp, self.sm, self.settings.maxDiff) for ap in interAPs: if not ap in availAccessPoints: self.log.debug("checkPool: ap {} non accessible from {}".format(ap.Name, startAp.Name)) ret = False if not ret: self.log.debug("checkPool. inter-area APs check failed") # cleanup self.sm.resetItems() self.restoreBossChecks() # check if we can reach/beat all bosses if ret: # always add G4 to mandatory bosses, even if not required by objectives mandatoryBosses = set(Objectives.getMandatoryBosses() + Bosses.Golden4()) for loc in self.lastRestricted: if loc.Name in self.bossesLocs: ret = False self.log.debug("unavail Boss: " + loc.Name) if ret: # revive bosses self.sm.addItems([item.Type for item in contPool if item.Category != 'Boss']) maxDiff = self.settings.maxDiff # see if phantoon doesn't block himself, and if we can reach draygon if she's alive ret = self.areaGraph.canAccess(self.sm, self.startAP, 'PhantoonRoomIn', maxDiff)\ and self.areaGraph.canAccess(self.sm, self.startAP, 'DraygonRoomIn', maxDiff) if ret: # see if we can beat bosses with this equipment (infinity as max diff for a "onlyBossesLeft" type check beatableBosses = sorted([loc.BossItemType for loc in self.services.currentLocations(self.startAP, container, diff=infinity) if loc.isBoss()]) self.log.debug("checkPool. beatableBosses="+str(beatableBosses)) self.log.debug("checkPool. mandatoryBosses: {}".format(mandatoryBosses)) ret = mandatoryBosses.issubset(set(beatableBosses)) and Objectives.checkLimitObjectives(beatableBosses) if ret: # check that we can then kill mother brain self.sm.addItems(Bosses.Golden4() + Bosses.miniBosses()) beatableMotherBrain = [loc.Name for loc in self.services.currentLocations(self.startAP, container, diff=infinity) if loc.Name == 'Mother Brain'] ret = len(beatableMotherBrain) > 0 self.log.debug("checkPool. beatable Mother Brain={}".format(ret)) else: self.log.debug('checkPool. locked by Phantoon or Draygon') self.log.debug('checkPool. boss access sanity check: '+str(ret)) if self.restrictions.isChozo() or self.restrictions.isScavenger(): # in chozo or scavenger, we cannot put other items than NoEnergy in the restricted locations, # we would be forced to put majors in there, which can make seed generation fail: # don't put more restricted major locations than removed major items # FIXME something to do there for chozo/ultra sparse, it gives us up to 3 more spots for nothing items restrictedLocs = self.restrictedLocs + [loc for loc in self.lastRestricted if loc not in self.restrictedLocs] nRestrictedMajor = sum(1 for loc in restrictedLocs if self.restrictions.isLocMajor(loc)) nNothingMajor = sum(1 for item in pool if self.restrictions.isItemMajor(item) and item.Category == 'Nothing') ret &= nRestrictedMajor <= nNothingMajor self.log.debug('checkPool. nRestrictedMajor='+str(nRestrictedMajor)+', nNothingMajor='+str(nNothingMajor)) self.log.debug('checkPool. result: '+str(ret)) return ret