mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Added Super Metroid support (#46)
Varia Randomizer based implementation LttPClient -> SNIClient
This commit is contained in:
790
worlds/sm/variaRandomizer/rando/Items.py
Normal file
790
worlds/sm/variaRandomizer/rando/Items.py
Normal file
@@ -0,0 +1,790 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user