Archipelago/worlds/sm/variaRandomizer/logic/helpers.py

842 lines
32 KiB
Python

import math
from ..logic.cache import Cache
from ..logic.smbool import SMBool, smboolFalse
from ..utils.parameters import Settings, easy, medium, diff2text
from ..rom.rom_patches import RomPatches
from ..utils.utils import normalizeRounding
class Helpers(object):
def __init__(self, smbm):
self.smbm = smbm
# return bool
def haveItemCount(self, item, count):
return self.smbm.itemCount(item) >= count
# return integer
@Cache.decorator
def energyReserveCount(self):
return self.smbm.itemCount('ETank') + self.smbm.itemCount('Reserve')
def energyReserveCountOkDiff(self, difficulties, mult=1.0):
if difficulties is None or len(difficulties) == 0:
return smboolFalse
def f(difficulty):
return self.smbm.energyReserveCountOk(normalizeRounding(difficulty[0] / mult), difficulty=difficulty[1])
result = f(difficulties[0])
for difficulty in difficulties[1:]:
result = self.smbm.wor(result, f(difficulty))
return result
def energyReserveCountOkHellRun(self, hellRunName, mult=1.0):
difficulties = Settings.hellRuns[hellRunName]
result = self.energyReserveCountOkDiff(difficulties, mult)
if result == True:
result.knows = [hellRunName+'HellRun']
return result
# gives damage reduction factor with the current suits
# envDmg : if true (default) will return environmental damage reduction
def getDmgReduction(self, envDmg=True):
ret = 1.0
sm = self.smbm
hasVaria = sm.haveItem('Varia')
hasGrav = sm.haveItem('Gravity')
items = []
if RomPatches.has(sm.player, RomPatches.NoGravityEnvProtection):
if hasVaria:
items = ['Varia']
if envDmg:
ret = 4.0
else:
ret = 2.0
if hasGrav and not envDmg:
ret = 4.0
items = ['Gravity']
elif RomPatches.has(sm.player, RomPatches.ProgressiveSuits):
if hasVaria:
items.append('Varia')
ret *= 2
if hasGrav:
items.append('Gravity')
ret *= 2
else:
if hasVaria:
ret = 2.0
items = ['Varia']
if hasGrav:
ret = 4.0
items = ['Gravity']
return (ret, items)
# higher values for mult means room is that much "easier" (HP mult)
def energyReserveCountOkHardRoom(self, roomName, mult=1.0):
difficulties = Settings.hardRooms[roomName]
(dmgRed, items) = self.getDmgReduction()
mult *= dmgRed
result = self.energyReserveCountOkDiff(difficulties, mult)
if result == True:
result.knows = ['HardRoom-'+roomName]
if dmgRed != 1.0:
result._items.append(items)
return result
@Cache.decorator
def heatProof(self):
sm = self.smbm
return sm.wor(sm.haveItem('Varia'),
sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.NoGravityEnvProtection)),
sm.wnot(RomPatches.has(sm.player, RomPatches.ProgressiveSuits)),
sm.haveItem('Gravity')))
# helper here because we can't define "sublambdas" in locations
def getPiratesPseudoScrewCoeff(self):
sm = self.smbm
ret = 1.0
if RomPatches.has(sm.player, RomPatches.NerfedCharge).bool == True:
ret = 4.0
return ret
@Cache.decorator
def canFireChargedShots(self):
sm = self.smbm
return sm.wor(sm.haveItem('Charge'), RomPatches.has(sm.player, RomPatches.NerfedCharge))
# higher values for mult means hell run is that much "easier" (HP mult)
def canHellRun(self, hellRun, mult=1.0, minE=2):
sm = self.smbm
items = []
isHeatProof = sm.heatProof()
if isHeatProof == True:
return isHeatProof
if sm.wand(RomPatches.has(sm.player, RomPatches.ProgressiveSuits), sm.haveItem('Gravity')).bool == True:
# half heat protection
mult *= 2.0
minE /= 2.0
items.append('Gravity')
if self.energyReserveCount() >= minE:
if hellRun != 'LowerNorfair':
ret = self.energyReserveCountOkHellRun(hellRun, mult)
if ret.bool == True:
ret._items.append(items)
return ret
else:
tanks = self.energyReserveCount()
multCF = mult
if tanks >= 14:
multCF *= 2.0
nCF = int(math.ceil(2/multCF))
ret = sm.wand(self.energyReserveCountOkHellRun(hellRun, mult),
self.canCrystalFlash(nCF))
if ret.bool == True:
if sm.haveItem('Gravity') == True:
ret.difficulty *= 0.7
ret._items.append('Gravity')
elif sm.haveItem('ScrewAttack') == True:
ret.difficulty *= 0.7
ret._items.append('ScrewAttack')
#nPB = self.smbm.itemCount('PowerBomb')
#print("canHellRun LN. tanks=" + str(tanks) + ", nCF=" + str(nCF) + ", nPB=" + str(nPB) + ", mult=" + str(mult) + ", heatProof=" + str(isHeatProof.bool) + ", ret=" + str(ret))
return ret
else:
return smboolFalse
@Cache.decorator
def canMockball(self):
sm = self.smbm
return sm.wand(sm.haveItem('Morph'),
sm.knowsMockball())
@Cache.decorator
def canFly(self):
sm = self.smbm
return sm.wor(sm.haveItem('SpaceJump'),
sm.canInfiniteBombJump())
@Cache.decorator
def canSimpleShortCharge(self):
sm = self.smbm
return sm.wand(sm.haveItem('SpeedBooster'),
sm.wor(sm.knowsSimpleShortCharge(),
sm.knowsShortCharge()))
@Cache.decorator
def canShortCharge(self):
sm = self.smbm
return sm.wand(sm.haveItem('SpeedBooster'), sm.knowsShortCharge())
@Cache.decorator
def canUseBombs(self):
sm = self.smbm
return sm.wand(sm.haveItem('Morph'), sm.haveItem('Bomb'))
@Cache.decorator
def canInfiniteBombJump(self):
sm = self.smbm
return sm.wand(sm.haveItem('Morph'), sm.haveItem('Bomb'), sm.knowsInfiniteBombJump())
@Cache.decorator
def canInfiniteBombJumpSuitless(self):
sm = self.smbm
return sm.wand(sm.haveItem('Morph'), sm.haveItem('Bomb'), sm.knowsInfiniteBombJumpSuitless())
@Cache.decorator
def haveMissileOrSuper(self):
sm = self.smbm
return sm.wor(sm.haveItem('Missile'), sm.haveItem('Super'))
@Cache.decorator
def canOpenRedDoors(self):
sm = self.smbm
return sm.wor(sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.RedDoorsMissileOnly)),
sm.haveMissileOrSuper()),
sm.haveItem('Missile'))
@Cache.decorator
def canOpenEyeDoors(self):
sm = self.smbm
return sm.wor(RomPatches.has(sm.player, RomPatches.NoGadoras),
sm.haveMissileOrSuper())
@Cache.decorator
def canOpenGreenDoors(self):
return self.smbm.haveItem('Super')
@Cache.decorator
def canGreenGateGlitch(self):
sm = self.smbm
return sm.wand(sm.haveItem('Super'),
sm.knowsGreenGateGlitch())
@Cache.decorator
def canBlueGateGlitch(self):
sm = self.smbm
return sm.wand(sm.haveMissileOrSuper(),
sm.knowsGreenGateGlitch())
@Cache.decorator
def canUsePowerBombs(self):
sm = self.smbm
return sm.wand(sm.haveItem('Morph'), sm.haveItem('PowerBomb'))
canOpenYellowDoors = canUsePowerBombs
@Cache.decorator
def canUseSpringBall(self):
sm = self.smbm
return sm.wand(sm.haveItem('Morph'),
sm.haveItem('SpringBall'))
@Cache.decorator
def canSpringBallJump(self):
sm = self.smbm
return sm.wand(sm.canUseSpringBall(),
sm.knowsSpringBallJump())
@Cache.decorator
def canDoubleSpringBallJump(self):
sm = self.smbm
return sm.wand(sm.canUseSpringBall(),
sm.haveItem('HiJump'),
sm.knowsDoubleSpringBallJump())
@Cache.decorator
def canSpringBallJumpFromWall(self):
sm = self.smbm
return sm.wand(sm.canUseSpringBall(),
sm.knowsSpringBallJumpFromWall())
@Cache.decorator
def canDestroyBombWalls(self):
sm = self.smbm
return sm.wor(sm.wand(sm.haveItem('Morph'),
sm.wor(sm.haveItem('Bomb'),
sm.haveItem('PowerBomb'))),
sm.haveItem('ScrewAttack'))
@Cache.decorator
def canDestroyBombWallsUnderwater(self):
sm = self.smbm
return sm.wor(sm.wand(sm.haveItem('Gravity'),
sm.canDestroyBombWalls()),
sm.wand(sm.haveItem('Morph'),
sm.wor(sm.haveItem('Bomb'),
sm.haveItem('PowerBomb'))))
@Cache.decorator
def canPassBombPassages(self):
sm = self.smbm
return sm.wor(sm.canUseBombs(),
sm.canUsePowerBombs())
@Cache.decorator
def canMorphJump(self):
# small hop in morph ball form
sm = self.smbm
return sm.wor(sm.canPassBombPassages(), sm.haveItem('SpringBall'))
def canCrystalFlash(self, n=1):
sm = self.smbm
return sm.wand(sm.canUsePowerBombs(),
sm.itemCountOk('Missile', 2*n),
sm.itemCountOk('Super', 2*n),
sm.itemCountOk('PowerBomb', 2*n+1))
@Cache.decorator
def canCrystalFlashClip(self):
sm = self.smbm
return sm.wand(sm.canCrystalFlash(),
sm.wor(sm.wand(sm.haveItem('Gravity'),
sm.canUseBombs(),
sm.knowsCrystalFlashClip()),
sm.wand(sm.knowsSuitlessCrystalFlashClip(),
sm.itemCountOk('PowerBomb', 4))))
@Cache.decorator
def canDoLowGauntlet(self):
sm = self.smbm
return sm.wand(sm.canShortCharge(),
sm.canUsePowerBombs(),
sm.itemCountOk('ETank', 1),
sm.knowsLowGauntlet())
@Cache.decorator
def canUseHyperBeam(self):
sm = self.smbm
return sm.haveItem('Hyper')
@Cache.decorator
def getBeamDamage(self):
sm = self.smbm
standardDamage = 20
if sm.wand(sm.haveItem('Ice'),
sm.haveItem('Wave'),
sm.haveItem('Plasma')) == True:
standardDamage = 300
elif sm.wand(sm.haveItem('Wave'),
sm.haveItem('Plasma')) == True:
standardDamage = 250
elif sm.wand(sm.haveItem('Ice'),
sm.haveItem('Plasma')) == True:
standardDamage = 200
elif sm.haveItem('Plasma') == True:
standardDamage = 150
elif sm.wand(sm.haveItem('Ice'),
sm.haveItem('Wave'),
sm.haveItem('Spazer')) == True:
standardDamage = 100
elif sm.wand(sm.haveItem('Wave'),
sm.haveItem('Spazer')) == True:
standardDamage = 70
elif sm.wand(sm.haveItem('Ice'),
sm.haveItem('Spazer')) == True:
standardDamage = 60
elif sm.wand(sm.haveItem('Ice'),
sm.haveItem('Wave')) == True:
standardDamage = 60
elif sm.haveItem('Wave') == True:
standardDamage = 50
elif sm.haveItem('Spazer') == True:
standardDamage = 40
elif sm.haveItem('Ice') == True:
standardDamage = 30
return standardDamage
# returns a tuple with :
#
# - a floating point number : 0 if boss is unbeatable with
# current equipment, and an ammo "margin" (ex : 1.5 means we have 50%
# more firepower than absolutely necessary). Useful to compute boss
# difficulty when not having charge. If player has charge, the actual
# value is not useful, and is guaranteed to be > 2.
#
# - estimation of the fight duration in seconds (well not really, it
# is if you fire and land shots perfectly and constantly), giving info
# to compute boss fight difficulty
def canInflictEnoughDamages(self, bossEnergy, doubleSuper=False, charge=True, power=False, givesDrops=True, ignoreMissiles=False, ignoreSupers=False):
# TODO: handle special beam attacks ? (http://deanyd.net/sm/index.php?title=Charge_Beam_Combos)
sm = self.smbm
items = []
# http://deanyd.net/sm/index.php?title=Damage
standardDamage = 0
if sm.canFireChargedShots().bool == True and charge == True:
standardDamage = self.getBeamDamage()
items.append('Charge')
# charge triples the damage
chargeDamage = standardDamage
if sm.haveItem('Charge').bool == True:
chargeDamage *= 3.0
# missile 100 damages, super missile 300 damages, PBs 200 dmg, 5 in each extension
missilesAmount = sm.itemCount('Missile') * 5
if ignoreMissiles == True:
missilesDamage = 0
else:
missilesDamage = missilesAmount * 100
if missilesAmount > 0:
items.append('Missile')
supersAmount = sm.itemCount('Super') * 5
if ignoreSupers == True:
oneSuper = 0
else:
oneSuper = 300.0
if supersAmount > 0:
items.append('Super')
if doubleSuper == True:
oneSuper *= 2
supersDamage = supersAmount * oneSuper
powerDamage = 0
powerAmount = 0
if power == True and sm.haveItem('PowerBomb') == True:
powerAmount = sm.itemCount('PowerBomb') * 5
powerDamage = powerAmount * 200
items.append('PowerBomb')
canBeatBoss = chargeDamage > 0 or givesDrops or (missilesDamage + supersDamage + powerDamage) >= bossEnergy
if not canBeatBoss:
return (0, 0, [])
ammoMargin = (missilesDamage + supersDamage + powerDamage) / bossEnergy
if chargeDamage > 0:
ammoMargin += 2
missilesDPS = Settings.algoSettings['missilesPerSecond'] * 100.0
supersDPS = Settings.algoSettings['supersPerSecond'] * 300.0
if doubleSuper == True:
supersDPS *= 2
if powerDamage > 0:
powerDPS = Settings.algoSettings['powerBombsPerSecond'] * 200.0
else:
powerDPS = 0.0
chargeDPS = chargeDamage * Settings.algoSettings['chargedShotsPerSecond']
# print("chargeDPS=" + str(chargeDPS))
dpsDict = {
missilesDPS: (missilesAmount, 100.0),
supersDPS: (supersAmount, oneSuper),
powerDPS: (powerAmount, 200.0),
# no boss will take more 10000 charged shots
chargeDPS: (10000, chargeDamage)
}
secs = 0
for dps in sorted(dpsDict, reverse=True):
amount = dpsDict[dps][0]
one = dpsDict[dps][1]
if dps == 0 or one == 0 or amount == 0:
continue
fire = min(bossEnergy / one, amount)
secs += fire * (one / dps)
bossEnergy -= fire * one
if bossEnergy <= 0:
break
if bossEnergy > 0:
# print ('!! drops !! ')
secs += bossEnergy * Settings.algoSettings['missileDropsPerMinute'] * 100 / 60
# print('ammoMargin = ' + str(ammoMargin) + ', secs = ' + str(secs))
return (ammoMargin, secs, items)
# return diff score, or -1 if below minimum energy in diffTbl
def computeBossDifficulty(self, ammoMargin, secs, diffTbl, energyDiff=0):
sm = self.smbm
# actual fight duration :
rate = None
if 'Rate' in diffTbl:
rate = float(diffTbl['Rate'])
if rate is None:
duration = 120.0
else:
duration = secs / rate
# print('rate=' + str(rate) + ', duration=' + str(duration))
(suitsCoeff, items) = sm.getDmgReduction(envDmg=False)
suitsCoeff /= 2.0
energyCount = self.energyReserveCount()
energy = suitsCoeff * (1 + energyCount + energyDiff)
# print("energy="+str(energy)+", energyCount="+str(energyCount)+",energyDiff="+str(energyDiff)+",suitsCoeff="+str(suitsCoeff))
# add all energy in used items
items += sm.energyReserveCountOk(energyCount).items
energyDict = None
if 'Energy' in diffTbl:
energyDict = diffTbl['Energy']
difficulty = medium
# get difficulty by energy
if energyDict:
energyDict = {float(k):float(v) for k,v in energyDict.items()}
keyz = sorted(energyDict.keys())
if len(keyz) > 0:
current = keyz[0]
if energy < current:
return (-1, [])
sup = None
difficulty = energyDict[current]
for k in keyz:
if k > energy:
sup=k
break
current = k
difficulty = energyDict[k]
# interpolate if we can
if energy > current and sup is not None:
difficulty += (energyDict[sup] - difficulty)/(sup - current) * (energy - current)
# print("energy=" + str(energy) + ", base diff=" + str(difficulty))
# adjust by fight duration
difficulty *= (duration / 120)
# and by ammo margin
# only augment difficulty in case of no charge, don't lower it.
# if we have charge, ammoMargin will have a huge value (see canInflictEnoughDamages),
# so this does not apply
diffAdjust = (1 - (ammoMargin - Settings.algoSettings['ammoMarginIfNoCharge']))
if diffAdjust > 1:
difficulty *= diffAdjust
# print("final diff: "+str(round(difficulty, 2)))
return (round(difficulty, 2), items)
@Cache.decorator
def enoughStuffSporeSpawn(self):
sm = self.smbm
return sm.wor(sm.haveItem('Missile'), sm.haveItem('Super'), sm.haveItem('Charge'))
@Cache.decorator
def enoughStuffCroc(self):
sm = self.smbm
# say croc has ~5000 energy, and ignore its useless drops
(ammoMargin, secs, items) = self.canInflictEnoughDamages(5000, givesDrops=False)
if ammoMargin == 0:
return sm.wand(sm.knowsLowAmmoCroc(),
sm.wor(sm.itemCountOk("Missile", 2),
sm.wand(sm.haveItem('Missile'),
sm.haveItem('Super'))))
else:
return SMBool(True, easy, items=items)
@Cache.decorator
def enoughStuffBotwoon(self):
sm = self.smbm
(ammoMargin, secs, items) = self.canInflictEnoughDamages(6000, givesDrops=False)
diff = SMBool(True, easy, [], items)
lowStuff = sm.knowsLowStuffBotwoon()
if ammoMargin == 0 and lowStuff.bool:
(ammoMargin, secs, items) = self.canInflictEnoughDamages(3500, givesDrops=False)
diff = SMBool(lowStuff.bool, lowStuff.difficulty, lowStuff.knows, items)
if ammoMargin == 0:
return smboolFalse
fight = sm.wor(sm.energyReserveCountOk(math.ceil(4/sm.getDmgReduction(envDmg=False)[0])),
lowStuff)
return sm.wandmax(fight, diff)
@Cache.decorator
def enoughStuffGT(self):
sm = self.smbm
hasBeams = sm.wand(sm.haveItem('Charge'), sm.haveItem('Plasma')).bool
(ammoMargin, secs, items) = self.canInflictEnoughDamages(9000, ignoreMissiles=True, givesDrops=hasBeams)
diff = SMBool(True, easy, [], items)
lowStuff = sm.knowsLowStuffGT()
if ammoMargin == 0 and lowStuff.bool:
(ammoMargin, secs, items) = self.canInflictEnoughDamages(3000, ignoreMissiles=True)
diff = SMBool(lowStuff.bool, lowStuff.difficulty, lowStuff.knows, items)
if ammoMargin == 0:
return smboolFalse
fight = sm.wor(sm.energyReserveCountOk(math.ceil(8/sm.getDmgReduction(envDmg=False)[0])),
lowStuff)
return sm.wandmax(fight, diff)
@Cache.decorator
def enoughStuffsRidley(self):
sm = self.smbm
if not sm.haveItem('Morph') and not sm.haveItem('ScrewAttack'):
return smboolFalse
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(18000, doubleSuper=True, power=True, givesDrops=False)
if ammoMargin == 0:
return smboolFalse
# print('RIDLEY', ammoMargin, secs)
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
Settings.bossesDifficulty['Ridley'])
if (sm.onlyBossLeft):
diff = 1
if diff < 0:
return smboolFalse
else:
return SMBool(True, diff, items=ammoItems+defenseItems)
@Cache.decorator
def enoughStuffsKraid(self):
sm = self.smbm
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(1000)
if ammoMargin == 0:
return smboolFalse
#print('KRAID True ', ammoMargin, secs)
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
Settings.bossesDifficulty['Kraid'])
if (sm.onlyBossLeft):
diff = 1
if diff < 0:
return smboolFalse
return SMBool(True, diff, items=ammoItems+defenseItems)
def adjustHealthDropDiff(self, difficulty):
(dmgRed, items) = self.getDmgReduction(envDmg=False)
# 2 is Varia suit, considered standard eqt for boss fights
# there's certainly a smarter way to do this but...
if dmgRed < 2:
difficulty *= Settings.algoSettings['dmgReductionDifficultyFactor']
elif dmgRed > 2:
difficulty /= Settings.algoSettings['dmgReductionDifficultyFactor']
return difficulty
@Cache.decorator
def enoughStuffsDraygon(self):
sm = self.smbm
if not sm.haveItem('Morph') and not sm.haveItem('Gravity'):
return smboolFalse
# some ammo to destroy the turrets during the fight
if not sm.haveMissileOrSuper():
return smboolFalse
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(6000)
# print('DRAY', ammoMargin, secs)
if ammoMargin > 0:
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
Settings.bossesDifficulty['Draygon'])
if diff < 0:
fight = smboolFalse
else:
fight = SMBool(True, diff, items=ammoItems+defenseItems)
if sm.haveItem('Gravity') == False:
fight.difficulty *= Settings.algoSettings['draygonNoGravityMalus']
else:
fight._items.append('Gravity')
if not sm.haveItem('Morph'):
fight.difficulty *= Settings.algoSettings['draygonNoMorphMalus']
if sm.haveItem('Gravity') and sm.haveItem('ScrewAttack'):
fight.difficulty /= Settings.algoSettings['draygonScrewBonus']
fight.difficulty = self.adjustHealthDropDiff(fight.difficulty)
if (sm.onlyBossLeft):
fight.difficulty = 1
else:
fight = smboolFalse
# for grapple kill considers energy drained by wall socket + 2 spankings by Dray
# (original 99 energy used for rounding)
nTanksGrapple = (240/sm.getDmgReduction(envDmg=True)[0] + 2*160/sm.getDmgReduction(envDmg=False)[0])/100
return sm.wor(fight,
sm.wand(sm.knowsDraygonGrappleKill(),
sm.haveItem('Grapple'),
sm.energyReserveCountOk(nTanksGrapple)),
sm.wand(sm.knowsMicrowaveDraygon(),
sm.haveItem('Plasma'),
sm.canFireChargedShots(),
sm.haveItem('XRayScope')),
sm.wand(sm.haveItem('Gravity'),
sm.energyReserveCountOk(3),
sm.knowsDraygonSparkKill(),
sm.haveItem('SpeedBooster')))
@Cache.decorator
def enoughStuffsPhantoon(self):
sm = self.smbm
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(2500, doubleSuper=True)
if ammoMargin == 0:
return smboolFalse
# print('PHANTOON', ammoMargin, secs)
(difficulty, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
Settings.bossesDifficulty['Phantoon'])
if difficulty < 0:
return smboolFalse
hasCharge = sm.canFireChargedShots()
hasScrew = sm.haveItem('ScrewAttack')
if hasScrew:
difficulty /= Settings.algoSettings['phantoonFlamesAvoidBonusScrew']
defenseItems += hasScrew.items
elif hasCharge:
difficulty /= Settings.algoSettings['phantoonFlamesAvoidBonusCharge']
defenseItems += hasCharge.items
elif not hasCharge and sm.itemCount('Missile') <= 2: # few missiles is harder
difficulty *= Settings.algoSettings['phantoonLowMissileMalus']
difficulty = self.adjustHealthDropDiff(difficulty)
if (sm.onlyBossLeft):
difficulty = 1
fight = SMBool(True, difficulty, items=ammoItems+defenseItems)
return sm.wor(fight,
sm.wand(sm.knowsMicrowavePhantoon(),
sm.haveItem('Plasma'),
sm.canFireChargedShots(),
sm.haveItem('XRayScope')))
def mbEtankCheck(self):
sm = self.smbm
if sm.wor(RomPatches.has(sm.player, RomPatches.NerfedRainbowBeam), RomPatches.has(sm.player, RomPatches.TourianSpeedup)):
# "add" energy for difficulty calculations
energy = 2.8 if sm.haveItem('Varia') else 2.6
return (True, energy)
nTanks = sm.energyReserveCount()
energyDiff = 0
if sm.haveItem('Varia') == False:
# "remove" 3 etanks (accounting for rainbow beam damage without varia)
if nTanks < 6:
return (False, 0)
energyDiff = -3
elif nTanks < 3:
return (False, 0)
return (True, energyDiff)
@Cache.decorator
def enoughStuffsMotherbrain(self):
sm = self.smbm
# MB1 can't be hit by charge beam
(ammoMargin, secs, _) = self.canInflictEnoughDamages(3000, charge=False, givesDrops=False)
if ammoMargin == 0:
return smboolFalse
# requires 10-10 to break the glass
if sm.itemCount('Missile') <= 1 or sm.itemCount('Super') <= 1:
return smboolFalse
# we actually don't give a shit about MB1 difficulty,
# since we embark its health in the following calc
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(18000 + 3000, givesDrops=False)
if ammoMargin == 0:
return smboolFalse
(possible, energyDiff) = self.mbEtankCheck()
if possible == False:
return smboolFalse
# print('MB2', ammoMargin, secs)
#print("ammoMargin: {}, secs: {}, settings: {}, energyDiff: {}".format(ammoMargin, secs, Settings.bossesDifficulty['MotherBrain'], energyDiff))
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs, Settings.bossesDifficulty['MotherBrain'], energyDiff)
if (sm.onlyBossLeft):
diff = 1
if diff < 0:
return smboolFalse
return SMBool(True, diff, items=ammoItems+defenseItems)
@Cache.decorator
def canPassMetroids(self):
sm = self.smbm
return sm.wor(sm.wand(sm.haveItem('Ice'), sm.haveMissileOrSuper()),
# to avoid leaving tourian to refill power bombs
sm.itemCountOk('PowerBomb', 3))
@Cache.decorator
def canPassZebetites(self):
sm = self.smbm
return sm.wor(sm.wand(sm.haveItem('Ice'), sm.knowsIceZebSkip()),
sm.wand(sm.haveItem('SpeedBooster'), sm.knowsSpeedZebSkip()),
# account for one zebetite, refill may be necessary
SMBool(self.canInflictEnoughDamages(1100, charge=False, givesDrops=False, ignoreSupers=True)[0] >= 1, 0))
@Cache.decorator
def enoughStuffTourian(self):
sm = self.smbm
ret = self.smbm.wand(sm.wor(RomPatches.has(sm.player, RomPatches.TourianSpeedup),
sm.wand(sm.canPassMetroids(), sm.canPassZebetites())),
sm.canOpenRedDoors(),
sm.enoughStuffsMotherbrain(),
sm.wor(RomPatches.has(sm.player, RomPatches.OpenZebetites), sm.haveItem('Morph')))
return ret
class Pickup:
def __init__(self, itemsPickup):
self.itemsPickup = itemsPickup
def enoughMinors(self, smbm, minorLocations):
if self.itemsPickup == 'all':
return len(minorLocations) == 0
else:
return True
def enoughMajors(self, smbm, majorLocations):
if self.itemsPickup == 'all':
return len(majorLocations) == 0
else:
return True
class Bosses:
# bosses helpers to know if they are dead
areaBosses = {
# classic areas
'Brinstar': 'Kraid',
'Norfair': 'Ridley',
'LowerNorfair': 'Ridley',
'WreckedShip': 'Phantoon',
'Maridia': 'Draygon',
# solver areas
'Blue Brinstar': 'Kraid',
'Brinstar Hills': 'Kraid',
'Bubble Norfair': 'Ridley',
'Bubble Norfair Bottom': 'Ridley',
'Bubble Norfair Reserve': 'Ridley',
'Bubble Norfair Speed': 'Ridley',
'Bubble Norfair Wave': 'Ridley',
'Draygon Boss': 'Draygon',
'Green Brinstar': 'Kraid',
'Green Brinstar Reserve': 'Kraid',
'Kraid': 'Kraid',
'Kraid Boss': 'Kraid',
'Left Sandpit': 'Draygon',
'Lower Norfair After Amphitheater': 'Ridley',
'Lower Norfair Before Amphitheater': 'Ridley',
'Lower Norfair Screw Attack': 'Ridley',
'Maridia Forgotten Highway': 'Draygon',
'Maridia Green': 'Draygon',
'Maridia Pink Bottom': 'Draygon',
'Maridia Pink Top': 'Draygon',
'Maridia Sandpits': 'Draygon',
'Norfair Entrance': 'Ridley',
'Norfair Grapple Escape': 'Ridley',
'Norfair Ice': 'Ridley',
'Phantoon Boss': 'Phantoon',
'Pink Brinstar': 'Kraid',
'Red Brinstar': 'Kraid',
'Red Brinstar Top': 'Kraid',
'Ridley Boss': 'Ridley',
'Right Sandpit': 'Draygon',
'Warehouse': 'Kraid',
'WreckedShip': 'Phantoon',
'WreckedShip Back': 'Phantoon',
'WreckedShip Bottom': 'Phantoon',
'WreckedShip Gravity': 'Phantoon',
'WreckedShip Main': 'Phantoon',
'WreckedShip Top': 'Phantoon'
}
@staticmethod
def Golden4():
return ['Draygon', 'Kraid', 'Phantoon', 'Ridley']
@staticmethod
def bossDead(sm, boss):
return sm.haveItem(boss)
@staticmethod
def areaBossDead(sm, area):
if area not in Bosses.areaBosses:
return True
return Bosses.bossDead(sm, Bosses.areaBosses[area])
@staticmethod
def allBossesDead(smbm):
return smbm.wand(Bosses.bossDead(smbm, 'Kraid'),
Bosses.bossDead(smbm, 'Phantoon'),
Bosses.bossDead(smbm, 'Draygon'),
Bosses.bossDead(smbm, 'Ridley'))
def diffValue2txt(diff):
last = 0
for d in sorted(diff2text.keys()):
if diff >= last and diff < d:
return diff2text[last]
last = d
return None