import utils.log, random, copy from graph.graph_utils import GraphUtils, vanillaTransitions, vanillaBossesTransitions, escapeSource, escapeTargets from logic.logic import Logic from graph.graph import AccessGraphRando as AccessGraph # creates graph and handles randomized escape class GraphBuilder(object): def __init__(self, graphSettings): self.graphSettings = graphSettings self.areaRando = graphSettings.areaRando self.bossRando = graphSettings.bossRando self.escapeRando = graphSettings.escapeRando self.minimizerN = graphSettings.minimizerN self.log = utils.log.get('GraphBuilder') # builds everything but escape transitions def createGraph(self): transitions = self.graphSettings.plandoRandoTransitions if transitions is None: transitions = [] if self.minimizerN is not None: transitions = GraphUtils.createMinimizerTransitions(self.graphSettings.startAP, self.minimizerN) else: if not self.bossRando: transitions += vanillaBossesTransitions else: transitions += GraphUtils.createBossesTransitions() if not self.areaRando: transitions += vanillaTransitions else: transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando) return AccessGraph(Logic.accessPoints, transitions, self.graphSettings.dotFile) # fills in escape transitions if escape rando is enabled # scavEscape = None or (itemLocs, scavItemLocs) couple from filler def escapeGraph(self, container, graph, maxDiff, scavEscape): if not self.escapeRando: return True emptyContainer = copy.copy(container) emptyContainer.resetCollected(reassignItemLocs=True) dst = None if scavEscape is None: possibleTargets, dst, path = self.getPossibleEscapeTargets(emptyContainer, graph, maxDiff) # update graph with escape transition graph.addTransition(escapeSource, dst) else: possibleTargets, path = self.getScavengerEscape(emptyContainer, graph, maxDiff, scavEscape) if path is None: return False # get timer value self.escapeTimer(graph, path, self.areaRando or scavEscape is not None) self.log.debug("escapeGraph: ({}, {}) timer: {}".format(escapeSource, dst, graph.EscapeAttributes['Timer'])) # animals GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst) return True def _getTargets(self, sm, graph, maxDiff): possibleTargets = [target for target in escapeTargets if graph.accessPath(sm, target, 'Landing Site', maxDiff) is not None] self.log.debug('_getTargets. targets='+str(possibleTargets)) # failsafe if len(possibleTargets) == 0: self.log.debug("Can't randomize escape, fallback to vanilla") possibleTargets.append('Climb Bottom Left') random.shuffle(possibleTargets) return possibleTargets def getPossibleEscapeTargets(self, emptyContainer, graph, maxDiff): sm = emptyContainer.sm # setup smbm with item pool # Ice not usable because of hyper beam # remove energy to avoid hell runs # (will add bosses as well) sm.addItems([item.Type for item in emptyContainer.itemPool if item.Type != 'Ice' and item.Category != 'Energy']) sm.addItem('Hyper') possibleTargets = self._getTargets(sm, graph, maxDiff) # pick one dst = possibleTargets.pop() path = graph.accessPath(sm, dst, 'Landing Site', maxDiff) return (possibleTargets, dst, path) def getScavengerEscape(self, emptyContainer, graph, maxDiff, scavEscape): sm = emptyContainer.sm itemLocs, lastScavItemLoc = scavEscape[0], scavEscape[1][-1] # collect all item/locations up until last scav for il in itemLocs: emptyContainer.collect(il) if il == lastScavItemLoc: break possibleTargets = self._getTargets(sm, graph, maxDiff) path = graph.accessPath(sm, lastScavItemLoc.Location.accessPoint, 'Landing Site', maxDiff) return (possibleTargets, path) # path: as returned by AccessGraph.accessPath def escapeTimer(self, graph, path, compute): if compute == True: if path[0].Name == 'Climb Bottom Left': graph.EscapeAttributes['Timer'] = None return traversedAreas = list(set([ap.GraphArea for ap in path])) self.log.debug("escapeTimer path: " + str([ap.Name for ap in path])) self.log.debug("escapeTimer traversedAreas: " + str(traversedAreas)) # rough estimates of navigation within areas to reach "borders" # (can obviously be completely off wrt to actual path, but on the generous side) traversals = { 'Crateria':90, 'GreenPinkBrinstar':90, 'WreckedShip':120, 'LowerNorfair':135, 'WestMaridia':75, 'EastMaridia':100, 'RedBrinstar':75, 'Norfair': 120, 'Kraid': 40, 'Crocomire': 40, # can't be on the path 'Tourian': 0, } t = 90 if self.areaRando else 0 for area in traversedAreas: t += traversals[area] t = max(t, 180) else: escapeTargetsTimer = { 'Climb Bottom Left': None, # vanilla 'Green Brinstar Main Shaft Top Left': 210, # brinstar 'Basement Left': 210, # wrecked ship 'Business Center Mid Left': 270, # norfair 'Crab Hole Bottom Right': 270 # maridia } t = escapeTargetsTimer[path[0].Name] self.log.debug("escapeTimer. t="+str(t)) graph.EscapeAttributes['Timer'] = t