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