diff options
| author | Supremist <sergkarv@gmail.com> | 2025-10-30 01:16:56 +0200 |
|---|---|---|
| committer | Supremist <sergkarv@gmail.com> | 2025-10-30 01:16:56 +0200 |
| commit | 9cc423f1e3b0a1d9e498c522cb550709e348eed7 (patch) | |
| tree | 9cf54a7b7d77cc6f905f091c7bff972430dfdc40 | |
| parent | 9fa4cfbe2f948f4067ca1572bbc5dbfab678e7da (diff) | |
| download | pf2e_calc-9cc423f1e3b0a1d9e498c522cb550709e348eed7.tar.xz pf2e_calc-9cc423f1e3b0a1d9e498c522cb550709e348eed7.zip | |
Add weapon damage comparison graph
| -rw-r--r-- | actions.py | 5 | ||||
| -rw-r--r-- | character.py | 15 | ||||
| -rw-r--r-- | game.py | 20 | ||||
| -rw-r--r-- | main.py | 89 |
4 files changed, 102 insertions, 27 deletions
@@ -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] @@ -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 @@ -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__': |
