Archipelago/worlds/sm/variaRandomizer/rando/Items.py

792 lines
30 KiB
Python

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()