135 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			135 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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
							 |