Kaelygon

Resilient hide Daggerheart Homebrew testing

Jun 25th, 2025 (edited)
15
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.55 KB | None | 0 0
  1. import random
  2. #Yes, this was drafted by chat gpt initially
  3. #I modified few parts like scale, armor and Player/PC
  4. #it looks right? maybe?
  5. #https://pastebin.com/7MhuWAZk
  6.  
  7. class Player:
  8. def __init__(self, name, tier, hp, stress, evasion, bt_minor, bt_major, bs, scales=1):
  9. self.name = name
  10. self.tier = tier
  11. self.base_hp = hp
  12. self.base_stress = stress
  13. self.evasion = evasion
  14. self.bt_minor = bt_minor
  15. self.bt_major = bt_major
  16. self.bs = bs
  17. self.scales = scales #scales feature
  18.  
  19. def reset(self):
  20. self.hp = self.base_hp
  21. self.stress = self.base_stress
  22. self.armor = self.bs
  23. self.hits_taken = 0
  24.  
  25. def take_hit(self, hit_value):
  26. if hit_value < self.evasion:
  27. self.hits_taken += 1
  28. return #Evade
  29.  
  30. #dmg range
  31. dmg = 0
  32. if hit_value < self.bt_minor:
  33. dmg = 1
  34. elif hit_value < self.bt_major:
  35. dmg = 2
  36. elif hit_value < 2 * self.bt_major:
  37. dmg = 3
  38. else:
  39. dmg = 4
  40.  
  41. #reduce armor using scales ancestry stress+bs
  42. if self.scales and dmg>(3-self.scales) and self.armor and self.stress:
  43. dmg -= 1
  44. self.stress -= 1
  45. self.armor -= 1
  46.  
  47. #reduce dmg by armor
  48. if dmg and self.armor:
  49. dmg -= 1
  50. self.armor -= 1
  51.  
  52. self.hp -= dmg
  53. self.hits_taken += 1
  54.  
  55.  
  56. class NPC:
  57. def __init__(self, difficulty, tier, dice_count, dice_sides, bonus):
  58. self.difficulty = difficulty
  59. self.tier = tier
  60. self.dice_count = dice_count
  61. self.dice_sides = dice_sides
  62. self.bonus = bonus
  63.  
  64. def roll_hit(self):
  65. return sum(random.randint(1, self.dice_sides) for _ in range(self.dice_count)) + self.bonus
  66.  
  67.  
  68. # Variants
  69. pc_variants = [
  70. # Natural
  71. Player("natural", tier=1, hp=4, stress=6, evasion=14, bt_minor=4, bt_major=8, bs=4, scales=1 ),
  72. Player("natural", tier=3, hp=6, stress=7, evasion=16, bt_minor=8, bt_major=22, bs=6, scales=1 ),
  73. Player("natural", tier=4, hp=7, stress=8, evasion=19, bt_minor=10, bt_major=29, bs=7, scales=1 ),
  74.  
  75. # Gambeson
  76. Player("gambeson", tier=1, hp=4, stress=6, evasion=13, bt_minor=5, bt_major=11, bs=3, scales=1),
  77. Player("gambeson", tier=3, hp=6, stress=7, evasion=14, bt_minor=9, bt_major=23, bs=5, scales=1),
  78. Player("gambeson", tier=4, hp=7, stress=8, evasion=16, bt_minor=11, bt_major=32, bs=6, scales=1),
  79.  
  80. # miscArmor
  81. Player("miscArmor", tier=1, hp=4, stress=6, evasion=12, bt_minor=6, bt_major=13, bs=3, scales=1), #Leather
  82. Player("miscArmor", tier=3, hp=6, stress=7, evasion=14, bt_minor=11, bt_major=27, bs=5, scales=1), #Bellamoi Fine Armor
  83. Player("miscArmor", tier=4, hp=7, stress=8, evasion=16, bt_minor=13, bt_major=36, bs=5, scales=1), #channeling armor
  84. ]
  85.  
  86. npc_variants = [
  87. #tier 1
  88. NPC("Patc", tier=1, dice_count=1, dice_sides=20, bonus=0),
  89. NPC("Toug", tier=1, dice_count=3, dice_sides=8, bonus=2),
  90. NPC("Hard", tier=1, dice_count=2, dice_sides=12, bonus=6),
  91.  
  92. #tier 3
  93. NPC("Flic", tier=3, dice_count=3, dice_sides=20, bonus=0),
  94. NPC("Jeal", tier=3, dice_count=3, dice_sides=8, bonus=3),
  95. NPC("Hubr", tier=3, dice_count=3, dice_sides=10, bonus=0),
  96. NPC("Eart", tier=3, dice_count=2, dice_sides=12, bonus=5),
  97. NPC("Head", tier=4, dice_count=2, dice_sides=20, bonus=4),
  98.  
  99. #tier 4
  100. NPC("Oute", tier=4, dice_count=4, dice_sides=6, bonus=13),
  101. NPC("Hall", tier=4, dice_count=4, dice_sides=8, bonus=8),
  102. NPC("High", tier=4, dice_count=4, dice_sides=10, bonus=10),
  103. NPC("Fall", tier=4, dice_count=4, dice_sides=12, bonus=13),
  104. ]
  105.  
  106.  
  107. SIMULATIONS = 1000 #No. simulation repeats per scenario
  108. hit_cap = 25 #Quit current scenario after hit_cap. By then we likely got enough data or the scenario always evades
  109.  
  110. results = []
  111.  
  112. for pc in pc_variants:
  113. for npc in npc_variants:
  114. if npc.tier != pc.tier:
  115. continue # Only match same-tier NPCs
  116.  
  117. total_hits = 0
  118. sim_count = 0
  119. full_evade_count = 0
  120.  
  121. for _ in range(SIMULATIONS):
  122. pc.reset()
  123. while pc.hp > 0 and pc.hits_taken < hit_cap:
  124. roll = npc.roll_hit()
  125. pc.take_hit(roll)
  126.  
  127. if pc.hits_taken >= hit_cap and pc.hp > 0:
  128. full_evade_count += 1
  129. continue
  130. else:
  131. total_hits += pc.hits_taken
  132. sim_count += 1
  133.  
  134. avg_hits = (total_hits / sim_count) if sim_count > 0 else float('nan')
  135.  
  136. results.append({
  137. "pc": pc.name,
  138. "tier": pc.tier,
  139. "npc": npc.difficulty,
  140. "avg_hits_survived": avg_hits,
  141. "always_evaded": full_evade_count
  142. })
  143.  
  144. # Detailed results
  145. for r in results:
  146. print(f"Player: {r['pc']:9} | Tier: {r['tier']} | NPC: {r['npc']:6} | "
  147. f"Avg Hits: {r['avg_hits_survived']:.2f} | Evaded: {r['always_evaded']}")
  148.  
  149. # Summary per Player variant
  150. summary = {}
  151. evade_summary = {}
  152.  
  153. for r in results:
  154. summary.setdefault(r["pc"], []).append(r["avg_hits_survived"])
  155. evade_summary.setdefault(r["pc"], 0)
  156. evade_summary[r["pc"]] += r["always_evaded"]
  157.  
  158. print("\nArmor type : avg hits survived | Evade counter")
  159. for pc_name, hits_list in summary.items():
  160. valid_hits = [h for h in hits_list if not isinstance(h, float) or not (h != h)] # exclude NaN
  161. if valid_hits:
  162. avg = sum(valid_hits) / len(valid_hits) if valid_hits else 0
  163. avg_str = f"{avg:.2f}"
  164. else:
  165. avg_str = "--"
  166. print(f"{pc_name:9}: {avg_str:>5} | Evaded: {evade_summary[pc_name]}")
Advertisement
Add Comment
Please, Sign In to add comment