from ..utils.utils import randGaussBounds, getRangeDict, chooseFromRange from ..utils import log import logging, copy, random class Item: __slots__ = ( 'Category', 'Class', 'Name', 'Code', 'Type', 'BeamBits', 'ItemBits', 'Id' ) def __init__(self, Category, Class, Name, Type, Code=None, BeamBits=0, ItemBits=0, Id=None): self.Category = Category self.Class = Class self.Code = Code self.Name = Name self.Type = Type self.BeamBits = BeamBits self.ItemBits = ItemBits self.Id = Id def withClass(self, Class): return Item(self.Category, Class, self.Name, self.Type, self.Code, self.BeamBits, self.ItemBits) def __eq__(self, other): # used to remove an item from a list return self.Type == other.Type and self.Class == other.Class def __hash__(self): # as we define __eq__ we have to also define __hash__ to use items as dictionnary keys # https://docs.python.org/3/reference/datamodel.html#object.__hash__ return id(self) def __repr__(self): return "Item({}, {}, {}, {}, {})".format(self.Category, self.Class, self.Code, self.Name, self.Type) def json(self): # as we have slots instead of dict return {key : getattr(self, key, None) for key in self.__slots__} class ItemManager: Items = { 'ETank': Item( Category='Energy', Class='Major', Code=0xf870, Name="Energy Tank", Type='ETank', Id=0 ), 'Missile': Item( Category='Ammo', Class='Minor', Code=0xf870, Name="Missile", Type='Missile', Id=1 ), 'Super': Item( Category='Ammo', Class='Minor', Code=0xf870, Name="Super Missile", Type='Super', Id=2 ), 'PowerBomb': Item( Category='Ammo', Class='Minor', Code=0xf870, Name="Power Bomb", Type='PowerBomb', Id=3 ), 'Bomb': Item( Category='Progression', Class='Major', Code=0xf870, Name="Bomb", Type='Bomb', ItemBits=0x1000, Id=4 ), 'Charge': Item( Category='Beam', Class='Major', Code=0xf870, Name="Charge Beam", Type='Charge', BeamBits=0x1000, Id=5 ), 'Ice': Item( Category='Progression', Class='Major', Code=0xf870, Name="Ice Beam", Type='Ice', BeamBits=0x2, Id=6 ), 'HiJump': Item( Category='Progression', Class='Major', Code=0xf870, Name="Hi-Jump Boots", Type='HiJump', ItemBits=0x100, Id=7 ), 'SpeedBooster': Item( Category='Progression', Class='Major', Code=0xf870, Name="Speed Booster", Type='SpeedBooster', ItemBits=0x2000, Id=8 ), 'Wave': Item( Category='Beam', Class='Major', Code=0xf870, Name="Wave Beam", Type='Wave', BeamBits=0x1, Id=9 ), 'Spazer': Item( Category='Beam', Class='Major', Code=0xf870, Name="Spazer", Type='Spazer', BeamBits=0x4, Id=10 ), 'SpringBall': Item( Category='Misc', Class='Major', Code=0xf870, Name="Spring Ball", Type='SpringBall', ItemBits=0x2, Id=11 ), 'Varia': Item( Category='Progression', Class='Major', Code=0xf870, Name="Varia Suit", Type='Varia', ItemBits=0x1, Id=12 ), 'Plasma': Item( Category='Beam', Class='Major', Code=0xf870, Name="Plasma Beam", Type='Plasma', BeamBits=0x8, Id=15 ), 'Grapple': Item( Category='Progression', Class='Major', Code=0xf870, Name="Grappling Beam", Type='Grapple', ItemBits=0x4000, Id=16 ), 'Morph': Item( Category='Progression', Class='Major', Code=0xf870, Name="Morph Ball", Type='Morph', ItemBits=0x4, Id=19 ), 'Reserve': Item( Category='Energy', Class='Major', Code=0xf870, Name="Reserve Tank", Type='Reserve', Id=20 ), 'Gravity': Item( Category='Progression', Class='Major', Code=0xf870, Name="Gravity Suit", Type='Gravity', ItemBits=0x20, Id=13 ), 'XRayScope': Item( Category='Misc', Class='Major', Code=0xf870, Name="X-Ray Scope", Type='XRayScope', ItemBits=0x8000, Id=14 ), 'SpaceJump': Item( Category='Progression', Class='Major', Code=0xf870, Name="Space Jump", Type='SpaceJump', ItemBits=0x200, Id=17 ), 'ScrewAttack': Item( Category='Misc', Class='Major', Code=0xf870, Name="Screw Attack", Type='ScrewAttack', ItemBits= 0x8, Id=18 ), 'Nothing': Item( Category='Nothing', Class='Minor', Code=0xbae9, # new nothing plm Name="Nothing", Type='Nothing', Id=22 ), 'NoEnergy': Item( Category='Nothing', Class='Major', Code=0xbae9, # see above Name="No Energy", Type='NoEnergy', Id=23 ), 'Kraid': Item( Category='Boss', Class='Boss', Name="Kraid", Type='Kraid', ), 'Phantoon': Item( Category='Boss', Class='Boss', Name="Phantoon", Type='Phantoon' ), 'Draygon': Item( Category='Boss', Class='Boss', Name="Draygon", Type='Draygon', ), 'Ridley': Item( Category='Boss', Class='Boss', Name="Ridley", Type='Ridley', ), 'MotherBrain': Item( Category='Boss', Class='Boss', Name="Mother Brain", Type='MotherBrain', ), # used only during escape path check 'Hyper': Item( Category='Beam', Class='Major', Code=0xffff, Name="Hyper Beam", Type='Hyper', ), 'ArchipelagoItem': Item( Category='ArchipelagoItem', Class='Major', Code=0xf870, Name="Generic", Type='ArchipelagoItem', Id=21 ) } for itemType, item in Items.items(): if item.Type != itemType: raise RuntimeError("Wrong item type for {} (expected {})".format(item, itemType)) @staticmethod def isBeam(item): return item.BeamBits != 0 @staticmethod def getItemTypeCode(item, itemVisibility): if item.Category == 'Nothing': if itemVisibility in ['Visible', 'Chozo']: modifier = 0 elif itemVisibility == 'Hidden': modifier = 4 else: if itemVisibility == 'Visible': modifier = 0 elif itemVisibility == 'Chozo': modifier = 4 elif itemVisibility == 'Hidden': modifier = 8 itemCode = item.Code + modifier return itemCode def __init__(self, majorsSplit, qty, sm, nLocs, maxDiff): self.qty = qty self.sm = sm self.majorsSplit = majorsSplit self.nLocs = nLocs self.maxDiff = maxDiff self.majorClass = 'Chozo' if majorsSplit == 'Chozo' else 'Major' self.itemPool = [] def newItemPool(self, addBosses=True): self.itemPool = [] if addBosses == True: # for the bosses for boss in ['Kraid', 'Phantoon', 'Draygon', 'Ridley', 'MotherBrain']: self.addMinor(boss) def getItemPool(self): return self.itemPool def setItemPool(self, pool): self.itemPool = pool def addItem(self, itemType, itemClass=None): self.itemPool.append(ItemManager.getItem(itemType, itemClass)) def addMinor(self, minorType): self.addItem(minorType, 'Minor') # remove from pool an item of given type. item type has to be in original Items list. def removeItem(self, itemType): for idx, item in enumerate(self.itemPool): if item.Type == itemType: self.itemPool = self.itemPool[0:idx] + self.itemPool[idx+1:] return item def removeForbiddenItems(self, forbiddenItems): # the pool is the one managed by the Randomizer for itemType in forbiddenItems: self.removeItem(itemType) self.addItem('NoEnergy', self.majorClass) return self.itemPool @staticmethod def getItem(itemType, itemClass=None): if itemClass is None: return copy.copy(ItemManager.Items[itemType]) else: return ItemManager.Items[itemType].withClass(itemClass) def createItemPool(self, exclude=None): itemPoolGenerator = ItemPoolGenerator.factory(self.majorsSplit, self, self.qty, self.sm, exclude, self.nLocs, self.maxDiff) self.itemPool = itemPoolGenerator.getItemPool() @staticmethod def getProgTypes(): return [item for item in ItemManager.Items if ItemManager.Items[item].Category == 'Progression'] def hasItemInPoolCount(self, itemName, count): return len([item for item in self.itemPool if item.Type == itemName]) >= count class ItemPoolGenerator(object): @staticmethod def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff): if majorsSplit == 'Chozo': return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff) elif majorsSplit == 'Plando': return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff) elif nLocs == 105: if majorsSplit == "Scavenger": return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff) else: return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff) else: return ItemPoolGeneratorMinimizer(itemManager, qty, sm, nLocs, maxDiff) def __init__(self, itemManager, qty, sm, maxDiff): self.itemManager = itemManager self.qty = qty self.sm = sm self.maxItems = 105 # 100 item locs and 5 bosses self.maxEnergy = 18 # 14E, 4R self.maxDiff = maxDiff self.log = log.get('ItemPool') def isUltraSparseNoTanks(self): # if low stuff botwoon is not known there is a hard energy req of one tank, even # with both suits lowStuffBotwoon = self.sm.knowsLowStuffBotwoon() return random.random() < 0.5 and (lowStuffBotwoon.bool == True and lowStuffBotwoon.difficulty <= self.maxDiff) def calcMaxMinors(self): pool = self.itemManager.getItemPool() energy = [item for item in pool if item.Category == 'Energy'] if len(energy) == 0: self.maxMinors = 0.66*(self.maxItems - 5) # 5 for bosses else: # if energy has been placed, we can be as accurate as possible self.maxMinors = self.maxItems - len(pool) + self.nbMinorsAlready def calcMaxAmmo(self): self.nbMinorsAlready = 5 # always add enough minors to pass zebetites (1100 damages) and mother brain 1 (3000 damages) # accounting for missile refill. so 15-10, or 10-10 if ice zeb skip is known (Ice is always in item pool) zebSkip = self.sm.knowsIceZebSkip() if zebSkip.bool == False or zebSkip.difficulty > self.maxDiff: self.log.debug("Add missile because ice zeb skip is not known") self.itemManager.addMinor('Missile') self.nbMinorsAlready += 1 self.calcMaxMinors() self.log.debug("maxMinors: "+str(self.maxMinors)) self.minorLocations = max(0, self.maxMinors*self.qty['minors']/100.0 - self.nbMinorsAlready) self.log.debug("minorLocations: {}".format(self.minorLocations)) # add ammo given quantity settings def addAmmo(self): self.calcMaxAmmo() # we have to remove the minors already added maxItems = min(len(self.itemManager.getItemPool()) + int(self.minorLocations), self.maxItems) self.log.debug("maxItems: {}, (self.maxItems={})".format(maxItems, self.maxItems)) ammoQty = self.qty['ammo'] if not self.qty['strictMinors']: rangeDict = getRangeDict(ammoQty) self.log.debug("rangeDict: {}".format(rangeDict)) while len(self.itemManager.getItemPool()) < maxItems: item = chooseFromRange(rangeDict) self.itemManager.addMinor(item) else: minorsTypes = ['Missile', 'Super', 'PowerBomb'] totalProps = sum(ammoQty[m] for m in minorsTypes) minorsByProp = sorted(minorsTypes, key=lambda m: ammoQty[m]) totalMinorLocations = self.minorLocations + self.nbMinorsAlready self.log.debug("totalMinorLocations: {}".format(totalMinorLocations)) def ammoCount(ammo): return float(len([item for item in self.itemManager.getItemPool() if item.Type == ammo])) def targetRatio(ammo): return round(float(ammoQty[ammo])/totalProps, 3) def cmpRatio(ammo, ratio): thisAmmo = ammoCount(ammo) thisRatio = round(thisAmmo/totalMinorLocations, 3) nextRatio = round((thisAmmo + 1)/totalMinorLocations, 3) self.log.debug("{} current, next/target ratio: {}, {}/{}".format(ammo, thisRatio, nextRatio, ratio)) return abs(nextRatio - ratio) < abs(thisRatio - ratio) def fillAmmoType(ammo, checkRatio=True): ratio = targetRatio(ammo) self.log.debug("{}: target ratio: {}".format(ammo, ratio)) while len(self.itemManager.getItemPool()) < maxItems and (not checkRatio or cmpRatio(ammo, ratio)): self.log.debug("Add {}".format(ammo)) self.itemManager.addMinor(ammo) for m in minorsByProp: fillAmmoType(m) # now that the ratios have been matched as exactly as possible, we distribute the error def getError(m, countOffset=0): return abs((ammoCount(m)+countOffset)/totalMinorLocations - targetRatio(m)) while len(self.itemManager.getItemPool()) < maxItems: minNextError = 1000 chosenAmmo = None for m in minorsByProp: nextError = getError(m, 1) if nextError < minNextError: minNextError = nextError chosenAmmo = m self.itemManager.addMinor(chosenAmmo) # fill up the rest with blank items for i in range(self.maxItems - maxItems): self.itemManager.addMinor('Nothing') class ItemPoolGeneratorChozo(ItemPoolGenerator): def addEnergy(self): total = 18 energyQty = self.qty['energy'] if energyQty == 'ultra sparse': # 0-1, remove reserve tank and two etanks, check if it also remove the last etank self.itemManager.removeItem('Reserve') self.itemManager.addItem('NoEnergy', 'Chozo') self.itemManager.removeItem('ETank') self.itemManager.addItem('NoEnergy', 'Chozo') self.itemManager.removeItem('ETank') self.itemManager.addItem('NoEnergy', 'Chozo') if self.isUltraSparseNoTanks(): # no etank nor reserve self.itemManager.removeItem('ETank') self.itemManager.addItem('NoEnergy', 'Chozo') elif random.random() < 0.5: # replace only etank with reserve self.itemManager.removeItem('ETank') self.itemManager.addItem('Reserve', 'Chozo') # complete up to 18 energies with nothing item alreadyInPool = 4 for i in range(total - alreadyInPool): self.itemManager.addItem('Nothing', 'Minor') elif energyQty == 'sparse': # 4-6 # already 3E and 1R alreadyInPool = 4 rest = randGaussBounds(2, 5) if rest >= 1: if random.random() < 0.5: self.itemManager.addItem('Reserve', 'Minor') else: self.itemManager.addItem('ETank', 'Minor') for i in range(rest-1): self.itemManager.addItem('ETank', 'Minor') # complete up to 18 energies with nothing item for i in range(total - alreadyInPool - rest): self.itemManager.addItem('Nothing', 'Minor') elif energyQty == 'medium': # 8-12 # add up to 3 Reserves or ETanks (cannot add more than 3 reserves) for i in range(3): if random.random() < 0.5: self.itemManager.addItem('Reserve', 'Minor') else: self.itemManager.addItem('ETank', 'Minor') # 7 already in the pool (3 E, 1 R, + the previous 3) alreadyInPool = 7 rest = 1 + randGaussBounds(4, 3.7) for i in range(rest): self.itemManager.addItem('ETank', 'Minor') # fill the rest with NoEnergy for i in range(total - alreadyInPool - rest): self.itemManager.addItem('Nothing', 'Minor') else: # add the vanilla 3 reserves and 13 Etanks for i in range(3): self.itemManager.addItem('Reserve', 'Minor') for i in range(11): self.itemManager.addItem('ETank', 'Minor') def getItemPool(self): self.itemManager.newItemPool() # 25 locs: 16 majors, 3 etanks, 1 reserve, 2 missile, 2 supers, 1 pb for itemType in ['ETank', 'ETank', 'ETank', 'Reserve', 'Missile', 'Missile', 'Super', 'Super', 'PowerBomb', 'Bomb', 'Charge', 'Ice', 'HiJump', 'SpeedBooster', 'Wave', 'Spazer', 'SpringBall', 'Varia', 'Plasma', 'Grapple', 'Morph', 'Gravity', 'XRayScope', 'SpaceJump', 'ScrewAttack']: self.itemManager.addItem(itemType, 'Chozo') self.addEnergy() self.addAmmo() return self.itemManager.getItemPool() class ItemPoolGeneratorMajors(ItemPoolGenerator): def __init__(self, itemManager, qty, sm, maxDiff): super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff) self.sparseRest = 1 + randGaussBounds(2, 5) self.mediumRest = 3 + randGaussBounds(4, 3.7) self.ultraSparseNoTanks = self.isUltraSparseNoTanks() def addNoEnergy(self): self.itemManager.addItem('NoEnergy') def addEnergy(self): total = self.maxEnergy alreadyInPool = 2 def getE(toAdd): nonlocal total, alreadyInPool d = total - alreadyInPool - toAdd if d < 0: toAdd += d return toAdd energyQty = self.qty['energy'] if energyQty == 'ultra sparse': # 0-1, add up to one energy (etank or reserve) self.itemManager.removeItem('Reserve') self.itemManager.removeItem('ETank') self.addNoEnergy() if self.ultraSparseNoTanks: # no energy at all self.addNoEnergy() else: if random.random() < 0.5: self.itemManager.addItem('ETank') else: self.itemManager.addItem('Reserve') # complete with nothing item for i in range(total - alreadyInPool): self.addNoEnergy() elif energyQty == 'sparse': # 4-6 if random.random() < 0.5: self.itemManager.addItem('Reserve') else: self.itemManager.addItem('ETank') # 3 in the pool (1 E, 1 R + the previous one) alreadyInPool = 3 rest = self.sparseRest for i in range(rest): self.itemManager.addItem('ETank') # complete with nothing item for i in range(total - alreadyInPool - rest): self.addNoEnergy() elif energyQty == 'medium': # 8-12 # add up to 3 Reserves or ETanks (cannot add more than 3 reserves) alreadyInPool = 2 n = getE(3) for i in range(n): if random.random() < 0.5: self.itemManager.addItem('Reserve') else: self.itemManager.addItem('ETank') alreadyInPool += n rest = getE(self.mediumRest) for i in range(rest): self.itemManager.addItem('ETank') # fill the rest with NoEnergy for i in range(total - alreadyInPool - rest): self.addNoEnergy() else: nE = getE(13) alreadyInPool += nE nR = getE(3) alreadyInPool += nR for i in range(nR): self.itemManager.addItem('Reserve') for i in range(nE): self.itemManager.addItem('ETank') for i in range(total - alreadyInPool): self.addNoEnergy() def getItemPool(self): self.itemManager.newItemPool() for itemType in ['ETank', 'Reserve', 'Bomb', 'Charge', 'Ice', 'HiJump', 'SpeedBooster', 'Wave', 'Spazer', 'SpringBall', 'Varia', 'Plasma', 'Grapple', 'Morph', 'Gravity', 'XRayScope', 'SpaceJump', 'ScrewAttack']: self.itemManager.addItem(itemType, 'Major') for itemType in ['Missile', 'Missile', 'Super', 'Super', 'PowerBomb']: self.itemManager.addItem(itemType, 'Minor') self.addEnergy() self.addAmmo() return self.itemManager.getItemPool() class ItemPoolGeneratorScavenger(ItemPoolGeneratorMajors): def __init__(self, itemManager, qty, sm, maxDiff): super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff) def addNoEnergy(self): self.itemManager.addItem('Nothing') class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors): def __init__(self, itemManager, qty, sm, nLocs, maxDiff): super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff) self.maxItems = nLocs self.calcMaxAmmo() nMajors = len([itemName for itemName,item in ItemManager.Items.items() if item.Class == 'Major' and item.Category != 'Energy']) energyQty = self.qty['energy'] if energyQty == 'medium': if nLocs < 40: self.maxEnergy = 5 elif nLocs < 55: self.maxEnergy = 6 else: self.maxEnergy = 5 + self.mediumRest elif energyQty == 'vanilla': if nLocs < 40: self.maxEnergy = 6 elif nLocs < 55: self.maxEnergy = 8 else: self.maxEnergy = 8 + int(float(nLocs - 55)/50.0 * 8) self.log.debug("maxEnergy: "+str(self.maxEnergy)) maxItems = self.maxItems - 10 # remove bosses and minimal minore self.maxEnergy = int(max(self.maxEnergy, maxItems - nMajors - self.minorLocations)) if self.maxEnergy > 18: self.maxEnergy = 18 elif energyQty == 'ultra sparse': self.maxEnergy = 0 if self.ultraSparseNoTanks else 1 elif energyQty == 'sparse': self.maxEnergy = 3 + self.sparseRest self.log.debug("maxEnergy: "+str(self.maxEnergy)) class ItemPoolGeneratorPlando(ItemPoolGenerator): def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff): super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff) # in exclude dict: # in alreadyPlacedItems: # dict of 'itemType: count' of items already added in the plando. # also a 'total: count' with the total number of items already added in the plando. # in forbiddenItems: list of item forbidden in the pool self.exclude = exclude self.maxItems = nLocs self.log.debug("maxItems: {}".format(self.maxItems)) self.log.debug("exclude: {}".format(self.exclude)) def getItemPool(self): exceptionMessage = "Too many items already placed by the plando or not enough available locations:" self.itemManager.newItemPool(addBosses=False) # add the already placed items by the plando for item, count in self.exclude['alreadyPlacedItems'].items(): if item == 'total': continue itemClass = 'Major' if item in ['Missile', 'Super', 'PowerBomb', 'Kraid', 'Phantoon', 'Draygon', 'Ridley', 'MotherBrain']: itemClass = 'Minor' for i in range(count): self.itemManager.addItem(item, itemClass) remain = self.maxItems - self.exclude['alreadyPlacedItems']['total'] self.log.debug("Plando: remain start: {}".format(remain)) if remain > 0: # add missing bosses for boss in ['Kraid', 'Phantoon', 'Draygon', 'Ridley', 'MotherBrain']: if self.exclude['alreadyPlacedItems'][boss] == 0: self.itemManager.addItem(boss, 'Minor') self.exclude['alreadyPlacedItems'][boss] = 1 remain -= 1 self.log.debug("Plando: remain after bosses: {}".format(remain)) if remain < 0: raise Exception("{} can't add the remaining bosses".format(exceptionMessage)) # add missing majors majors = [] for itemType in ['Bomb', 'Charge', 'Ice', 'HiJump', 'SpeedBooster', 'Wave', 'Spazer', 'SpringBall', 'Varia', 'Plasma', 'Grapple', 'Morph', 'Gravity', 'XRayScope', 'SpaceJump', 'ScrewAttack']: if self.exclude['alreadyPlacedItems'][itemType] == 0 and itemType not in self.exclude['forbiddenItems']: self.itemManager.addItem(itemType, 'Major') self.exclude['alreadyPlacedItems'][itemType] = 1 majors.append(itemType) remain -= 1 self.log.debug("Plando: remain after majors: {}".format(remain)) if remain < 0: raise Exception("{} can't add the remaining majors: {}".format(exceptionMessage, ', '.join(majors))) # add minimum minors to finish the game for (itemType, minimum) in [('Missile', 3), ('Super', 2), ('PowerBomb', 1)]: while self.exclude['alreadyPlacedItems'][itemType] < minimum and itemType not in self.exclude['forbiddenItems']: self.itemManager.addItem(itemType, 'Minor') self.exclude['alreadyPlacedItems'][itemType] += 1 remain -= 1 self.log.debug("Plando: remain after minimum minors: {}".format(remain)) if remain < 0: raise Exception("{} can't add the minimum minors to finish the game".format(exceptionMessage)) # add energy energyQty = self.qty['energy'] limits = { "sparse": [('ETank', 4), ('Reserve', 1)], "medium": [('ETank', 8), ('Reserve', 2)], "vanilla": [('ETank', 14), ('Reserve', 4)] } for (itemType, minimum) in limits[energyQty]: while self.exclude['alreadyPlacedItems'][itemType] < minimum and itemType not in self.exclude['forbiddenItems']: self.itemManager.addItem(itemType, 'Major') self.exclude['alreadyPlacedItems'][itemType] += 1 remain -= 1 self.log.debug("Plando: remain after energy: {}".format(remain)) if remain < 0: raise Exception("{} can't add energy".format(exceptionMessage)) # add ammo nbMinorsAlready = self.exclude['alreadyPlacedItems']['Missile'] + self.exclude['alreadyPlacedItems']['Super'] + self.exclude['alreadyPlacedItems']['PowerBomb'] minorLocations = max(0, 0.66*self.qty['minors'] - nbMinorsAlready) maxItems = len(self.itemManager.getItemPool()) + int(minorLocations) ammoQty = {itemType: qty for itemType, qty in self.qty['ammo'].items() if itemType not in self.exclude['forbiddenItems']} if ammoQty: rangeDict = getRangeDict(ammoQty) while len(self.itemManager.getItemPool()) < maxItems and remain > 0: item = chooseFromRange(rangeDict) self.itemManager.addMinor(item) remain -= 1 self.log.debug("Plando: remain after ammo: {}".format(remain)) # add nothing while remain > 0: self.itemManager.addMinor('Nothing') remain -= 1 self.log.debug("Plando: remain after nothing: {}".format(remain)) return self.itemManager.getItemPool()