214 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			214 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import utils.log, random
							 | 
						||
| 
								 | 
							
								from utils.utils import getRangeDict, chooseFromRange
							 | 
						||
| 
								 | 
							
								from rando.ItemLocContainer import ItemLocation
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# helper object to choose item/loc
							 | 
						||
| 
								 | 
							
								class Choice(object):
							 | 
						||
| 
								 | 
							
								    def __init__(self, restrictions):
							 | 
						||
| 
								 | 
							
								        self.restrictions = restrictions
							 | 
						||
| 
								 | 
							
								        self.settings = restrictions.settings
							 | 
						||
| 
								 | 
							
								        self.log = utils.log.get("Choice")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # args are return from RandoServices.getPossiblePlacements
							 | 
						||
| 
								 | 
							
								    # return itemLoc dict, or None if no possible choice
							 | 
						||
| 
								 | 
							
								    def chooseItemLoc(self, itemLocDict, isProg):
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def getItemList(self, itemLocDict):
							 | 
						||
| 
								 | 
							
								        return sorted([item for item in itemLocDict.keys()], key=lambda item: item.Type)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def getLocList(self, itemLocDict, item):
							 | 
						||
| 
								 | 
							
								        return sorted(itemLocDict[item], key=lambda loc: loc.Name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# simple random choice, that chooses an item first, then a locatio to put it in
							 | 
						||
| 
								 | 
							
								class ItemThenLocChoice(Choice):
							 | 
						||
| 
								 | 
							
								    def __init__(self, restrictions):
							 | 
						||
| 
								 | 
							
								        super(ItemThenLocChoice, self).__init__(restrictions)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItemLoc(self, itemLocDict, isProg):
							 | 
						||
| 
								 | 
							
								        itemList = self.getItemList(itemLocDict)
							 | 
						||
| 
								 | 
							
								        item = self.chooseItem(itemList, isProg)
							 | 
						||
| 
								 | 
							
								        if item is None:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        locList = self.getLocList(itemLocDict, item)
							 | 
						||
| 
								 | 
							
								        loc = self.chooseLocation(locList, item, isProg)
							 | 
						||
| 
								 | 
							
								        if loc is None:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        return ItemLocation(item, loc)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItem(self, itemList, isProg):
							 | 
						||
| 
								 | 
							
								        if len(itemList) == 0:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        if isProg:
							 | 
						||
| 
								 | 
							
								            return self.chooseItemProg(itemList)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return self.chooseItemRandom(itemList)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItemProg(self, itemList):
							 | 
						||
| 
								 | 
							
								        return self.chooseItemRandom(itemList)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItemRandom(self, itemList):
							 | 
						||
| 
								 | 
							
								        return random.choice(itemList)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseLocation(self, locList, item, isProg):
							 | 
						||
| 
								 | 
							
								        if len(locList) == 0:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        if isProg:
							 | 
						||
| 
								 | 
							
								            return self.chooseLocationProg(locList, item)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return self.chooseLocationRandom(locList)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseLocationProg(self, locList, item):
							 | 
						||
| 
								 | 
							
								        return self.chooseLocationRandom(locList)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseLocationRandom(self, locList):
							 | 
						||
| 
								 | 
							
								        return random.choice(locList)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Choice specialization for prog speed based filler
							 | 
						||
| 
								 | 
							
								class ItemThenLocChoiceProgSpeed(ItemThenLocChoice):
							 | 
						||
| 
								 | 
							
								    def __init__(self, restrictions, progSpeedParams, distanceProp, services):
							 | 
						||
| 
								 | 
							
								        super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions)
							 | 
						||
| 
								 | 
							
								        self.progSpeedParams = progSpeedParams
							 | 
						||
| 
								 | 
							
								        self.distanceProp = distanceProp
							 | 
						||
| 
								 | 
							
								        self.services = services
							 | 
						||
| 
								 | 
							
								        self.chooseItemFuncs = {
							 | 
						||
| 
								 | 
							
								            'Random' : self.chooseItemRandom,
							 | 
						||
| 
								 | 
							
								            'MinProgression' : self.chooseItemMinProgression,
							 | 
						||
| 
								 | 
							
								            'MaxProgression' : self.chooseItemMaxProgression
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        self.chooseLocFuncs = {
							 | 
						||
| 
								 | 
							
								            'Random' : self.chooseLocationRandom,
							 | 
						||
| 
								 | 
							
								            'MinDiff' : self.chooseLocationMinDiff,
							 | 
						||
| 
								 | 
							
								            'MaxDiff' : self.chooseLocationMaxDiff
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def currentLocations(self, item=None):
							 | 
						||
| 
								 | 
							
								        return self.services.currentLocations(self.ap, self.container, item=item)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def processLateDoors(self, itemLocDict, ap, container):
							 | 
						||
| 
								 | 
							
								        doorBeams = self.restrictions.mandatoryBeams
							 | 
						||
| 
								 | 
							
								        def canOpenExtendedDoors(item):
							 | 
						||
| 
								 | 
							
								            return item.Category == 'Ammo' or item.Type in doorBeams
							 | 
						||
| 
								 | 
							
								        # exclude door items from itemLocDict
							 | 
						||
| 
								 | 
							
								        noDoorsLocDict = {item:locList for item,locList in itemLocDict.items() if not canOpenExtendedDoors(item) or container.sm.haveItem(item.Type)}
							 | 
						||
| 
								 | 
							
								        if len(noDoorsLocDict) > 0:
							 | 
						||
| 
								 | 
							
								            self.log.debug('processLateDoors. no doors')
							 | 
						||
| 
								 | 
							
								            itemLocDict.clear()
							 | 
						||
| 
								 | 
							
								            itemLocDict.update(noDoorsLocDict)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItemLoc(self, itemLocDict, isProg, progressionItemLocs, ap, container):
							 | 
						||
| 
								 | 
							
								        # if late morph, redo the late morph check if morph is the
							 | 
						||
| 
								 | 
							
								        # only possibility since we can rollback
							 | 
						||
| 
								 | 
							
								        canRollback = len(container.currentItems) > 0
							 | 
						||
| 
								 | 
							
								        if self.restrictions.isLateMorph() and canRollback and len(itemLocDict) == 1:
							 | 
						||
| 
								 | 
							
								            item, locList = list(itemLocDict.items())[0]
							 | 
						||
| 
								 | 
							
								            if item.Type == 'Morph':
							 | 
						||
| 
								 | 
							
								                morphLocs = self.restrictions.lateMorphCheck(container, locList)
							 | 
						||
| 
								 | 
							
								                if morphLocs is not None:
							 | 
						||
| 
								 | 
							
								                    itemLocDict[item] = morphLocs
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    return None
							 | 
						||
| 
								 | 
							
								        # if a boss is available, choose it right away
							 | 
						||
| 
								 | 
							
								        for item,locs in itemLocDict.items():
							 | 
						||
| 
								 | 
							
								            if item.Category == 'Boss':
							 | 
						||
| 
								 | 
							
								                assert len(locs) == 1 and locs[0].Name == item.Name
							 | 
						||
| 
								 | 
							
								                return ItemLocation(item, locs[0])
							 | 
						||
| 
								 | 
							
								        # late doors check for random door colors
							 | 
						||
| 
								 | 
							
								        if self.restrictions.isLateDoors() and random.random() < self.lateDoorsProb:
							 | 
						||
| 
								 | 
							
								            self.processLateDoors(itemLocDict, ap, container)
							 | 
						||
| 
								 | 
							
								        self.progressionItemLocs = progressionItemLocs
							 | 
						||
| 
								 | 
							
								        self.ap = ap
							 | 
						||
| 
								 | 
							
								        self.container = container
							 | 
						||
| 
								 | 
							
								        return super(ItemThenLocChoiceProgSpeed, self).chooseItemLoc(itemLocDict, isProg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def determineParameters(self, progSpeed=None, progDiff=None):
							 | 
						||
| 
								 | 
							
								        self.chooseLocRanges = getRangeDict(self.getChooseLocs(progDiff))
							 | 
						||
| 
								 | 
							
								        self.chooseItemRanges = getRangeDict(self.getChooseItems(progSpeed))
							 | 
						||
| 
								 | 
							
								        self.spreadProb = self.progSpeedParams.getSpreadFactor(progSpeed)
							 | 
						||
| 
								 | 
							
								        self.lateDoorsProb = self.progSpeedParams.getLateDoorsProb(progSpeed)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def getChooseLocs(self, progDiff=None):
							 | 
						||
| 
								 | 
							
								        if progDiff is None:
							 | 
						||
| 
								 | 
							
								            progDiff = self.settings.progDiff
							 | 
						||
| 
								 | 
							
								        return self.progSpeedParams.getChooseLocDict(progDiff)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def getChooseItems(self, progSpeed):
							 | 
						||
| 
								 | 
							
								        if progSpeed is None:
							 | 
						||
| 
								 | 
							
								            progSpeed = self.settings.progSpeed
							 | 
						||
| 
								 | 
							
								        return self.progSpeedParams.getChooseItemDict(progSpeed)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItemProg(self, itemList):
							 | 
						||
| 
								 | 
							
								        ret = self.getChooseFunc(self.chooseItemRanges, self.chooseItemFuncs)(itemList)
							 | 
						||
| 
								 | 
							
								        self.log.debug('chooseItemProg. ret='+ret.Type)
							 | 
						||
| 
								 | 
							
								        return ret
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseLocationProg(self, locs, item):
							 | 
						||
| 
								 | 
							
								        locs = self.getLocsSpreadProgression(locs)
							 | 
						||
| 
								 | 
							
								        random.shuffle(locs)
							 | 
						||
| 
								 | 
							
								        ret = self.getChooseFunc(self.chooseLocRanges, self.chooseLocFuncs)(locs)
							 | 
						||
| 
								 | 
							
								        self.log.debug('chooseLocationProg. ret='+ret.Name)
							 | 
						||
| 
								 | 
							
								        return ret
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # get choose function from a weighted dict
							 | 
						||
| 
								 | 
							
								    def getChooseFunc(self, rangeDict, funcDict):
							 | 
						||
| 
								 | 
							
								        v = chooseFromRange(rangeDict)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return funcDict[v]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItemMinProgression(self, items):
							 | 
						||
| 
								 | 
							
								        minNewLocs = 1000
							 | 
						||
| 
								 | 
							
								        ret = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for item in items:
							 | 
						||
| 
								 | 
							
								            newLocs = len(self.currentLocations(item))
							 | 
						||
| 
								 | 
							
								            if newLocs < minNewLocs:
							 | 
						||
| 
								 | 
							
								                minNewLocs = newLocs
							 | 
						||
| 
								 | 
							
								                ret = item
							 | 
						||
| 
								 | 
							
								        return ret
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseItemMaxProgression(self, items):
							 | 
						||
| 
								 | 
							
								        maxNewLocs = 0
							 | 
						||
| 
								 | 
							
								        ret = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for item in items:
							 | 
						||
| 
								 | 
							
								            newLocs = len(self.currentLocations(item))
							 | 
						||
| 
								 | 
							
								            if newLocs > maxNewLocs:
							 | 
						||
| 
								 | 
							
								                maxNewLocs = newLocs
							 | 
						||
| 
								 | 
							
								                ret = item
							 | 
						||
| 
								 | 
							
								        return ret
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseLocationMaxDiff(self, availableLocations):
							 | 
						||
| 
								 | 
							
								        self.log.debug("MAX")
							 | 
						||
| 
								 | 
							
								        self.log.debug("chooseLocationMaxDiff: {}".format([(l.Name, l.difficulty) for l in availableLocations]))
							 | 
						||
| 
								 | 
							
								        return max(availableLocations, key=lambda loc:loc.difficulty.difficulty)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def chooseLocationMinDiff(self, availableLocations):
							 | 
						||
| 
								 | 
							
								        self.log.debug("MIN")
							 | 
						||
| 
								 | 
							
								        self.log.debug("chooseLocationMinDiff: {}".format([(l.Name, l.difficulty) for l in availableLocations]))
							 | 
						||
| 
								 | 
							
								        return min(availableLocations, key=lambda loc:loc.difficulty.difficulty)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def areaDistance(self, loc, otherLocs):
							 | 
						||
| 
								 | 
							
								        areas = [getattr(l, self.distanceProp) for l in otherLocs]
							 | 
						||
| 
								 | 
							
								        cnt = areas.count(getattr(loc, self.distanceProp))
							 | 
						||
| 
								 | 
							
								        d = None
							 | 
						||
| 
								 | 
							
								        if cnt == 0:
							 | 
						||
| 
								 | 
							
								            d = 2
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            d = 1.0/cnt
							 | 
						||
| 
								 | 
							
								        return d
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def getLocsSpreadProgression(self, availableLocations):
							 | 
						||
| 
								 | 
							
								        split = self.restrictions.split
							 | 
						||
| 
								 | 
							
								        cond = lambda item: ((split == 'Full' and item.Class == 'Major') or split == item.Class) and item.Category != "Energy"
							 | 
						||
| 
								 | 
							
								        progLocs = [il.Location for il in self.progressionItemLocs if cond(il.Item)]
							 | 
						||
| 
								 | 
							
								        distances = [self.areaDistance(loc, progLocs) for loc in availableLocations]
							 | 
						||
| 
								 | 
							
								        maxDist = max(distances)
							 | 
						||
| 
								 | 
							
								        locs = []
							 | 
						||
| 
								 | 
							
								        for i in range(len(availableLocations)):
							 | 
						||
| 
								 | 
							
								            loc = availableLocations[i]
							 | 
						||
| 
								 | 
							
								            d = distances[i]
							 | 
						||
| 
								 | 
							
								            if d == maxDist or random.random() >= self.spreadProb:
							 | 
						||
| 
								 | 
							
								                locs.append(loc)
							 | 
						||
| 
								 | 
							
								        return locs
							 |