def iterate(self, stateJson, scope, action, params): self.debug = params["debug"] self.smbm = SMBoolManager() state = SolverState() state.fromJson(stateJson) state.toSolver(self) self.loadPreset(self.presetFileName) # add already collected items to smbm self.smbm.addItems(self.collectedItems) if scope == 'item': if action == 'clear': self.clearItems(True) else: if action == 'add': if self.mode == 'plando' or self.mode == 'seedless': if params['loc'] != None: if self.mode == 'plando': self.setItemAt(params['loc'], params['item'], params['hide']) else: self.setItemAt(params['loc'], params.get('item', 'Nothing'), False) else: self.increaseItem(params['item']) else: # pickup item at locName self.pickItemAt(params['loc']) elif action == 'remove': if 'loc' in params: self.removeItemAt(params['loc']) elif 'count' in params: # remove last collected item self.cancelLastItems(params['count']) else: self.decreaseItem(params['item']) elif action == 'replace': self.replaceItemAt(params['loc'], params['item'], params['hide']) elif action == 'toggle': self.toggleItem(params['item']) elif scope == 'area': if action == 'clear': self.clearTransitions() else: if action == 'add': startPoint = params['startPoint'] endPoint = params['endPoint'] self.addTransition(self.transWeb2Internal[startPoint], self.transWeb2Internal[endPoint]) elif action == 'remove': if 'startPoint' in params: self.cancelTransition( self.transWeb2Internal[params['startPoint']]) else: # remove last transition self.cancelLastTransition() elif scope == 'door': if action == 'replace': doorName = params['doorName'] newColor = params['newColor'] DoorsManager.doors[doorName].setColor(newColor) elif action == 'toggle': doorName = params['doorName'] DoorsManager.switchVisibility(doorName) self.areaGraph = AccessGraph(accessPoints, self.curGraphTransitions) if scope == 'common': if action == 'save': return self.savePlando(params['lock'], params['escapeTimer']) elif action == 'randomize': self.randoPlando(params) # if last loc added was a sequence break, recompute its difficulty, # as it may be available with the newly placed item. if len(self.visitedLocations) > 0: lastVisited = self.visitedLocations[-1] if lastVisited.difficulty.difficulty == -1: self.visitedLocations.remove(lastVisited) self.majorLocations.append(lastVisited) else: lastVisited = None else: lastVisited = None # compute new available locations self.clearLocs(self.majorLocations) self.computeLocationsDifficulty(self.majorLocations) # put back last visited location if lastVisited != None: self.majorLocations.remove(lastVisited) self.visitedLocations.append(lastVisited) if lastVisited.difficulty == False: # if the loc is still sequence break, put it back as sequence break lastVisited.difficulty = SMBool(True, -1) # return them self.dumpState()
# V + G + bomb: 3 energy # 0 + 0 + bomb: dead with 13 energy # V + 0 + bomb: 6 energy # 0 + G + bomb: 3 energy # V + G + 3 PB: 2 energy # from etank to landing site: # V + G + bomb: 4 energy # V + G + 5 PB: 3 energy # V + 0 + 5 PB: 7 energy # lambda sm: sm.wor(sm.canEnterAndLeaveGauntlet(), # sm.wand(sm.canShortCharge(), # sm.canEnterAndLeaveGauntletQty(1, 0)), # thanks ponk! https://youtu.be/jil5zTBCF1s # sm.canDoLowGauntlet()) #) locationsDict["Bomb"].AccessFrom = { 'Landing Site': lambda sm: SMBool(True) } locationsDict["Bomb"].Available = ( lambda sm: sm.wand(sm.haveItem('Morph'), sm.traverse('FlywayRight')) ) locationsDict["Energy Tank, Terminator"].AccessFrom = { 'Landing Site': lambda sm: sm.canPassTerminatorBombWall(), 'Lower Mushrooms Left': lambda sm: sm.canPassCrateriaGreenPirates(), # 'Gauntlet Top': lambda sm: sm.haveItem('Morph') } locationsDict["Energy Tank, Terminator"].Available = ( lambda sm: SMBool(True) ) locationsDict["Reserve Tank, Brinstar"].AccessFrom = { 'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
def canPassTerminatorBombWall(self, fromLandingSite=True): sm = self.smbm return sm.wor(sm.wand(sm.haveItem('SpeedBooster'), sm.wor(SMBool(not fromLandingSite, 0), sm.knowsSimpleShortCharge(), sm.knowsShortCharge())), sm.canDestroyBombWalls())
def escapeTrigger(self, emptyContainer, graph, maxDiff, escapeTrigger): container = emptyContainer sm = container.sm allItemLocs,progItemLocs,split = escapeTrigger[0],escapeTrigger[1],escapeTrigger[2] # check if crateria is connected, if not replace Tourian # connection with Climb and add special escape patch to Climb if not any(il.Location.GraphArea == "Crateria" for il in allItemLocs): escapeAttr = graph.EscapeAttributes if "patches" not in escapeAttr: escapeAttr['patches'] = [] escapeAttr['patches'] += ['climb_disable_bomb_blocks.ips', "Climb_Asleep"] src, _ = next(t for t in graph.InterAreaTransitions if t[1].Name == "Golden Four") graph.removeTransitions("Golden Four") graph.addTransition(src.Name, "Climb Bottom Left") # disconnect the other side of G4 graph.addTransition("Golden Four", "Golden Four") # remove vanilla escape transition graph.addTransition('Tourian Escape Room 4 Top Right', 'Tourian Escape Room 4 Top Right') # filter garbage itemLocs ilCheck = lambda il: not il.Location.isBoss() and not il.Location.restricted and il.Item.Category != "Nothing" # update item% objectives accessibleItems = [il.Item for il in allItemLocs if ilCheck(il)] majorUpgrades = [item.Type for item in accessibleItems if item.BeamBits != 0 or item.ItemBits != 0] sm.objectives.setItemPercentFuncs(len(accessibleItems), majorUpgrades) if split == "Scavenger": # update escape access for scav with last scav loc lastScavItemLoc = progItemLocs[-1] sm.objectives.updateScavengerEscapeAccess(lastScavItemLoc.Location.accessPoint) sm.objectives.setScavengerHuntFunc(lambda sm, ap: sm.haveItem(lastScavItemLoc.Item.Type)) else: # update "collect all items in areas" funcs availLocsByArea=defaultdict(list) for itemLoc in allItemLocs: if ilCheck(itemLoc) and (split.startswith("Full") or itemLoc.Location.isClass(split)): availLocsByArea[itemLoc.Location.GraphArea].append(itemLoc.Location.Name) self.log.debug("escapeTrigger. availLocsByArea="+str(availLocsByArea)) sm.objectives.setAreaFuncs({area:lambda sm,ap:SMBool(len(container.getLocs(lambda loc: loc.Name in availLocsByArea[area]))==0) for area in availLocsByArea}) self.log.debug("escapeTrigger. collect locs until G4 access") # collect all item/locations up until we can pass G4 (the escape triggers) itemLocs = allItemLocs[:] ap = "Landing Site" # dummy value it'll be overwritten at first collection while len(itemLocs) > 0 and not (sm.canPassG4() and graph.canAccess(sm, ap, "Landing Site", maxDiff)): il = itemLocs.pop(0) if il.Location.restricted: continue self.log.debug("collecting " + getItemLocStr(il)) container.collect(il) ap = il.Location.accessPoint # final update of item% obj collectedLocsAccessPoints = {il.Location.accessPoint for il in container.itemLocations} sm.objectives.updateItemPercentEscapeAccess(list(collectedLocsAccessPoints)) possibleTargets = self._getTargets(sm, graph, maxDiff) # try to escape from all the possible objectives APs possiblePaths = [] for goal in Objectives.activeGoals: n, possibleAccessPoints = goal.escapeAccessPoints count = 0 for ap in possibleAccessPoints: self.log.debug("escapeTrigger. testing AP " + ap) path = graph.accessPath(sm, ap, 'Landing Site', maxDiff) if path is not None: self.log.debug("escapeTrigger. add path from "+ap) possiblePaths.append(path) count += 1 if count < n: # there is a goal we cannot escape from self.log.debug("escapeTrigger. goal %s: found %d/%d possible escapes, abort" % (goal.name, count, n)) return (None, None) # try and get a path from all possible areas self.log.debug("escapeTrigger. completing paths") allAreas = {il.Location.GraphArea for il in allItemLocs if not il.Location.restricted and not il.Location.GraphArea in ["Tourian", "Ceres"]} def getStartArea(path): return path[0].GraphArea def apCheck(ap): nonlocal graph, possiblePaths apObj = graph.accessPoints[ap] return apObj.GraphArea not in [getStartArea(path) for path in possiblePaths] escapeAPs = [ap for ap in collectedLocsAccessPoints if apCheck(ap)] for ap in escapeAPs: path = graph.accessPath(sm, ap, 'Landing Site', maxDiff) if path is not None: self.log.debug("escapeTrigger. add path from "+ap) possiblePaths.append(path) def areaPathCheck(): nonlocal allAreas, possiblePaths startAreas = {getStartArea(path) for path in possiblePaths} return len(allAreas - startAreas) == 0 while not areaPathCheck() and len(itemLocs) > 0: il = itemLocs.pop(0) if il.Location.restricted: continue self.log.debug("collecting " + getItemLocStr(il)) container.collect(il) ap = il.Location.accessPoint if apCheck(ap): path = graph.accessPath(sm, ap, 'Landing Site', maxDiff) if path is not None: self.log.debug("escapeTrigger. add path from "+ap) possiblePaths.append(path) return (possibleTargets, possiblePaths)
def canClearGoals(smbm, ap): result = SMBool(True) for goal in Objectives.activeGoals: result = smbm.wand(result, goal.canClearGoal(smbm, ap)) return result
def isVanillaDraygon(self): return SMBool(self.getDraygonConnection() == 'DraygonRoomIn')
def canExitPreciousRoomVanilla(self): return SMBool(True) # handled by canExitDraygonVanilla
def computeLocDiff(self, tdiff, diff, pdiff): allDiff = SMBool.wandmax(tdiff, diff, pdiff) return (allDiff, None)
from logic.helpers import Bosses from utils.parameters import Settings from rom.rom_patches import RomPatches from logic.smbool import SMBool from graph.location import locationsDict locationsDict["Energy Tank, Gauntlet"].AccessFrom = { 'Landing Site': lambda sm: SMBool(True) } locationsDict["Energy Tank, Gauntlet"].Available = ( lambda sm: sm.wor( sm.canEnterAndLeaveGauntlet(), sm.wand(sm.canShortCharge(), sm.canEnterAndLeaveGauntletQty(1, 0) ), # thanks ponk! https://youtu.be/jil5zTBCF1s sm.canDoLowGauntlet())) locationsDict["Bomb"].AccessFrom = {'Landing Site': lambda sm: SMBool(True)} locationsDict["Bomb"].Available = ( lambda sm: sm.wand(sm.haveItem('Morph'), sm.traverse('FlywayRight'))) locationsDict["Bomb"].PostAvailable = ( lambda sm: sm.wor(sm.knowsAlcatrazEscape(), sm.canPassBombPassages())) locationsDict["Energy Tank, Terminator"].AccessFrom = { 'Landing Site': lambda sm: sm.canPassTerminatorBombWall(), 'Lower Mushrooms Left': lambda sm: sm.canPassCrateriaGreenPirates(), 'Gauntlet Top': lambda sm: sm.haveItem('Morph') } locationsDict["Energy Tank, Terminator"].Available = (lambda sm: SMBool(True)) locationsDict["Reserve Tank, Brinstar"].AccessFrom = { 'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight')) }
def xBossesDead(smbm, target): count = sum([1 for boss in Bosses.Golden4() if Bosses.bossDead(smbm, boss)]) return SMBool(count >= target)
def xMiniBossesDead(smbm, target): count = sum([1 for miniboss in Bosses.miniBosses() if Bosses.bossDead(smbm, miniboss)]) return SMBool(count >= target)
def haveItems(self, items): for item in items: if not self.haveItem(item): return smboolFalse return SMBool(True)
def _setKnowsFunction(self, knows, k): setattr(self, 'knows' + knows, lambda: SMBool(k.bool, k.difficulty, knows=[knows]))
def hasItemsPercent(self, percent, totalItemsCount=None): if totalItemsCount is None: totalItemsCount = self.objectives.getTotalItemsCount() currentItemsCount = self.getCollectedItemsCount() return SMBool(100 * (currentItemsCount / totalItemsCount) >= percent)
def randoPlando(self, parameters): # if all the locations are visited, do nothing if len(self.majorLocations) == 0: return plandoLocsItems = {} for loc in self.visitedLocations: plandoLocsItems[loc.Name] = loc.itemName plandoCurrent = { "locsItems": plandoLocsItems, "transitions": self.curGraphTransitions, "patches": RomPatches.ActivePatches, "doors": DoorsManager.serialize() } plandoCurrentJson = json.dumps(plandoCurrent) pythonExec = "python{}.{}".format(sys.version_info.major, sys.version_info.minor) params = [ pythonExec, os.path.expanduser("~/RandomMetroidSolver/randomizer.py"), '--runtime', '10', '--param', self.presetFileName, '--output', self.outputFileName, '--plandoRando', plandoCurrentJson, '--progressionSpeed', 'speedrun', '--minorQty', parameters["minorQty"], '--maxDifficulty', 'hardcore', '--energyQty', parameters["energyQty"], '--startAP', self.startAP ] import subprocess subprocess.call(params) with open(self.outputFileName, 'r') as jsonFile: data = json.load(jsonFile) self.errorMsg = data["errorMsg"] # load the locations if "itemLocs" in data: self.clearItems(reload=True) itemsLocs = data["itemLocs"] # create a copy because we need self.locations to be full, else the state will be empty self.majorLocations = self.locations[:] for itemLoc in itemsLocs: locName = itemLoc["Location"]["Name"] loc = self.getLoc(locName) # we can have locations from non connected areas if "difficulty" in itemLoc["Location"]: difficulty = itemLoc["Location"]["difficulty"] smbool = SMBool(difficulty["bool"], difficulty["difficulty"], difficulty["knows"], difficulty["items"]) loc.difficulty = smbool itemName = itemLoc["Item"]["Type"] if itemName == "Boss": itemName = "Nothing" loc.itemName = itemName loc.accessPoint = itemLoc["Location"]["accessPoint"] self.collectMajor(loc)
"kill spore spawn", "kill botwoon", "kill crocomire", "kill golden torizo", "kill one miniboss", "kill two minibosses", "kill three minibosses" ] }, items=["SporeSpawn", "Botwoon", "Crocomire", "GoldenTorizo"], text="{} all mini bosses", expandableList=[ "kill spore spawn", "kill botwoon", "kill crocomire", "kill golden torizo" ], category="Minibosses"), Goal( "finish scavenger hunt", "other", lambda sm, ap: SMBool(True), "scavenger_hunt_completed", exclusion={"list": []}, # will be auto-completed available=False), Goal("nothing", "other", lambda sm, ap: Objectives.canAccess(sm, ap, "Landing Site"), "nothing_objective", escapeAccessPoints=(1, ["Landing Site"]) ), # with no objectives at all, escape auto triggers only in crateria Goal("collect 25% items", "items", lambda sm, ap: SMBool(True), "collect_25_items", exclusion={ "list":
def canPassZebetites(self): sm = self.smbm return sm.wor(sm.wand(sm.haveItem('Ice'), sm.knowsIceZebSkip()), sm.wand(sm.haveItem('SpeedBooster'), sm.knowsSpeedZebSkip()), # account for one zebetite, refill may be necessary SMBool(self.canInflictEnoughDamages(1100, charge=False, givesDrops=False, ignoreSupers=True)[0] >= 1, 0))
def canAccess(sm, src, dst): return SMBool( Objectives.graph.canAccess(sm, src, dst, Objectives.maxDiff))
def isVanillaCroc(self): crocRoom = getAccessPoint('Crocomire Room Top') return SMBool(crocRoom.ConnectedTo == 'Crocomire Speedway Bottom')
def canAccessLocation(sm, ap, locName): loc = locationsDict[locName] availLocs = Objectives.graph.getAvailableLocations([loc], sm, Objectives.maxDiff, ap) return SMBool(loc in availLocs)
def has(patch): return SMBool(patch in RomPatches.ActivePatches)
def has(player, patch): return SMBool(patch in RomPatches.ActivePatches[player])