842 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			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
 |