def onlyBossesLeft(self, ap, container): if self.settings.maxDiff == infinity: return False self.log.debug('onlyBossesLeft, diff=' + str(self.settings.maxDiff) + ", ap="+ap) sm = container.sm bossesLeft = container.getAllItemsInPoolFromCategory('Boss') if len(bossesLeft) == 0: return False def getLocList(): curLocs = self.currentLocations(ap, container) self.log.debug('onlyBossesLeft, curLocs=' + getLocListStr(curLocs)) return self.getPlacementLocs(ap, container, ComebackCheckType.JustComeback, None, curLocs) prevLocs = getLocList() self.log.debug("onlyBossesLeft. prevLocs="+getLocListStr(prevLocs)) # fake kill remaining bosses and see if we can access the rest of the game if self.cache is not None: self.cache.reset() for boss in bossesLeft: self.log.debug('onlyBossesLeft. kill '+boss.Name) sm.addItem(boss.Type) # get bosses locations and newly accessible locations (for bosses that open up locs) newLocs = getLocList() self.log.debug("onlyBossesLeft. newLocs="+getLocListStr(newLocs)) locs = newLocs + container.getLocs(lambda loc: loc.isBoss() and not loc in newLocs) self.log.debug("onlyBossesLeft. locs="+getLocListStr(locs)) ret = (len(locs) > len(prevLocs) and len(locs) == len(container.unusedLocations)) # restore bosses killed state for boss in bossesLeft: self.log.debug('onlyBossesLeft. revive '+boss.Name) sm.removeItem(boss.Type) if self.cache is not None: self.cache.reset() self.log.debug("onlyBossesLeft? " +str(ret)) return ret
def nextDecision(self, majorsAvailable, minorsAvailable, hasEnoughMinors, diffThreshold): # since solver split is forced to Full, majorsAvailable=minorsAvailable # we don't care about hasEnoughMinors, we're gonna pick enough anyway # we can ignore diffThreshold as well, we have self.maxDiff scavAvailable = [ loc for loc in majorsAvailable if loc in self.remainingScavLocs and loc.difficulty.difficulty <= self.maxDiff and loc.comeBack == True ] minorsAvailable = [ loc for loc in majorsAvailable if loc not in self.remainingScavLocs and loc.difficulty.difficulty <= self.maxDiff and loc.comeBack == True ] ret = None if len(minorsAvailable) > 0: nextMinor = random.choice(minorsAvailable) ret = self.collectMajor(nextMinor) elif len(scavAvailable) > 0: scavAvailable = self.filterScavAvailable(scavAvailable) self.log.debug("scavAvailable: " + getLocListStr(scavAvailable)) nextScav = self.chooseNextScavLoc(scavAvailable) self.pickupScav(nextScav) ret = self.collectMajor(nextScav) else: # fallback to base solver behaviour to handle comeback etc ret = super(ScavengerSolver, self).nextDecision( majorsAvailable, majorsAvailable, hasEnoughMinors, diffThreshold ) # not a typo, the args are the same, and we overwrote minorsAvailable if ret is not None and ret in self.remainingScavLocs: self.pickupScav(ret) if ret is not None: self.visited.append(ret) return ret
def prepareFirstPhase(self): self.changedKnows = {} # forces IceZebSkip if necessary to finish with 10-10-5 if Knows.IceZebSkip.bool == False or Knows.IceZebSkip.difficulty > self.maxDiff: self.changedKnows['IceZebSkip'] = Knows.IceZebSkip Knows.IceZebSkip = SMBool(True, 0, []) # hack knows to remove those > maxDiff for attr,k in Knows.__dict__.items(): if isKnows(attr) and k.bool == True and k.difficulty > self.maxDiff: self.log.debug("prepareFirstPhase. disabling knows "+attr) self.changedKnows[attr] = k setattr(Knows, attr, SMBool(False, 0)) # set max diff to god (for hard rooms/hellruns/bosses) self.settings.maxDiff = god # prepare 1st phase container itemCond = isChozoItem locCond = lambda loc: loc.isChozo() or loc.isBoss() # this will create a new smbm with new knows functions cont = self.baseContainer.slice(itemCond, locCond) secondPhaseItems = [item for item in self.baseContainer.itemPool if item not in cont.itemPool] contLocs = self.baseContainer.extractLocs(cont.unusedLocations) secondPhaseLocs = [loc for loc in self.baseContainer.unusedLocations if loc not in contLocs] self.log.debug("prepareFirstPhase. secondPhaseItems="+getItemListStr(secondPhaseItems)) self.log.debug("prepareFirstPhase. secondPhaseLocs="+getLocListStr(secondPhaseLocs)) self.secondPhaseContainer = ItemLocContainer(cont.sm, secondPhaseItems, secondPhaseLocs) return self.fillerFactory.createFirstPhaseFiller(cont)
def fill(locs, getPred): self.log.debug("fillRestrictedLocations. locs=" + getLocListStr(locs)) for loc in locs: loc.restricted = True itemLocation = ItemLocation(None, loc) if self.container.hasItemInPool(getPred('Nothing')): itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Nothing')) elif self.container.hasItemInPool(getPred('NoEnergy')): itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('NoEnergy')) elif self.container.countItems(getPred('Missile')) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Missile')) elif self.container.countItems(getPred('Super')) > 2: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Super')) elif self.container.countItems(getPred('PowerBomb')) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('PowerBomb')) elif self.container.countItems(getPred('Reserve')) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Reserve')) elif self.container.countItems(getPred('ETank')) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('ETank')) else: raise RuntimeError("Cannot fill restricted locations") self.log.debug("Fill: {} at {}".format( itemLocation.Item.Type, itemLocation.Location.Name)) self.container.collect(itemLocation, False)
def fillRestrictedLocations(self): def getPred(itemType, loc): return lambda item: (itemType is None or item.Type == itemType) and self.restrictions.canPlaceAtLocation(item, loc, self.container) locs = self.restrictedLocs self.log.debug("fillRestrictedLocations. locs="+getLocListStr(locs)) for loc in locs: itemLocation = ItemLocation(None, loc) if loc.BossItemType is not None: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred(loc.BossItemType, loc)) elif self.container.hasItemInPool(getPred('Nothing', loc)): itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Nothing', loc)) elif self.container.hasItemInPool(getPred('NoEnergy', loc)): itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('NoEnergy', loc)) elif self.container.countItems(getPred('Missile', loc)) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Missile', loc)) elif self.container.countItems(getPred('Super', loc)) > 2: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Super', loc)) elif self.container.countItems(getPred('PowerBomb', loc)) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('PowerBomb', loc)) elif self.container.countItems(getPred('Reserve', loc)) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Reserve', loc)) elif self.container.countItems(getPred('ETank', loc)) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('ETank', loc)) else: raise RuntimeError("Cannot fill restricted locations") self.log.debug("Fill: {}/{} at {}".format(itemLocation.Item.Type, itemLocation.Item.Class, itemLocation.Location.Name)) self.container.collect(itemLocation, False)
def lateMorphInit(self, ap, emptyContainer, services): assert self.isLateMorph() morph = emptyContainer.getNextItemInPool('Morph') assert morph is not None locs = services.possibleLocations(morph, ap, emptyContainer) self.log.debug('lateMorphInit. locs=' + getLocListStr(locs)) self.lateMorphLimit = len(locs) if len(set([loc['GraphArea'] for loc in locs])) > 1: self.lateMorphForbiddenArea = getAccessPoint(ap).GraphArea else: self.lateMorphForbiddenArea = None
def __init__(self, startAP, graph, restrictions, fullContainer, endDate=infinity): super(FillerScavenger, self).__init__(startAP, graph, restrictions, fullContainer, endDate) self.remainingScavLocs = restrictions.scavLocs[:] self.log.debug("FillerScavenger ctor. scavLocs=" + getLocListStr(self.remainingScavLocs))
def step(self): if len(self.conditions) > 1: self.determineParameters() curLocs = [] while self.firstPhaseIndex < len(self.firstPhaseItemLocs): self.cache.reset() newCurLocs = [loc for loc in self.currentLocations() if loc not in curLocs] curLocs += newCurLocs cond = self.nextMetCondition() if cond is not None: self.log.debug('step. cond item='+cond[0]) self.conditions.remove(cond) break itemLoc = self.firstPhaseItemLocs[self.firstPhaseIndex] self.collect(itemLoc, container=self.firstPhaseContainer) self.firstPhaseIndex += 1 self.log.debug('step. curLocs='+getLocListStr(curLocs)) restrictedItemTypes = [cond[0] for cond in self.conditions] self.log.debug('step. restrictedItemTypes='+str(restrictedItemTypes)) basePool = self.container.itemPool[:] itemPool = [] self.log.debug('step. basePool: {}'.format(getItemListStr(basePool))) while len(itemPool) < len(curLocs): item = random.choice(basePool) if item.Type not in restrictedItemTypes or\ random.random() < self.restrictedItemProba or\ self.restrictedItemProba == 0 and not any(item for item in basePool if item.Type not in restrictedItemTypes): itemPool.append(item) basePool.remove(item) self.log.debug('step. itemPool='+getItemListStr(itemPool)) cont = ItemLocContainer(self.container.sm, itemPool, curLocs) self.container.transferCollected(cont) filler = FillerRandomItems(self.ap, self.graph, self.restrictions, cont, self.endDate) (stuck, itemLocs, prog) = filler.generateItems() if stuck: if len(filler.errorMsg) > 0: self.errorMsg += '\n'+filler.errorMsg return False for itemLoc in itemLocs: if itemLoc.Location in self.container.unusedLocations: self.log.debug("step. POST COLLECT "+itemLoc.Item.Type+" at "+itemLoc.Location.Name) self.container.collect(itemLoc) else: # merge collected of 1st phase and 2nd phase so far for seed to be solvable by random fill self.container.itemLocations += self.firstPhaseItemLocs self.log.debug("step. LAST FILL. cont: "+self.container.dump()) filler = FillerRandomNoCopy(self.startAP, self.graph, self.restrictions, self.container, self.endDate, diffSteps=100) (stuck, itemLocs, prog) = filler.generateItems() if len(filler.errorMsg) > 0: self.errorMsg += '\n'+filler.errorMsg if stuck: return False return True
def possibleLocations(self, item, ap, emptyContainer, bossesKilled=True): assert len(emptyContainer.currentItems) == 0, "Invalid call to possibleLocations. emptyContainer had collected items" emptyContainer.sm.resetItems() self.log.debug('possibleLocations. item='+item.Type) if bossesKilled: itemLambda = lambda it: it.Type != item.Type else: itemLambda = lambda it: it.Type != item.Type and it.Category != 'Boss' allBut = emptyContainer.getItems(itemLambda) self.log.debug('possibleLocations. allBut='+getItemListStr(allBut)) emptyContainer.sm.addItems([it.Type for it in allBut]) ret = [loc for loc in self.currentLocations(ap, emptyContainer, post=True) if self.restrictions.canPlaceAtLocation(item, loc, emptyContainer)] self.log.debug('possibleLocations='+getLocListStr(ret)) emptyContainer.sm.resetItems() return ret
def initScavenger(self, endDate, vcr=None): attempts = 30 if self.restrictions.scavIsVanilla else 1 majorLocs = [loc for loc in self.container.unusedLocations if self.restrictions.isLocMajor(loc) and loc not in self.restrictedLocs and (not self.restrictions.scavIsVanilla or (loc.VanillaItemType not in self.forbiddenItems and self.container.getNextItemInPool(loc.VanillaItemType) is not None))] # if scav randomized and super funs we have to remove super fun items superFunCount = len(self.forbiddenItems) if not self.restrictions.scavIsVanilla and len(self.forbiddenItems) > 0 else 0 nLocs = min(self.settings.restrictions['ScavengerParams']['numLocs'], len(majorLocs) - superFunCount) self.log.debug("initScavenger. nLocs="+str(nLocs)) cont = None restr = None i = 0 def checkRestrictionsDict(r): # check if there are items impossible to place allTypes = [] okTypes = [] for area, entry in r.items(): for itemType, locs in entry.items(): if itemType not in allTypes: allTypes.append(itemType) if itemType not in okTypes and len(locs) > 0: okTypes.append(itemType) self.log.debug("checkRestrictionsDict. allTypes="+str(allTypes)) self.log.debug("checkRestrictionsDict. okTypes="+str(okTypes)) return len(okTypes) == len(allTypes) if len(majorLocs) > nLocs: while restr is None and i < attempts: random.shuffle(majorLocs) self.restrictions.setScavengerLocs(majorLocs[:nLocs]) self.log.debug("initScavenger. attempt "+str(i)+", scavLocs="+getLocListStr(self.restrictions.scavLocs)) r = self.getRestrictionsDict() if checkRestrictionsDict(r): restr = r i += 1 else: self.restrictions.setScavengerLocs(majorLocs) r = self.getRestrictionsDict() if checkRestrictionsDict(r): restr = r if restr is not None: self.log.debug("initScavenger. got list after "+str(i+1)+" attempts") # finally, actually do the randomization using a speedrun filler (stepLimit attempts heuristic) stepLimit = 50 self.restrictions.setPlacementRestrictions(restr) filler = FillerRandomSpeedrun(self.graphSettings, self.areaGraph, self.restrictions, self.container, endDate=endDate, diffSteps=stepLimit) stepCond = filler.createStepCountCondition(stepLimit) filler.generateItems(condition=lambda: filler.itemPoolCondition() and stepCond(), vcr=vcr) if not filler.itemPoolCondition(): cont = filler.container return cont
def lateMorphInit(self, ap, emptyContainer, services): assert self.isLateMorph() morph = emptyContainer.getNextItemInPool('Morph') assert morph is not None locs = services.possibleLocations(morph, ap, emptyContainer, bossesKilled=False) self.lateMorphLimit = len(locs) self.log.debug('lateMorphInit. {} locs: {}'.format(self.lateMorphLimit, getLocListStr(locs))) areas = {} for loc in locs: areas[loc.GraphArea] = areas.get(loc.GraphArea, 0) + 1 self.log.debug('lateMorphLimit. areas: {}'.format(areas)) if len(areas) > 1: self.lateMorphForbiddenArea = getAccessPoint(ap).GraphArea self.log.debug('lateMorphLimit. forbid start area: {}'.format(self.lateMorphForbiddenArea)) else: self.lateMorphForbiddenArea = None
def __init__(self, graphSettings, locations, services): self.sm = SMBoolManager() self.settings = services.settings self.graphSettings = graphSettings self.startAP = graphSettings.startAP self.superFun = self.settings.getSuperFun() self.container = None self.services = services self.restrictions = services.restrictions self.areaGraph = services.areaGraph self.allLocations = locations self.locations = self.areaGraph.getAccessibleLocations( locations, self.startAP) # print("nLocs Setup: "+str(len(self.locations))) self.itemManager = self.settings.getItemManager( self.sm, len(self.locations)) self.forbiddenItems = [] self.restrictedLocs = [] self.lastRestricted = [] self.bossesLocs = sorted( ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain']) self.suits = ['Varia', 'Gravity'] # organized by priority self.movementItems = [ 'SpaceJump', 'HiJump', 'SpeedBooster', 'Bomb', 'Grapple', 'SpringBall' ] # organized by priority self.combatItems = ['ScrewAttack', 'Plasma', 'Wave', 'Spazer'] # OMG self.bossChecks = { 'Kraid': self.sm.enoughStuffsKraid, 'Phantoon': self.sm.enoughStuffsPhantoon, 'Draygon': self.sm.enoughStuffsDraygon, 'Ridley': self.sm.enoughStuffsRidley, 'Mother Brain': self.sm.enoughStuffsMotherbrain } self.okay = lambda: SMBool(True, 0) exclude = self.settings.getExcludeItems(self.locations) # we have to use item manager only once, otherwise pool will change self.itemManager.createItemPool(exclude) self.basePool = self.itemManager.getItemPool()[:] self.log = utils.log.get('RandoSetup') if len(locations) != len(self.locations): self.log.debug("inaccessible locations :" + getLocListStr( [loc for loc in locations if loc not in self.locations]))
def getPossiblePlacements(self, ap, container, comebackCheck): curLocs = self.currentLocations(ap, container) self.log.debug('getPossiblePlacements. nCurLocs='+str(len(curLocs))) self.log.debug('getPossiblePlacements. curLocs='+getLocListStr(curLocs)) self.log.debug('getPossiblePlacements. comebackCheck='+str(comebackCheck)) sm = container.sm poolDict = container.getPoolDict() itemLocDict = {} possibleProg = False nonProgList = None def getLocList(itemObj): nonlocal curLocs return self.getPlacementLocs(ap, container, comebackCheck, itemObj, curLocs) def getNonProgLocList(): nonlocal nonProgList if nonProgList is None: nonProgList = [loc for loc in self.currentLocations(ap, container) if self.fullComebackCheck(container, ap, None, loc, comebackCheck)] self.log.debug("nonProgLocList="+str([loc.Name for loc in nonProgList])) return [loc for loc in nonProgList if self.restrictions.canPlaceAtLocation(itemObj, loc, container)] for itemType,items in sorted(poolDict.items()): itemObj = items[0] cont = True prog = False if self.isProgression(itemObj, ap, container): cont = False prog = True elif not possibleProg: cont = False if cont: # ignore non prog items if a prog item has already been found continue # check possible locations for this item type # self.log.debug('getPossiblePlacements. itemType=' + itemType + ', curLocs='+str([loc.Name for loc in curLocs])) locations = getLocList(itemObj) if prog else getNonProgLocList() if len(locations) == 0: continue if prog and not possibleProg: possibleProg = True itemLocDict = {} # forget all the crap ones we stored just in case # self.log.debug('getPossiblePlacements. itemType=' + itemType + ', locs='+str([loc.Name for loc in locations])) for item in items: itemLocDict[item] = locations self.processPlacementRestrictions(ap, container, comebackCheck, itemLocDict, curLocs) self.printItemLocDict(itemLocDict) self.log.debug('possibleProg='+str(possibleProg)) return (itemLocDict, possibleProg)
def filterScavAvailable(self, scavAvailable): # special case of Space Jump/Plasma : # # If both are available (i.e. they are both in the list, Dray is dead and Plasma is accessible), # and the only way out of draygon/precious we have is CF, we must pick up space before plasma. # Indeed, we cannot CF exit a second time, we need Draygon for that. # # This is kind of a global logic issue, but it only ever causes trouble in scavenger when pickup # order is forced in-game. Indeed in other cases, even if the solver or rando can get/place something # outside draygon's lair before space jump, the player can pick up space jump loc first anyway. if not any(loc.Name == "Space Jump" for loc in scavAvailable) or not any( loc.Name == "Plasma Beam" for loc in scavAvailable): return scavAvailable # check that Draygon CF is known k = self.smbm.knowsDraygonRoomCrystalFlash() if k.bool == False or k.difficulty > self.maxDiff: return scavAvailable self.log.debug("Space/Plasma special. Scav list: " + getLocListStr(scavAvailable)) # check that Draygon CF is required by removing it from known tech self.smbm.changeKnows("DraygonRoomCrystalFlash", SMBool(False)) # (check only AP path to remove current loc/AP constraints, # indeed we could have picked up any minor loc after draygon # and be anywhere) self.areaGraph.resetCache() path = self.areaGraph.accessPath(self.smbm, "Draygon Room Bottom", "Toilet Top", self.maxDiff) self.log.debug("Space/Plasma special. Path:" + str(path)) isRequired = (path is None) self.smbm.restoreKnows('DraygonRoomCrystalFlash') self.areaGraph.resetCache() if isRequired == True: self.log.debug("Space/Plasma special. Filtering out plasma") # filter out plasma until space jump is placed return [loc for loc in scavAvailable if loc.Name != 'Plasma Beam'] return scavAvailable
def getLocList(): curLocs = self.currentLocations(ap, container) self.log.debug('onlyBossesLeft, curLocs=' + getLocListStr(curLocs)) return self.getPlacementLocs(ap, container, ComebackCheckType.JustComeback, None, curLocs)
def getProgressionItemLocations(self): self.log.debug("Final Scavenger list: {}".format( getLocListStr(self.solver.scavOrder))) return [ self.container.getItemLoc(loc) for loc in self.solver.scavOrder ]
def setScavengerLocs(self, scavLocs): self.scavLocs = scavLocs self.log.debug("scavLocs=" + getLocListStr(scavLocs)) self.scavItemTypes = [loc.VanillaItemType for loc in scavLocs]