summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSupremist <sergkarv@gmail.com>2025-10-30 01:16:56 +0200
committerSupremist <sergkarv@gmail.com>2025-10-30 01:16:56 +0200
commit9cc423f1e3b0a1d9e498c522cb550709e348eed7 (patch)
tree9cf54a7b7d77cc6f905f091c7bff972430dfdc40
parent9fa4cfbe2f948f4067ca1572bbc5dbfab678e7da (diff)
downloadpf2e_calc-9cc423f1e3b0a1d9e498c522cb550709e348eed7.tar.xz
pf2e_calc-9cc423f1e3b0a1d9e498c522cb550709e348eed7.zip
Add weapon damage comparison graph
-rw-r--r--actions.py5
-rw-r--r--character.py15
-rw-r--r--game.py20
-rw-r--r--main.py89
4 files changed, 102 insertions, 27 deletions
diff --git a/actions.py b/actions.py
index f3cd39c..9710faa 100644
--- a/actions.py
+++ b/actions.py
@@ -27,10 +27,13 @@ class Attack:
return self.damage(check)
return self.roll().apply(strike_dmg)
+ def damage_bonus(self):
+ return self.attacker.attack_damage_bonus(self.weapon, self.strike_type)
+
def damage(self, check=Roll.Success):
persistant_damage_factor = 3 + 1/3
wp = self.weapon
- dmg_bonus = self.attacker.attack_damage_bonus(wp, self.strike_type)
+ dmg_bonus = self.damage_bonus()
if wp.backstabber:
dmg_bonus += 1
if wp.potency >= 3:
diff --git a/character.py b/character.py
index 14a7e42..9cc2498 100644
--- a/character.py
+++ b/character.py
@@ -159,6 +159,14 @@ class Character:
weapon.striking = self.features["striking"]
except KeyError:
pass
+
+ def weapon_proficiency_bonus(self, weapon):
+ return max(self.features.get(weapon.category, 0),
+ self.features.get(weapon.name, 0))
+
+ def weapon_specialization_bonus(self, weapon):
+ factor = self.features.get("weapon specialization", 0)
+ return self.weapon_proficiency_bonus(weapon) * factor // 2
def attack_bonus(self, weapon: Weapon, strike_type="melee"):
dex = self.ability_modifier("dex").total()
@@ -168,13 +176,13 @@ class Character:
mod = max(mod, dex)
elif strike_type == "ranged":
mod = dex
- prof = max(self.features.get(weapon.category, 0),
- self.features.get(weapon.name, 0))
- return mod + prof + weapon.potency
+ prof = self.weapon_proficiency_bonus(weapon)
+ return mod + prof + self.level + weapon.potency
def attack_damage_bonus(self, weapon: Weapon, strike_type="melee"):
str_mod = self.ability_modifier("str")
dmg_mod = self.conditions.modifier("damage")
+ dmg_mod += self.weapon_specialization_bonus(weapon)
if strike_type == "melee":
if "thief" in self.features and weapon.finesse:
@@ -221,6 +229,7 @@ class CharacterPlan(LevelingPlan):
blacklist = duplicates
blacklist.extend(spells)
blacklist.extend(equipment)
+ blacklist.append("feats")
for key in blacklist:
del data[key]
diff --git a/game.py b/game.py
index 7adcc8a..0115a4a 100644
--- a/game.py
+++ b/game.py
@@ -48,6 +48,26 @@ class Roll:
def clamp(value):
return max(Roll.Fumble, min(Roll.Crit, value))
+ @staticmethod
+ def icon(check_res):
+ if check_res == Roll.Crit:
+ return "🎯"
+ if check_res == Roll.Success:
+ return "✅"
+ if check_res == Roll.Fail:
+ return "💢"
+ if check_res == Roll.Fumble:
+ return "💀"
+ return "?"
+
+ @staticmethod
+ def short_str(dist: Distribution):
+ res = list()
+ for roll in range(2, -2, -1):
+ number = int(round(dist.probabilities[roll] * 20))
+ res.append(Roll.icon(roll) + str(number))
+ return " ".join(res)
+
class Difficulty(Enum):
Terrible = -2
diff --git a/main.py b/main.py
index 53993f1..0a25394 100644
--- a/main.py
+++ b/main.py
@@ -5,8 +5,15 @@ from creature import CreaturePlan
from actions import Attack
from items import Weapon
+from collections import defaultdict
+
+import matplotlib.pyplot as plt
+from matplotlib.ticker import AutoLocator, AutoMinorLocator, MultipleLocator
+
import pb2e
+plt.style.use('dark_background')
+
def treat_wounds(check_res, heal_bonus=0):
if check_res <= Roll.Fumble:
return -d(8)
@@ -60,36 +67,72 @@ def main():
treat = d(20).apply(Roll.check, 20, bonus+1).apply(treat_wounds, 15)
show_stat("Treat Wounds 20 +1", treat)
-def main2():
- ac = 18
- bonus = +8
- dmg = d(6)+4
- attack = d(20).apply(Roll.check, ac, bonus)
- show_stat("attack", attack)
- total_dmg = attack.apply(strike, dmg+1)
- show_stat("dogslicer", total_dmg)
- total_dmg = attack.apply(strike, dmg, d(8))
- show_stat("rapier", total_dmg)
def test3():
- level = 2
- #char_js = pb2e.download_character(pfb_id=160180)
+ # char_js = pb2e.download_character(pfb_id=160180)
char_js = pb2e.open_character("quizrel2")
char_plan = CharacterPlan(char_js)
- char = char_plan.build(level)
- falcata = Weapon("falcata", "martial", "sword", 8, fatal=12)
- char.weapons.append(falcata)
- char.update_fundamental_runes()
-
creature_plan = CreaturePlan(default_difficulty=Difficulty.Moderate)
# creature_plan["reflex"] = Difficulty.High
# creature_plan["ac"] = Difficulty.High
- creature = creature_plan.build(level)
- attack = Attack(char, creature, falcata)
- print(char.toJSON())
- print(f"Attack +{attack.bonus()} vs AC {attack.target.ac()}")
- show_stat("roll", attack.roll())
- show_stat("falcata", attack.strike())
+
+ def calc_damage(level):
+ char = char_plan.build(level)
+ creature = creature_plan.build(level)
+ creature["ac"] -= 2
+ falcata = Weapon("falcata", "martial", "sword", 8, fatal=12)
+ sword = Weapon("sword", "martial", "sword", 8)
+ rapier = Weapon("rapier", "martial", "sword", 6, finesse=True, deadly=8)
+ sword2 = Weapon("sword_d6", "martial", "sword", 6)
+ char.weapons = [falcata, sword, rapier, sword2]
+ char.update_fundamental_runes()
+
+ # print(char.toJSON())
+ # attack = Attack(char, creature, falcata)
+ # print(Roll.short_str(attack.roll()))
+ # print(f"Attack +{attack.bonus()} vs AC {attack.target.ac()} dmg +{attack.damage_bonus()}")
+ # show_stat("roll", attack.roll())
+ # show_stat("falcata", attack.strike())
+
+ res = dict()
+ for weapon in char.weapons:
+ attack = Attack(char, creature, weapon)
+ res[weapon.name] = attack.strike().expected()
+ # res["hp"] = creature["hp"]
+ return res
+
+ # calc_damage(20)
+
+ levelup_view("Weapons Comparison", "Expected Damage", calc_damage)
+
+
+
+def levelup_view(title, ylabel, fn):
+ # Example data
+ levels = list(range(1, 21))
+ table = defaultdict(list)
+ for level in levels:
+ res = fn(level)
+ for k,v in res.items():
+ table[k].append(v)
+
+ fig, ax = plt.subplots()
+ fig.set_size_inches(18.5, 10.5)
+
+ # Plot multiple lines on the same plane
+ for label, values in table.items():
+ plt.plot(levels, values, label=label)
+
+ # Add labels and legend
+ plt.xlabel("Level")
+ plt.ylabel(ylabel)
+ plt.title(title)
+ ax.grid(True, linestyle='--', alpha=0.4)
+ ax.xaxis.set_major_locator(MultipleLocator(1))
+ ax.yaxis.set_major_locator(AutoLocator())
+ ax.yaxis.set_minor_locator(AutoMinorLocator())
+ plt.legend()
+ plt.show()
if __name__ == '__main__':