Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # ----08-03-2025 14:15:57----
- # excerpted from crawl.py
- from collections import defaultdict
- # calculates the average factor by which incoming dmg is reduced by AC
- def ACReduction(ac, dam, gdr = 0):
- totreduction = 0
- gdr_amt = int(gdr * 100 * dam) // 100
- if gdr_amt > ac // 2:
- gdr_amt = ac // 2
- totdam = 0
- for roll1 in range(1, dam + 1): # damage roll
- for roll2 in range(ac + 1): # ac roll
- if roll2 < gdr_amt:
- roll2 = gdr_amt
- if roll2 > roll1:
- roll2 = roll1
- totreduction += roll2
- totdam += roll1
- if totreduction == totdam:
- return float("inf") # 100% damage reduction, infinite reduction factor
- return float(totdam) / (totdam - totreduction)
- def ACReductionMultipleAttacks(ac, dam, gdr = 0):
- acreduc = 0
- if ac == 0:
- return 1.0
- elif type(dam) == list:
- treduc = 0
- for d in dam:
- treduc += d - (float(d) / ACReduction(ac, d, gdr))
- acreduc = sum(dam) / (sum(dam) - treduc)
- else:
- acreduc = ACReduction(ac, dam, gdr)
- return acreduc
- def calcToHit(hitdice, fighter):
- tohit = 18
- if fighter:
- tohit += int(25 * hitdice / 10)
- else:
- tohit += int(15 * hitdice / 10)
- return tohit
- # reduction for hitting the enemy with a bolt
- def EVReductionEnemyBolt(ev, tohit):
- totreduction = 0
- totdam = 0
- if ev == 0:
- return 1.0
- for rand_ev in range(ev):
- for attack in range(tohit):
- for roll in range(rand_ev):
- for roll2 in range(rand_ev + 1):
- if attack < (roll + roll2) // 2:
- totreduction += 1 # 1 damage dodged
- totdam += 1
- return float(totdam) / (totdam - totreduction)
- # reduction for hitting the player with a bolt
- def EVReductionPlayerBolt(ev, tohit):
- totreduction = 0
- totdam = 0
- if ev == 0:
- return 1.0
- for attack in range(tohit):
- for roll in range(ev):
- for roll2 in range(ev + 1):
- if attack < (roll + roll2) // 2:
- totreduction += 1 # 1 damage dodged
- totdam += 1
- return float(totdam) / (totdam - totreduction)
- # calculates the average factor by which incoming dmg is reduced by EV
- # provide _either_ hitdice and fighter, _or_ tohit, but not both
- def EVReduction(ev, hitdice = 0, fighter = False, tohit = None):
- if tohit == None:
- tohit = calcToHit(hitdice, fighter)
- totreduction = 0
- totdam = 0
- if ev == 0:
- return 1 / 0.975
- for roll1 in range(tohit + 1):
- for roll2 in range(2 * ev):
- for roll3 in range(2 * ev + 1):
- if roll1 < (roll2 + roll3) // 2:
- totreduction += 1 # 1 damage dodged
- totdam += 1
- # normalize to 1.0 totdam and acct for the fact the preceding calculation only applied to 95% of cases
- totreduction = 0.95 * (float(totreduction) / totdam)
- # now account for 2.5% automatic hits and 2.5% automatic misses
- return 1.00 / (1.00 - (totreduction + 0.025))
- def BlockChancePerAttack(sh, pastblocks = 0, hitdice = 0):
- totdam = 0
- totreduction = 0
- if sh == 0:
- return 0
- for roll1 in range(15 + hitdice * 2 // 3 + 5 * pastblocks**2):
- for roll2 in range(4 * sh):
- for roll3 in range(4 * sh + 1):
- blockval = (roll2 + roll3) // 6 - 1
- if roll1 <= blockval:
- totreduction += 1 # blocked
- totdam += 1
- return float(totreduction) / totdam
- import math
- # Note that there is sometimes a complex interaction between armor and shield reduction
- # (not simply multiplicative)
- # 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,
- # and the effect of armor on the different attacks is unequal as well
- # We'll just ignore it since it's OK when the attacks are the same and these calculations don't have to be perfect
- def SHReductionMultipleAttacks(sh, attacks, speed, hitdice):
- if type(attacks) is int:
- attacks = [attacks]
- bcpa = [] # bcpa[blocks] = probability of a block given previous blocks
- maxatk = len(attacks) * int(math.ceil(speed))
- for pastblocks in range(maxatk):
- bcpa.append(BlockChancePerAttack(sh, pastblocks, hitdice))
- bcpa.append(0) # dummy value, we'll never get this many past blocks
- # now weight by how often each number of pastblocks is happening
- # in a given turn, player will be attacked either ceil(speed) times,
- # or floor(speed) times.
- # In the long run over N turns, player will be attacked N * speed times.
- # Therefore, a * ceil(speed) + (N - a) * floor(speed) = N * speed
- # where a is the number of times the player is attacked ceil(speed) times
- # a * (ceil(speed) - floor(speed)) = N * speed - N * floor(speed)
- # a/N = speed - floor(speed) assuming speed != floor(speed)
- #
- # weights[k] = probability k'th attack will happen in a turn
- weights = [0] + [1] * (len(attacks) * int(math.floor(speed)))
- if math.floor(speed) != speed:
- weights.append([speed - math.floor(speed)] * len(attacks))
- # now calculate probability of different blocking results
- 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)
- for x in range(maxatk+1): # boundary values
- bcpa2[0][x] = (1 - bcpa[0]) ** x
- for block in range(1, maxatk+1): # fill in bcpa2 with dynamic programming
- for attack in range(block, maxatk+1):
- bcpa2[block][attack] = bcpa2[block-1][attack-1] * bcpa[block - 1] + bcpa2[block][attack-1] * (1 - bcpa[block])
- # account for the fact that the last attack may not always happen
- if math.floor(speed) != speed:
- for block in range(maxatk+1):
- for atk in range(maxatk+1 - len(attacks), maxatk+1):
- bcpa2[block][attack] *= speed - math.floor(speed)
- # now calculate actual damage reduction
- totdmg = 0
- totreduction = 0
- for attack in range(1, maxatk+1):
- dmg = attacks[(attack - 1) % len(attacks)] * weights[attack] # proportional to the average damage dealt by the nth attack in a turn
- blockpr = 0
- norm = 0
- for block in range(0, attack+1):
- norm += bcpa2[block][attack]
- if bcpa2[block][attack] == 0: # never have this combination of blocks and attacks
- continue
- if block == 0: # couldn't have been blocked
- continue
- #p = bcpa[block-1] * bcpa2[block-1][attack-1] / bcpa2[block][attack] # chance the last attack was blocked
- #blockpr += p * bcpa2[block][attack]
- blockpr += bcpa[block-1] * bcpa2[block-1][attack-1]
- blockpr = blockpr / norm
- totdmg += dmg
- totreduction += dmg * blockpr
- return totdmg / (totdmg - totreduction)
- def EVReductionRanged(ev, hitdice = 0, fighter = False, tohit = None, deflect=0, beam=False):
- if beam and deflect == 2:
- tohit = random2(tohit*2) // 3
- elif beam and deflect == 1 and tohit >= 2:
- tohit = random_range((tohit+1) // 2 + 1, tohit)
- elif deflect>0: # not beam
- tohit = random2(tohit // deflect)
- # monsters
- # dam, hitdice, fighter, name, poisontype
- # poisontype is 1 for poison, 2 for strong poison
- iguana = [15, 3, False, "Iguana", 0]
- yak = [18, 7, False, "Yak", 0]
- deathyak = [30, 14, False, "Death Yak", 0]
- deeptroll = [[27, 20, 20], 10, False, "Deep Troll", 0]
- veryugly = [36, 18, False, "Purple Very Ugly Thing", 0]
- direelephant = [[40, 15], 15, False, "Dire Elephant", 0]
- hydra6 = [[18, 18, 18, 18, 18, 18], 13, False, "Six-headed Hydra", 0]
- orbguardian = [45, 15, True, "Orb Guardian", 0]
- juggernaut = [120, 20, True, "Juggernaut", 0]
- bonedragon = [[30,20,20], 20, False, "Bone Dragon", 0]
- alligator = [[30,15],12,False,"Alligator", 0]
- blackmamba = [20,7,False,"Black Mamba", 1]
- adder = [5, 2, False, "Adder", 1]
- stonegiant = [45, 16, False, "Stone Giant", 0]
- # poison damage for strong poison is hitdice*11/3 - hitdice*13/2
- # for regular poison, hitdice*2-hitdice*4
- monsters = [yak, deathyak, deeptroll, veryugly, direelephant, hydra6, orbguardian, bonedragon, blackmamba, adder, stonegiant]
- def all_ev_ac_sh_tables():
- for monster in monsters:
- ev_ac_sh_table(monster, 6, "DV")
- import sys
- def all_summaries():
- 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")
- for monster in monsters:
- ev_ac_sh_summary(monster)
- sys.stdout.write("\n")
- # adjust the list of damage reduction factors for mode
- def transform(values, mode):
- if mode == "DV":
- return [math.log(x, 1.1) for x in values]
- elif mode == "MDV":
- values = [math.log(x, 1.1) for x in values]
- return [(y - x) for x, y in zip(values, values[1:])] # table of differences
- def plot_ac_ev_tradeoff(ofile=sys.stdout, tot=50):
- ofile.write('ev ')
- for m in monsters:
- dam, hitdice, fighter, name = m
- ofile.write('"' + name + '" ')
- ofile.write('\n')
- for ev in range(tot):
- ofile.write(str(ev) + " ")
- for m in monsters:
- factor = drf(tot-ev, ev, 0, m, gdr=0.34)
- ofile.write(str(factor) + " ")
- ofile.write("\n")
- # report monster stats in space separated format for gnuplot
- # mode is either "DV" (defense value) or "MDV" (marginal defense value)
- # how to plot the results:
- # set title "Adder"
- # set xlabel "Amount of Attribute"
- # set ylabel "Defensive Value" (or "Marginal Defensive Value")
- # set style data linespoints
- # set grid
- # 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)
- def plot_monster_vitals(monster, mode="DV", header = True, sep=",", ofile=sys.stdout):
- dam, hitdice, fighter, name, damtype = monster
- # column headers
- if header:
- ofile.write('"val" "AC at GDR 0" "AC at GDR 0.34" "EV" "SH" "SH with 2 attacks/turn"\n')
- evvalues = []
- for ev in range(60):
- evvalues.append(EVReduction(ev, hitdice, fighter))
- evvalues = transform(evvalues, mode)
- acvalues_all = []
- for gdr in [0, 0.34]:
- acvalues = []
- for ac in range(80):
- acvalues.append(ACReductionMultipleAttacks(ac, dam, gdr)) # GDR 0
- acvalues_all.append(transform(acvalues,mode))
- shvalues_all = []
- for speed in range(1, 3):
- shvalues = []
- for sh in range(40):
- shvalues.append(SHReductionMultipleAttacks(sh, dam, speed, hitdice))
- shvalues_all.append(transform(shvalues,mode))
- for row in range(80):
- ofile.write(str(row) + sep)
- if row < len(acvalues_all[0]):
- ofile.write(str(acvalues_all[0][row]) + sep)
- ofile.write(str(acvalues_all[1][row]) + sep)
- if row < len(evvalues):
- ofile.write(str(evvalues[row]) + sep)
- if row < len(shvalues_all[0]):
- ofile.write(str(shvalues_all[0][row]) + sep)
- ofile.write(str(shvalues_all[1][row]) + sep)
- ofile.write("\n")
- # mode is either "DV" (defense value) or "MDV" (marginal defense value)
- def ev_ac_sh_table(monster, colwidth, mode):
- dam, hitdice, fighter, name, damtype = monster
- sys.stdout.write("=====" + name + "=====\n")
- def lines(headerlabel, values):
- sys.stdout.write(headerlabel.ljust(colwidth))
- for x in range(0, 10):
- sys.stdout.write(str(x).ljust(colwidth))
- sys.stdout.write("\n")
- for line in range(len(values) // 10):
- fr = line * 10
- to = min(line * 10 + 10, len(values))
- if fr >= to:
- continue
- sys.stdout.write((str(fr) + "+").ljust(colwidth))
- for x in range(fr, to):
- sys.stdout.write(("%.2f" % values[x]).ljust(colwidth))
- sys.stdout.write("\n")
- sys.stdout.write("\n")
- evvalues = []
- for ev in range(60):
- evvalues.append(EVReduction(ev, hitdice, fighter))
- sys.stdout.write(" - Evasion - \n")
- lines("EV",transform(evvalues, mode))
- sys.stdout.write(" - Armour Class - \n")
- for gdr in [0, 0.34]:
- acvalues = []
- for ac in range(80):
- acvalues.append(ACReductionMultipleAttacks(ac, dam, gdr)) # GDR 0
- sys.stdout.write("With GDR " + str(gdr) + ":\n")
- lines("AC",transform(acvalues, mode))
- sys.stdout.write(" - Shields - \n")
- for speed in range(1, 4):
- shvalues = []
- for sh in range(40):
- shvalues.append(SHReductionMultipleAttacks(sh, dam, speed, hitdice))
- sys.stdout.write("Getting attacked " + str(speed) + " time(s) per turn:\n")
- lines("SH", transform(shvalues, mode))
- # a brief summary of a monster
- def ev_ac_sh_summary(monster):
- dam, hitdice, fighter, name, damtype = monster
- sys.stdout.write(name + ":\n")
- sys.stdout.write("AC @ GDR 0: ")
- for ac in range(10, 80, 10):
- sys.stdout.write("%.2f" % ACReductionMultipleAttacks(ac, dam, 0) + " ")
- sys.stdout.write("\nAC @ GDR 0.34: ")
- for ac in range(10, 80, 10):
- sys.stdout.write("%.2f" % ACReductionMultipleAttacks(ac, dam, 0.34) + " ")
- sys.stdout.write("\nEV: ")
- for ev in range(10, 60, 10):
- sys.stdout.write("%.2f" % EVReduction(ev, hitdice, fighter) + " ")
- sys.stdout.write("\nSH: ")
- for sh in range(10, 40, 10):
- sys.stdout.write("%.2f" % SHReductionMultipleAttacks(sh, dam, 1, hitdice) + " ")
- sys.stdout.write("\n")
- # total damage reduction factor
- def drf(ac, ev, sh, monster, gdr=0):
- dam, hitdice, fighter, name, damtype = monster
- return ACReductionMultipleAttacks(ac, dam, gdr) * SHReductionMultipleAttacks(sh, dam, 1, hitdice) * EVReduction(ev, hitdice, fighter)
- def ev_ac_table(fromAC, toAC, stepAC, fromEV, toEV, stepEV, gdr, monster, ofile = None):
- dam, hitdice, fighter, name, damtype = monster
- colwidth = 6
- sys.stdout.write("ac\\ev".ljust(colwidth))
- for ev in range(fromEV, toEV+1, stepEV):
- sys.stdout.write(str(ev).ljust(colwidth))
- sys.stdout.write("\n")
- for ac in range(fromAC, toAC+1, stepAC):
- sys.stdout.write(str(ac).ljust(colwidth))
- for ev in range(fromEV, toEV+1, stepEV):
- # in case of multiple damages
- sys.stdout.write(("%.2f" % (ACReductionMultipleAttacks(ac, dam, gdr) * EVReduction(ev, hitdice, fighter))).ljust(colwidth))
- sys.stdout.write("\n")
- sys.stdout.write("\n")
Advertisement
Add Comment
Please, Sign In to add comment