Guest User

Untitled

a guest
Aug 3rd, 2025
24
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.23 KB | None | 0 0
  1. # ----08-03-2025 14:15:57----
  2. # excerpted from crawl.py
  3.  
  4. from collections import defaultdict
  5.  
  6. # calculates the average factor by which incoming dmg is reduced by AC
  7. def ACReduction(ac, dam, gdr = 0):
  8.      totreduction = 0
  9.      gdr_amt = int(gdr * 100 * dam) // 100
  10.      if gdr_amt > ac // 2:
  11.           gdr_amt = ac // 2
  12.      totdam = 0
  13.      for roll1 in range(1, dam + 1): # damage roll
  14.           for roll2 in range(ac + 1): # ac roll
  15.                if roll2 < gdr_amt:
  16.                     roll2 = gdr_amt
  17.                if roll2 > roll1:
  18.                     roll2 = roll1
  19.                totreduction += roll2
  20.                totdam += roll1
  21.      if totreduction == totdam:
  22.           return float("inf")  # 100% damage reduction, infinite reduction factor
  23.      return float(totdam) / (totdam - totreduction)
  24.  
  25. def ACReductionMultipleAttacks(ac, dam, gdr = 0):
  26.      acreduc = 0
  27.      if ac == 0:
  28.           return 1.0
  29.      elif type(dam) == list:
  30.           treduc = 0
  31.           for d in dam:
  32.                treduc += d - (float(d) / ACReduction(ac, d, gdr))
  33.                acreduc = sum(dam) / (sum(dam) - treduc)
  34.      else:
  35.           acreduc = ACReduction(ac, dam, gdr)
  36.      return acreduc
  37.  
  38.  
  39. def calcToHit(hitdice, fighter):
  40.      tohit = 18
  41.      if fighter:
  42.           tohit += int(25 * hitdice / 10)
  43.      else:
  44.           tohit += int(15 * hitdice / 10)
  45.      return tohit
  46.  
  47. # reduction for hitting the enemy with a bolt
  48. def EVReductionEnemyBolt(ev, tohit):
  49.      totreduction = 0
  50.      totdam = 0
  51.      if ev == 0:
  52.           return 1.0
  53.      for rand_ev in range(ev):
  54.           for attack in range(tohit):
  55.                for roll in range(rand_ev):
  56.                     for roll2 in range(rand_ev + 1):
  57.                          if attack < (roll + roll2) // 2:
  58.                               totreduction += 1 # 1 damage dodged
  59.                          totdam += 1
  60.      return float(totdam) / (totdam - totreduction)
  61.  
  62. # reduction for hitting the player with a bolt
  63. def EVReductionPlayerBolt(ev, tohit):
  64.      totreduction = 0
  65.      totdam = 0
  66.      if ev == 0:
  67.           return 1.0
  68.      for attack in range(tohit):
  69.           for roll in range(ev):
  70.                for roll2 in range(ev + 1):
  71.                     if attack < (roll + roll2) // 2:
  72.                          totreduction += 1 # 1 damage dodged
  73.                     totdam += 1
  74.      return float(totdam) / (totdam - totreduction)
  75.  
  76.  
  77. # calculates the average factor by which incoming dmg is reduced by EV
  78. # provide _either_ hitdice and fighter, _or_ tohit, but not both
  79. def EVReduction(ev, hitdice = 0, fighter = False, tohit = None):
  80.      if tohit == None:
  81.           tohit = calcToHit(hitdice, fighter)
  82.      totreduction = 0
  83.      totdam = 0
  84.      if ev == 0:
  85.           return 1 / 0.975
  86.      for roll1 in range(tohit + 1):
  87.           for roll2 in range(2 * ev):
  88.                for roll3 in range(2 * ev + 1):
  89.                     if roll1 < (roll2 + roll3) // 2:
  90.                          totreduction += 1 # 1 damage dodged
  91.                     totdam += 1
  92.      # normalize to 1.0 totdam and acct for the fact the preceding calculation only applied to 95% of cases
  93.      totreduction = 0.95 * (float(totreduction) / totdam)
  94.      # now account for 2.5% automatic hits and 2.5% automatic misses
  95.      return 1.00 / (1.00 - (totreduction + 0.025))
  96.  
  97. def BlockChancePerAttack(sh, pastblocks = 0, hitdice = 0):
  98.      totdam = 0
  99.      totreduction = 0
  100.      if sh == 0:
  101.           return 0
  102.      for roll1 in range(15 + hitdice * 2 // 3 + 5 * pastblocks**2):
  103.           for roll2 in range(4 * sh):
  104.                for roll3 in range(4 * sh + 1):
  105.                     blockval = (roll2 + roll3) // 6 - 1
  106.                     if roll1 <= blockval:
  107.                          totreduction += 1 # blocked
  108.                     totdam += 1
  109.      return float(totreduction) / totdam
  110.  
  111. import math
  112.  
  113. # Note that there is sometimes a complex interaction between armor and shield reduction
  114. # (not simply multiplicative)
  115. # In the case where the attacks are not all the same, the earlier attacks will be blocked more and the later attacks will be blocked less,
  116. # and the effect of armor on the different attacks is unequal as well
  117. # We'll just ignore it since it's OK when the attacks are the same and these calculations don't have to be perfect
  118. def SHReductionMultipleAttacks(sh, attacks, speed, hitdice):
  119.      if type(attacks) is int:
  120.           attacks = [attacks]
  121.      bcpa = [] # bcpa[blocks] = probability of a block given previous blocks
  122.      maxatk = len(attacks) * int(math.ceil(speed))
  123.      for pastblocks in range(maxatk):
  124.           bcpa.append(BlockChancePerAttack(sh, pastblocks, hitdice))
  125.      bcpa.append(0) # dummy value, we'll never get this many past blocks
  126.      # now weight by how often each number of pastblocks is happening
  127.      # in a given turn, player will be attacked either ceil(speed) times,
  128.      #  or floor(speed) times.
  129.      # In the long run over N turns, player will be attacked N * speed times.
  130.      # Therefore, a * ceil(speed) + (N - a) * floor(speed) = N * speed
  131.      # where a is the number of times the player is attacked ceil(speed) times
  132.      #  a * (ceil(speed) - floor(speed)) = N * speed - N * floor(speed)
  133.      #  a/N = speed - floor(speed) assuming speed != floor(speed)
  134.      #  
  135.      # weights[k] = probability k'th attack will happen in a turn
  136.      weights = [0] + [1] * (len(attacks) * int(math.floor(speed)))
  137.      if math.floor(speed) != speed:
  138.           weights.append([speed - math.floor(speed)] * len(attacks))
  139.      # now calculate probability of different blocking results
  140.      bcpa2 = [[0]*(maxatk+1) for x in range(maxatk+1)] # bcpa2[nBlocks][nAttacks] = P(we see the combination (nBlocks, nAttacks) at some point in a given turn)
  141.      for x in range(maxatk+1): # boundary values
  142.           bcpa2[0][x] = (1 - bcpa[0]) ** x
  143.      for block in range(1, maxatk+1):  # fill in bcpa2 with dynamic programming
  144.           for attack in range(block, maxatk+1):
  145.                bcpa2[block][attack] = bcpa2[block-1][attack-1] * bcpa[block - 1] + bcpa2[block][attack-1] * (1 - bcpa[block])
  146.      # account for the fact that the last attack may not always happen
  147.      if math.floor(speed) != speed:
  148.           for block in range(maxatk+1):
  149.                for atk in range(maxatk+1 - len(attacks), maxatk+1):
  150.                     bcpa2[block][attack] *= speed - math.floor(speed)
  151.      # now calculate actual damage reduction
  152.      totdmg = 0
  153.      totreduction = 0
  154.      for attack in range(1, maxatk+1):
  155.           dmg = attacks[(attack - 1) % len(attacks)] * weights[attack] # proportional to the average damage dealt by the nth attack in a turn
  156.           blockpr = 0
  157.           norm = 0
  158.           for block in range(0, attack+1):
  159.                norm += bcpa2[block][attack]
  160.                if bcpa2[block][attack] == 0: # never have this combination of blocks and attacks
  161.                     continue
  162.                if block == 0:  # couldn't have been blocked
  163.                     continue
  164.                #p = bcpa[block-1] * bcpa2[block-1][attack-1] / bcpa2[block][attack] # chance the last attack was blocked
  165.                #blockpr += p * bcpa2[block][attack]
  166.                blockpr += bcpa[block-1] * bcpa2[block-1][attack-1]
  167.           blockpr = blockpr / norm
  168.           totdmg += dmg
  169.           totreduction += dmg * blockpr
  170.      return totdmg / (totdmg - totreduction)
  171.  
  172. def EVReductionRanged(ev, hitdice = 0, fighter = False, tohit = None, deflect=0, beam=False):
  173.      if beam and deflect == 2:
  174.           tohit = random2(tohit*2) // 3
  175.      elif beam and deflect == 1 and tohit >= 2:
  176.           tohit = random_range((tohit+1) // 2 + 1, tohit)
  177.      elif deflect>0: # not beam
  178.           tohit = random2(tohit // deflect)
  179.          
  180.  
  181. # monsters
  182. # dam, hitdice, fighter, name, poisontype
  183. # poisontype is 1 for poison, 2 for strong poison
  184. iguana = [15, 3, False, "Iguana", 0]
  185. yak = [18, 7, False, "Yak", 0]
  186. deathyak = [30, 14, False, "Death Yak", 0]
  187. deeptroll = [[27, 20, 20], 10, False, "Deep Troll", 0]
  188. veryugly = [36, 18, False, "Purple Very Ugly Thing", 0]
  189. direelephant = [[40, 15], 15, False, "Dire Elephant", 0]
  190. hydra6 = [[18, 18, 18, 18, 18, 18], 13, False, "Six-headed Hydra", 0]
  191. orbguardian = [45, 15, True, "Orb Guardian", 0]
  192. juggernaut = [120, 20, True, "Juggernaut", 0]
  193. bonedragon = [[30,20,20], 20, False, "Bone Dragon", 0]
  194. alligator = [[30,15],12,False,"Alligator", 0]
  195. blackmamba = [20,7,False,"Black Mamba", 1]
  196. adder = [5, 2, False, "Adder", 1]
  197. stonegiant = [45, 16, False, "Stone Giant", 0]
  198.  
  199. # poison damage for strong poison is hitdice*11/3 - hitdice*13/2
  200. # for regular poison, hitdice*2-hitdice*4
  201.  
  202. monsters = [yak, deathyak, deeptroll, veryugly, direelephant, hydra6, orbguardian, bonedragon, blackmamba, adder, stonegiant]
  203.  
  204. def all_ev_ac_sh_tables():
  205.      for monster in monsters:
  206.           ev_ac_sh_table(monster, 6, "DV")
  207.  
  208. import sys
  209. def all_summaries():
  210.      sys.stdout.write("Summary of monsters - damage reduction factors for each given category (ev, ac, sh) start at 10 (e.g. sh 10) and increase in increments of 10\n\n")
  211.      for monster in monsters:
  212.           ev_ac_sh_summary(monster)
  213.           sys.stdout.write("\n")
  214.  
  215. # adjust the list of damage reduction factors for mode
  216. def transform(values, mode):
  217.      if mode == "DV":
  218.           return [math.log(x, 1.1) for x in values]
  219.      elif mode == "MDV":
  220.           values = [math.log(x, 1.1) for x in values]
  221.           return [(y - x) for x, y in zip(values, values[1:])] # table of differences
  222.  
  223. def plot_ac_ev_tradeoff(ofile=sys.stdout, tot=50):
  224.      ofile.write('ev ')
  225.      for m in monsters:
  226.           dam, hitdice, fighter, name = m
  227.           ofile.write('"' + name + '" ')
  228.      ofile.write('\n')
  229.      for ev in range(tot):
  230.           ofile.write(str(ev) + " ")
  231.           for m in monsters:
  232.                factor = drf(tot-ev, ev, 0, m, gdr=0.34)
  233.                ofile.write(str(factor) + " ")
  234.           ofile.write("\n")
  235.  
  236. # report monster stats in space separated format for gnuplot
  237. # mode is either "DV" (defense value) or "MDV" (marginal defense value)
  238. # how to plot the results:
  239. # set title "Adder"
  240. # set xlabel "Amount of Attribute"
  241. # set ylabel "Defensive Value"  (or "Marginal Defensive Value")
  242. # set style data linespoints
  243. # set grid
  244. # plot "adder.dat" u 1:2 t columnheader(2), "" u 1:3 t columnheader(3), "" u 1:4 t columnheader(4), "" u 1:5 t columnheader(5)
  245. def plot_monster_vitals(monster, mode="DV", header = True, sep=",", ofile=sys.stdout):
  246.      dam, hitdice, fighter, name, damtype = monster
  247.      # column headers
  248.      if header:
  249.           ofile.write('"val" "AC at GDR 0" "AC at GDR 0.34" "EV" "SH" "SH with 2 attacks/turn"\n')
  250.      evvalues = []
  251.      for ev in range(60):
  252.           evvalues.append(EVReduction(ev, hitdice, fighter))
  253.      evvalues = transform(evvalues, mode)
  254.      acvalues_all = []
  255.      for gdr in [0, 0.34]:
  256.           acvalues = []
  257.           for ac in range(80):
  258.                acvalues.append(ACReductionMultipleAttacks(ac, dam, gdr))  # GDR 0
  259.           acvalues_all.append(transform(acvalues,mode))
  260.      shvalues_all = []
  261.      for speed in range(1, 3):
  262.           shvalues = []
  263.           for sh in range(40):
  264.                shvalues.append(SHReductionMultipleAttacks(sh, dam, speed, hitdice))
  265.           shvalues_all.append(transform(shvalues,mode))
  266.      for row in range(80):
  267.           ofile.write(str(row) + sep)
  268.           if row < len(acvalues_all[0]):
  269.                ofile.write(str(acvalues_all[0][row]) + sep)
  270.                ofile.write(str(acvalues_all[1][row]) + sep)
  271.           if row < len(evvalues):
  272.                ofile.write(str(evvalues[row]) + sep)
  273.           if row < len(shvalues_all[0]):
  274.                ofile.write(str(shvalues_all[0][row]) + sep)
  275.                ofile.write(str(shvalues_all[1][row]) + sep)
  276.           ofile.write("\n")
  277.      
  278.  
  279. # mode is either "DV" (defense value) or "MDV" (marginal defense value)
  280. def ev_ac_sh_table(monster, colwidth, mode):
  281.      dam, hitdice, fighter, name, damtype = monster
  282.      sys.stdout.write("=====" + name + "=====\n")
  283.      def lines(headerlabel, values):
  284.           sys.stdout.write(headerlabel.ljust(colwidth))
  285.           for x in range(0, 10):
  286.                sys.stdout.write(str(x).ljust(colwidth))
  287.           sys.stdout.write("\n")
  288.           for line in range(len(values) // 10):
  289.                fr = line * 10
  290.                to = min(line * 10 + 10, len(values))
  291.                if fr >= to:
  292.                     continue
  293.                sys.stdout.write((str(fr) + "+").ljust(colwidth))
  294.                for x in range(fr, to):
  295.                     sys.stdout.write(("%.2f" % values[x]).ljust(colwidth))
  296.                sys.stdout.write("\n")
  297.           sys.stdout.write("\n")
  298.      evvalues = []
  299.      for ev in range(60):
  300.           evvalues.append(EVReduction(ev, hitdice, fighter))
  301.      sys.stdout.write(" - Evasion - \n")
  302.      lines("EV",transform(evvalues, mode))
  303.      sys.stdout.write(" - Armour Class - \n")
  304.      for gdr in [0, 0.34]:
  305.           acvalues = []
  306.           for ac in range(80):
  307.                acvalues.append(ACReductionMultipleAttacks(ac, dam, gdr))  # GDR 0
  308.           sys.stdout.write("With GDR " + str(gdr) + ":\n")
  309.           lines("AC",transform(acvalues, mode))
  310.      sys.stdout.write(" - Shields - \n")
  311.      for speed in range(1, 4):
  312.           shvalues = []
  313.           for sh in range(40):
  314.                shvalues.append(SHReductionMultipleAttacks(sh, dam, speed, hitdice))
  315.           sys.stdout.write("Getting attacked " + str(speed) + " time(s) per turn:\n")
  316.           lines("SH", transform(shvalues, mode))
  317.  
  318. # a brief summary of a monster
  319. def ev_ac_sh_summary(monster):
  320.      dam, hitdice, fighter, name, damtype = monster
  321.      sys.stdout.write(name + ":\n")
  322.      sys.stdout.write("AC @ GDR 0:    ")
  323.      for ac in range(10, 80, 10):
  324.           sys.stdout.write("%.2f" % ACReductionMultipleAttacks(ac, dam, 0) + " ")
  325.      sys.stdout.write("\nAC @ GDR 0.34: ")
  326.      for ac in range(10, 80, 10):
  327.           sys.stdout.write("%.2f" % ACReductionMultipleAttacks(ac, dam, 0.34) + " ")
  328.      sys.stdout.write("\nEV:            ")
  329.      for ev in range(10, 60, 10):
  330.           sys.stdout.write("%.2f" % EVReduction(ev, hitdice, fighter) + " ")
  331.      sys.stdout.write("\nSH:            ")
  332.      for sh in range(10, 40, 10):
  333.           sys.stdout.write("%.2f" % SHReductionMultipleAttacks(sh, dam, 1, hitdice) + " ")
  334.      sys.stdout.write("\n")
  335.  
  336. # total damage reduction factor
  337. def drf(ac, ev, sh, monster, gdr=0):
  338.      dam, hitdice, fighter, name, damtype = monster
  339.      return ACReductionMultipleAttacks(ac, dam, gdr) * SHReductionMultipleAttacks(sh, dam, 1, hitdice) * EVReduction(ev, hitdice, fighter)
  340.  
  341. def ev_ac_table(fromAC, toAC, stepAC, fromEV, toEV, stepEV, gdr, monster, ofile = None):
  342.      dam, hitdice, fighter, name, damtype = monster
  343.      colwidth = 6
  344.      sys.stdout.write("ac\\ev".ljust(colwidth))
  345.      for ev in range(fromEV, toEV+1, stepEV):
  346.           sys.stdout.write(str(ev).ljust(colwidth))
  347.      sys.stdout.write("\n")
  348.      for ac in range(fromAC, toAC+1, stepAC):
  349.           sys.stdout.write(str(ac).ljust(colwidth))
  350.           for ev in range(fromEV, toEV+1, stepEV):
  351.                # in case of multiple damages
  352.                sys.stdout.write(("%.2f" % (ACReductionMultipleAttacks(ac, dam, gdr) * EVReduction(ev, hitdice, fighter))).ljust(colwidth))
  353.           sys.stdout.write("\n")
  354.      sys.stdout.write("\n")
  355.  
  356.  
Advertisement
Add Comment
Please, Sign In to add comment