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
 | 
