791 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			791 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | from utils.utils import randGaussBounds, getRangeDict, chooseFromRange | ||
|  | import utils.log, 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 = utils.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() |