diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index ec0f23e..a2dab75 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.3: +#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #This version of the calculator will run all top line 1Hander options in the provided scenario. @@ -46,6 +46,12 @@ #Traits: Ironjaw = 0 #Reduces injury susceptibility. GloriousEndurance = 0 #The Bear's unique trait. Reduces damage by 5% each time you are hit, up to a 25% max reduction. +#Ijirok Armor: #Choose one between Heal10/Heal20 and one between 1 or 2 Turn Heal interval. +#Note: Ijirok tests are imperfect in a sandbox calculator. 1v1 tests are biased in its favor, while lack of hit chance is biased against it. +IjirokHeal10 = 0 #Ijirok armor passive for one piece. Heals 10 HP at start of player turn. +IjirokHeal20 = 0 #Ijirok armor passive for both pieces. Heals 20 HP at start of player turn. +Ijirok1TurnHeal = 0 #Applies Ijirok healing after every attack. +Ijirok2TurnHeal = 0 #Applies Ijirok healing after every other attack, better simulating 4AP enemies/weapons, or being attacked by multiple enemies per turn. #ATTACKER FLAGS: Set these values to 1 if they apply and 0 otherwise. #1Hander specific: @@ -130,7 +136,7 @@ DPreAmbusher = 0 # 40hp, 25/35. DPreShaman = 0 # 70hp, 35/45. DPreOverseer = 0 # 70hp, 120/180. -DPreReaverHeavy = 0 # 80hp, 145/95, Resilient. +DPreReaverHeavy = 0 # 120hp, 145/95, Resilient. DPreChosenLight = 0 # 130hp, 145/140, Forge, Resilient. DPreChosenHeavy = 0 # 130hp, 190/230, Forge, Resilient. DPreBarbKing = 0 # 150hp, 250/270, Forge, Resilient. @@ -139,20 +145,20 @@ DPreBillman = 0 # 70hp, 80/130, Forge. DPreArbalester = 0 # 60hp, 80/65. DPreBannerHeavy = 0 # 80hp, 215/150, SteelBrow. -DPreKnight = 0 # 125hp, 300/300, Forge. -DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow. (-18 Fat) +DPreKnight = 0 # 135hp, 300/300, Forge. +DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow, Resilient. (-18 Fat) DPreZweiHeavy = 0 # 90hp, 160/240, Forge, SteelBrow. -DPreRaiderHeavy = 0 # 70hp, 140/115. +DPreRaiderHeavy = 0 # 75hp, 140/115. DPreMarkman = 0 # 60hp, 45/70. -DPreLeaderHeavy = 0 # 100hp, 250/230, NineLives. +DPreLeaderHeavy = 0 # 100hp, 250/210, NineLives. DPreMercenaryHeavy = 0 # 90hp, 230/260, Forge. DPreMercRange = 0 # 65hp, 115/115, Nimble. (-18 Fat) DPreHedgeKnight = 0 # 150hp, 300/300, Forge, Resilient. -DPreSwordmaster = 0 # 70hp, 70/115, Nimble, SteelBrow. (-15 Fat) +DPreSwordmaster = 0 # 70hp, 80/115, Nimble, SteelBrow. (-16 Fat) DPreMasterArcher = 0 # 80hp, 30/115, Nimble, SteelBrow. (-12 Fat) DPreOutlawHeavy = 0 # 75hp, 125/105. DPreConscript = 0 # 55hp, 105/110, Nimble. (-16 Fat) -DPreOfficer = 0 # 100hp, 290/290, Forge. +DPreOfficer = 0 # 110hp, 290/290, Forge. DPreAssassinHeavy = 0 # 80hp, 140/120, Nimble. (-15 Fat) # ------------------------------------------------------------------------ @@ -186,6 +192,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -242,7 +249,7 @@ if DPreOverseer == 1: Def_HP, Def_Helmet, Def_Armor = 70, 120, 180 if DPreReaverHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Resilient = 80, 145, 95, 1 + Def_HP, Def_Helmet, Def_Armor, Resilient = 120, 145, 95, 1 if DPreChosenLight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 130, 145, 140, 1, 1 if DPreChosenHeavy == 1: @@ -260,17 +267,17 @@ if DPreBannerHeavy == 1: Def_HP, Def_Helmet, Def_Armor, SteelBrow = 80, 215, 150, 1 if DPreKnight == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 125, 300, 300, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 135, 300, 300, 1 if DPreSergeant == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 100, 0, 150, -18, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow, Resilient = 100, 0, 150, -18, 1, 1, 1 if DPreZweiHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge, SteelBrow = 90, 160, 240, 1, 1 if DPreRaiderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor = 70, 140, 115 + Def_HP, Def_Helmet, Def_Armor = 75, 140, 115 if DPreMarkman == 1: Def_HP, Def_Helmet, Def_Armor = 60, 45, 70 if DPreLeaderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 230, 1 + Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 210, 1 if DPreMercenaryHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge = 90, 230, 260, 1 if DPreMercRange == 1: @@ -278,7 +285,7 @@ if DPreHedgeKnight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 150, 300, 300, 1, 1 if DPreSwordmaster == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 70, 115, -15, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 80, 115, -16, 1, 1 if DPreMasterArcher == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 80, 30, 115, -12, 1, 1 if DPreOutlawHeavy == 1: @@ -286,7 +293,7 @@ if DPreConscript == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 55, 105, 110, -16, 1 if DPreOfficer == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 100, 290, 290, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 110, 290, 290, 1 if DPreAssassinHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 80, 140, 120, -15, 1 @@ -379,6 +386,12 @@ def NimbleCalc(): else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -439,6 +452,8 @@ def calc(): Ignore *= 1.25 if Duelist == 1: Ignore += .25 + if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -523,6 +538,17 @@ def calc(): else: AimedShotMod = 1 + #Injury rate modifiers: + InjuryMod = 1 + if CripplingStrikes == 1: + InjuryMod *= 0.66 + if Shamshir == 1: + InjuryMod *= 0.66 + if ShamshirMastery == 1: + InjuryMod *= 0.5 + if Ironjaw == 1: + InjuryMod *= 1.25 + #Lists for later analysis: hits_until_death = [] #This list will hold how many hits until death for each iteration. hits_until_1st_injury = [] #This list will hold how many hits until first injury for each iteration. @@ -530,7 +556,9 @@ def calc(): hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). - hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) + Total_Ijirok_Healing = [] #This list will hold the amount of total Ijirok healing from the Ijirok armor for each iteration (only displays if Ijirok switches are checked). + hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). + hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) NimbleCalc() @@ -555,6 +583,11 @@ def calc(): NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 HeavyInjuryChance = 0 UseHeadShotInjuryFormula = 0 @@ -565,7 +598,9 @@ def calc(): Bleedstack2T = 0 ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. - + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. + IjirokTotalHeal = 0 #Tracker for amount of Ijirok healing received. + count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. while hp > 0: #Continue looping until death. @@ -579,7 +614,7 @@ def calc(): DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -599,12 +634,7 @@ def calc(): ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (count * 2)) - else: - GladMod = 1 - (.05 * count) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -618,6 +648,9 @@ def calc(): Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. @@ -632,464 +665,223 @@ def calc(): HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HHeadshot + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. - armor_roll = min(helmet,(armor_roll * ForgeMod)) #Applying Forge, and armor damage cannot exceed current armor. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. - else: #If not a headshot, do the following. + else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp. + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) UseHeadShotInjuryFormula = 0 - else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - - #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body injury formula. + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + + #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan + if SplitMan == 1: + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 + #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: if math.floor(hp_roll) > 0: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) else: if math.floor(hp_roll) >= 15: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) @@ -1103,11 +895,31 @@ def calc(): if math.floor(hp_roll) > 0 and math.floor(hp_roll) < 15: FearsomeProcs += 1 + #Ijirok armor check: + if (hp > 0 or NineLivesMod == 1) and (IjirokHeal10 == 1 or IjirokHeal20 == 1): + if Ijirok1TurnHeal == 1: #Block to apply healing after every attack. + hp = hp + IjirokHealing #Applying Healing + IjirokTotalHeal += IjirokHealing #Tracking HP Healed for later analysis. + if hp > Def_HP: #Block to ensure HP doesn't exceed max. + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + elif Ijirok2TurnHeal == 1: #Block to apply healing every other attack. + if count % 2 == 0: + hp = hp + IjirokHealing + IjirokTotalHeal += IjirokHealing + if hp > Def_HP: + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + #Bleeding check: if (CleaverBleed == 1 or CleaverMastery == 1) and Undead != 1: #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1134,21 +946,28 @@ def calc(): NineLivesMod = 0 Bleedstack1T = 0 Bleedstack2T = 0 - elif Fearsome == 1: - if Forge == 1: - Forge_bonus_armor.append(ForgeSaved) - if Flail3Head == 1: - hits_until_death.append(count/3) - else: - hits_until_death.append(count) - NumberFearsomeProcs.append(FearsomeProcs) else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + Total_Ijirok_Healing.append(IjirokTotalHeal) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) if Flail3Head == 1: - hits_until_death.append(count/3) + hits_until_death.append(math.ceil(count/3)) else: hits_until_death.append(count) + #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. + if Injury == 0: + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1177,9 +996,15 @@ def calc(): if Forge == 1: if len(Forge_bonus_armor) != 0: AvgForgeArmor = statistics.mean(Forge_bonus_armor) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + if len(Total_Ijirok_Healing) != 0: + AvgIjirokHealing = statistics.mean(Total_Ijirok_Healing) if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) + if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) #Results: if DeathMean == 1: @@ -1189,7 +1014,7 @@ def calc(): if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) if Undead != 1 and Savant != 1: - if len(hits_until_1st_injury) == 0: + if hits_to_injure >= HitsToDeath: if InjuryMean == 1 or InjuryPercent == 1: print("No chance of injury.") else: @@ -1197,7 +1022,7 @@ def calc(): print("First injury in " + str(hits_to_injure) + " hits on average.") if InjuryPercent == 1: print("% First injury in: " + str(HitsToInjurePercent)) - if len(hits_until_1st_heavy_injury_chance) == 0: + if hits_to_1st_heavy_injury_chance >= HitsToDeath: if HeavyInjuryMean == 1 or HeavyInjuryPercent == 1: print("No chance of heavy injury.") else: @@ -1214,8 +1039,12 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") - if Ambusher == 1: + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + print(str(AvgIjirokHealing) + " HP healed by Ijirok armor on average.") + if (Ambusher == 1 and len(hits_until_1st_poison) != 0): print("First poison in " + str(hits_to_posion) + " hits on average.") + if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " hits on average.") print("-----") #Added for readability. If this annoys you then remove this line. #The following will repeatedly run the scenario with different weapons. @@ -1341,7 +1170,7 @@ def calc(): Maxd = 40 Ignore = 20 ArmorMod = .7 -Shamshir = 0 +ShamshirMastery = 0 print("Rondel Dagger:") calc() @@ -1371,7 +1200,7 @@ def calc(): calc() Mind = 30 -Ignore = 25 +Ignore = 30 ArmorMod = 1.15 Headchance = 30 print("Heavy Axes - 2 Range:") @@ -1383,6 +1212,7 @@ def calc(): #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1455,4 +1285,46 @@ def calc(): #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#-- Fixed Throwing Axe ignore% to 30 (from 25) in the test. Tooltip lists 25% ignore, but the "Throw Axe" skill modifies it to 30%. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#-- Fixed a mistake exclusive to this calculator variant where the ShamshirMastery tag was not properly turning off after the Shamshir test, causing the injury bonus effect to persist on the dagger, spear, and throwing tests that ran after. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. \ No newline at end of file diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index c6063cc..e5cad6c 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.3: +#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #This version of the calculator will run all top line 2Hander options in the provided scenario. @@ -46,6 +46,12 @@ #Traits: Ironjaw = 0 #Reduces injury susceptibility. GloriousEndurance = 0 #The Bear's unique trait. Reduces damage by 5% each time you are hit, up to a 25% max reduction. +#Ijirok Armor: #Choose one between Heal10/Heal20 and one between 1 or 2 Turn Heal interval. +#Note: Ijirok tests are imperfect in a sandbox calculator. 1v1 tests are biased in its favor, while lack of hit chance is biased against it. +IjirokHeal10 = 0 #Ijirok armor passive for one piece. Heals 10 HP at start of player turn. +IjirokHeal20 = 0 #Ijirok armor passive for both pieces. Heals 20 HP at start of player turn. +Ijirok1TurnHeal = 0 #Applies Ijirok healing after every attack. +Ijirok2TurnHeal = 0 #Applies Ijirok healing after every other attack, better simulating 4AP enemies/weapons, or being attacked by multiple enemies per turn. #ATTACKER FLAGS: Set these values to 1 if they apply and 0 otherwise. #Perks: @@ -128,7 +134,7 @@ DPreAmbusher = 0 # 40hp, 25/35. DPreShaman = 0 # 70hp, 35/45. DPreOverseer = 0 # 70hp, 120/180. -DPreReaverHeavy = 0 # 80hp, 145/95, Resilient. +DPreReaverHeavy = 0 # 120hp, 145/95, Resilient. DPreChosenLight = 0 # 130hp, 145/140, Forge, Resilient. DPreChosenHeavy = 0 # 130hp, 190/230, Forge, Resilient. DPreBarbKing = 0 # 150hp, 250/270, Forge, Resilient. @@ -137,20 +143,20 @@ DPreBillman = 0 # 70hp, 80/130, Forge. DPreArbalester = 0 # 60hp, 80/65. DPreBannerHeavy = 0 # 80hp, 215/150, SteelBrow. -DPreKnight = 0 # 125hp, 300/300, Forge. -DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow. (-18 Fat) +DPreKnight = 0 # 135hp, 300/300, Forge. +DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow, Resilient. (-18 Fat) DPreZweiHeavy = 0 # 90hp, 160/240, Forge, SteelBrow. -DPreRaiderHeavy = 0 # 70hp, 140/115. +DPreRaiderHeavy = 0 # 75hp, 140/115. DPreMarkman = 0 # 60hp, 45/70. -DPreLeaderHeavy = 0 # 100hp, 250/230, NineLives. +DPreLeaderHeavy = 0 # 100hp, 250/210, NineLives. DPreMercenaryHeavy = 0 # 90hp, 230/260, Forge. DPreMercRange = 0 # 65hp, 115/115, Nimble. (-18 Fat) DPreHedgeKnight = 0 # 150hp, 300/300, Forge, Resilient. -DPreSwordmaster = 0 # 70hp, 70/115, Nimble, SteelBrow. (-15 Fat) +DPreSwordmaster = 0 # 70hp, 80/115, Nimble, SteelBrow. (-16 Fat) DPreMasterArcher = 0 # 80hp, 30/115, Nimble, SteelBrow. (-12 Fat) DPreOutlawHeavy = 0 # 75hp, 125/105. DPreConscript = 0 # 55hp, 105/110, Nimble. (-16 Fat) -DPreOfficer = 0 # 100hp, 290/290, Forge. +DPreOfficer = 0 # 110hp, 290/290, Forge. DPreAssassinHeavy = 0 # 80hp, 140/120, Nimble. (-15 Fat) # ------------------------------------------------------------------------ @@ -185,6 +191,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -240,7 +247,7 @@ if DPreOverseer == 1: Def_HP, Def_Helmet, Def_Armor = 70, 120, 180 if DPreReaverHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Resilient = 80, 145, 95, 1 + Def_HP, Def_Helmet, Def_Armor, Resilient = 120, 145, 95, 1 if DPreChosenLight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 130, 145, 140, 1, 1 if DPreChosenHeavy == 1: @@ -258,17 +265,17 @@ if DPreBannerHeavy == 1: Def_HP, Def_Helmet, Def_Armor, SteelBrow = 80, 215, 150, 1 if DPreKnight == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 125, 300, 300, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 135, 300, 300, 1 if DPreSergeant == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 100, 0, 150, -18, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow, Resilient = 100, 0, 150, -18, 1, 1, 1 if DPreZweiHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge, SteelBrow = 90, 160, 240, 1, 1 if DPreRaiderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor = 70, 140, 115 + Def_HP, Def_Helmet, Def_Armor = 75, 140, 115 if DPreMarkman == 1: Def_HP, Def_Helmet, Def_Armor = 60, 45, 70 if DPreLeaderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 230, 1 + Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 210, 1 if DPreMercenaryHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge = 90, 230, 260, 1 if DPreMercRange == 1: @@ -276,7 +283,7 @@ if DPreHedgeKnight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 150, 300, 300, 1, 1 if DPreSwordmaster == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 70, 115, -15, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 80, 115, -16, 1, 1 if DPreMasterArcher == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 80, 30, 115, -12, 1, 1 if DPreOutlawHeavy == 1: @@ -284,7 +291,7 @@ if DPreConscript == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 55, 105, 110, -16, 1 if DPreOfficer == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 100, 290, 290, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 110, 290, 290, 1 if DPreAssassinHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 80, 140, 120, -15, 1 @@ -377,6 +384,12 @@ def NimbleCalc(): else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -433,6 +446,8 @@ def calc(): Ignore *= 1.25 if Duelist == 1: Ignore += .25 + if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -515,6 +530,17 @@ def calc(): else: AimedShotMod = 1 + #Injury rate modifiers: + InjuryMod = 1 + if CripplingStrikes == 1: + InjuryMod *= 0.66 + if Shamshir == 1: + InjuryMod *= 0.66 + if ShamshirMastery == 1: + InjuryMod *= 0.5 + if Ironjaw == 1: + InjuryMod *= 1.25 + #Lists for later analysis: hits_until_death = [] #This list will hold how many hits until death for each iteration. hits_until_1st_injury = [] #This list will hold how many hits until first injury for each iteration. @@ -522,7 +548,9 @@ def calc(): hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). - hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) + Total_Ijirok_Healing = [] #This list will hold the amount of total Ijirok healing from the Ijirok armor for each iteration (only displays if Ijirok switches are checked). + hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). + hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) NimbleCalc() @@ -547,6 +575,11 @@ def calc(): NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 HeavyInjuryChance = 0 UseHeadShotInjuryFormula = 0 @@ -557,7 +590,9 @@ def calc(): Bleedstack2T = 0 ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. - + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. + IjirokTotalHeal = 0 #Tracker for amount of Ijirok healing received. + count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. while hp > 0: #Continue looping until death. @@ -571,7 +606,7 @@ def calc(): DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -591,12 +626,7 @@ def calc(): ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (count * 2)) - else: - GladMod = 1 - (.05 * count) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -610,6 +640,9 @@ def calc(): Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. @@ -624,449 +657,208 @@ def calc(): HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HHeadshot + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. - armor_roll = min(helmet,(armor_roll * ForgeMod)) #Applying Forge, and armor damage cannot exceed current armor. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp. + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) UseHeadShotInjuryFormula = 0 - else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - - #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body injury formula. + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + + #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan + if SplitMan == 1: + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 #Morale check: if FirstMoraleCheck == 0: @@ -1074,14 +866,14 @@ def calc(): if math.floor(hp_roll) > 0: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) else: if math.floor(hp_roll) >= 15: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) @@ -1095,11 +887,31 @@ def calc(): if math.floor(hp_roll) > 0 and math.floor(hp_roll) < 15: FearsomeProcs += 1 + #Ijirok armor check: + if (hp > 0 or NineLivesMod == 1) and (IjirokHeal10 == 1 or IjirokHeal20 == 1): + if Ijirok1TurnHeal == 1: #Block to apply healing after every attack. + hp = hp + IjirokHealing #Applying Healing + IjirokTotalHeal += IjirokHealing #Tracking HP Healed for later analysis. + if hp > Def_HP: #Block to ensure HP doesn't exceed max. + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + elif Ijirok2TurnHeal == 1: #Block to apply healing every other attack. + if count % 2 == 0: + hp = hp + IjirokHealing + IjirokTotalHeal += IjirokHealing + if hp > Def_HP: + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + #Bleeding check: if (CleaverBleed == 1 or CleaverMastery == 1) and Undead != 1: #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1126,21 +938,28 @@ def calc(): NineLivesMod = 0 Bleedstack1T = 0 Bleedstack2T = 0 - elif Fearsome == 1: - if Forge == 1: - Forge_bonus_armor.append(ForgeSaved) - if Flail3Head == 1: - hits_until_death.append(count/3) - else: - hits_until_death.append(count) - NumberFearsomeProcs.append(FearsomeProcs) else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + Total_Ijirok_Healing.append(IjirokTotalHeal) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) if Flail3Head == 1: - hits_until_death.append(count/3) + hits_until_death.append(math.ceil(count/3)) else: hits_until_death.append(count) + #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. + if Injury == 0: + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1169,9 +988,15 @@ def calc(): if Forge == 1: if len(Forge_bonus_armor) != 0: AvgForgeArmor = statistics.mean(Forge_bonus_armor) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + if len(Total_Ijirok_Healing) != 0: + AvgIjirokHealing = statistics.mean(Total_Ijirok_Healing) if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) + if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) #Results: if DeathMean == 1: @@ -1181,7 +1006,7 @@ def calc(): if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) if Undead != 1 and Savant != 1: - if len(hits_until_1st_injury) == 0: + if hits_to_injure >= HitsToDeath: if InjuryMean == 1 or InjuryPercent == 1: print("No chance of injury.") else: @@ -1189,7 +1014,7 @@ def calc(): print("First injury in " + str(hits_to_injure) + " hits on average.") if InjuryPercent == 1: print("% First injury in: " + str(HitsToInjurePercent)) - if len(hits_until_1st_heavy_injury_chance) == 0: + if hits_to_1st_heavy_injury_chance >= HitsToDeath: if HeavyInjuryMean == 1 or HeavyInjuryPercent == 1: print("No chance of heavy injury.") else: @@ -1206,8 +1031,12 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") - if Ambusher == 1: + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + print(str(AvgIjirokHealing) + " HP healed by Ijirok armor on average.") + if (Ambusher == 1 and len(hits_until_1st_poison) != 0): print("First poison in " + str(hits_to_posion) + " hits on average.") + if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " hits on average.") print("-----") #Added for readability. If this annoys you then remove this line. #The following will repeatedly run the scenario with different weapons. @@ -1243,7 +1072,7 @@ def calc(): Mind = 60 Maxd = 90 -Ignore = 50 +Ignore = 40 print("2H Hammer - AoE:") calc() @@ -1364,12 +1193,12 @@ def calc(): Maxd = 80 Headchance = 25 Ignore = 30 -ArmorMod = 1.04 +ArmorMod = 1.05 print("Warscythe:") calc() Ignore = 25 -ArmorMod = 1.04 +ArmorMod = 1.05 print("Warscythe - AoE:") calc() @@ -1388,7 +1217,7 @@ def calc(): Mind = 35 Maxd = 75 Ignore = 25 -ArmodMod = .9 +ArmorMod = .9 print("Handgonne") calc() @@ -1398,6 +1227,7 @@ def calc(): #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1478,4 +1308,45 @@ def calc(): #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset and Berserk Chain autorun for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. +#-- Fixed an oversight on the 2H Hammer AoE test where I forgot to give it 10% less armor ignore than the single target test. (Thank you smr_rst). \ No newline at end of file diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index 17bdb95..47e63a7 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.3: +#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #This version of the calculator will run a given attacker against 30 different enemies. @@ -51,6 +51,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -164,7 +165,7 @@ DPreAmbusher = 0 # 40hp, 25/35. DPreShaman = 0 # 70hp, 35/45. DPreOverseer = 0 # 70hp, 120/180. -DPreReaverHeavy = 0 # 80hp, 145/95, Resilient. +DPreReaverHeavy = 0 # 120hp, 145/95, Resilient. DPreChosenLight = 0 # 130hp, 145/140, Forge, Resilient. DPreChosenHeavy = 0 # 130hp, 190/230, Forge, Resilient. DPreBarbKing = 0 # 150hp, 250/270, Forge, Resilient. @@ -173,20 +174,20 @@ DPreBillman = 0 # 70hp, 80/130, Forge. DPreArbalester = 0 # 60hp, 80/65. DPreBannerHeavy = 0 # 80hp, 215/150, SteelBrow. -DPreKnight = 0 # 125hp, 300/300, Forge. -DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow. (-18 Fat) +DPreKnight = 0 # 135hp, 300/300, Forge. +DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow, Resilient. (-18 Fat) DPreZweiHeavy = 0 # 90hp, 160/240, Forge, SteelBrow. -DPreRaiderHeavy = 0 # 70hp, 140/115. +DPreRaiderHeavy = 0 # 75hp, 140/115. DPreMarkman = 0 # 60hp, 45/70. -DPreLeaderHeavy = 0 # 100hp, 250/230, NineLives. +DPreLeaderHeavy = 0 # 100hp, 250/210, NineLives. DPreMercenaryHeavy = 0 # 90hp, 230/260, Forge. DPreMercRange = 0 # 65hp, 115/115, Nimble. (-18 Fat) DPreHedgeKnight = 0 # 150hp, 300/300, Forge, Resilient. -DPreSwordmaster = 0 # 70hp, 70/115, Nimble, SteelBrow. (-15 Fat) +DPreSwordmaster = 0 # 70hp, 80/115, Nimble, SteelBrow. (-16 Fat) DPreMasterArcher = 0 # 80hp, 30/115, Nimble, SteelBrow. (-12 Fat) DPreOutlawHeavy = 0 # 75hp, 125/105. DPreConscript = 0 # 55hp, 105/110, Nimble. (-16 Fat) -DPreOfficer = 0 # 100hp, 290/290, Forge. +DPreOfficer = 0 # 110hp, 290/290, Forge. DPreAssassinHeavy = 0 # 80hp, 140/120, Nimble. (-15 Fat) #RACE FLAGS (ATTACKER): @@ -252,7 +253,7 @@ def PresetCalc(): if DPreOverseer == 1: Def_HP, Def_Helmet, Def_Armor = 70, 120, 180 if DPreReaverHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Resilient = 80, 145, 95, 1 + Def_HP, Def_Helmet, Def_Armor, Resilient = 120, 145, 95, 1 if DPreChosenLight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 130, 145, 140, 1, 1 if DPreChosenHeavy == 1: @@ -270,17 +271,17 @@ def PresetCalc(): if DPreBannerHeavy == 1: Def_HP, Def_Helmet, Def_Armor, SteelBrow = 80, 215, 150, 1 if DPreKnight == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 125, 300, 300, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 135, 300, 300, 1 if DPreSergeant == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 100, 0, 150, -18, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow, Resilient = 100, 0, 150, -18, 1, 1, 1 if DPreZweiHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge, SteelBrow = 90, 160, 240, 1, 1 if DPreRaiderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor = 70, 140, 115 + Def_HP, Def_Helmet, Def_Armor = 75, 140, 115 if DPreMarkman == 1: Def_HP, Def_Helmet, Def_Armor = 60, 45, 70 if DPreLeaderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 230, 1 + Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 210, 1 if DPreMercenaryHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge = 90, 230, 260, 1 if DPreMercRange == 1: @@ -288,7 +289,7 @@ def PresetCalc(): if DPreHedgeKnight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 150, 300, 300, 1, 1 if DPreSwordmaster == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 70, 115, -15, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 80, 115, -16, 1, 1 if DPreMasterArcher == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 80, 30, 115, -12, 1, 1 if DPreOutlawHeavy == 1: @@ -296,7 +297,7 @@ def PresetCalc(): if DPreConscript == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 55, 105, 110, -16, 1 if DPreOfficer == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 100, 290, 290, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 110, 290, 290, 1 if DPreAssassinHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 80, 140, 120, -15, 1 @@ -364,6 +365,8 @@ def PresetCalc(): Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -492,6 +495,17 @@ def NimbleCalc(): else: AimedShotMod = 1 +#Injury rate modifiers: +InjuryMod = 1 +if CripplingStrikes == 1: + InjuryMod *= 0.66 +if Shamshir == 1: + InjuryMod *= 0.66 +if ShamshirMastery == 1: + InjuryMod *= 0.5 +if Ironjaw == 1: + InjuryMod *= 1.25 + #Indomitable: if Indomitable == 1: IndomMod = .5 @@ -543,7 +557,8 @@ def calc(): hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). - hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) + hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). + hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) NimbleCalc() @@ -568,6 +583,11 @@ def calc(): NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 HeavyInjuryChance = 0 UseHeadShotInjuryFormula = 0 @@ -578,6 +598,7 @@ def calc(): Bleedstack2T = 0 ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. @@ -592,7 +613,7 @@ def calc(): DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -612,12 +633,7 @@ def calc(): ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (count * 2)) - else: - GladMod = 1 - (.05 * count) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -631,6 +647,9 @@ def calc(): Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. @@ -645,464 +664,223 @@ def calc(): HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: - Ignore = Flail2HHeadshot + Ignore = Flail2HHeadshot + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. - armor_roll = min(helmet,(armor_roll * ForgeMod)) #Applying Forge, and armor damage cannot exceed current armor. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp. + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) UseHeadShotInjuryFormula = 0 - else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - - #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body injury formula. + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + + #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan + if SplitMan == 1: + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 + #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: if math.floor(hp_roll) > 0: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) else: if math.floor(hp_roll) >= 15: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) @@ -1121,6 +899,10 @@ def calc(): #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1147,21 +929,26 @@ def calc(): NineLivesMod = 0 Bleedstack1T = 0 Bleedstack2T = 0 - elif Fearsome == 1: - if Forge == 1: - Forge_bonus_armor.append(ForgeSaved) - if Flail3Head == 1: - hits_until_death.append(count/3) - else: - hits_until_death.append(count) - NumberFearsomeProcs.append(FearsomeProcs) else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) if Flail3Head == 1: - hits_until_death.append(count/3) + hits_until_death.append(math.ceil(count/3)) else: hits_until_death.append(count) + #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. + if Injury == 0: + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1194,6 +981,9 @@ def calc(): if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) + if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) #Results: if DeathMean == 1: @@ -1203,7 +993,7 @@ def calc(): if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) if Undead != 1 and Savant != 1: - if len(hits_until_1st_injury) == 0: + if hits_to_injure >= HitsToDeath: if InjuryMean == 1 or InjuryPercent == 1: print("No chance of injury.") else: @@ -1211,7 +1001,7 @@ def calc(): print("First injury in " + str(hits_to_injure) + " hits on average.") if InjuryPercent == 1: print("% First injury in: " + str(HitsToInjurePercent)) - if len(hits_until_1st_heavy_injury_chance) == 0: + if hits_to_1st_heavy_injury_chance >= HitsToDeath: if HeavyInjuryMean == 1 or HeavyInjuryPercent == 1: print("No chance of heavy injury.") else: @@ -1228,8 +1018,10 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") - if Ambusher == 1: + if (Ambusher == 1 and len(hits_until_1st_poison) != 0): print("First poison in " + str(hits_to_posion) + " hits on average.") + if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " hits on average.") print("-----") #Added for readability. If this annoys you then remove this line. #The following will repeatedly run the scenario against different enemies. @@ -1371,6 +1163,7 @@ def calc(): DPreSergeant = 0 Nimble = 0 +Resilient = 0 DPreZweiHeavy = 1 PresetCalc() print("Zweihander:") @@ -1453,6 +1246,7 @@ def calc(): #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1531,4 +1325,44 @@ def calc(): #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests in the other calculators. This variant of the calculator remained unchanged. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. \ No newline at end of file diff --git a/BBCalc.py b/BBCalc.py index 52feb1f..70a0a99 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator Version 1.6.3: +#Battle Brothers Damage Calculator Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #The calculator expects you to make smart decisions, such as not giving Xbow Mastery to a Hammer. #Written in Python 3.7, earlier versions of Python 3 should work, but Python 2 will not. @@ -28,7 +28,7 @@ #Attacker Stats: #Example is Ancient Bladed Pike, follow that formatting. If you wish to use a attacker Preset, then skip this section. Mind = 55 #Mind = 55 -Maxd = 80 #Maxd = 80 +Maxd = 80 #Maxd = 80 Headchance = 30 #Headchance = 30 Ignore = 30 #Ignore = 30 ArmorMod = 125 #ArmorMod = 125 @@ -38,7 +38,7 @@ Def_Helmet = 120 Def_Armor = 95 Fatigue = -15 #Fatigue value only effects Nimble. -Def_Resolve = 50 #Used only if morale drop data returns are enabled. +Def_Resolve = 55 #Used only if morale drop data returns are enabled. #DEFENDER FLAGS: Set these values to 1 if they apply and 0 otherwise. If you select a Preset then leave these on 0. #Perks: @@ -61,6 +61,12 @@ #Traits: Ironjaw = 0 #Reduces injury susceptibility. GloriousEndurance = 0 #The Bear's unique trait. Reduces damage by 5% each time you are hit, up to a 25% max reduction. +#Ijirok Armor: #Choose one between Heal10/Heal20 and one between 1 or 2 Turn Heal interval. +#Note: Ijirok tests are imperfect in a sandbox calculator. 1v1 tests are biased in its favor, while lack of hit chance is biased against it. +IjirokHeal10 = 0 #Ijirok armor passive for one piece. Heals 10 HP at start of player turn. +IjirokHeal20 = 0 #Ijirok armor passive for both pieces. Heals 20 HP at start of player turn. +Ijirok1TurnHeal = 0 #Applies Ijirok healing after every attack. +Ijirok2TurnHeal = 0 #Applies Ijirok healing after every other attack, better simulating 4AP enemies/weapons, or being attacked by multiple enemies per turn. #ATTACKER FLAGS: Set these values to 1 if they apply and 0 otherwise. If you select a Preset then leave these on 0. #Weapons: @@ -74,6 +80,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -100,7 +107,7 @@ Duelist = 0 #All Duelists should also be given DoubleGrip except for Throwing weapons. KillingFrenzy = 0 Fearsome = 0 #Will also return # of extra Fearsome checks, which are all attacks that deal 1-14 damage. Assign Attacker Resolve below. -Atk_Resolve = 50 #Only used if Fearsome is selected. 20% of (Resolve -10) is applied as a penalty to defender Resolve during morale checks. +Atk_Resolve = 50 #Only used if Fearsome is selected. 15% of Resolve is applied as a penalty to defender Resolve during morale checks. #Traits: Brute = 0 #Headshot damage +15%. Drunkard = 0 #Damage +10%. @@ -158,7 +165,7 @@ #Does not disable perks that shouldn't be active. For example, don't activate Duelist and then check the Chosen Preset. APreAncientSword = 0 #Ancient Dead: 38-43, 20% Ignore, 80% Armor, Fearsome. APreBladedPike = 0 #Ancient Dead: 55-80, 30% Ignore, 125% Armor, 30% Head, Fearsome. -APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 104% Armor, Fearsome. +APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 105% Armor, Fearsome. APreCryptCleaver = 0 #Ancient Dead: 60-80, 25% Ignore, 120% Armor, Fearsome, Cleaver Mastery. APreKhopesh = 0 #Necrosavant: 35-55, 25% Ignore, 120% Armor, HeadHunter, Crippling, Double Grip, CleaverBleed. APreFHGreatAxe = 0 #Fallen Hero: 80-100, 40 %Ignore, 150% Armor, Fearsome, Split Man. @@ -181,14 +188,14 @@ APreLongAxe = 0 #Raider: 70-95, 30% Ignore, 110% Armor, 30% Head, Executioner. APreMedXbow = 0 #Marksman: 40-60, 50% Ignore, 70% Armor, Xbow Mastery. APreNobleSword = 0 #Swordmaster: 45-50, 20% Ignore, 85% Armor, Duelist, Double Grip, Crippling, Executioner. -APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% Armor, Crippling, Executioner, HeadHunter, Master Archer. +APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 60% Armor, Crippling, Executioner, HeadHunter, Master Archer. APrePoleMace = 0 #Conscript: 60-75, 40% Ignore, 120% Armor, 30% Head. APreHandgonne = 0 #Gunner: 35-75, 25% Ignore, 90% Armor, Fearsome. -APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner. +APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner, Cleaver Mastery. APreQatal = 0 #Assassin: 30-45, 20% Ignore, 70% Armor, Duelist, Double Grip, Executioner. APreFDirewolf = 0 #Frenzied Direwolf: 30-50, 20% Ignore, 70% Armor, Executioner, Frenzied Direwolf. APreNachTier3 = 0 #Tier 3 Nachzehrer: 55-80, 10% Ignore, 75% Armor. -APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 140% Armor, Fearsome. +APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 150% Armor, Fearsome. APreUnhold = 0 #Unhold: 40-80, 40% Ignore, 80% Armor, Crippling. APreSchrat = 0 #Schrat: 70-100, 50% Ignore, 80% Armor, Crippling. @@ -212,7 +219,7 @@ DPreAmbusher = 0 # 40hp, 25/35. DPreShaman = 0 # 70hp, 35/45. DPreOverseer = 0 # 70hp, 120/180. -DPreReaverHeavy = 0 # 80hp, 145/95, Resilient. +DPreReaverHeavy = 0 # 120hp, 145/95, Resilient. DPreChosenLight = 0 # 130hp, 145/140, Forge, Resilient. DPreChosenHeavy = 0 # 130hp, 190/230, Forge, Resilient. DPreBarbKing = 0 # 150hp, 250/270, Forge, Resilient. @@ -221,20 +228,20 @@ DPreBillman = 0 # 70hp, 80/130, Forge. DPreArbalester = 0 # 60hp, 80/65. DPreBannerHeavy = 0 # 80hp, 215/150, SteelBrow. -DPreKnight = 0 # 125hp, 300/300, Forge. -DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow. (-18 Fat) +DPreKnight = 0 # 135hp, 300/300, Forge. +DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow, Resilient. (-18 Fat) DPreZweiHeavy = 0 # 90hp, 160/240, Forge, SteelBrow. -DPreRaiderHeavy = 0 # 70hp, 140/115. +DPreRaiderHeavy = 0 # 75hp, 140/115. DPreMarkman = 0 # 60hp, 45/70. -DPreLeaderHeavy = 0 # 100hp, 250/230, NineLives. +DPreLeaderHeavy = 0 # 100hp, 250/210, NineLives. DPreMercenaryHeavy = 0 # 90hp, 230/260, Forge. DPreMercRange = 0 # 65hp, 115/115, Nimble. (-18 Fat) DPreHedgeKnight = 0 # 150hp, 300/300, Forge, Resilient. -DPreSwordmaster = 0 # 70hp, 70/115, Nimble, SteelBrow. (-15 Fat) +DPreSwordmaster = 0 # 70hp, 80/115, Nimble, SteelBrow. (-16 Fat) DPreMasterArcher = 0 # 80hp, 30/115, Nimble, SteelBrow. (-12 Fat) DPreOutlawHeavy = 0 # 75hp, 125/105. DPreConscript = 0 # 55hp, 105/110, Nimble. (-16 Fat) -DPreOfficer = 0 # 100hp, 290/290, Forge. +DPreOfficer = 0 # 110hp, 290/290, Forge. DPreAssassinHeavy = 0 # 80hp, 140/120, Nimble. (-15 Fat) # ------------------------------------------------------------------------ @@ -250,17 +257,17 @@ #Attacker presets: if APreAncientSword == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 38, 43, 25, 20, 80, 1, 80 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 38, 43, 25, 20, 80, 1, 100 if APreBladedPike == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 55, 80, 30, 30, 125, 1, 80 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 55, 80, 30, 30, 125, 1, 100 if APreWarscytheAoE == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 55, 80, 25, 25, 104, 1, 100 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 55, 80, 25, 25, 105, 1, 130 if APreCryptCleaver == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve, CleaverMastery = 60, 80, 25, 25, 120, 1, 100, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve, CleaverMastery = 60, 80, 25, 25, 120, 1, 130, 1 if APreKhopesh == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, HeadHunter, CripplingStrikes, DoubleGrip, CleaverBleed = 35, 55, 25, 25, 120, 1, 1, 1, 1 if APreFHGreatAxe == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve, SplitMan = 80, 100, 25, 40, 150, 1, 100, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve, SplitMan = 80, 100, 25, 40, 150, 1, 130, 1 if APreBerserkChain == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, TwoHander20, Flail2HPound, FlailMastery, Berserker = 50, 100, 40, 30, 125, 1, 1, 1, 1 if APreHeadSplitter == 1: @@ -300,7 +307,7 @@ if APreNobleSword == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Duelist, DoubleGrip, CripplingStrikes, Executioner = 45, 50, 25, 20, 85, 1, 1, 1, 1 if APreWarbow == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 65, 1, 1, 1, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 60, 1, 1, 1, 1 if APrePoleMace == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 60, 75, 30, 40, 120 if APreHandgonne == 1: @@ -314,7 +321,7 @@ if APreNachTier3 == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 55, 80, 25, 10, 75 if APreLindwurm == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 80, 140, 25, 35, 140, 1, 180 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 80, 140, 25, 35, 150, 1, 180 if APreUnhold == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes = 40, 80, 25, 40, 80, 1 if APreSchrat == 1: @@ -356,7 +363,7 @@ if DPreOverseer == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve = 70, 120, 180, 70 if DPreReaverHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Resilient = 80, 145, 95, 80, 1 + Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Resilient = 120, 145, 95, 80, 1 if DPreChosenLight == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge, Resilient = 130, 145, 140, 90, 1, 1 if DPreChosenHeavy == 1: @@ -374,17 +381,17 @@ if DPreBannerHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, SteelBrow = 80, 215, 150, 80, 1 if DPreKnight == 1: - Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge = 125, 300, 300, 90, 1 + Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge = 135, 300, 300, 90, 1 if DPreSergeant == 1: - Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Fatigue, Nimble, SteelBrow = 100, 0, 150, 80, -18, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Fatigue, Nimble, SteelBrow, Resilient = 100, 0, 150, 80, -18, 1, 1, 1 if DPreZweiHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge, SteelBrow = 90, 160, 240, 70, 1, 1 if DPreRaiderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Def_Resolve = 70, 140, 115, 55 + Def_HP, Def_Helmet, Def_Armor, Def_Resolve = 75, 140, 115, 55 if DPreMarkman == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve = 60, 45, 70, 50 if DPreLeaderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Def_Resolve, NineLives = 100, 250, 230, 70, 1 + Def_HP, Def_Helmet, Def_Armor, Def_Resolve, NineLives = 100, 250, 210, 70, 1 if DPreMercenaryHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge = 90, 230, 260, 70, 1 if DPreMercRange == 1: @@ -392,7 +399,7 @@ if DPreHedgeKnight == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge, Resilient = 150, 300, 300, 90, 1, 1 if DPreSwordmaster == 1: - Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Fatigue, Nimble, SteelBrow = 70, 70, 115, 90, -15, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Fatigue, Nimble, SteelBrow = 70, 80, 115, 90, -16, 1, 1 if DPreMasterArcher == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Fatigue, Nimble, SteelBrow = 80, 30, 115, 70, -12, 1, 1 if DPreOutlawHeavy == 1: @@ -400,7 +407,7 @@ if DPreConscript == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Fatigue, Nimble = 55, 105, 110, 70, -16, 1 if DPreOfficer == 1: - Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge = 100, 290, 290, 80, 1 + Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Forge = 110, 290, 290, 80, 1 if DPreAssassinHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Def_Resolve, Fatigue, Nimble = 80, 140, 120, 85, -15, 1 @@ -463,6 +470,8 @@ Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -528,7 +537,7 @@ #Fearsome if Fearsome == 1: - FearsomeMod = min((Atk_Resolve - 10) / 5,18) + FearsomeMod = math.floor(min(Atk_Resolve * .15,20)) #Damage modifiers: DamageMod = 1 @@ -606,6 +615,17 @@ else: AimedShotMod = 1 +#Injury rate modifiers: +InjuryMod = 1 +if CripplingStrikes == 1: + InjuryMod *= 0.66 +if Shamshir == 1: + InjuryMod *= 0.66 +if ShamshirMastery == 1: + InjuryMod *= 0.5 +if Ironjaw == 1: + InjuryMod *= 1.25 + #Indomitable: if Indomitable == 1: IndomMod = .5 @@ -624,6 +644,12 @@ else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -639,12 +665,15 @@ hits_until_1st_heavy_injury_chance = [] #This list will hold how many hits until a chance of heavy injury for each iteration. hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. Total_Morale_Checks = [] #This list will hold how many morale checks occur for each iteration. -hits_until_wavering = [] #this list will hold how many hits until morale falls to wavering for each iteration. +hits_until_wavering = [] #this list will hold how many hits until morale falls to for each iteration. hits_until_breaking = [] #this list will hold how many hits until morale falls to breaking for each iteration. hits_until_fleeing = [] #this list will hold how many hits until morale falls to fleeing for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). -hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) +Total_Ijirok_Healing = [] #This list will hold the amount of total Ijirok healing from the Ijirok armor for each iteration (only displays if Ijirok switches are checked). +hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). +hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). + print("-----") #Added for readability. If this annoys you then remove this line. print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) @@ -669,6 +698,11 @@ NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 #Tracker for when first injury occurs. HeavyInjuryChance = 0 #Tracker for when when first chance of heavy injury occurs. UseHeadShotInjuryFormula = 0 #Tracker to use headshot injury formula on headshots. @@ -684,6 +718,8 @@ Bleedstack2T = 0 #Tracker for bleed stacks with two turns remaining. ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. + IjirokTotalHeal = 0 #Tracker for amount of Ijirok healing received. count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. @@ -698,7 +734,7 @@ DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -718,12 +754,7 @@ ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (count * 2)) - else: - GladMod = 1 - (.05 * count) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -737,14 +768,15 @@ Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. head_roll = random.randint(1,100) #Random roll to determine if hit is a headshot. if head_roll <= Headshotchance: #If headshot, do the following code blocks. #Headshot Injury Flag -- Headshot injuries use a different formula. This flag will signal later when Injury is checked. - #print("headshot") - #print(Headshotchance) UseHeadShotInjuryFormula = 1 UseHeadShotInjuryFormulaHeavy = 1 #HeadHunter check -- Lose current stack if you had one. Gain stack if you didn't. @@ -753,453 +785,208 @@ HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HHeadshot - + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. - armor_roll = min(helmet,(armor_roll * ForgeMod)) #Applying Forge, and armor damage cannot exceed current armor. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp. + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 - count += 1 #Add +1 to the number of hits taken. + count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if hp > 0 or NineLivesMod == 1: - if Injury != 1: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + UseHeadShotInjuryFormula = 0 else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if HeavyInjuryChance != 1: - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan + if SplitMan == 1: + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 #Morale check: if (hp > 0 or NineLivesMod == 1) and Fleeing != 1: @@ -1218,20 +1005,20 @@ Wavering = 1 ResolveMod = .9 #Wavering morale gives a -10% Resolve penalty, making us more vulnerable to further drops. if Flail3Head == 1: - hits_until_wavering.append(count/3) + hits_until_wavering.append(math.ceil(count/3)) else: hits_until_wavering.append(count) #Return the time until Wavering for later data analysis. elif Breaking == 0: Breaking = 1 ResolveMod = .8 if Flail3Head == 1: - hits_until_breaking.append(count/3) + hits_until_breaking.append(math.ceil(count/3)) else: hits_until_breaking.append(count) elif Fleeing == 0: Fleeing = 1 if Flail3Head == 1: - hits_until_fleeing.append(count/3) + hits_until_fleeing.append(math.ceil(count/3)) else: hits_until_fleeing.append(count) else: @@ -1246,14 +1033,14 @@ if Wavering == 0: Wavering = 1 ResolveMod = .9 - hits_until_wavering.append(count/3) + hits_until_wavering.append(math.ceil(count/3)) elif Breaking == 0: Breaking = 1 ResolveMod = .8 - hits_until_breaking.append(count/3) + hits_until_breaking.append(math.ceil(count/3)) elif Fleeing == 0: Fleeing = 1 - hits_until_fleeing.append(count/3) + hits_until_fleeing.append(math.ceil(count/3)) if SplitMan == 1: #Split Man block. Split Man doesn't get the 1-14 Fearsome effect. if math.floor(SMhp_roll) >= 15: @@ -1286,20 +1073,20 @@ Wavering = 1 ResolveMod = .9 if Flail3Head == 1: - hits_until_wavering.append(count/3) + hits_until_wavering.append(math.ceil(count/3)) else: hits_until_wavering.append(count) elif Breaking == 0: Breaking = 1 ResolveMod = .8 if Flail3Head == 1: - hits_until_breaking.append(count/3) + hits_until_breaking.append(math.ceil(count/3)) else: hits_until_breaking.append(count) elif Fleeing == 0: Fleeing = 1 if Flail3Head == 1: - hits_until_fleeing.append(count/3) + hits_until_fleeing.append(math.ceil(count/3)) else: hits_until_fleeing.append(count) if SplitMan == 1: @@ -1328,27 +1115,47 @@ if math.floor(hp_roll) > 0: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) else: if math.floor(hp_roll) >= 15: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) if FirstMoraleCheck == 0 and SplitMan == 1: if math.floor(SMhp_roll) >= 15: FirstMoraleCheck = 1 hits_until_1st_morale.append(count) - + + #Ijirok armor check: + if (hp > 0 or NineLivesMod == 1) and (IjirokHeal10 == 1 or IjirokHeal20 == 1): + if Ijirok1TurnHeal == 1: #Block to apply healing after every attack. + hp = hp + IjirokHealing #Applying Healing + IjirokTotalHeal += IjirokHealing #Tracking HP Healed for later analysis. + if hp > Def_HP: #Block to ensure HP doesn't exceed max. + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + elif Ijirok2TurnHeal == 1: #Block to apply healing every other attack. + if count % 2 == 0: + hp = hp + IjirokHealing + IjirokTotalHeal += IjirokHealing + if hp > Def_HP: + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + #Bleeding check: if (CleaverBleed == 1 or CleaverMastery == 1) and Undead != 1: #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if hp > 0 or NineLivesMod == 1: if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1378,39 +1185,42 @@ else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + Total_Ijirok_Healing.append(IjirokTotalHeal) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) if Flail3Head == 1: - hits_until_death.append(count/3) + hits_until_death.append(math.ceil(count/3)) + #hits_until_death.append(math.ceil(count/3)) else: hits_until_death.append(count) Total_Morale_Checks.append(MoraleChecks) #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. if Injury == 0: if Flail3Head == 1: - hits_until_1st_injury.append(count/3) + hits_until_1st_injury.append(math.ceil(count/3)) else: hits_until_1st_injury.append(count) if HeavyInjuryChance == 0: if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) else: hits_until_1st_heavy_injury_chance.append(count) if Wavering == 0: if Flail3Head == 1: - hits_until_wavering.append(count/3) + hits_until_wavering.append(math.ceil(count/3)) else: hits_until_wavering.append(count) if Breaking == 0: if Flail3Head == 1: - hits_until_breaking.append(count/3) + hits_until_breaking.append(math.ceil(count/3)) else: hits_until_breaking.append(count) if Fleeing == 0: if Flail3Head == 1: - hits_until_fleeing.append(count/3) + hits_until_fleeing.append(math.ceil(count/3)) else: hits_until_fleeing.append(count) - if Fearsome == 1: - NumberFearsomeProcs.append(FearsomeProcs) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1456,9 +1266,15 @@ if Forge == 1: if len(Forge_bonus_armor) != 0: AvgForgeArmor = statistics.mean(Forge_bonus_armor) +if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + if len(Total_Ijirok_Healing) != 0: + AvgIjirokHealing = statistics.mean(Total_Ijirok_Healing) if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) +if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) #Results: if DeathMean == 1: @@ -1467,6 +1283,7 @@ print("StDev: " + str(StDev)) if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) + if Undead != 1 and Savant != 1: #Injury Data Return @@ -1532,8 +1349,12 @@ if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") -if Ambusher == 1: +if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + print(str(AvgIjirokHealing) + " HP healed by Ijirok armor on average.") +if (Ambusher == 1 and len(hits_until_1st_poison) != 0): print("First poison in " + str(hits_to_posion) + " hits on average.") +if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " hits on average.") print("-----") #Added for readability. If this annoys you then remove this line. #CREDITS: @@ -1542,6 +1363,7 @@ #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1635,4 +1457,46 @@ #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#-- Adjusted Fearsome formula to use 15% of Resolve instead of 20% of (Resolve - 10) to account for recent change in game. +#-- Adjusted Ancient Dead and Fallen Hero preset Atk_Resolve values as they were increased in game to counter-act the Fearsome formula nerf. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index 193b8c8..74aabee 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.3: +#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #This version of the calculator will run 35 different enemies against a given defender. @@ -52,6 +52,12 @@ #Traits: Ironjaw = 0 #Reduces injury susceptibility. GloriousEndurance = 0 #The Bear's unique trait. Reduces damage by 5% each time you are hit, up to a 25% max reduction. +#Ijirok Armor: #Choose one between Heal10/Heal20 and one between 1 or 2 Turn Heal interval. +#Note: Ijirok tests are imperfect in a sandbox calculator. 1v1 tests are biased in its favor, while lack of hit chance is biased against it. +IjirokHeal10 = 0 #Ijirok armor passive for one piece. Heals 10 HP at start of player turn. +IjirokHeal20 = 0 #Ijirok armor passive for both pieces. Heals 20 HP at start of player turn. +Ijirok1TurnHeal = 0 #Applies Ijirok healing after every attack. +Ijirok2TurnHeal = 0 #Applies Ijirok healing after every other attack, better simulating 4AP enemies/weapons, or being attacked by multiple enemies per turn. #Defender Preset: Set these values to 1 if you wish to use a defender preset, and 0 otherwise. #A preset will automatically set defender stats and defender perks. @@ -73,7 +79,7 @@ DPreAmbusher = 0 # 40hp, 25/35. DPreShaman = 0 # 70hp, 35/45. DPreOverseer = 0 # 70hp, 120/180. -DPreReaverHeavy = 0 # 80hp, 145/95, Resilient. +DPreReaverHeavy = 0 # 120hp, 145/95, Resilient. DPreChosenLight = 0 # 130hp, 145/140, Forge, Resilient. DPreChosenHeavy = 0 # 130hp, 190/230, Forge, Resilient. DPreBarbKing = 0 # 150hp, 250/270, Forge, Resilient. @@ -82,20 +88,20 @@ DPreBillman = 0 # 70hp, 80/130, Forge. DPreArbalester = 0 # 60hp, 80/65. DPreBannerHeavy = 0 # 80hp, 215/150, SteelBrow. -DPreKnight = 0 # 125hp, 300/300, Forge. -DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow. (-18 Fat) +DPreKnight = 0 # 135hp, 300/300, Forge. +DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow, Resilient. (-18 Fat) DPreZweiHeavy = 0 # 90hp, 160/240, Forge, SteelBrow. -DPreRaiderHeavy = 0 # 70hp, 140/115. +DPreRaiderHeavy = 0 # 75hp, 140/115. DPreMarkman = 0 # 60hp, 45/70. -DPreLeaderHeavy = 0 # 100hp, 250/230, NineLives. +DPreLeaderHeavy = 0 # 100hp, 250/210, NineLives. DPreMercenaryHeavy = 0 # 90hp, 230/260, Forge. DPreMercRange = 0 # 65hp, 115/115, Nimble. (-18 Fat) DPreHedgeKnight = 0 # 150hp, 300/300, Forge, Resilient. -DPreSwordmaster = 0 # 70hp, 70/115, Nimble, SteelBrow. (-15 Fat) +DPreSwordmaster = 0 # 70hp, 80/115, Nimble, SteelBrow. (-16 Fat) DPreMasterArcher = 0 # 80hp, 30/115, Nimble, SteelBrow. (-12 Fat) DPreOutlawHeavy = 0 # 75hp, 125/105. DPreConscript = 0 # 55hp, 105/110, Nimble. (-16 Fat) -DPreOfficer = 0 # 100hp, 290/290, Forge. +DPreOfficer = 0 # 110hp, 290/290, Forge. DPreAssassinHeavy = 0 # 80hp, 140/120, Nimble. (-15 Fat) # ------------------------------------------------------------------------ @@ -127,6 +133,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -211,7 +218,7 @@ #Does not disable perks that shouldn't be active. For example, don't activate Duelist and then check the Chosen Preset. APreAncientSword = 0 #Ancient Dead: 38-43, 20% Ignore, 80% Armor, Fearsome. APreBladedPike = 0 #Ancient Dead: 55-80, 30% Ignore, 125% Armor, 30% Head, Fearsome. -APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 104% Armor, Fearsome. +APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 105% Armor, Fearsome. APreCryptCleaver = 0 #Ancient Dead: 60-80, 25% Ignore, 120% Armor, Fearsome, Cleaver Mastery. APreKhopesh = 0 #Necrosavant: 35-55, 25% Ignore, 120% Armor, HeadHunter, Crippling, Double Grip, CleaverBleed. APreFHGreatAxe = 0 #Fallen Hero: 80-100, 40 %Ignore, 150% Armor, Fearsome, Split Man. @@ -234,14 +241,14 @@ APreLongAxe = 0 #Raider: 70-95, 30% Ignore, 110% Armor, 30% Head, Executioner. APreMedXbow = 0 #Marksman: 40-60, 50% Ignore, 70% Armor, Xbow Mastery. APreNobleSword = 0 #Swordmaster: 45-50, 20% Ignore, 85% Armor, Duelist, Double Grip, Crippling, Executioner. -APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% Armor, Crippling, Executioner, HeadHunter, Master Archer. +APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 60% Armor, Crippling, Executioner, HeadHunter, Master Archer. APrePoleMace = 0 #Conscript: 60-75, 40% Ignore, 120% Armor, 30% Head. APreHandgonne = 0 #Gunner: 35-75, 25% Ignore, 90% Armor, Fearsome. -APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner. +APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner, Cleaver Mastery. APreQatal = 0 #Assassin: 30-45, 20% Ignore, 70% Armor, Duelist, Double Grip, Executioner. APreFDirewolf = 0 #Frenzied Direwolf: 30-50, 20% Ignore, 70% Armor, Executioner, Frenzied Direwolf. APreNachTier3 = 0 #Tier 3 Nachzehrer: 55-80, 10% Ignore, 75% Armor. -APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 140% Armor, Fearsome. +APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 150% Armor, Fearsome. APreUnhold = 0 #Unhold: 40-80, 40% Ignore, 80% Armor, Crippling. APreSchrat = 0 #Schrat: 70-100, 50% Ignore, 80% Armor, Crippling. @@ -253,7 +260,7 @@ def PresetCalc(): if APreBladedPike == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 30, 30, 125, 1 if APreWarscytheAoE == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 104, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 105, 1 if APreCryptCleaver == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, CleaverMastery = 60, 80, 25, 25, 120, 1, 1 if APreKhopesh == 1: @@ -299,7 +306,7 @@ def PresetCalc(): if APreNobleSword == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Duelist, DoubleGrip, CripplingStrikes, Executioner = 45, 50, 25, 20, 85, 1, 1, 1, 1 if APreWarbow == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 65, 1, 1, 1, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 60, 1, 1, 1, 1 if APrePoleMace == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 60, 75, 30, 40, 120 if APreHandgonne == 1: @@ -313,7 +320,7 @@ def PresetCalc(): if APreNachTier3 == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 55, 80, 25, 10, 75 if APreLindwurm == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 140, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 150, 1 if APreUnhold == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes = 40, 80, 25, 40, 80, 1 if APreSchrat == 1: @@ -355,7 +362,7 @@ def PresetCalc(): if DPreOverseer == 1: Def_HP, Def_Helmet, Def_Armor = 70, 120, 180 if DPreReaverHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Resilient = 80, 145, 95, 1 + Def_HP, Def_Helmet, Def_Armor, Resilient = 120, 145, 95, 1 if DPreChosenLight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 130, 145, 140, 1, 1 if DPreChosenHeavy == 1: @@ -373,17 +380,17 @@ def PresetCalc(): if DPreBannerHeavy == 1: Def_HP, Def_Helmet, Def_Armor, SteelBrow = 80, 215, 150, 1 if DPreKnight == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 125, 300, 300, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 135, 300, 300, 1 if DPreSergeant == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 100, 0, 150, -18, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow, Resilient = 100, 0, 150, -18, 1, 1, 1 if DPreZweiHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge, SteelBrow = 90, 160, 240, 1, 1 if DPreRaiderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor = 70, 140, 115 + Def_HP, Def_Helmet, Def_Armor = 75, 140, 115 if DPreMarkman == 1: Def_HP, Def_Helmet, Def_Armor = 60, 45, 70 if DPreLeaderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 230, 1 + Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 210, 1 if DPreMercenaryHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge = 90, 230, 260, 1 if DPreMercRange == 1: @@ -391,7 +398,7 @@ def PresetCalc(): if DPreHedgeKnight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 150, 300, 300, 1, 1 if DPreSwordmaster == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 70, 115, -15, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 80, 115, -16, 1, 1 if DPreMasterArcher == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 80, 30, 115, -12, 1, 1 if DPreOutlawHeavy == 1: @@ -399,7 +406,7 @@ def PresetCalc(): if DPreConscript == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 55, 105, 110, -16, 1 if DPreOfficer == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 100, 290, 290, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 110, 290, 290, 1 if DPreAssassinHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 80, 140, 120, -15, 1 @@ -543,6 +550,8 @@ def calc(): Ignore *= 1.25 if Duelist == 1: Ignore += .25 + if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -627,6 +636,23 @@ def calc(): else: AimedShotMod = 1 + #Injury rate modifiers: + InjuryMod = 1 + if CripplingStrikes == 1: + InjuryMod *= 0.66 + if Shamshir == 1: + InjuryMod *= 0.66 + if ShamshirMastery == 1: + InjuryMod *= 0.5 + if Ironjaw == 1: + InjuryMod *= 1.25 + + #Ijirok: + if IjirokHeal10 == 1: + IjirokHealing = 10 + if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -643,7 +669,9 @@ def calc(): hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). - hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) + Total_Ijirok_Healing = [] #This list will hold the amount of total Ijirok healing from the Ijirok armor for each iteration (only displays if Ijirok switches are checked). + hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). + hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) NimbleCalc() @@ -668,6 +696,11 @@ def calc(): NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 HeavyInjuryChance = 0 UseHeadShotInjuryFormula = 0 @@ -678,7 +711,9 @@ def calc(): Bleedstack2T = 0 ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. - + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. + IjirokTotalHeal = 0 #Tracker for amount of Ijirok healing received. + count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. while hp > 0: #Continue looping until death. @@ -692,7 +727,7 @@ def calc(): DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -712,12 +747,7 @@ def calc(): ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (count * 2)) - else: - GladMod = 1 - (.05 * count) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -731,6 +761,9 @@ def calc(): Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. @@ -745,464 +778,223 @@ def calc(): HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HHeadshot + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. - armor_roll = min(helmet,(armor_roll * ForgeMod)) #Applying Forge, and armor damage cannot exceed current armor. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp. + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 - count += 1 #Add +1 to the number of hits taken. + count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) UseHeadShotInjuryFormula = 0 - else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - - #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body injury formula. + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + + #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan + if SplitMan == 1: + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 + #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: if math.floor(hp_roll) > 0: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) else: if math.floor(hp_roll) >= 15: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) @@ -1216,11 +1008,31 @@ def calc(): if math.floor(hp_roll) > 0 and math.floor(hp_roll) < 15: FearsomeProcs += 1 + #Ijirok armor check: + if (hp > 0 or NineLivesMod == 1) and (IjirokHeal10 == 1 or IjirokHeal20 == 1): + if Ijirok1TurnHeal == 1: #Block to apply healing after every attack. + hp = hp + IjirokHealing #Applying Healing + IjirokTotalHeal += IjirokHealing #Tracking HP Healed for later analysis. + if hp > Def_HP: #Block to ensure HP doesn't exceed max. + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + elif Ijirok2TurnHeal == 1: #Block to apply healing every other attack. + if count % 2 == 0: + hp = hp + IjirokHealing + IjirokTotalHeal += IjirokHealing + if hp > Def_HP: + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + #Bleeding check: if (CleaverBleed == 1 or CleaverMastery == 1) and Undead != 1: #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1247,21 +1059,28 @@ def calc(): NineLivesMod = 0 Bleedstack1T = 0 Bleedstack2T = 0 - elif Fearsome == 1: - if Forge == 1: - Forge_bonus_armor.append(ForgeSaved) - if Flail3Head == 1: - hits_until_death.append(count/3) - else: - hits_until_death.append(count) - NumberFearsomeProcs.append(FearsomeProcs) else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + Total_Ijirok_Healing.append(IjirokTotalHeal) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) if Flail3Head == 1: - hits_until_death.append(count/3) + hits_until_death.append(math.ceil(count/3)) else: hits_until_death.append(count) + #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. + if Injury == 0: + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1291,9 +1110,15 @@ def calc(): if Forge == 1: if len(Forge_bonus_armor) != 0: AvgForgeArmor = statistics.mean(Forge_bonus_armor) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + if len(Total_Ijirok_Healing) != 0: + AvgIjirokHealing = statistics.mean(Total_Ijirok_Healing) if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) + if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) #Results: if DeathMean == 1: @@ -1303,7 +1128,7 @@ def calc(): if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) if Undead != 1 and Savant != 1: - if len(hits_until_1st_injury) == 0: + if hits_to_injure >= HitsToDeath: if InjuryMean == 1 or InjuryPercent == 1: print("No chance of injury.") else: @@ -1311,7 +1136,7 @@ def calc(): print("First injury in " + str(hits_to_injure) + " hits on average.") if InjuryPercent == 1: print("% First injury in: " + str(HitsToInjurePercent)) - if len(hits_until_1st_heavy_injury_chance) == 0: + if hits_to_1st_heavy_injury_chance >= HitsToDeath: if HeavyInjuryMean == 1 or HeavyInjuryPercent == 1: print("No chance of heavy injury.") else: @@ -1328,8 +1153,12 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") - if Ambusher == 1: + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + print(str(AvgIjirokHealing) + " HP healed by Ijirok armor on average.") + if (Ambusher == 1 and len(hits_until_1st_poison) != 0): print("First poison in " + str(hits_to_posion) + " hits on average.") + if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " hits on average.") print("-----") #Added for readability. If this annoys you then remove this line. #The following will repeatedly run the scenario with different attackers. @@ -1609,6 +1438,7 @@ def calc(): #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1687,4 +1517,44 @@ def calc(): #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. \ No newline at end of file diff --git a/BBHitChance.py b/BBHitChance.py index 00b2a30..4bd6262 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.3: +#Battle Brothers Damage Calculator -- Hit Chance Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #This version of the calculator is a very basic addition of Hit Chance to the regular calculator. @@ -61,6 +61,12 @@ #Traits: Ironjaw = 0 #Reduces injury susceptibility. GloriousEndurance = 0 #The Bear's unique trait. Reduces damage by 5% each time you are hit, up to a 25% max reduction. +#Ijirok Armor: #Choose one between Heal10/Heal20 and one between 1 or 2 Turn Heal interval. +#Note: Ijirok tests are imperfect in a sandbox calculator. 1v1 tests are biased in its favor, while lack of hit chance is biased against it. +IjirokHeal10 = 0 #Ijirok armor passive for one piece. Heals 10 HP at start of player turn. +IjirokHeal20 = 0 #Ijirok armor passive for both pieces. Heals 20 HP at start of player turn. +Ijirok1TurnHeal = 0 #Applies Ijirok healing after every attack. +Ijirok2TurnHeal = 0 #Applies Ijirok healing after every other attack, better simulating 4AP enemies/weapons, or being attacked by multiple enemies per turn. #ATTACKER FLAGS: Set these values to 1 if they apply and 0 otherwise. If you select a Preset then leave these on 0. #Weapons: @@ -74,6 +80,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -158,7 +165,7 @@ #Does not disable perks that shouldn't be active. For example, don't activate Duelist and then check the Chosen Preset. APreAncientSword = 0 #Ancient Dead: 38-43, 20% Ignore, 80% Armor, Fearsome. APreBladedPike = 0 #Ancient Dead: 55-80, 30% Ignore, 125% Armor, 30% Head, Fearsome. -APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 104% Armor, Fearsome. +APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 105% Armor, Fearsome. APreCryptCleaver = 0 #Ancient Dead: 60-80, 25% Ignore, 120% Armor, Fearsome, Cleaver Mastery. APreKhopesh = 0 #Necrosavant: 35-55, 25% Ignore, 120% Armor, HeadHunter, Crippling, Double Grip, CleaverBleed. APreFHGreatAxe = 0 #Fallen Hero: 80-100, 40 %Ignore, 150% Armor, Fearsome, Split Man. @@ -181,14 +188,14 @@ APreLongAxe = 0 #Raider: 70-95, 30% Ignore, 110% Armor, 30% Head, Executioner. APreMedXbow = 0 #Marksman: 40-60, 50% Ignore, 70% Armor, Xbow Mastery. APreNobleSword = 0 #Swordmaster: 45-50, 20% Ignore, 85% Armor, Duelist, Double Grip, Crippling, Executioner. -APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% Armor, Crippling, Executioner, HeadHunter, Master Archer. +APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 60% Armor, Crippling, Executioner, HeadHunter, Master Archer. APrePoleMace = 0 #Conscript: 60-75, 40% Ignore, 120% Armor, 30% Head. APreHandgonne = 0 #Gunner: 35-75, 25% Ignore, 90% Armor, Fearsome. -APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner. +APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner, Cleaver Mastery. APreQatal = 0 #Assassin: 30-45, 20% Ignore, 70% Armor, Duelist, Double Grip, Executioner. APreFDirewolf = 0 #Frenzied Direwolf: 30-50, 20% Ignore, 70% Armor, Executioner, Frenzied Direwolf. APreNachTier3 = 0 #Tier 3 Nachzehrer: 55-80, 10% Ignore, 75% Armor. -APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 140% Armor, Fearsome. +APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 150% Armor, Fearsome. APreUnhold = 0 #Unhold: 40-80, 40% Ignore, 80% Armor, Crippling. APreSchrat = 0 #Schrat: 70-100, 50% Ignore, 80% Armor, Crippling. @@ -212,7 +219,7 @@ DPreAmbusher = 0 # 40hp, 25/35. DPreShaman = 0 # 70hp, 35/45. DPreOverseer = 0 # 70hp, 120/180. -DPreReaverHeavy = 0 # 80hp, 145/95, Resilient. +DPreReaverHeavy = 0 # 120hp, 145/95, Resilient. DPreChosenLight = 0 # 130hp, 145/140, Forge, Resilient. DPreChosenHeavy = 0 # 130hp, 190/230, Forge, Resilient. DPreBarbKing = 0 # 150hp, 250/270, Forge, Resilient. @@ -221,20 +228,20 @@ DPreBillman = 0 # 70hp, 80/130, Forge. DPreArbalester = 0 # 60hp, 80/65. DPreBannerHeavy = 0 # 80hp, 215/150, SteelBrow. -DPreKnight = 0 # 125hp, 300/300, Forge. -DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow. (-18 Fat) +DPreKnight = 0 # 135hp, 300/300, Forge. +DPreSergeant = 0 # 100hp, 0/150, Nimble, SteelBrow, Resilient. (-18 Fat) DPreZweiHeavy = 0 # 90hp, 160/240, Forge, SteelBrow. -DPreRaiderHeavy = 0 # 70hp, 140/115. +DPreRaiderHeavy = 0 # 75hp, 140/115. DPreMarkman = 0 # 60hp, 45/70. -DPreLeaderHeavy = 0 # 100hp, 250/230, NineLives. +DPreLeaderHeavy = 0 # 100hp, 250/210, NineLives. DPreMercenaryHeavy = 0 # 90hp, 230/260, Forge. DPreMercRange = 0 # 65hp, 115/115, Nimble. (-18 Fat) DPreHedgeKnight = 0 # 150hp, 300/300, Forge, Resilient. -DPreSwordmaster = 0 # 70hp, 70/115, Nimble, SteelBrow. (-15 Fat) +DPreSwordmaster = 0 # 70hp, 80/115, Nimble, SteelBrow. (-16 Fat) DPreMasterArcher = 0 # 80hp, 30/115, Nimble, SteelBrow. (-12 Fat) DPreOutlawHeavy = 0 # 75hp, 125/105. DPreConscript = 0 # 55hp, 105/110, Nimble. (-16 Fat) -DPreOfficer = 0 # 100hp, 290/290, Forge. +DPreOfficer = 0 # 110hp, 290/290, Forge. DPreAssassinHeavy = 0 # 80hp, 140/120, Nimble. (-15 Fat) # ------------------------------------------------------------------------ @@ -254,7 +261,7 @@ if APreBladedPike == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 30, 30, 125, 1 if APreWarscytheAoE == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 104, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 105, 1 if APreCryptCleaver == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, CleaverMastery = 60, 80, 25, 25, 120, 1, 1 if APreKhopesh == 1: @@ -300,7 +307,7 @@ if APreNobleSword == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Duelist, DoubleGrip, CripplingStrikes, Executioner = 45, 50, 25, 20, 85, 1, 1, 1, 1 if APreWarbow == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 65, 1, 1, 1, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 60, 1, 1, 1, 1 if APrePoleMace == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 60, 75, 30, 40, 120 if APreHandgonne == 1: @@ -314,7 +321,7 @@ if APreNachTier3 == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 55, 80, 25, 10, 75 if APreLindwurm == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 140, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 150, 1 if APreUnhold == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes = 40, 80, 25, 40, 80, 1 if APreSchrat == 1: @@ -356,7 +363,7 @@ if DPreOverseer == 1: Def_HP, Def_Helmet, Def_Armor = 70, 120, 180 if DPreReaverHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, Resilient = 80, 145, 95, 1 + Def_HP, Def_Helmet, Def_Armor, Resilient = 120, 145, 95, 1 if DPreChosenLight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 130, 145, 140, 1, 1 if DPreChosenHeavy == 1: @@ -374,17 +381,17 @@ if DPreBannerHeavy == 1: Def_HP, Def_Helmet, Def_Armor, SteelBrow = 80, 215, 150, 1 if DPreKnight == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 125, 300, 300, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 135, 300, 300, 1 if DPreSergeant == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 100, 0, 150, -18, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow, Resilient = 100, 0, 150, -18, 1, 1, 1 if DPreZweiHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge, SteelBrow = 90, 160, 240, 1, 1 if DPreRaiderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor = 70, 140, 115 + Def_HP, Def_Helmet, Def_Armor = 75, 140, 115 if DPreMarkman == 1: Def_HP, Def_Helmet, Def_Armor = 60, 45, 70 if DPreLeaderHeavy == 1: - Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 230, 1 + Def_HP, Def_Helmet, Def_Armor, NineLives = 100, 250, 210, 1 if DPreMercenaryHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Forge = 90, 230, 260, 1 if DPreMercRange == 1: @@ -392,7 +399,7 @@ if DPreHedgeKnight == 1: Def_HP, Def_Helmet, Def_Armor, Forge, Resilient = 150, 300, 300, 1, 1 if DPreSwordmaster == 1: - Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 70, 115, -15, 1, 1 + Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 70, 80, 115, -16, 1, 1 if DPreMasterArcher == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble, SteelBrow = 80, 30, 115, -12, 1, 1 if DPreOutlawHeavy == 1: @@ -400,7 +407,7 @@ if DPreConscript == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 55, 105, 110, -16, 1 if DPreOfficer == 1: - Def_HP, Def_Helmet, Def_Armor, Forge = 100, 290, 290, 1 + Def_HP, Def_Helmet, Def_Armor, Forge = 110, 290, 290, 1 if DPreAssassinHeavy == 1: Def_HP, Def_Helmet, Def_Armor, Fatigue, Nimble = 80, 140, 120, -15, 1 @@ -465,6 +472,8 @@ Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -607,6 +616,17 @@ else: AimedShotMod = 1 +#Injury rate modifiers: +InjuryMod = 1 +if CripplingStrikes == 1: + InjuryMod *= 0.66 +if Shamshir == 1: + InjuryMod *= 0.66 +if ShamshirMastery == 1: + InjuryMod *= 0.5 +if Ironjaw == 1: + InjuryMod *= 1.25 + #Indomitable: if Indomitable == 1: IndomMod = .5 @@ -625,6 +645,12 @@ else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -641,7 +667,9 @@ hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). -hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) +Total_Ijirok_Healing = [] #This list will hold the amount of total Ijirok healing from the Ijirok armor for each iteration (only displays if Ijirok switches are checked). +hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). +hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). print("-----") #Added for readability. If this annoys you then remove this line. print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) @@ -667,6 +695,11 @@ NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 HeavyInjuryChance = 0 UseHeadShotInjuryFormula = 0 @@ -677,9 +710,10 @@ Bleedstack2T = 0 ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. - + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. + IjirokTotalHeal = 0 #Tracker for amount of Ijirok healing received. + count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. - Hits = 0 #Unique to this calculator. Used for Glorious Endurance. Starts at 0 and goes up after each hit. while hp > 0: #Continue looping until death. #Check various modifiers that change over the course of one's life. These will be re-checked after each attack. @@ -692,7 +726,7 @@ DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -712,12 +746,7 @@ ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (Hits * 2)) - else: - GladMod = 1 - (.05 * Hits) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -731,11 +760,13 @@ Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod HitChanceCheck = random.randint(1,100) #Random roll to determine hit chance check. if HitChanceCheck <= (min(95,HitChance + FastAdMod)): #If hit chance roll is lower or equal to hit chance, hit is successful. FastAdMod = 0 #Reset FastAd because of successful hit. - Hits += 1 #Unique to this version of the calculator. Used for Glorious Endurance. #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. head_roll = random.randint(1,100) #Random roll to determine if hit is a headshot. @@ -749,468 +780,228 @@ HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HHeadshot + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. - armor_roll = min(helmet,(armor_roll * ForgeMod)) #Applying Forge, and armor damage cannot exceed current armor. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 + else: #This block is run if attack misses. hp_roll = 0 if FastAdaptation == 1: #If Fast Adaptation is selected, gain a stack. FastAdMod += 10 count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) UseHeadShotInjuryFormula = 0 - else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - - #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body injury formula. + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + + #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan. This will only run if the primary attack didn't miss. + if SplitMan == 1 and (SplitManBodyFollowUp == 1 or SplitManHeadFollowUp == 1): + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 + #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: if math.floor(hp_roll) > 0: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) else: if math.floor(hp_roll) >= 15: FirstMoraleCheck = 1 if Flail3Head == 1: - hits_until_1st_morale.append(count/3) + hits_until_1st_morale.append(math.ceil(count/3)) else: hits_until_1st_morale.append(count) @@ -1224,12 +1015,31 @@ if math.floor(hp_roll) > 0 and math.floor(hp_roll) < 15: FearsomeProcs += 1 + #Ijirok armor check: + if (hp > 0 or NineLivesMod == 1) and (IjirokHeal10 == 1 or IjirokHeal20 == 1): + if Ijirok1TurnHeal == 1: #Block to apply healing after every attack. + hp = hp + IjirokHealing #Applying Healing + IjirokTotalHeal += IjirokHealing #Tracking HP Healed for later analysis. + if hp > Def_HP: #Block to ensure HP doesn't exceed max. + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + elif Ijirok2TurnHeal == 1: #Block to apply healing every other attack. + if count % 2 == 0: + hp = hp + IjirokHealing + IjirokTotalHeal += IjirokHealing + if hp > Def_HP: + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP #Bleeding check: if (CleaverBleed == 1 or CleaverMastery == 1) and Undead != 1: #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1256,21 +1066,28 @@ NineLivesMod = 0 Bleedstack1T = 0 Bleedstack2T = 0 - elif Fearsome == 1: - if Forge == 1: - Forge_bonus_armor.append(ForgeSaved) - if Flail3Head == 1: - hits_until_death.append(count/3) - else: - hits_until_death.append(count) - NumberFearsomeProcs.append(FearsomeProcs) else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + Total_Ijirok_Healing.append(IjirokTotalHeal) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) if Flail3Head == 1: - hits_until_death.append(count/3) + hits_until_death.append(math.ceil(count/3)) else: hits_until_death.append(count) + #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. + if Injury == 0: + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1299,9 +1116,15 @@ if Forge == 1: if len(Forge_bonus_armor) != 0: AvgForgeArmor = statistics.mean(Forge_bonus_armor) +if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + if len(Total_Ijirok_Healing) != 0: + AvgIjirokHealing = statistics.mean(Total_Ijirok_Healing) if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) +if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) #Results: if DeathMean == 1: @@ -1311,7 +1134,7 @@ if DeathPercent == 1: print("% Swings to die: " + str(HitsToDeathPercent)) if Undead != 1 and Savant != 1: - if len(hits_until_1st_injury) == 0: + if hits_to_injure >= HitsToDeath: if InjuryMean == 1 or InjuryPercent == 1: print("No chance of injury.") else: @@ -1319,7 +1142,7 @@ print("First injury in " + str(hits_to_injure) + " swings on average.") if InjuryPercent == 1: print("% First injury in: " + str(HitsToInjurePercent)) - if len(hits_until_1st_heavy_injury_chance) == 0: + if hits_to_1st_heavy_injury_chance >= HitsToDeath: if HeavyInjuryMean == 1 or HeavyInjuryPercent == 1: print("No chance of heavy injury.") else: @@ -1336,8 +1159,12 @@ print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") -if Ambusher == 1: - print("First poison in " + str(hits_to_posion) + " hits on average.") +if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + print(str(AvgIjirokHealing) + " HP healed by Ijirok armor on average.") +if (Ambusher == 1 and len(hits_until_1st_poison) != 0): + print("First poison in " + str(hits_to_posion) + " swings on average.") +if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " swings on average.") print("-----") #Added for readability. If this annoys you then remove this line. #CREDITS: @@ -1346,6 +1173,7 @@ #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1419,4 +1247,44 @@ #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. \ No newline at end of file diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index 27b9e35..a84e252 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.3: +#Battle Brothers Damage Calculator -- Nimble Battery Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #The calculator expects you to make smart decisions, such as not giving Xbow Mastery to a Hammer. @@ -69,6 +69,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -152,7 +153,7 @@ #Does not disable perks that shouldn't be active. For example, don't activate Duelist and then check the Chosen Preset. APreAncientSword = 0 #Ancient Dead: 38-43, 20% Ignore, 80% Armor, Fearsome. APreBladedPike = 0 #Ancient Dead: 55-80, 30% Ignore, 125% Armor, 30% Head, Fearsome. -APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 104% Armor, Fearsome. +APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 105% Armor, Fearsome. APreCryptCleaver = 0 #Ancient Dead: 60-80, 25% Ignore, 120% Armor, Fearsome, Cleaver Mastery. APreKhopesh = 0 #Necrosavant: 35-55, 25% Ignore, 120% Armor, HeadHunter, Crippling, Double Grip, CleaverBleed. APreFHGreatAxe = 0 #Fallen Hero: 80-100, 40 %Ignore, 150% Armor, Fearsome, Split Man. @@ -175,14 +176,14 @@ APreLongAxe = 0 #Raider: 70-95, 30% Ignore, 110% Armor, 30% Head, Executioner. APreMedXbow = 0 #Marksman: 40-60, 50% Ignore, 70% Armor, Xbow Mastery. APreNobleSword = 0 #Swordmaster: 45-50, 20% Ignore, 85% Armor, Duelist, Double Grip, Crippling, Executioner. -APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% Armor, Crippling, Executioner, HeadHunter, Master Archer. +APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 60% Armor, Crippling, Executioner, HeadHunter, Master Archer. APrePoleMace = 0 #Conscript: 60-75, 40% Ignore, 120% Armor, 30% Head. APreHandgonne = 0 #Gunner: 35-75, 25% Ignore, 90% Armor, Fearsome. -APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner. +APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner, Cleaver Mastery. APreQatal = 0 #Assassin: 30-45, 20% Ignore, 70% Armor, Duelist, Double Grip, Executioner. APreFDirewolf = 0 #Frenzied Direwolf: 30-50, 20% Ignore, 70% Armor, Executioner, Frenzied Direwolf. APreNachTier3 = 0 #Tier 3 Nachzehrer: 55-80, 10% Ignore, 75% Armor. -APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 140% Armor, Fearsome. +APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 150% Armor, Fearsome. APreUnhold = 0 #Unhold: 40-80, 40% Ignore, 80% Armor, Crippling. APreSchrat = 0 #Schrat: 70-100, 50% Ignore, 80% Armor, Crippling. @@ -203,7 +204,7 @@ if APreBladedPike == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 30, 30, 125, 1 if APreWarscytheAoE == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 104, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 105, 1 if APreCryptCleaver == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, CleaverMastery = 60, 80, 25, 25, 120, 1, 1 if APreKhopesh == 1: @@ -249,7 +250,7 @@ if APreNobleSword == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Duelist, DoubleGrip, CripplingStrikes, Executioner = 45, 50, 25, 20, 85, 1, 1, 1, 1 if APreWarbow == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 65, 1, 1, 1, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 60, 1, 1, 1, 1 if APrePoleMace == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 60, 75, 30, 40, 120 if APreHandgonne == 1: @@ -263,7 +264,7 @@ if APreNachTier3 == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 55, 80, 25, 10, 75 if APreLindwurm == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 140, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 150, 1 if APreUnhold == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes = 40, 80, 25, 40, 80, 1 if APreSchrat == 1: @@ -328,6 +329,8 @@ Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -501,6 +504,17 @@ def NimbleCalc(): else: AimedShotMod = 1 +#Injury rate modifiers: +InjuryMod = 1 +if CripplingStrikes == 1: + InjuryMod *= 0.66 +if Shamshir == 1: + InjuryMod *= 0.66 +if ShamshirMastery == 1: + InjuryMod *= 0.5 +if Ironjaw == 1: + InjuryMod *= 1.25 + #Indomitable: if Indomitable == 1: IndomMod = .5 @@ -531,7 +545,7 @@ def NimbleCalc(): print("-----") #Added for readability. If this annoys you then remove this line. def calc(): - global Headshotchance,Ignore + global Headshotchance,Ignore, HHStack #Lists for later analysis: hits_until_death = [] #This list will hold how many hits until death for each iteration. @@ -540,7 +554,8 @@ def calc(): hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). - hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) + hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). + hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). AttachmentCalc() print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) @@ -565,6 +580,11 @@ def calc(): NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 HeavyInjuryChance = 0 UseHeadShotInjuryFormula = 0 @@ -575,7 +595,8 @@ def calc(): Bleedstack2T = 0 ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. - + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. + count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. while hp > 0: #Continue looping until death. @@ -589,7 +610,7 @@ def calc(): DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -609,12 +630,7 @@ def calc(): ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (count * 2)) - else: - GladMod = 1 - (.05 * count) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -628,6 +644,9 @@ def calc(): Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. @@ -642,450 +661,209 @@ def calc(): HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HHeadshot + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod - ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers + ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp. + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) UseHeadShotInjuryFormula = 0 - else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - - #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body injury formula. + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + + #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan + if SplitMan == 1: + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 + #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: @@ -1112,6 +890,10 @@ def calc(): #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1138,15 +920,26 @@ def calc(): NineLivesMod = 0 Bleedstack1T = 0 Bleedstack2T = 0 - elif Fearsome == 1: - if Forge == 1: - Forge_bonus_armor.append(ForgeSaved) - hits_until_death.append(count) - NumberFearsomeProcs.append(FearsomeProcs) else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) - hits_until_death.append(count) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) + if Flail3Head == 1: + hits_until_death.append(math.ceil(count/3)) + else: + hits_until_death.append(count) + #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. + if Injury == 0: + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1178,6 +971,10 @@ def calc(): if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) + if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) + #Results: if DeathMean == 1: print("Death in " + str(HitsToDeath) + " hits on average.") @@ -1186,7 +983,7 @@ def calc(): if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) if Undead != 1 and Savant != 1: - if len(hits_until_1st_injury) == 0: + if hits_to_injure >= HitsToDeath: if InjuryMean == 1 or InjuryPercent == 1: print("No chance of injury.") else: @@ -1194,7 +991,7 @@ def calc(): print("First injury in " + str(hits_to_injure) + " hits on average.") if InjuryPercent == 1: print("% First injury in: " + str(HitsToInjurePercent)) - if len(hits_until_1st_heavy_injury_chance) == 0: + if hits_to_1st_heavy_injury_chance >= HitsToDeath: if HeavyInjuryMean == 1 or HeavyInjuryPercent == 1: print("No chance of heavy injury.") else: @@ -1211,8 +1008,10 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") - if Ambusher == 1: + if (Ambusher == 1 and len(hits_until_1st_poison) != 0): print("First poison in " + str(hits_to_posion) + " hits on average.") + if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " hits on average.") print("-----") #Added for readability. If this annoys you then remove this line. #The following will repeatedly run the scenario with different armor options. @@ -1302,6 +1101,7 @@ def calc(): #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1376,4 +1176,45 @@ def calc(): #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#-- Fixed an error where HeadHunter tests were causing the calculator to break. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests in the other calculators. This variant remains unchanged. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. \ No newline at end of file diff --git a/BBRaisingHp.py b/BBRaisingHp.py index f261afb..fbb3f25 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- HP Changing Version 1.6.3: +#Battle Brothers Damage Calculator -- HP Changing Version 1.7.1: #Welcome. Modify the below values as necessary until you reach the line ----- break. #The calculator expects you to make smart decisions, such as not giving Xbow Mastery to a Hammer. @@ -56,6 +56,12 @@ #Traits: Ironjaw = 0 #Reduces injury susceptibility. GloriousEndurance = 0 #The Bear's unique trait. Reduces damage by 5% each time you are hit, up to a 25% max reduction. +#Ijirok Armor: #Choose one between Heal10/Heal20 and one between 1 or 2 Turn Heal interval. +#Note: Ijirok tests are imperfect in a sandbox calculator. 1v1 tests are biased in its favor, while lack of hit chance is biased against it. +IjirokHeal10 = 0 #Ijirok armor passive for one piece. Heals 10 HP at start of player turn. +IjirokHeal20 = 0 #Ijirok armor passive for both pieces. Heals 20 HP at start of player turn. +Ijirok1TurnHeal = 0 #Applies Ijirok healing after every attack. +Ijirok2TurnHeal = 0 #Applies Ijirok healing after every other attack, better simulating 4AP enemies/weapons, or being attacked by multiple enemies per turn. #ATTACKER FLAGS: Set these values to 1 if they apply and 0 otherwise. If you select a Preset then leave these on 0. #Weapons: @@ -69,6 +75,7 @@ DestroyArmor = 0 #Will use Destroy Armor once and then switch to normal attacks. DestroyArmorMastery = 0 #Hammer Mastery. Will use Destroy Armor once and then switch to normal attacks. DestroyArmorTwice = 0 #Uses Destroy Armor two times instead of 1. Does nothing unless DestroyArmor or DestroyArmorMastery are set. +AoE2HHammer = 0 #Applies to Shatter, reduces Ignore by 10%. Axe1H = 0 #Applies bonus damage to Headshots. Gets negated by SteelBrow. SplitMan = 0 #Applies to single target 2HAxe except for Longaxe. AoE2HAxe = 0 #Applies to Round Swing and Split in Two (Bardiche), reduces Ignore by 10%. @@ -152,7 +159,7 @@ #Does not disable perks that shouldn't be active. For example, don't activate Duelist and then check the Chosen Preset. APreAncientSword = 0 #Ancient Dead: 38-43, 20% Ignore, 80% Armor, Fearsome. APreBladedPike = 0 #Ancient Dead: 55-80, 30% Ignore, 125% Armor, 30% Head, Fearsome. -APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 104% Armor, Fearsome. +APreWarscytheAoE = 0 #Ancient Dead: 55-80, 25% Ignore, 105% Armor, Fearsome. APreCryptCleaver = 0 #Ancient Dead: 60-80, 25% Ignore, 120% Armor, Fearsome, Cleaver Mastery. APreKhopesh = 0 #Necrosavant: 35-55, 25% Ignore, 120% Armor, HeadHunter, Crippling, Double Grip, CleaverBleed. APreFHGreatAxe = 0 #Fallen Hero: 80-100, 40 %Ignore, 150% Armor, Fearsome, Split Man. @@ -175,14 +182,14 @@ APreLongAxe = 0 #Raider: 70-95, 30% Ignore, 110% Armor, 30% Head, Executioner. APreMedXbow = 0 #Marksman: 40-60, 50% Ignore, 70% Armor, Xbow Mastery. APreNobleSword = 0 #Swordmaster: 45-50, 20% Ignore, 85% Armor, Duelist, Double Grip, Crippling, Executioner. -APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% Armor, Crippling, Executioner, HeadHunter, Master Archer. +APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 60% Armor, Crippling, Executioner, HeadHunter, Master Archer. APrePoleMace = 0 #Conscript: 60-75, 40% Ignore, 120% Armor, 30% Head. APreHandgonne = 0 #Gunner: 35-75, 25% Ignore, 90% Armor, Fearsome. -APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner. +APre2HScimitar = 0 #Officer: 65-85, 25% Ignore, 110% Armor, Crippling, Executioner, Cleaver Mastery. APreQatal = 0 #Assassin: 30-45, 20% Ignore, 70% Armor, Duelist, Double Grip, Executioner. APreFDirewolf = 0 #Frenzied Direwolf: 30-50, 20% Ignore, 70% Armor, Executioner, Frenzied Direwolf. APreNachTier3 = 0 #Tier 3 Nachzehrer: 55-80, 10% Ignore, 75% Armor. -APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 140% Armor, Fearsome. +APreLindwurm = 0 #Lindwurm Head: 80-140, 35% Ignore, 150% Armor, Fearsome. APreUnhold = 0 #Unhold: 40-80, 40% Ignore, 80% Armor, Crippling. APreSchrat = 0 #Schrat: 70-100, 50% Ignore, 80% Armor, Crippling. @@ -203,7 +210,7 @@ if APreBladedPike == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 30, 30, 125, 1 if APreWarscytheAoE == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 104, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 55, 80, 25, 25, 105, 1 if APreCryptCleaver == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, CleaverMastery = 60, 80, 25, 25, 120, 1, 1 if APreKhopesh == 1: @@ -249,7 +256,7 @@ if APreNobleSword == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Duelist, DoubleGrip, CripplingStrikes, Executioner = 45, 50, 25, 20, 85, 1, 1, 1, 1 if APreWarbow == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 65, 1, 1, 1, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes, Executioner, MasterArcher, HeadHunter = 50, 70, 25, 35, 60, 1, 1, 1, 1 if APrePoleMace == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 60, 75, 30, 40, 120 if APreHandgonne == 1: @@ -263,7 +270,7 @@ if APreNachTier3 == 1: Mind, Maxd, Headchance, Ignore, ArmorMod = 55, 80, 25, 10, 75 if APreLindwurm == 1: - Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 140, 1 + Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome = 80, 140, 25, 35, 150, 1 if APreUnhold == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, CripplingStrikes = 40, 80, 25, 40, 80, 1 if APreSchrat == 1: @@ -328,6 +335,8 @@ Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -472,6 +481,17 @@ def NimbleCalc(): else: AimedShotMod = 1 +#Injury rate modifiers: +InjuryMod = 1 +if CripplingStrikes == 1: + InjuryMod *= 0.66 +if Shamshir == 1: + InjuryMod *= 0.66 +if ShamshirMastery == 1: + InjuryMod *= 0.5 +if Ironjaw == 1: + InjuryMod *= 1.25 + #Indomitable: if Indomitable == 1: IndomMod = .5 @@ -490,6 +510,12 @@ def NimbleCalc(): else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -502,7 +528,7 @@ def NimbleCalc(): print("-----") #Added for readability. If this annoys you then remove this line. def calc(): - global Headshotchance,Ignore + global Headshotchance,Ignore, HHStack #Lists for later analysis: hits_until_death = [] #This list will hold how many hits until death for each iteration. @@ -511,7 +537,9 @@ def calc(): hits_until_1st_morale = [] #This list will hold how many hits until first morale check for each iteration. NumberFearsomeProcs = [] #This list will hold number of Fearsome procs for each iteration (only displays if Fearsome is checked). Forge_bonus_armor = [] #This list will hold the amount of extra armor provided by Forge for each iteration (only displays if Forge is checked). - hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked) + Total_Ijirok_Healing = [] #This list will hold the amount of total Ijirok healing from the Ijirok armor for each iteration (only displays if Ijirok switches are checked). + hits_until_1st_poison = [] #This list will hold how many hits until first poisoning against Ambushers (only displays if Ambusher is checked). + hits_until_1st_bleed = [] #This list will hold how many hits until first bleed against cleavers (only displays if CleaverBleed or CleaverMastery is checked). print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) NimbleCalc() @@ -536,6 +564,11 @@ def calc(): NineLivesMod = 1 else: NineLivesMod = 0 + if SplitMan == 1: + SplitManHeadFollowUp = 0 + SplitManBodyFollowUp = 0 + if GloriousEndurance: #The Bear Gladiator trait. + GloriousEnduranceStacks = 0 Injury = 0 HeavyInjuryChance = 0 UseHeadShotInjuryFormula = 0 @@ -546,7 +579,9 @@ def calc(): Bleedstack2T = 0 ForgeSaved = 0 #Tracker to add the amount of armor gained from Forge for each iteration. Poison = 0 #Tracker for when first poisoning occurs against Ambushers. - + Bleed = 0 #Tracker for when first bleeding occurs against cleavers. + IjirokTotalHeal = 0 #Tracker for amount of Ijirok healing received. + count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. while hp > 0: #Continue looping until death. @@ -560,7 +595,7 @@ def calc(): DecapMod = 2 - hp / Def_HP else: DecapMod = 1 - #Destory Armor: + #Destroy Armor: if DestroyArmor == 1 and count == 0: DArmorMod = 1.5 elif DestroyArmor == 1 and count == 1 and DestroyArmorTwice == 1: @@ -580,12 +615,7 @@ def calc(): ForgeMod = 1 #Gladiator - The Bear - Glorious Endurance: if GloriousEndurance == 1: - if SplitMan == 1: - GladMod = 1 - (.05 * (count * 2)) - else: - GladMod = 1 - (.05 * count) - if GladMod < .75: - GladMod = .75 + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) else: GladMod = 1 #Executioner: @@ -599,6 +629,9 @@ def calc(): Headshotchance = 100 else: Headshotchance = Headchance + #Combining various damage modifiers used in damage calculations: + HPDamageModifiers = NimbleMod * SkeletonMod * GladMod * IndomMod * DamageMod * ExecMod * AimedShotMod * DecapMod + ArmorDamageModifiers = ArmorMod * GladMod * IndomMod * DamageMod * ExecMod #Begin damage rolls: hp_roll = random.randint(Mind,Maxd) #Random roll to determine unmodified hp damage. @@ -613,450 +646,209 @@ def calc(): HHStack = 1 elif HHStack == 1: HHStack = 0 + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up body hit later in the code. + if SplitMan == 1: + SplitManBodyFollowUp = 1 #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HHeadshot + + #Begin damage calculation. #Destroy armor check -- if Destroy Armor special is active do this code block and skip the rest. if DArmorMod != 1: hp_roll = 10 #DestroyArmor forces hp damage to = 10. - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(helmet,(armor_roll * ForgeMod)) - helmet = math.ceil(helmet - armor_roll) #Rounding armor damage. - #If not DestoryArmor, and no armor is present, apply damage directly to hp. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) + helmet -= armor_roll + #If not DestroyArmor, and no armor is present, apply damage directly to hp. Damage formula still distincts armor ignore vs. non armor ignore despite no armor present. elif helmet == 0: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) * HeadMod - if Hammer10 == 1: #If 1H Hammer, deal 10 damage minimum. - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) #Rounding hp damage. - #Otherwise, do the following. - else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Otherwise (armor is present), do the following. + else: #Starts by calculating armor damage. + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers ForgeSaved += armor_roll - armor_roll * ForgeMod #Calculate how much armor is saved by Forge. - armor_roll = min(helmet,(armor_roll * ForgeMod)) #Applying Forge, and armor damage cannot exceed current armor. + armor_roll = math.floor(min(helmet,(armor_roll * ForgeMod))) #Applying Forge, and armor damage cannot exceed current armor. helmet -= armor_roll #Armor damage applied to helmet. #If the helmet does not get destroyed by the attack, do the following. - if helmet > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (helmet * 0.1)) * HeadMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - helmet = math.ceil(helmet) - hp = math.ceil(hp - hp_roll) - #If the helmet did get destoryed by the attack, do the following. - else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore) * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = (hp_roll * Ignore * NimbleMod * SkeletonMod * GladMod * IndomMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage) * HeadMod - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus body hit. - if SplitMan == 1: - if BoneplateMod == 1: - BoneplateMod = 0 - else: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) - body -= SMarmor_roll - if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + if helmet > 0: #Helmet was not destroyed. We are calculating how much armor ignoring hp damage is dealt. + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)) * HeadMod)) + + #If the helmet did get destroyed by the attack, do the following to see how much hp damage is dealt. + else: #Damage is split between armor ignoring damage and non-ignoring damage. Non-ignoring damage cannot be negative but it can be zero. + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore) * HPDamageModifiers - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers + hp_roll = math.floor((Armor_Ignore_Damage + Non_Ignore_Damage) * HeadMod) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. else: #If not a headshot, do the following. #2H Flail Check -- Have a higher armor ignoring% on Pound for headshots compared to bodyshots. if Flail2HPound == 1: Ignore = Flail2HBodyshot + #Split Man Flag -- If SplitMan is being used, this will trigger a follow up head hit later in the code. + if SplitMan == 1: + SplitManHeadFollowUp = 1 #Bone Plates check -- Attack is negated if Boneplates are online, then turns off Boneplates until next trial. - if BoneplateMod == 1: + if BoneplateMod == 1 and Puncture != 1: BoneplateMod = 0 hp_roll = 0 else: if DArmorMod != 1: hp_roll = 10 - hp -= hp_roll - armor_roll = random.randint(Mind,Maxd) * ArmorMod * DArmorMod * GladMod * IndomMod * DamageMod * ExecMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * DArmorMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) - body = math.ceil(body - armor_roll) - elif body == 0 or Puncture == 1: - hp_roll = hp_roll * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) + body -= armor_roll + + elif Puncture == 1: #Puncture ignores armor entirely, including attachments. + hp_roll = math.floor(hp_roll * HPDamageModifiers) + + elif body == 0: #If no armor is present, do the following. + Non_Ignore_Damage = math.floor(hp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + else: - armor_roll = random.randint(Mind,Maxd) * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod * AttachMod + armor_roll = random.randint(Mind,Maxd) * ArmorDamageModifiers * AttachMod ForgeSaved += armor_roll - armor_roll * ForgeMod - armor_roll = min(body,(armor_roll * ForgeMod)) + armor_roll = math.floor(min(body,(armor_roll * ForgeMod))) body -= armor_roll if body > 0: - hp_roll = max(0,(hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - (body * 0.1))) - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - body = math.ceil(body) - hp = math.ceil(hp - hp_roll) + hp_roll = math.floor(max(0,(hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * SkeletonMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) - armor_roll)) - hp_roll = hp_roll * Ignore * NimbleMod * SkeletonMod * AdFurPadMod * GladMod * IndomMod * AttachMod * ((DamageMod * ExecMod * AimedShotMod) * DecapMod) + OverflowDamage - if Hammer10 == 1: - hp_roll = max(hp_roll,10) - hp = math.ceil(hp - hp_roll) - #If SplitMan is active, do the following code block for the bonus head hit. - if SplitMan == 1: - SMhp_roll = random.randint(Mind,Maxd) * .5 - if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod - hp = math.ceil(hp - SMhp_roll) - else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod - ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) - helmet -= SMarmor_roll - if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) - else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + Non_Ignore_Damage = math.floor(max(0,(hp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - armor_roll))) + Armor_Ignore_Damage = hp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + hp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + + #Apply damage to defender: + if Hammer10 == 1: #1H Hammer check to do at least 10 hp damage. + hp_roll = max(hp_roll,10) + hp -= hp_roll #Reducing defender hp. + + #Gladiator - Bear trait. Add a stack: + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 count += 1 #Add +1 to the number of hits taken. + if count > 500: #This if statement is here to prevent accidental infinite loops with Ijirok armor, or simply any abnormal testing scenario that would take a very long time to compute. + print("Defender is surviving over 500 attacks, please adjust testing parameters.") + exit() #Injury check: - if UseHeadShotInjuryFormula == 1: - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/192): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/128): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/144): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - UseHeadShotInjuryFormula = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) + if (hp > 0 or NineLivesMod == 1) and Undead != 1 and Savant != 1: + if Injury == 0: + if UseHeadShotInjuryFormula == 1: #Use headshot injury formula. + InjuryThreshold = 0.3125 * InjuryMod #Base injury rate is 0.25 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) UseHeadShotInjuryFormula = 0 - else: #Use body injury formula. - if Injury == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/48): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 12: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/32): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 8: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/36): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 9: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - Injury = 1 - if Flail3Head == 1: - hits_until_1st_injury.append(count/3) - else: - hits_until_1st_injury.append(count) - - #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. - if UseHeadShotInjuryFormulaHeavy == 1: - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/96): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/64): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/72): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/48): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - UseHeadShotInjuryFormulaHeavy = 0 - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (25/32): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body injury formula. + InjuryThreshold = 0.25 * InjuryMod #Base injury rate is 0.25 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + Injury = 1 + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + + #Heavy injury check: Heavy injuries are not guaranteed even when conditions are met, so this is only checking for chance of heavy injury. + if HeavyInjuryChance == 0: + if UseHeadShotInjuryFormulaHeavy == 1: #Use headshot heavy injury formula. + InjuryThreshold = 0.625 * InjuryMod #Base heavy injury rate is 0.5 * 1.25 (headshot) * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) UseHeadShotInjuryFormulaHeavy = 0 - else: #Use body injury formula. - if HeavyInjuryChance == 0 and Undead != 1 and Savant != 1: - if CripplingStrikes == 1 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/24): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 6: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 0 and ShamshirMastery == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/16): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP / 4: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 and Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/18): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if math.floor(hp_roll) >= Def_HP * (2/9): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - elif CripplingStrikes == 1 or Shamshir == 1: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/12): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + else: #Use body heavy injury formula. + InjuryThreshold = 0.5 * InjuryMod #Base heavy injury rate is 0.5 * InjuryMod (Crippling, Shamshir, Ironjaw) + if math.floor(hp_roll) >= Def_HP * InjuryThreshold: + HeavyInjuryChance = 1 + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) + + #SplitMan secondary hits: The following code block accounts for the extra hit for SplitMan + if SplitMan == 1: + #Before calculating damage for the second hit, we need to re-evaluate the value of Forge, Bear trait, and Executioner if they are in play, to account for damage taken by the first hit. + #Battleforged: + if Forge == 1: + ForgeMod = 1 - ((helmet + body) *.0005) + else: + ForgeMod = 1 + #Gladiator - The Bear - Glorious Endurance: + if GloriousEndurance == 1: + GladMod = max(1 - (.05 * GloriousEnduranceStacks),.75) + else: + GladMod = 1 + #Executioner: + if Injury == 1 and Executioner == 1: + ExecMod = 1.2 + else: + ExecMod = 1 + + #If SplitMan is active, do the following code block for the bonus body hit if the original hit was a headshot. + if SplitManBodyFollowUp == 1: + SplitManBodyFollowUp = 0 + if BoneplateMod == 1: + BoneplateMod = 0 + SMhp_roll = 0 + else: + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. + if body == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers * AttachMod) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) else: - if math.floor(hp_roll) >= Def_HP / 3: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - else: - if Ironjaw == 1: - if math.floor(hp_roll) >= Def_HP * (5/8): - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) + body -= SMarmor_roll + if body > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) + else: + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * HPDamageModifiers * AttachMod - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) + #Applying damage to defender hp. + hp -= SMhp_roll + + #If SplitMan is active, do the following code block for the bonus head hit if the original hit was a body hit. Split Man secondary hit does not get a headshot bonus. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + Non_Ignore_Damage = math.floor(SMhp_roll * (1 - Ignore) * HPDamageModifiers) #Non armor ignoring side of the damage formula. Gets rounded down. + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers #Armor ignoring side of the damage formula. + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers + ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) + helmet -= SMarmor_roll + if helmet > 0: + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - if math.floor(hp_roll) >= Def_HP / 2: - HeavyInjuryChance = 1 - if Flail3Head == 1: - hits_until_1st_heavy_injury_chance.append(count/3) - else: - hits_until_1st_heavy_injury_chance.append(count) - + Non_Ignore_Damage = math.floor(max(0,(SMhp_roll * (1 - Ignore) * HPDamageModifiers - SMarmor_roll))) + Armor_Ignore_Damage = SMhp_roll * Ignore * HPDamageModifiers + SMhp_roll = math.floor(Armor_Ignore_Damage + Non_Ignore_Damage) #Adding both sides of the formula together. Applies headshot modifier. Rounds down after. + #Applying damage to defender hp. + hp -= SMhp_roll + + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 + #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: @@ -1078,11 +870,31 @@ def calc(): if math.floor(hp_roll) > 0 and math.floor(hp_roll) < 15: FearsomeProcs += 1 + #Ijirok armor check: + if (hp > 0 or NineLivesMod == 1) and (IjirokHeal10 == 1 or IjirokHeal20 == 1): + if Ijirok1TurnHeal == 1: #Block to apply healing after every attack. + hp = hp + IjirokHealing #Applying Healing + IjirokTotalHeal += IjirokHealing #Tracking HP Healed for later analysis. + if hp > Def_HP: #Block to ensure HP doesn't exceed max. + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + elif Ijirok2TurnHeal == 1: #Block to apply healing every other attack. + if count % 2 == 0: + hp = hp + IjirokHealing + IjirokTotalHeal += IjirokHealing + if hp > Def_HP: + IjirokTotalHeal -= (hp - Def_HP) + hp = Def_HP + #Bleeding check: if (CleaverBleed == 1 or CleaverMastery == 1) and Undead != 1: #If damage taken >= 6 and Decapitate isn't in play, then apply a 2 turn bleed stack. if math.floor(hp_roll) >= 6 and DecapMod == 1 and Decapitate != 1: Bleedstack2T += 1 + #Track fist instance of bleed for later data return. + if Bleed == 0: + Bleed = 1 + hits_until_1st_bleed.append(count) #Every two attacks (1 turn for Cleavers), apply bleed damage based on current bleed stacks. #If Resilient, 2 turn bleed stacks apply damage and then are removed. Otherwise 2 turn bleed stacks apply damage and convert into 1 turn bleed stacks. if count % 2 == 0: @@ -1109,15 +921,28 @@ def calc(): NineLivesMod = 0 Bleedstack1T = 0 Bleedstack2T = 0 - elif Fearsome == 1: - if Forge == 1: - Forge_bonus_armor.append(ForgeSaved) - hits_until_death.append(count) - NumberFearsomeProcs.append(FearsomeProcs) else: if Forge == 1: Forge_bonus_armor.append(ForgeSaved) - hits_until_death.append(count) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + Total_Ijirok_Healing.append(IjirokTotalHeal) + if Fearsome == 1: + NumberFearsomeProcs.append(FearsomeProcs) + if Flail3Head == 1: + hits_until_death.append(math.ceil(count/3)) + else: + hits_until_death.append(count) + #Check if the following trackers were hit and if not, append the time until death to their lists for later analysis instead of having an empty data point. + if Injury == 0: + if Flail3Head == 1: + hits_until_1st_injury.append(math.ceil(count/3)) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(math.ceil(count/3)) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1146,9 +971,15 @@ def calc(): if Forge == 1: if len(Forge_bonus_armor) != 0: AvgForgeArmor = statistics.mean(Forge_bonus_armor) + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + if len(Total_Ijirok_Healing) != 0: + AvgIjirokHealing = statistics.mean(Total_Ijirok_Healing) if Ambusher == 1: if len(hits_until_1st_poison) != 0: hits_to_posion = statistics.mean(hits_until_1st_poison) + if (CleaverBleed == 1 or CleaverMastery == 1): + if len(hits_until_1st_bleed) != 0: + hits_to_bleed = statistics.mean(hits_until_1st_bleed) #Results: if DeathMean == 1: @@ -1158,7 +989,7 @@ def calc(): if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) if Undead != 1 and Savant != 1: - if len(hits_until_1st_injury) == 0: + if hits_to_injure >= HitsToDeath: if InjuryMean == 1 or InjuryPercent == 1: print("No chance of injury.") else: @@ -1166,7 +997,7 @@ def calc(): print("First injury in " + str(hits_to_injure) + " hits on average.") if InjuryPercent == 1: print("% First injury in: " + str(HitsToInjurePercent)) - if len(hits_until_1st_heavy_injury_chance) == 0: + if hits_to_1st_heavy_injury_chance >= HitsToDeath: if HeavyInjuryMean == 1 or HeavyInjuryPercent == 1: print("No chance of heavy injury.") else: @@ -1183,8 +1014,12 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge on average.") - if Ambusher == 1: + if (IjirokHeal10 == 1 or IjirokHeal20 == 1) and (Ijirok1TurnHeal == 1 or Ijirok2TurnHeal == 1): + print(str(AvgIjirokHealing) + " HP healed by Ijirok armor on average.") + if (Ambusher == 1 and len(hits_until_1st_poison) != 0): print("First poison in " + str(hits_to_posion) + " hits on average.") + if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): + print("First bleed in " + str(hits_to_bleed) + " hits on average.") print("-----") #Added for readability. If this annoys you then remove this line. #The following will repeatedly run the scenario with different HP counts. @@ -1226,6 +1061,7 @@ def calc(): #Copyright 2019, turtle225. All rights reserved. #Special Thanks: #-- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +#-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ #-- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. #-- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. #-- Overhype: For making an amazing game for us to play. @@ -1299,4 +1135,45 @@ def calc(): #Version 1.6.2 (3/14/2022) #-- Adjusted Orc Berserker preset for new buff to Berserk Chain to 50-100, up from 40-100. #Version 1.6.3 (4/11/2022) -#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. \ No newline at end of file +#-- Fixed a bug with Forge + Split Man interaction where having low armor with Forge was giving much better survivability than it should have been against Split man. +#Version 1.6.4 (6/27/2023) +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#-- Fixed an error where HeadHunter tests were causing the calculator to break. +#Version 1.6.5 (8/7/2024) +#-- Fixed a error/break when Split Man, Boneplates, and Morale Checks were all enabled. (Thank you to Osgboy for pointing this out). +#Version 1.6.6 (8/22/2024) +#-- Fixed some presets that were incorrect due to either human error on my part, or it became outdated from a change in the game that I had missed. See below. +#-- Defender presets: +#---- Raider: HP Updated to 75 (was 70). +#---- Reaver: HP Updated to 120 (was 80). +#---- Knight: HP updated to 135 (was 125). +#---- Officer: HP updated to 110 (was 100). +#---- Sergeant: Added Resilient perk. +#---- Brigand Leader: Body armor changed from 230 to 210. (Leaders cannot spawn with 230 body armor so switching to Reinforced Hauberk at 210). +#---- SwordMaster: helmet changed from 70 to 80. Total fatigue (for Nimble) to 16 from 15. (Swordmasters cannot spawn with 70/-3 Duelist Hat so switching to Mail Coif at 80/-4). +#-- Attacker presets: +#---- Warcythe: Armor% changed to 105% (was 104%). Used in Ancient Dead preset. (Note - Ingame tooltip incorrectly displays 104%). +#---- Warbow: Armor% changed to 60% (was 65%). Used in Master Archer preset. +#---- Lindwurm: armor% changed to 150% (was 140%). Used in Lindwurm preset. +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. +#Version 1.6.7 (10/1/2024) +#-- Added logic and switches for Ijirok armor tests. +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 (3/20/2025) +#-- Readjusted Split Man calculations to match recent bug fix in game where it previously did not account for offensive damage modifiers. +#---- This means that the second hit can now use offensive modifiers like Executioner, Huge, Orc bonuses, etc. +#-- Added logic to update Forge, Glorious Endurance trait (Bear), and Executioner before rolling the damage of the second hit of Split Man. +#---- This means that Executioner can turn online before the second hit calculates. Forge gets weaker before the second hit. Bear gets tankier before second hit. +#-- Recoded the Injury Check section to be much more concise (Thank you Osgboy for suggestion/advice). +#-- Changed injury multiplier for Crippling Strikes and Shamshir (without Mastery) to .66 to match how it is in game (previously was using 2/3). +#-- Fixed an oversight with all sub-variants of the calculator where they could return injury rates that were faster than reality in circumstances where the enemy could get their first injury on the same hit where they die. The main BBCalc.py did not have this problem. +#Version 1.7.1 (3/31/2025) +#-- Improved the accuracy of when the damage formula rounds damage (Thank you Calandro), to accurately match the game. +#---- The damage formula splits ignore and non-ignore damage regardless of whether armor is present or not. The non-ignore part rounds down before adding into the ignore part. The calculator was previously missing this extra instance of rounding. +#---- Armor rounding occurs (rounds down) before the 10% remaining armor value is calculated. Previosly the calculator was not rounding the armor damage down until the end of the formula. +#---- The impact of these changes is minor, but the result is that defenders do slightly better than before. Effect less noticeable on strong attackers. +#-- Recoded parts of the damage calculation sections of the code to try and be more efficient. +#---- Combined various hp and armor damage modifiers into two variables instead of writing each as their own variable repeatedly in the code. +#-- Reworked how 3Head Flail data is tracked to return the number of swings rather than tracking by each individual sub-hit. So instead of showing .33|.66|1 hits to kill, these would all be rounded up to 1. +#---- Tracking by sub-hit skewed the averages down and made the weapon look stronger (Thank you smr_rst). Realistically it does not matter if you kill in a sub-hit but rather how many total swings it takes. +#-- Added a AoE 2HHammer switch to the weapon options. \ No newline at end of file diff --git a/FastAd.py b/FastAd.py new file mode 100644 index 0000000..eea2c9f --- /dev/null +++ b/FastAd.py @@ -0,0 +1,35 @@ +#This is a script that you can use to calculate the expected return of Fast Adaptation from a given base hit chance. +#The only value you should edit is the value BASE_HIT_CHANCE. Everything else can be left alone. +#If you want to see the rolls appended to a file then you can remove the #comment in front of lines 15,20, and 33. + +import random +import statistics +import collections + +BASE_HIT_CHANCE = 40 + +data_list = [] + +current_hit_chance = BASE_HIT_CHANCE + +#f = open("FastAd.txt","w") + +for i in range(0,1000000): + roll = (random.randint(1,100)) + data_list.append(current_hit_chance) + #f.write(str(current_hit_chance)+ "\n") + if roll > current_hit_chance: + current_hit_chance += 10 + if current_hit_chance > 95: + current_hit_chance = 95 + else: + current_hit_chance = BASE_HIT_CHANCE + +Avg_Hit_Chance = statistics.mean(data_list) +Counter_for_Rolls = collections.Counter(data_list) +Percentage_of_Rolls_at_Each_Hit_Value = [(i,Counter_for_Rolls[i]/len(data_list)*100) for i in Counter_for_Rolls] +print("Average hit chance: " + str(Avg_Hit_Chance)) +print("% of rolls at each hit%: " +str(Percentage_of_Rolls_at_Each_Hit_Value)) +#f.close() + +#Author: turtle225 \ No newline at end of file diff --git a/README.md b/README.md index b90f257..81dfe9d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ # Battle-Brothers-Damage-Calculator Updated for Of Flesh and Faith -Latest Update: 4/11/2022 - Fixed a bug where having Forge with low armor against Split Man was giving much better survivability than it should have been. Heavy armor Forge vs. Split Man tests were unlikely to be impacted by this bug, as they tend to die before armor was destroyed anyway. +Latest Update: 3/31/2025 - Improved the accuracy of rounding for damage calculation. (Thank you Calandro). Changed 3Head Flail data to return number of swings instead of tracking by each individual subhit, as subhits were skewing the data in its favor. (Thank you smr_rst). See bottom of calculator script for full details. + +Latest Update: 3/20/2025 - Readjusted Split Man logic to account for recent change in game where it now interacts with damage modifiers on the second hit. Adjusted Fearsome formula to 15% to match recent change in game. Other minor changes - see bottom of calculator script for full details. + +Note: Osgboy has made a web-app version of the calculator if you don't want to have to download and use the raw code. Check it out here: https://osgboy.pythonanywhere.com/ +Big thanks to Osgboy for adapting the code and buliding the site to make it more user friendly. + +Overview: A script that simulates the damage formula used in Battle Brothers, returning expected hits until death, injury, heavy injury, and morale check given whatever scenario you provide. Also returns % chance of death by hit, and can also return % chance of first injuries or morale by hit. @@ -17,23 +24,11 @@ At the bottom of each calculator is a version history, where you can see changel IMPORTANT: -For instruction on setting up the calculator and how to use it once it is ready, please refer to the Installation and User Guide documents provided in the repository. I walk through step by step how to setup the calculator and use it with pictures and examples. Although I do not have a gui, the calculator is easy to use once you know how. The guide will help you get it running. The guide assists with setting up VSCode which is my preferred way to use the calculator, but if you want to use a different IDE then you can do so, or if you don't want to bother with any user setup then use Replit (see below). - -An alternative method to use the calculator is to load it into Repl.it. - -https://repl.it/ - -You don't have to sign up, choose "start coding" or "new repl" in the top right. Choose "Import from GitHub" and put my link url - -https://github.com/turtle225/Battle-Brothers-Damage-Calculator - -Once it loads in, on the top right you can configure the "run" button if you wish to use it (it isn't necessary). To do so select Python from the drop down and in the box type "python BBCalc.py" without the quotes. This will make it so that the green run button runs the main calculator, but this won't work for the other calculators. +For instruction on setting up the calculator and how to use it once it is ready, please refer to the Installation and User Guide documents provided in the repository. I walk through step by step how to setup the calculator and use it with pictures and examples. Although I do not have a gui, the calculator is easy to use once you know how. The guide will help you get it running. The guide assists with setting up VSCode which is my preferred way to use the calculator, but if you want to use a different IDE then you can do so. -Alternatively, don't worry about the run button, you can run any of the calculator versions by typing "python calcname.py" without the quotes into the command line on the right, where calcname is the name of the calculator you wish to use. Remember to press your Tab button to autocomplete the command for convenience. +If you don't want to bother with manually installing and interacting with the code, then please use the web-app gui that osgboy has created here: https://osgboy.pythonanywhere.com/ -Using Replit skips the first half of the installation and user guide. Refer to part 2 to understand how to edit and use the calculator (note that it is unlikely that the user guide will load in Replit, open it elsewhere on your computer). Make sure any edits you make are done saving before you run the calculator. You can see a greyed out "saved" status next to the open file tab. - -The advantage to using Replit is that it is faster and easier to setup than getting your own VSCode going. Once you have the calculator loaded in Replit, you can also save that url link as a bookmark/favorite to quickly load the calculator again later without having to upload it into Replit again. The downside is that it makes the code scrunched and hideous to look at, which makes it hard to read the helpful comments I tried to put in. It may also take longer to compute (lower trials variable if it is taking too long). +Another alternative to manually setting up your own coding environment is to import the code into https://replit.com/. This will still involve manually interacting with the code, but it makes it so that you don't have to download your own IDE. This is not my preferred way of using the calculator but I still wanted to mention it as an option. Limitations: @@ -50,12 +45,24 @@ Special Thanks: -- Abel (aka) Villain Joueur: For grabbing the damage formula out of the game code, writing the damage page on the wiki, and for helping me with many questions along the way. +-- Osgboy: For making a web-app gui version of the calculator making it much more user friendly and accessible to people. Located here: https://osgboy.pythonanywhere.com/ + -- Wall (aka) Wlira: For helping me with some questions along the way and having an existing calculator for me to test against. Also for pointing out Replit as an option for using the calculator. -- You: If you are using the calculator, thank you! If you find any bugs or have feedback/questions/suggestions, you can usually find me on the Steam forums or send me an email. -- Overhype: For making an amazing game for us to play. +Upcoming update (note written 8/4/2024): There is currently a bug in the game with Split Man where it does not interact with generic damage modifiers on the second hit (ie things like Frenzy, Huge, Dazed, etc.). This is reportedly fixed in the next update of the game, but that has not been released yet. I will update the calculator once that update goes live. The calculator currently accounts for the bug existing. + +Prior Update: 10/1/2024 - Added logic for Ijirok armor testing. Added a condition for the code to terminate if the defender survives abnormally long. + +Prior Update: 8/22/2024 - Fixed some attacker/defender presets that were incorrect. Fixed Boneplates working against Puncture. + +Prior Update: 6/27/2023 - Added the ability to track the first instance of a bleed proc for cleaver tests and return this data in the output. Now you can test various armor lines, perks, or attachments, and see how that helps prolong the first instance of suffering bleed. + +Prior update: 4/11/2022 - Fixed a bug where having Forge with low armor against Split Man was giving much better survivability than it should have been. Heavy armor Forge vs. Split Man tests were unlikely to be impacted by this bug, as they tend to die before armor was destroyed anyway. + Prior Update: 3/13/2022 - Adjusted Orc Berserker preset for Berserk Chain damage buff. Prior update: 3/10/2022 - Added the changes that occurred from the Of Flash and Faith dlc balance pass. Notably, 2H Flails got reworked and that logic has been configured. HeadHunter change only effects the hit chance version of the calculator. Nine Lives, Handgonne, Throwing, and Fearsome changes are all in. Also added +5% armor ignore to Aimed Shot which I never realized it had before. diff --git a/data.txt b/data.txt index e10063e..1bcc184 100644 --- a/data.txt +++ b/data.txt @@ -29,7 +29,7 @@ Overseer: 70hp, 120 helmet, 180 armor. Xbow Mastery. Barbarians: Thrall: 70hp, 0 or 30-50 helmet, 0 or 30-45 armor. Resilient. -Reaver: 80hp, 0 or 30-145 helmet, 65-95 armor. Resilient, Cleaver Mastery, Hammer Master, Throwing Mastery. +Reaver: 120hp, 0 or 30-145 helmet, 65-95 armor. Resilient, Cleaver Mastery, Hammer Master, Throwing Mastery. Chosen: 130hp, 145-190 helmet, 140-230 armor. Resilient, Forge, Crippling, Executioner, Cleaver Mastery. King: 150hp, 250 helmet, 270 armor. Resilient, Forge, Crippling, Executioner, Cleaver Mastery, (Frenzy). Madman: 160hp, 300 helmet, 300 armor. Resilient, Forge, Crippling, Executioner, Cleaver Mastery. @@ -42,8 +42,8 @@ Footman: 70hp, 80-215 helmet, 65-150 armor. Forge, Hammer Mastery. Billman: 70hp, 0 or 80-215 helmet, 50-130 armor. Forge. Arbalester: 60hp, 20-80 helmet, 50-65 armor. Xbow Mastery. Bannerman: 80hp, 0 or 115-215 helmet, 115-150 armor. SteelBrow. -Knight: 125hp, 300 helmet, 210-320 armor. Forge, Crippling, Executioner. -Sergeant: 100hp, 0 helmet, 150-210 armor. Nimble, SteelBrow, Duelist, Cleaver Mastery, Hammer Mastery. +Knight: 135hp, 300 helmet, 210-320 armor. Forge, Crippling, Executioner. +Sergeant: 100hp, 0 helmet, 150-210 armor. Nimble, SteelBrow, Resilient, Duelist, Cleaver Mastery, Hammer Mastery. Zweihander: 90hp, 70 or 160 helmet, 150-240 armor. Forge, SteelBrow. Wardog: 50hp, 0 helmet, 55 armor. SteelBrow. @@ -52,7 +52,7 @@ Thug: 55hp, 0-45 helmet, 5-70 armor. Poacher: 55hp, 0-20 helmet, 20 armor. Raider: 75hp, 0-140 helmet, 55-115 armor. Executioner, All Wpn Mastery. Marksman: 60hp, 0-45 helmet, 20-70 armor. Xbow Mastery. -Leader: 100hp, 90-250 helmet, 110-230 armor. NineLives, Executioner, All Wpn Mastery. +Leader: 100hp, 90-250 helmet, 110-210 armor. NineLives, Executioner, All Wpn Mastery. Sellswords: Mercenary: 90hp, 0-230 helmet, 80-260 armor. Forge, All Wpn Mastery. @@ -155,7 +155,7 @@ Reinforced Wood Flail: 20-45, 30% Ignore, 80% Armor, 35% Head. Flail: 25-55, 30% Ignore, 100% Armor, 35% Head. 3Head Flail: 30-75, 30% Ignore, 100% Armor, 35% Head. -2H Wood Flail: 25-60, 30% Ignore, 80% Armor, 40% Head. +2H Wood Flail: 30-60, 30% Ignore, 80% Armor, 40% Head. 2H Flail: 45-90, 30% Ignore, 115% Armor, 40% Head. Berserk Chain: 50-100, 30% Ignore, 125% Armor, 40% Head. @@ -194,7 +194,7 @@ Banner: 50-70, 30% Ignore, 100% Armor. Jagged Pike: 50-70, 25% Ignore, 90% Armor, 30% Head. Broken Bladed Pike: 35-55, 30% Ignore, 80% Armor, 30% Head. Ancient Bladed Pike: 55-80, 30% Ignore, 125% Armor, 30% Head. -Warscythe: 55-80, 35% Ignore, 104% Armor. (Note: actual Ignore is 30% on single target and 25% on AoE). +Warscythe: 55-80, 35% Ignore, 105% Armor. (Note: actual Ignore is 30% on single target and 25% on AoE). Swordlance: 60-80, 30% Ignore, 90% Armor. (Note: actual Ignore is 25% on AoE). Throwing: