From cd827ee22f0aea41c58cdb8381a11020a4756961 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Tue, 27 Jun 2023 11:22:13 -0500 Subject: [PATCH 01/27] Add files via upload --- BB1HanderBattery.py | 21 +++++++++++++++++---- BB2HanderBattery.py | 21 +++++++++++++++++---- BBAttackerVsEnemies.py | 21 +++++++++++++++++---- BBCalc.py | 29 +++++++++++++++++++++-------- BBEnemiesVsDefender.py | 23 ++++++++++++++++++----- BBHitChance.py | 25 +++++++++++++++++++------ BBNimbleBattery.py | 27 +++++++++++++++++++++------ BBRaisingHp.py | 26 ++++++++++++++++++++------ 8 files changed, 150 insertions(+), 43 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index ec0f23e..0d730f6 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.6.4: #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. @@ -530,7 +530,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() @@ -565,6 +566,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. @@ -1108,6 +1110,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: @@ -1180,6 +1186,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: @@ -1214,8 +1223,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 weapons. @@ -1455,4 +1466,6 @@ 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. \ No newline at end of file diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index c6063cc..445cef9 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.6.4: #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. @@ -522,7 +522,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() @@ -557,6 +558,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. @@ -1100,6 +1102,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: @@ -1172,6 +1178,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: @@ -1206,8 +1215,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 weapons. @@ -1478,4 +1489,6 @@ 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. \ No newline at end of file diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index 17bdb95..edf8182 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.6.4: #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. @@ -543,7 +543,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() @@ -578,6 +579,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. @@ -1121,6 +1123,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: @@ -1194,6 +1200,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: @@ -1228,8 +1237,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. @@ -1531,4 +1542,6 @@ 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. \ No newline at end of file diff --git a/BBCalc.py b/BBCalc.py index 52feb1f..9d273fe 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.6.4: #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 = 70 #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: @@ -184,7 +184,7 @@ APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% 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. @@ -639,12 +639,13 @@ 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) +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)) @@ -684,6 +685,7 @@ 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. count = 0 #Number of hits until death. Starts at 0 and goes up after each attack. @@ -1349,6 +1351,10 @@ 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: @@ -1459,6 +1465,9 @@ 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: @@ -1532,8 +1541,10 @@ 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. #CREDITS: @@ -1635,4 +1646,6 @@ #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. \ No newline at end of file diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index 193b8c8..3b746b5 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.6.4: #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. @@ -237,7 +237,7 @@ APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% 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. @@ -643,7 +643,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() @@ -678,6 +679,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. @@ -1221,6 +1223,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: @@ -1294,6 +1300,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: @@ -1328,8 +1337,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 attackers. @@ -1687,4 +1698,6 @@ 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. \ No newline at end of file diff --git a/BBHitChance.py b/BBHitChance.py index 00b2a30..9006a1e 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.6.4: #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. @@ -184,7 +184,7 @@ APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% 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. @@ -641,7 +641,8 @@ 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("-----") #Added for readability. If this annoys you then remove this line. print("HP = " + str(Def_HP) + ", Helmet = " + str(Def_Helmet) + ", Armor = " + str(Def_Armor)) @@ -677,6 +678,7 @@ 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. Hits = 0 #Unique to this calculator. Used for Glorious Endurance. Starts at 0 and goes up after each hit. @@ -1230,6 +1232,10 @@ #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: @@ -1302,6 +1308,9 @@ 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: @@ -1336,8 +1345,10 @@ 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 (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: @@ -1419,4 +1430,6 @@ #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. \ No newline at end of file diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index 27b9e35..a07952b 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.6.4: #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. @@ -178,7 +178,7 @@ APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% 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. @@ -531,7 +531,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 +540,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)) @@ -575,6 +576,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. @@ -1112,6 +1114,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: @@ -1178,6 +1184,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.") @@ -1211,8 +1221,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. @@ -1376,4 +1388,7 @@ 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. \ No newline at end of file diff --git a/BBRaisingHp.py b/BBRaisingHp.py index f261afb..23ff2cf 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.6.4: #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. @@ -178,7 +178,7 @@ APreWarbow = 0 #Master Archer: 50-70, 35% Ignore, 65% 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. @@ -502,7 +502,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 +511,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() @@ -546,6 +547,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. @@ -1083,6 +1085,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: @@ -1149,6 +1155,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: @@ -1183,8 +1192,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 HP counts. @@ -1299,4 +1310,7 @@ 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. \ No newline at end of file From fd95a88c7268ffd2c74eb6dfda8c3c076a37b715 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Tue, 27 Jun 2023 11:29:36 -0500 Subject: [PATCH 02/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b90f257..5fe1255 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 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: 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. 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. From 896e3c57f44e227ad77fae8ab77e80edc15dab9b Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Tue, 27 Jun 2023 16:57:33 -0500 Subject: [PATCH 03/27] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5fe1255..dc26d46 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ helping me with many questions along the way. -- Overhype: For making an amazing game for us to play. +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. From 74eff2ac5832a4ffbd8d0c0c253bf961c28607ec Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:16:32 -0600 Subject: [PATCH 04/27] Add files via upload --- FastAd.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 FastAd.py 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 From bd59792c92972e8439db5aa8b0343e3d68b05242 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 8 Feb 2024 23:46:42 -0600 Subject: [PATCH 05/27] Update BB2HanderBattery.py Fixed a typo in the handgonne section. Thank you vsobotka for finding it. --- BB2HanderBattery.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index 445cef9..54e48c9 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.4: +#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.5: #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. @@ -1399,7 +1399,7 @@ def calc(): Mind = 35 Maxd = 75 Ignore = 25 -ArmodMod = .9 +ArmorMod = .9 print("Handgonne") calc() @@ -1491,4 +1491,6 @@ def calc(): #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. #Version 1.6.4 (6/27/2023) -#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. \ No newline at end of file +#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. +#Version 1.6.5 +#-- Fixed a typo in the Handgonne section causing incorrect results. Thank you to vsobotka for pointing this out. From 7551352ddaa28eaf8a6a8ca0ede7550d366fd157 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 8 Feb 2024 23:47:30 -0600 Subject: [PATCH 06/27] Update BB2HanderBattery.py --- BB2HanderBattery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index 54e48c9..fc73d56 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1492,5 +1492,5 @@ def calc(): #-- 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 +#Version 1.6.5 (2/8/2024) #-- Fixed a typo in the Handgonne section causing incorrect results. Thank you to vsobotka for pointing this out. From cdd0f36855c5a40e6315f216708fddb5ca9d94f4 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Sun, 4 Aug 2024 09:30:27 -0500 Subject: [PATCH 07/27] Update README.md --- README.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index dc26d46..0da5607 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ Updated for Of Flesh and Faith Latest 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. +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. Includes over 80 different switches from unique weapon cases, perks, attachments, race, etc. to create almost any scenario you can imagine from in game. There are 35 attacker presets and 41 defender presets provided for convenience. @@ -17,23 +21,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 +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. -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. +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/ -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. - -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 +42,16 @@ 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. + -- 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: 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. From 20a48d8307aed75aa10241ea65e45743bde5960f Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Sun, 4 Aug 2024 09:35:29 -0500 Subject: [PATCH 08/27] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0da5607..3ce58d9 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Note: Osgboy has made a web-app version of the calculator if you don't want to h 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. Includes over 80 different switches from unique weapon cases, perks, attachments, race, etc. to create almost any scenario you can imagine from in game. There are 35 attacker presets and 41 defender presets provided for convenience. From 10662ef06f7907b1672001ff139918221e0f6b79 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:06:36 -0500 Subject: [PATCH 09/27] Update BBCalc.py Fixed break when Split Man, Boneplates, and Morale Checks were all enabled together. Thank you Osgboy --- BBCalc.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BBCalc.py b/BBCalc.py index 9d273fe..74f6559 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator Version 1.6.4: +#Battle Brothers Damage Calculator Version 1.6.5: #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. @@ -797,6 +797,7 @@ if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1648,4 +1649,6 @@ #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. #Version 1.6.4 (6/27/2023) -#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. \ No newline at end of file +#-- 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). From 23c560d765c7d634e3e42160b74f123ff21e3f6a Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:07:39 -0500 Subject: [PATCH 10/27] Update BB1HanderBattery.py --- BB1HanderBattery.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index 0d730f6..6a73af1 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.4: +#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.5: #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. @@ -675,6 +675,7 @@ def calc(): if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1468,4 +1469,6 @@ def calc(): #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. #Version 1.6.4 (6/27/2023) -#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. \ No newline at end of file +#-- 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). From 318456c1f39f9a7327836b4f9c628b5d3a63759f Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:09:38 -0500 Subject: [PATCH 11/27] Update BB2HanderBattery.py --- BB2HanderBattery.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index fc73d56..fbabd3d 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.5: +#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.6: #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. @@ -667,6 +667,7 @@ def calc(): if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1494,3 +1495,5 @@ def calc(): #-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. #Version 1.6.5 (2/8/2024) #-- Fixed a typo in the Handgonne section causing incorrect results. Thank you to vsobotka for pointing this out. +#Version 1.6.6 (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). From 1213e3829a3a2f8ce59a3b57b9516309779e893d Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:10:44 -0500 Subject: [PATCH 12/27] Update BBAttackerVsEnemies.py --- BBAttackerVsEnemies.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index edf8182..2089e73 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.4: +#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.5: #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. @@ -688,6 +688,7 @@ def calc(): if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1544,4 +1545,6 @@ def calc(): #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. #Version 1.6.4 (6/27/2023) -#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. \ No newline at end of file +#-- 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). From c8199c2ff6d6bfdf53ee031bb3d925f46637acdc Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:12:52 -0500 Subject: [PATCH 13/27] Update BBEnemiesVsDefender.py --- BBEnemiesVsDefender.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index 3b746b5..d91cf75 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.4: +#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.5: #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. @@ -788,6 +788,7 @@ def calc(): if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1700,4 +1701,6 @@ def calc(): #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. #Version 1.6.4 (6/27/2023) -#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. \ No newline at end of file +#-- 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). From d606b1ab11c8ebd25df4bd59d11b585346f9c008 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:14:06 -0500 Subject: [PATCH 14/27] Update BBHitChance.py --- BBHitChance.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BBHitChance.py b/BBHitChance.py index 9006a1e..cd0b8fc 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.4: +#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.5: #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. @@ -792,6 +792,7 @@ if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1432,4 +1433,6 @@ #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. #Version 1.6.4 (6/27/2023) -#-- Added a tracker that returns the average hits until first bleed proc for cleaver tests. \ No newline at end of file +#-- 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). From 39a43956e50d8fc988065f5de315da4c7b505b99 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:14:54 -0500 Subject: [PATCH 15/27] Update BBNimbleBattery.py --- BBNimbleBattery.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index a07952b..69d4852 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.4: +#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.5: #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. @@ -685,6 +685,7 @@ def calc(): if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1391,4 +1392,6 @@ def calc(): #-- 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. \ No newline at end of file +#-- 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). From cf1ff39e6ab29926c5258c58318a62c3a2ee50ab Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:16:17 -0500 Subject: [PATCH 16/27] Update BBRaisingHp.py --- BBRaisingHp.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BBRaisingHp.py b/BBRaisingHp.py index 23ff2cf..61d43d7 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- HP Changing Version 1.6.4: +#Battle Brothers Damage Calculator -- HP Changing Version 1.6.5: #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. @@ -656,6 +656,7 @@ def calc(): if SplitMan == 1: if BoneplateMod == 1: BoneplateMod = 0 + SMhp_roll = 0 else: SMhp_roll = random.randint(Mind,Maxd) * .5 if body == 0: @@ -1313,4 +1314,6 @@ def calc(): #-- 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. \ No newline at end of file +#-- 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). From 42ca94e6779998e5b5db14c4fd1100fb8d6c7104 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:25:24 -0500 Subject: [PATCH 17/27] Add files via upload --- BB1HanderBattery.py | 51 ++++++++++++++++++++++------------ BB2HanderBattery.py | 50 ++++++++++++++++++++++------------ BBAttackerVsEnemies.py | 49 ++++++++++++++++++++++----------- BBCalc.py | 62 ++++++++++++++++++++++++++---------------- BBEnemiesVsDefender.py | 60 +++++++++++++++++++++++++--------------- BBHitChance.py | 60 +++++++++++++++++++++++++--------------- BBNimbleBattery.py | 32 ++++++++++++++++------ BBRaisingHp.py | 32 ++++++++++++++++------ data.txt | 12 ++++---- 9 files changed, 268 insertions(+), 140 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index 6a73af1..54437a7 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.5: +#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.6: #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. @@ -130,7 +130,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 +139,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) # ------------------------------------------------------------------------ @@ -242,7 +242,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 +260,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 +278,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 +286,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 @@ -700,7 +700,7 @@ def calc(): if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1383,7 +1383,7 @@ def calc(): calc() Mind = 30 -Ignore = 25 +Ignore = 30 ArmorMod = 1.15 Headchance = 30 print("Heavy Axes - 2 Range:") @@ -1395,6 +1395,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. @@ -1472,3 +1473,19 @@ def calc(): #-- 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%. \ No newline at end of file diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index fbabd3d..51e656e 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -128,7 +128,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 +137,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) # ------------------------------------------------------------------------ @@ -240,7 +240,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 +258,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 +276,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 +284,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 @@ -692,7 +692,7 @@ def calc(): if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1410,6 +1410,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. @@ -1493,7 +1494,20 @@ def calc(): #-- 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 (2/8/2024) -#-- Fixed a typo in the Handgonne section causing incorrect results. Thank you to vsobotka for pointing this out. -#Version 1.6.6 (8/7/2024) +#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. \ No newline at end of file diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index 2089e73..a4e4be9 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.5: +#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.6: #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. @@ -164,7 +164,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 +173,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 +252,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 +270,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 +288,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 +296,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 @@ -713,7 +713,7 @@ def calc(): if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1383,6 +1383,7 @@ def calc(): DPreSergeant = 0 Nimble = 0 +Resilient = 0 DPreZweiHeavy = 1 PresetCalc() print("Zweihander:") @@ -1465,6 +1466,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. @@ -1548,3 +1550,18 @@ def calc(): #-- 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. \ No newline at end of file diff --git a/BBCalc.py b/BBCalc.py index 74f6559..b48dfe6 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator Version 1.6.5: +#Battle Brothers Damage Calculator Version 1.6.6: #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. @@ -38,7 +38,7 @@ Def_Helmet = 120 Def_Armor = 95 Fatigue = -15 #Fatigue value only effects Nimble. -Def_Resolve = 70 #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: @@ -158,7 +158,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 +181,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, 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 +212,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 +221,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 +254,7 @@ if APreBladedPike == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve = 55, 80, 30, 30, 125, 1, 80 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, 100 if APreCryptCleaver == 1: Mind, Maxd, Headchance, Ignore, ArmorMod, Fearsome, Atk_Resolve, CleaverMastery = 60, 80, 25, 25, 120, 1, 100, 1 if APreKhopesh == 1: @@ -300,7 +300,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 +314,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 +356,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 +374,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 +392,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 +400,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 @@ -822,7 +822,7 @@ if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1554,6 +1554,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. @@ -1652,3 +1653,18 @@ #-- 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. \ No newline at end of file diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index d91cf75..e491f6c 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.5: +#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.6: #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. @@ -73,7 +73,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 +82,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) # ------------------------------------------------------------------------ @@ -211,7 +211,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 +234,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, 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 +253,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 +299,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 +313,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 +355,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 +373,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 +391,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 +399,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 @@ -813,7 +813,7 @@ def calc(): if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1621,6 +1621,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. @@ -1704,3 +1705,18 @@ def calc(): #-- 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. \ No newline at end of file diff --git a/BBHitChance.py b/BBHitChance.py index cd0b8fc..80e61ae 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.5: +#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.6: #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. @@ -158,7 +158,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 +181,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, 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 +212,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 +221,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 +254,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 +300,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 +314,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 +356,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 +374,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 +392,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 +400,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 @@ -817,7 +817,7 @@ if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1358,6 +1358,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. @@ -1436,3 +1437,18 @@ #-- 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. \ No newline at end of file diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index 69d4852..b5db23b 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.5: +#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.6: #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. @@ -152,7 +152,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 +175,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, 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 +203,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 +249,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 +263,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: @@ -710,7 +710,7 @@ def calc(): if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1315,6 +1315,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. @@ -1395,3 +1396,18 @@ def calc(): #-- 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. \ No newline at end of file diff --git a/BBRaisingHp.py b/BBRaisingHp.py index 61d43d7..0763825 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- HP Changing Version 1.6.5: +#Battle Brothers Damage Calculator -- HP Changing Version 1.6.6: #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. @@ -152,7 +152,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 +175,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, 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 +203,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 +249,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 +263,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: @@ -681,7 +681,7 @@ def calc(): if Flail2HPound == 1: Ignore = Flail2HBodyshot #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: @@ -1238,6 +1238,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. @@ -1317,3 +1318,18 @@ def calc(): #-- 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. \ No newline at end of file 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: From c8d174125ad13684ebdd2ec5921ede13ff745dde Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:29:04 -0500 Subject: [PATCH 18/27] Update README.md Added update 8/22/2024 --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ce58d9..8111e2e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Battle-Brothers-Damage-Calculator Updated for Of Flesh and Faith -Latest 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. +Latest Update: 8/22/2024 - Fixed some attacker/defender presets that were incorrect. Fixed Boneplates working against Puncture. 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. @@ -43,7 +43,7 @@ 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. +-- 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. @@ -53,6 +53,8 @@ helping me with many questions along the way. 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: 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. From 69c22f29ee0bc20ab91f17fd2cf42ac74923cd13 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Tue, 27 Aug 2024 09:48:56 -0500 Subject: [PATCH 19/27] Update BB2HanderBattery.py Fixed Warscythe armor damage. --- BB2HanderBattery.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index 51e656e..59917eb 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1376,12 +1376,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() @@ -1510,4 +1510,4 @@ def calc(): #---- 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. \ No newline at end of file +#-- Fixed an oversight where BonePlates attachment was blocking a hit against Puncture tests when it shouldn't be able to. From cad49843bd8a5a886641778030d54d4ff646d209 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:36:43 -0500 Subject: [PATCH 20/27] Add files via upload Added Ijirok armor logic. Added a condition for the code to terminate if the defender survives more than 500 attacks. --- BB1HanderBattery.py | 59 ++++++++++++++++++++++++++++++++-------- BB2HanderBattery.py | 57 ++++++++++++++++++++++++++++++++------- BBAttackerVsEnemies.py | 20 +++++++------- BBCalc.py | 56 +++++++++++++++++++++++++++++++++----- BBEnemiesVsDefender.py | 61 +++++++++++++++++++++++++++++++++--------- BBHitChance.py | 58 +++++++++++++++++++++++++++++++-------- BBNimbleBattery.py | 24 ++++++++++------- BBRaisingHp.py | 61 +++++++++++++++++++++++++++++++++++------- 8 files changed, 318 insertions(+), 78 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index 54437a7..e819d4f 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.6: +#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.7: #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: @@ -379,6 +385,12 @@ def NimbleCalc(): else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -530,6 +542,7 @@ 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). + 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). @@ -567,7 +580,8 @@ def calc(): 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. @@ -754,6 +768,9 @@ def calc(): hp = math.ceil(hp - SMhp_roll) 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: @@ -1106,6 +1123,22 @@ 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. @@ -1141,17 +1174,13 @@ 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) else: @@ -1184,6 +1213,9 @@ 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) @@ -1224,6 +1256,8 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge 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) + " hits on average.") if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): @@ -1488,4 +1522,7 @@ def calc(): #---- 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%. \ No newline at end of file +#-- 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. \ No newline at end of file diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index 59917eb..33199fc 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.6: +#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.7: #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: @@ -377,6 +383,12 @@ def NimbleCalc(): else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -522,6 +534,7 @@ 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). + 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). @@ -559,7 +572,8 @@ def calc(): 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. @@ -746,6 +760,9 @@ def calc(): hp = math.ceil(hp - SMhp_roll) 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: @@ -1098,6 +1115,22 @@ 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. @@ -1133,17 +1166,13 @@ 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) else: @@ -1176,6 +1205,9 @@ 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) @@ -1216,6 +1248,8 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge 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) + " hits on average.") if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): @@ -1511,3 +1545,6 @@ def calc(): #---- 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. \ No newline at end of file diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index a4e4be9..181efa3 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.6: +#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.7: #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. @@ -767,6 +767,9 @@ def calc(): hp = math.ceil(hp - SMhp_roll) 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: @@ -1154,17 +1157,11 @@ 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) else: @@ -1564,4 +1561,7 @@ def calc(): #---- 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. \ No newline at end of file +#-- 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. \ No newline at end of file diff --git a/BBCalc.py b/BBCalc.py index b48dfe6..864de56 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator Version 1.6.6: +#Battle Brothers Damage Calculator Version 1.6.7: #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. @@ -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: @@ -624,6 +630,12 @@ else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -644,9 +656,11 @@ 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). +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)) if Nimble == 1: @@ -686,6 +700,7 @@ 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. @@ -875,7 +890,10 @@ SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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: @@ -1345,7 +1363,23 @@ 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. @@ -1385,6 +1419,10 @@ 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) else: @@ -1416,8 +1454,6 @@ hits_until_fleeing.append(count/3) else: hits_until_fleeing.append(count) - if Fearsome == 1: - NumberFearsomeProcs.append(FearsomeProcs) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1463,6 +1499,9 @@ 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) @@ -1542,6 +1581,8 @@ if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge 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) + " hits on average.") if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): @@ -1667,4 +1708,7 @@ #---- 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. \ No newline at end of file +#-- 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. \ No newline at end of file diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index e491f6c..7137100 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.6: +#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.7: #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. @@ -627,6 +633,12 @@ def calc(): else: AimedShotMod = 1 + #Ijirok: + if IjirokHeal10 == 1: + IjirokHealing = 10 + if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -643,6 +655,7 @@ 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). + 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). @@ -680,7 +693,8 @@ def calc(): 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. @@ -866,7 +880,10 @@ def calc(): SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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: @@ -1219,6 +1236,22 @@ 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. @@ -1254,17 +1287,13 @@ 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) else: @@ -1298,6 +1327,9 @@ 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) @@ -1338,6 +1370,8 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge 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) + " hits on average.") if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): @@ -1719,4 +1753,7 @@ def calc(): #---- 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. \ No newline at end of file +#-- 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. \ No newline at end of file diff --git a/BBHitChance.py b/BBHitChance.py index 80e61ae..9b928c5 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.6: +#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.7: #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: @@ -625,6 +631,12 @@ else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -641,6 +653,7 @@ 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). +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). @@ -679,7 +692,8 @@ 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. @@ -875,6 +889,9 @@ 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: @@ -1227,6 +1244,21 @@ 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: @@ -1263,17 +1295,13 @@ 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) else: @@ -1306,6 +1334,9 @@ 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) @@ -1346,6 +1377,8 @@ print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge 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): @@ -1451,4 +1484,7 @@ #---- 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. \ No newline at end of file +#-- 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. \ No newline at end of file diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index b5db23b..044f52e 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.6: +#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.7: #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. @@ -577,7 +577,7 @@ def calc(): 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. @@ -764,6 +764,9 @@ def calc(): hp = math.ceil(hp - SMhp_roll) 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: @@ -1145,15 +1148,15 @@ 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(count/3) + else: + hits_until_death.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1410,4 +1413,7 @@ def calc(): #---- 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. \ No newline at end of file +#-- 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. \ No newline at end of file diff --git a/BBRaisingHp.py b/BBRaisingHp.py index 0763825..e721d02 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- HP Changing Version 1.6.6: +#Battle Brothers Damage Calculator -- HP Changing Version 1.6.7: #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: @@ -490,6 +496,12 @@ def NimbleCalc(): else: SkeletonMod = 1 +#Ijirok: +if IjirokHeal10 == 1: + IjirokHealing = 10 +if IjirokHeal20 == 1: + IjirokHealing = 20 + #Bleeding damage: BleedDamage = 0 if CleaverBleed == 1: @@ -511,6 +523,7 @@ 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). + 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). @@ -548,7 +561,8 @@ def calc(): 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. @@ -735,6 +749,9 @@ def calc(): hp = math.ceil(hp - SMhp_roll) 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: @@ -1081,6 +1098,22 @@ 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. @@ -1116,15 +1149,17 @@ 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(count/3) + else: + hits_until_death.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1153,6 +1188,9 @@ 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) @@ -1193,6 +1231,8 @@ def calc(): print (str(AvgFearsomeProcs) + " Fearsome procs on average.") if Forge == 1: print(str(AvgForgeArmor) + " bonus armor from Forge 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) + " hits on average.") if (CleaverBleed == 1 or CleaverMastery == 1 and len(hits_until_1st_bleed) != 0): @@ -1332,4 +1372,7 @@ def calc(): #---- 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. \ No newline at end of file +#-- 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. \ No newline at end of file From 2a288dd546eea927d30251517a228039eec87b7f Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:39:20 -0500 Subject: [PATCH 21/27] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8111e2e..07a2b9f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Battle-Brothers-Damage-Calculator Updated for Of Flesh and Faith -Latest Update: 8/22/2024 - Fixed some attacker/defender presets that were incorrect. Fixed Boneplates working against Puncture. +Latest Update: 10/1/2024 - Added logic for Ijirok armor testing. Added a condition for the code to terminate if the defender survives abnormally long. 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. @@ -53,6 +53,8 @@ helping me with many questions along the way. 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: 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. From 77b27c17ed93941025faa678076c11339e42f09e Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:43:18 -0500 Subject: [PATCH 22/27] Add files via upload --- BB1HanderBattery.py | 525 ++++++++++++----------------------------- BB2HanderBattery.py | 522 ++++++++++++---------------------------- BBAttackerVsEnemies.py | 524 ++++++++++++---------------------------- BBCalc.py | 512 +++++++++++----------------------------- BBEnemiesVsDefender.py | 522 ++++++++++++---------------------------- BBHitChance.py | 525 ++++++++++++----------------------------- BBNimbleBattery.py | 522 ++++++++++++---------------------------- BBRaisingHp.py | 522 ++++++++++++---------------------------- 8 files changed, 1223 insertions(+), 2951 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index e819d4f..20f70a3 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.6.7: +#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.7.0: #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. @@ -535,6 +535,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. @@ -569,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 @@ -615,12 +631,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: @@ -648,9 +659,13 @@ 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 + #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. @@ -685,34 +700,14 @@ def calc(): 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -747,355 +742,127 @@ def calc(): 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: + + #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 (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(count/3) + else: + hits_until_1st_injury.append(count) + UseHeadShotInjuryFormula = 0 + + 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(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(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) + UseHeadShotInjuryFormulaHeavy = 0 + + 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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 SMhp_roll = random.randint(Mind,Maxd) * .5 if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod hp = math.ceil(hp - SMhp_roll) else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod 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))) + SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (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 + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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) - 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) - UseHeadShotInjuryFormulaHeavy = 0 + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 - 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) - #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: @@ -1185,6 +952,17 @@ def calc(): hits_until_death.append(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(count/3) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1231,7 +1009,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: @@ -1239,7 +1017,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: @@ -1387,7 +1165,7 @@ def calc(): Maxd = 40 Ignore = 20 ArmorMod = .7 -Shamshir = 0 +ShamshirMastery = 0 print("Rondel Dagger:") calc() @@ -1525,4 +1303,13 @@ def calc(): #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index 33199fc..26ad015 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.6.7: +#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.7.0: #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. @@ -527,6 +527,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. @@ -561,6 +572,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 @@ -607,12 +623,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: @@ -640,9 +651,13 @@ 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 + #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. @@ -677,34 +692,14 @@ def calc(): 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -739,25 +734,10 @@ def calc(): 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) + + #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. @@ -765,328 +745,115 @@ def calc(): 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(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(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(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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (helmet * 0.1))) + helmet = math.ceil(helmet) + hp = math.ceil(hp - SMhp_roll) 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) + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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: @@ -1177,6 +944,17 @@ def calc(): hits_until_death.append(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(count/3) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1223,7 +1001,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: @@ -1231,7 +1009,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: @@ -1547,4 +1325,12 @@ def calc(): #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index 181efa3..5789214 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.6.7: +#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.7.0: #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. @@ -492,6 +492,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 @@ -569,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 @@ -614,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: @@ -647,9 +658,13 @@ 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 + #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. @@ -684,34 +699,14 @@ def calc(): 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -746,355 +741,127 @@ def calc(): 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: + + #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 (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(count/3) + else: + hits_until_1st_injury.append(count) + UseHeadShotInjuryFormula = 0 + + 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(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(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) + UseHeadShotInjuryFormulaHeavy = 0 + + 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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 SMhp_roll = random.randint(Mind,Maxd) * .5 if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod hp = math.ceil(hp - SMhp_roll) else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod 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))) + SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (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 + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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() + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 - #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) - 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) - 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) - #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: @@ -1166,6 +933,17 @@ def calc(): hits_until_death.append(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(count/3) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1210,7 +988,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: @@ -1218,7 +996,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: @@ -1564,4 +1342,12 @@ def calc(): #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. diff --git a/BBCalc.py b/BBCalc.py index 864de56..2735eda 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator Version 1.6.7: +#Battle Brothers Damage Calculator Version 1.7.0: #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. @@ -612,6 +612,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 @@ -684,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 #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. @@ -735,12 +751,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: @@ -760,8 +771,6 @@ 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. @@ -770,6 +779,9 @@ 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 @@ -808,34 +820,14 @@ 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -870,357 +862,126 @@ 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: + + #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 (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(count/3) + else: + hits_until_1st_injury.append(count) + UseHeadShotInjuryFormula = 0 + + 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(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(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) + UseHeadShotInjuryFormulaHeavy = 0 + + 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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 SMhp_roll = random.randint(Mind,Maxd) * .5 if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod hp = math.ceil(hp - SMhp_roll) else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod 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))) + SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (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 + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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 - - 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 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 - - 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) + #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: @@ -1516,6 +1277,7 @@ print("StDev: " + str(StDev)) if DeathPercent == 1: print("% Hits to die: " + str(HitsToDeathPercent)) + if Undead != 1 and Savant != 1: #Injury Data Return @@ -1711,4 +1473,12 @@ #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index 7137100..d2d29c0 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.6.7: +#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.7.0: #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. @@ -633,6 +633,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 + #Ijirok: if IjirokHeal10 == 1: IjirokHealing = 10 @@ -682,6 +693,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 @@ -728,12 +744,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: @@ -761,9 +772,13 @@ 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 + #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. @@ -798,34 +813,14 @@ def calc(): 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -860,355 +855,127 @@ def calc(): 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: + + #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 (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(count/3) + else: + hits_until_1st_injury.append(count) + UseHeadShotInjuryFormula = 0 + + 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(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(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) + UseHeadShotInjuryFormulaHeavy = 0 + + 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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 SMhp_roll = random.randint(Mind,Maxd) * .5 if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod hp = math.ceil(hp - SMhp_roll) else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod 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))) + SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (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 + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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) - 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) - UseHeadShotInjuryFormulaHeavy = 0 + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 - 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) - #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: @@ -1298,6 +1065,17 @@ def calc(): hits_until_death.append(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(count/3) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1345,7 +1123,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: @@ -1353,7 +1131,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: @@ -1756,4 +1534,12 @@ def calc(): #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. diff --git a/BBHitChance.py b/BBHitChance.py index 9b928c5..e4c9448 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Hit Chance Version 1.6.7: +#Battle Brothers Damage Calculator -- Hit Chance Version 1.7.0: #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. @@ -613,6 +613,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 @@ -681,6 +692,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 @@ -695,7 +711,6 @@ 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. @@ -728,12 +743,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: @@ -751,7 +761,6 @@ 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. @@ -765,9 +774,13 @@ 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 + #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. @@ -802,34 +815,14 @@ 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -864,25 +857,11 @@ 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) + + #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. @@ -894,329 +873,116 @@ 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(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) + 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(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) + #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(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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 + SMhp_roll = random.randint(Mind,Maxd) * .5 + if helmet == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (helmet * 0.1))) + helmet = math.ceil(helmet) + hp = math.ceil(hp - SMhp_roll) 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) - + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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: @@ -1306,6 +1072,17 @@ hits_until_death.append(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(count/3) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1352,7 +1129,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: @@ -1360,7 +1137,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: @@ -1487,4 +1264,12 @@ #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index 044f52e..d6bf942 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Nimble Battery Version 1.6.7: +#Battle Brothers Damage Calculator -- Nimble Battery Version 1.7.0: #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. @@ -501,6 +501,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 @@ -566,6 +577,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 @@ -611,12 +627,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: @@ -644,9 +655,13 @@ 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 + #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. @@ -681,34 +696,14 @@ def calc(): 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -743,355 +738,127 @@ def calc(): 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: + + #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 (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(count/3) + else: + hits_until_1st_injury.append(count) + UseHeadShotInjuryFormula = 0 + + 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(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(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) + UseHeadShotInjuryFormulaHeavy = 0 + + 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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 SMhp_roll = random.randint(Mind,Maxd) * .5 if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod hp = math.ceil(hp - SMhp_roll) else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod 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))) + SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (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 + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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() + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 - #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) - 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) - 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) - #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: @@ -1157,6 +924,17 @@ def calc(): hits_until_death.append(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(count/3) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1200,7 +978,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: @@ -1208,7 +986,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: @@ -1416,4 +1194,12 @@ def calc(): #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. diff --git a/BBRaisingHp.py b/BBRaisingHp.py index e721d02..d285fb4 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- HP Changing Version 1.6.7: +#Battle Brothers Damage Calculator -- HP Changing Version 1.7.0: #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. @@ -478,6 +478,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 @@ -550,6 +561,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 @@ -596,12 +612,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: @@ -629,9 +640,13 @@ 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 + #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. @@ -666,34 +681,14 @@ def calc(): 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 - SMhp_roll = 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) 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 and Puncture != 1: BoneplateMod = 0 @@ -728,355 +723,127 @@ def calc(): 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: + + #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 (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(count/3) + else: + hits_until_1st_injury.append(count) + UseHeadShotInjuryFormula = 0 + + 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(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(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) + UseHeadShotInjuryFormulaHeavy = 0 + + 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(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 + if body == 0: + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + hp = math.ceil(hp - SMhp_roll) + else: + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + 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 * DamageMod * ExecMod - (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 * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage + hp = math.ceil(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. + if SplitManHeadFollowUp == 1: + SplitManHeadFollowUp = 0 SMhp_roll = random.randint(Mind,Maxd) * .5 if helmet == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod + SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod hp = math.ceil(hp - SMhp_roll) else: - SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod 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))) + SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (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 + OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) + SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage hp = math.ceil(hp - SMhp_roll) - 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) - 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) - UseHeadShotInjuryFormulaHeavy = 0 + #Gladiator - Bear trait check: Add another stack for the Bear to account for the second hit from Split Man + if GloriousEndurance == 1: + GloriousEnduranceStacks += 1 - 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) - #Morale check: if FirstMoraleCheck == 0: if Fearsome == 1: @@ -1160,6 +927,17 @@ def calc(): hits_until_death.append(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(count/3) + else: + hits_until_1st_injury.append(count) + if HeavyInjuryChance == 0: + if Flail3Head == 1: + hits_until_1st_heavy_injury_chance.append(count/3) + else: + hits_until_1st_heavy_injury_chance.append(count) #Analysis on data collection: HitsToDeath = statistics.mean(hits_until_death) @@ -1206,7 +984,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: @@ -1214,7 +992,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: @@ -1375,4 +1153,12 @@ def calc(): #-- 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. \ No newline at end of file +#-- Added a condition for the code to terminate if a defender is surviving over 500 attacks. +#Version 1.7.0 +#-- 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. From f24b8a2cbcce1c1b2b5b37c9e6731a4bc02b28fe Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 20 Mar 2025 12:05:51 -0500 Subject: [PATCH 23/27] Update README.md Added latest update 3/20/2025 to top of readme. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 07a2b9f..c3043df 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Battle-Brothers-Damage-Calculator Updated for Of Flesh and Faith -Latest Update: 10/1/2024 - Added logic for Ijirok armor testing. Added a condition for the code to terminate if the defender survives abnormally long. +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. @@ -53,6 +53,8 @@ helping me with many questions along the way. 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. From c868a4409e285444200de4a3fd064cd0e6e2885d Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 20 Mar 2025 12:06:40 -0500 Subject: [PATCH 24/27] Add files via upload --- BB1HanderBattery.py | 2 +- BB2HanderBattery.py | 2 +- BBAttackerVsEnemies.py | 2 +- BBCalc.py | 18 ++++++++++-------- BBEnemiesVsDefender.py | 2 +- BBHitChance.py | 2 +- BBNimbleBattery.py | 2 +- BBRaisingHp.py | 2 +- 8 files changed, 17 insertions(+), 15 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index 20f70a3..1efaa88 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -1304,7 +1304,7 @@ def calc(): #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 +#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. diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index 26ad015..9a38c6b 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1326,7 +1326,7 @@ def calc(): #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 +#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. diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index 5789214..2370111 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -1343,7 +1343,7 @@ def calc(): #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 +#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. diff --git a/BBCalc.py b/BBCalc.py index 2735eda..2b9c5c2 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -106,7 +106,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%. @@ -256,17 +256,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, 105, 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: @@ -534,7 +534,7 @@ #Fearsome if Fearsome == 1: - FearsomeMod = min((Atk_Resolve - 10) / 5,18) + FearsomeMod = math.floor(min(Atk_Resolve * .15,20)) #Damage modifiers: DamageMod = 1 @@ -1474,7 +1474,7 @@ #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 +#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. @@ -1482,3 +1482,5 @@ #-- 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. \ No newline at end of file diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index d2d29c0..5d30fce 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -1535,7 +1535,7 @@ def calc(): #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 +#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. diff --git a/BBHitChance.py b/BBHitChance.py index e4c9448..382566f 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -1265,7 +1265,7 @@ #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 +#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. diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index d6bf942..6cfed7f 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -1195,7 +1195,7 @@ def calc(): #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 +#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. diff --git a/BBRaisingHp.py b/BBRaisingHp.py index d285fb4..453ec84 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -1154,7 +1154,7 @@ def calc(): #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 +#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. From 294e8567ea354a1b52ea97cb429b3fdf87dc4724 Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Thu, 20 Mar 2025 13:57:40 -0500 Subject: [PATCH 25/27] Add files via upload Fixed some typos in comments. --- BB1HanderBattery.py | 6 +++--- BB2HanderBattery.py | 6 +++--- BBAttackerVsEnemies.py | 6 +++--- BBCalc.py | 6 +++--- BBEnemiesVsDefender.py | 6 +++--- BBHitChance.py | 6 +++--- BBNimbleBattery.py | 6 +++--- BBRaisingHp.py | 6 +++--- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index 1efaa88..b6c93c3 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -611,7 +611,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: @@ -674,7 +674,7 @@ def calc(): 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -693,7 +693,7 @@ def calc(): 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. + #If the helmet did get destroyed 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 diff --git a/BB2HanderBattery.py b/BB2HanderBattery.py index 9a38c6b..421deff 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -603,7 +603,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: @@ -666,7 +666,7 @@ def calc(): 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -685,7 +685,7 @@ def calc(): 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. + #If the helmet did get destroyed 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 diff --git a/BBAttackerVsEnemies.py b/BBAttackerVsEnemies.py index 2370111..fdf4004 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -610,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: @@ -673,7 +673,7 @@ def calc(): 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -692,7 +692,7 @@ def calc(): 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. + #If the helmet did get destroyed 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 diff --git a/BBCalc.py b/BBCalc.py index 2b9c5c2..ed8a415 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -731,7 +731,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: @@ -794,7 +794,7 @@ 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -813,7 +813,7 @@ 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. + #If the helmet did get destroyed 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 diff --git a/BBEnemiesVsDefender.py b/BBEnemiesVsDefender.py index 5d30fce..916de98 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -724,7 +724,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: @@ -787,7 +787,7 @@ def calc(): 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -806,7 +806,7 @@ def calc(): 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. + #If the helmet did get destroyed 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 diff --git a/BBHitChance.py b/BBHitChance.py index 382566f..7e80067 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -723,7 +723,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: @@ -789,7 +789,7 @@ 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -808,7 +808,7 @@ 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. + #If the helmet did get destroyed 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 diff --git a/BBNimbleBattery.py b/BBNimbleBattery.py index 6cfed7f..a9a7380 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -607,7 +607,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: @@ -670,7 +670,7 @@ def calc(): 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -689,7 +689,7 @@ def calc(): 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. + #If the helmet did get destroyed 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 diff --git a/BBRaisingHp.py b/BBRaisingHp.py index 453ec84..5593c7b 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -592,7 +592,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: @@ -655,7 +655,7 @@ def calc(): 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. + #If not DestroyArmor, and no armor is present, apply damage directly to hp. 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. @@ -674,7 +674,7 @@ def calc(): 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. + #If the helmet did get destroyed 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 From 157d02f8374374d1c3f987a2369a3ac83d424bac Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:16:59 -0500 Subject: [PATCH 26/27] Add files via upload Updating all calculator variants to v1.7.1 - Made rounding more accurate to the game. - Reworked how 3Head Flails are recorded to track by number of swings instead of by number of hits. - Other minor changes. See bottom of script for full changelog details. --- BB1HanderBattery.py | 175 +++++++++++++++++++---------------- BB2HanderBattery.py | 176 +++++++++++++++++++---------------- BBAttackerVsEnemies.py | 173 +++++++++++++++++++---------------- BBCalc.py | 202 ++++++++++++++++++++++------------------- BBEnemiesVsDefender.py | 175 +++++++++++++++++++---------------- BBHitChance.py | 175 +++++++++++++++++++---------------- BBNimbleBattery.py | 171 ++++++++++++++++++---------------- BBRaisingHp.py | 169 ++++++++++++++++++---------------- 8 files changed, 769 insertions(+), 647 deletions(-) diff --git a/BB1HanderBattery.py b/BB1HanderBattery.py index b6c93c3..a2dab75 100644 --- a/BB1HanderBattery.py +++ b/BB1HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 1Hander Battery Version 1.7.0: +#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. @@ -192,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%. @@ -451,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: @@ -645,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. @@ -666,42 +672,36 @@ def calc(): 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 DestroyArmor, 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 destroyed 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 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 @@ -715,33 +715,35 @@ def calc(): 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) + 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: @@ -760,7 +762,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -770,7 +772,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -781,7 +783,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -791,7 +793,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -821,43 +823,46 @@ def calc(): BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -869,14 +874,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) @@ -949,18 +954,18 @@ def calc(): 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(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) @@ -1313,3 +1318,13 @@ def calc(): #-- 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 421deff..e5cad6c 100644 --- a/BB2HanderBattery.py +++ b/BB2HanderBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- 2Hander Battery Version 1.7.0: +#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. @@ -191,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%. @@ -445,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: @@ -637,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. @@ -658,40 +664,34 @@ def calc(): 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 DestroyArmor, 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 destroyed 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 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. @@ -707,33 +707,35 @@ def calc(): 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) + 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: @@ -752,7 +754,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -762,7 +764,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -773,7 +775,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -783,7 +785,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -813,43 +815,46 @@ def calc(): BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -861,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) @@ -941,18 +946,18 @@ def calc(): 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(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) @@ -1067,7 +1072,7 @@ def calc(): Mind = 60 Maxd = 90 -Ignore = 50 +Ignore = 40 print("2H Hammer - AoE:") calc() @@ -1334,3 +1339,14 @@ def calc(): #-- 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 fdf4004..47e63a7 100644 --- a/BBAttackerVsEnemies.py +++ b/BBAttackerVsEnemies.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Attacker Vs. Enemies Version 1.7.0: +#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%. @@ -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: @@ -644,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. @@ -665,40 +671,34 @@ def calc(): 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 DestroyArmor, 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 destroyed 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 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. @@ -714,33 +714,35 @@ def calc(): 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) + 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: @@ -759,7 +761,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -769,7 +771,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -780,7 +782,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -790,7 +792,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -820,43 +822,46 @@ def calc(): BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -868,14 +873,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) @@ -930,18 +935,18 @@ def calc(): 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(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) @@ -1351,3 +1356,13 @@ def calc(): #-- 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 ed8a415..70a0a99 100644 --- a/BBCalc.py +++ b/BBCalc.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator Version 1.7.0: +#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. @@ -80,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%. @@ -469,6 +470,8 @@ Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -765,6 +768,9 @@ 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. @@ -785,41 +791,35 @@ #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 DestroyArmor, 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 destroyed 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 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. @@ -835,33 +835,35 @@ 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) + 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: @@ -880,7 +882,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -890,7 +892,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -901,7 +903,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -911,7 +913,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -941,43 +943,46 @@ BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -1000,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: @@ -1028,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: @@ -1068,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: @@ -1110,14 +1115,14 @@ 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: @@ -1185,34 +1190,35 @@ 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) @@ -1483,4 +1489,14 @@ #-- 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. \ No newline at end of file +#-- 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 916de98..74aabee 100644 --- a/BBEnemiesVsDefender.py +++ b/BBEnemiesVsDefender.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Enemies Vs. Defender Version 1.7.0: +#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. @@ -133,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%. @@ -549,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: @@ -758,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. @@ -778,41 +784,35 @@ def calc(): #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 DestroyArmor, 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 destroyed 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 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. @@ -828,33 +828,35 @@ def calc(): 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) + 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: @@ -873,7 +875,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -883,7 +885,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -894,7 +896,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -904,7 +906,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -934,43 +936,46 @@ def calc(): BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -982,14 +987,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) @@ -1062,18 +1067,18 @@ def calc(): 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(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) @@ -1543,3 +1548,13 @@ def calc(): #-- 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 7e80067..4bd6262 100644 --- a/BBHitChance.py +++ b/BBHitChance.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Hit Chance Version 1.7.0: +#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. @@ -80,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%. @@ -471,6 +472,8 @@ Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -757,6 +760,9 @@ 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. @@ -780,41 +786,35 @@ #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 DestroyArmor, 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 destroyed 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 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. @@ -830,33 +830,35 @@ 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) + 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: @@ -880,7 +882,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -890,7 +892,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -901,7 +903,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -911,7 +913,7 @@ if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -941,43 +943,46 @@ BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -989,14 +994,14 @@ 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) @@ -1069,18 +1074,18 @@ 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(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) @@ -1273,3 +1278,13 @@ #-- 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 a9a7380..a84e252 100644 --- a/BBNimbleBattery.py +++ b/BBNimbleBattery.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- Nimble Battery Version 1.7.0: +#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%. @@ -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: @@ -641,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. @@ -662,40 +668,34 @@ def calc(): 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 DestroyArmor, 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 destroyed 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 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. @@ -711,33 +711,35 @@ def calc(): 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) + 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: @@ -756,7 +758,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -766,7 +768,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -777,7 +779,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -787,7 +789,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -817,43 +819,46 @@ def calc(): BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -921,18 +926,18 @@ def calc(): 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(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) @@ -1203,3 +1208,13 @@ def calc(): #-- 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 5593c7b..fbb3f25 100644 --- a/BBRaisingHp.py +++ b/BBRaisingHp.py @@ -1,4 +1,4 @@ -#Battle Brothers Damage Calculator -- HP Changing Version 1.7.0: +#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. @@ -75,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%. @@ -334,6 +335,8 @@ Ignore *= 1.25 if Duelist == 1: Ignore += .25 +if AoE2HHammer == 1: + Ignore -= .1 if AoE2HAxe == 1: Ignore -= .1 if Ignore > 1: @@ -626,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. @@ -647,40 +653,34 @@ def calc(): 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 DestroyArmor, 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 destroyed 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 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. @@ -696,33 +696,35 @@ def calc(): 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) + 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: @@ -741,7 +743,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) UseHeadShotInjuryFormula = 0 @@ -751,7 +753,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: Injury = 1 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) @@ -762,7 +764,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) UseHeadShotInjuryFormulaHeavy = 0 @@ -772,7 +774,7 @@ def calc(): if math.floor(hp_roll) >= Def_HP * InjuryThreshold: HeavyInjuryChance = 1 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) @@ -802,43 +804,46 @@ def calc(): BoneplateMod = 0 SMhp_roll = 0 else: - SMhp_roll = random.randint(Mind,Maxd) * .5 + SMhp_roll = random.randint(Mind,Maxd) * .5 #Split Man has a 50% damage modifier. if body == 0: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers * AttachMod ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(body,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(body,(SMarmor_roll * ForgeMod))) body -= SMarmor_roll if body > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - (body * 0.1))) - body = math.ceil(body) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers * AdFurPadMod * AttachMod - (body * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore * AdFurPadMod) * NimbleMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * AdFurPadMod * GladMod * IndomMod * AttachMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(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. + 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: - SMhp_roll = SMhp_roll * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - hp = math.ceil(hp - SMhp_roll) + 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 * ArmorMod * GladMod * IndomMod * DamageMod * ExecMod + SMarmor_roll = random.randint(Mind,Maxd) * .5 * ArmorDamageModifiers ForgeSaved += SMarmor_roll - SMarmor_roll * ForgeMod - SMarmor_roll = min(helmet,(SMarmor_roll * ForgeMod)) + SMarmor_roll = math.floor(min(helmet,(SMarmor_roll * ForgeMod))) helmet -= SMarmor_roll if helmet > 0: - SMhp_roll = max(0,(SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - (helmet * 0.1))) - helmet = math.ceil(helmet) - hp = math.ceil(hp - SMhp_roll) + SMhp_roll = math.floor(max(0,(SMhp_roll * Ignore * HPDamageModifiers - (helmet * 0.1)))) else: - OverflowDamage = max(0,(SMhp_roll * (1 - Ignore) * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod - SMarmor_roll)) - SMhp_roll = SMhp_roll * Ignore * NimbleMod * GladMod * IndomMod * DamageMod * ExecMod + OverflowDamage - hp = math.ceil(hp - SMhp_roll) + 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: @@ -924,18 +929,18 @@ def calc(): 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(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) @@ -1162,3 +1167,13 @@ def calc(): #-- 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 From d430ec0375d8716eff4ad16abc7b76cf0f6c6b4d Mon Sep 17 00:00:00 2001 From: turtle225 <59210589+turtle225@users.noreply.github.com> Date: Mon, 31 Mar 2025 20:01:50 -0500 Subject: [PATCH 27/27] Update README.md Added Latest Update: 3/31/2025 to top of readme. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c3043df..81dfe9d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Battle-Brothers-Damage-Calculator Updated for Of Flesh and Faith +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/