792 lines
30 KiB
Python
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()
|