Skip to content

Instantly share code, notes, and snippets.

@mutability
Last active May 26, 2019 19:59
Show Gist options
  • Save mutability/625b8edb72aceb6abfa161e0acc1c681 to your computer and use it in GitHub Desktop.
Save mutability/625b8edb72aceb6abfa161e0acc1c681 to your computer and use it in GitHub Desktop.
cdda ranged damage simulation
#!/usr/bin/env python3
import math, sys
from contextlib import closing
def normal_cdf(x, mu, sigma):
return 0.5 * (1 + math.erf((x - mu) / (sigma * math.sqrt(2))))
def rng_normal_cdf(x, hi):
mu = hi / 2.0
sigma = hi / 4.0
if x < 0:
return 0.0
if x >= hi:
return 1.0
return normal_cdf(x, mu, sigma)
def arcmin(v):
return v * math.pi / 180 / 60
def as_arcmin(v):
return v * 60 * 180 / math.pi
def iso_tangent(distance, vertex):
return math.sqrt(2 * distance**2 * (1 - math.cos(arcmin(vertex))))
def inverse_iso_tangent(distance, tangent):
if tangent > distance * 2:
# impossible, return 180 degrees
return (180 * 60)
ac = 1 - tangent**2 / (2 * distance**2)
return as_arcmin(math.acos(ac))
# return the probability that a shot will miss by less than "x"
def stock_cdf(distance, dispersion, x):
disp = inverse_iso_tangent(distance, x)
return rng_normal_cdf(disp, dispersion)
dispersion_sigmas = 2.4
occupied_tile_fraction = 0.5
def new_cdf(distance, dispersion, x):
disp = inverse_iso_tangent(distance, x * occupied_tile_fraction)
return normal_cdf(disp, 0, dispersion / dispersion_sigmas) - normal_cdf(-disp, 0, dispersion / dispersion_sigmas)
def chance_graph(fn, min_distance, max_distance, max_dispersion, lo_chance, hi_chance, path):
with closing(open(path, 'w')) as f:
for distance in range(min_distance, max_distance):
p = fn(distance, max_dispersion, hi_chance) - fn(distance, max_dispersion, lo_chance)
print("{0}\t{1:4.1f}".format(distance, p * 100.0), file=f)
def expected_damage(fn, distance, max_dispersion):
p_headshot = fn(distance, max_dispersion, 0.1) - fn(distance, max_dispersion, 0.0)
p_critical = fn(distance, max_dispersion, 0.2) - fn(distance, max_dispersion, 0.1)
p_goodhit = fn(distance, max_dispersion, 0.5) - fn(distance, max_dispersion, 0.2)
p_standard = fn(distance, max_dispersion, 0.8) - fn(distance, max_dispersion, 0.5)
p_graze = fn(distance, max_dispersion, 1.0) - fn(distance, max_dispersion, 0.8)
expected = (p_headshot * (2.45 + 3.35)/2 +
p_critical * (1.75 + 2.30)/2 +
p_goodhit * (1.0 + 1.5)/2 +
p_standard * (0.5 + 1.0)/2 +
p_graze * (0.0 + 0.25)/2)
return expected
def expected_damage_graph(fn, min_distance, max_distance, max_dispersion, path):
with closing(open(path, 'w')) as f:
for distance in range(min_distance, max_distance):
print("{0}\t{1:4.1f}".format(distance, expected_damage(fn, distance, max_dispersion) * 100.0), file=f)
def range_for_damage(fn, dispersion, target):
best = None
distance = 0.1
while distance < 30.0:
damage = expected_damage(fn, distance, dispersion)
if best is None or abs(damage - target) < best_error:
best = distance
best_error = abs(damage - target)
distance += 0.1
return best
def range_for_damage_graph(fn, min_dispersion, max_dispersion, target, path):
with closing(open(path, 'w')) as f:
for dispersion in range(min_dispersion, max_dispersion):
print("{0}\t{1:4.1f}".format(dispersion, range_for_damage(fn, dispersion, target)), file=f)
for disp in [150, 300, 600, 900, 1200]:
chance_graph(stock_cdf, 1, 30, disp, 0.0, 0.1, 'stock_headshot_{0}.tsv'.format(disp))
chance_graph(stock_cdf, 1, 30, disp, 0.1, 0.2, 'stock_critical_{0}.tsv'.format(disp))
chance_graph(stock_cdf, 1, 30, disp, 0.2, 0.5, 'stock_goodhit_{0}.tsv'.format(disp))
chance_graph(stock_cdf, 1, 30, disp, 0.5, 0.8, 'stock_normal_{0}.tsv'.format(disp))
chance_graph(stock_cdf, 1, 30, disp, 0.8, 1.0, 'stock_graze_{0}.tsv'.format(disp))
chance_graph(stock_cdf, 1, 30, disp, 1.0, 100.0, 'stock_miss_{0}.tsv'.format(disp))
expected_damage_graph(stock_cdf, 1, 30, disp, 'stock_dmg_{0}.tsv'.format(disp))
chance_graph(new_cdf, 1, 30, disp, 0.0, 0.1, 'new_headshot_{0}.tsv'.format(disp))
chance_graph(new_cdf, 1, 30, disp, 0.1, 0.2, 'new_critical_{0}.tsv'.format(disp))
chance_graph(new_cdf, 1, 30, disp, 0.2, 0.5, 'new_goodhit_{0}.tsv'.format(disp))
chance_graph(new_cdf, 1, 30, disp, 0.5, 0.8, 'new_normal_{0}.tsv'.format(disp))
chance_graph(new_cdf, 1, 30, disp, 0.8, 1.0, 'new_graze_{0}.tsv'.format(disp))
chance_graph(new_cdf, 1, 30, disp, 1.0, 100.0, 'new_miss_{0}.tsv'.format(disp))
expected_damage_graph(new_cdf, 1, 30, disp, 'new_dmg_{0}.tsv'.format(disp))
range_for_damage_graph(stock_cdf, 50, 1200, 2.0, 'stock_200damage_range.tsv')
range_for_damage_graph(new_cdf, 50, 1200, 2.0, 'new_200damage_range.tsv')
range_for_damage_graph(stock_cdf, 50, 1200, 1.0, 'stock_100damage_range.tsv')
range_for_damage_graph(new_cdf, 50, 1200, 1.0, 'new_100damage_range.tsv')
range_for_damage_graph(stock_cdf, 50, 1200, 0.5, 'stock_50damage_range.tsv')
range_for_damage_graph(new_cdf, 50, 1200, 0.5, 'new_50damage_range.tsv')
#!/bin/sh
for disp in 150 300 600 900 1200
do
gnuplot -<<EOF
set terminal png size 600,1200
set output "dispersion_$disp.png"
set multiplot layout 2,1
set title "Stock, Hit chance, dispersion $disp arcmin"
set xlabel "Range (tiles)"
set ylabel "Chance (%)
plot [1:] [0:100] \
"stock_headshot_$disp.tsv" with lines title "Headshot", \
"stock_critical_$disp.tsv" with lines title "Critical", \
"stock_goodhit_$disp.tsv" with lines title "Good hit", \
"stock_normal_$disp.tsv" with lines title "Normal", \
"stock_graze_$disp.tsv" with lines title "Grazing", \
"stock_miss_$disp.tsv" with lines title "Miss"
set title "New, Hit chance, dispersion $disp arcmin"
set xlabel "Range (tiles)"
set ylabel "Chance (%)
plot [1:] [0:100] \
"new_headshot_$disp.tsv" with lines title "Headshot", \
"new_critical_$disp.tsv" with lines title "Critical", \
"new_goodhit_$disp.tsv" with lines title "Good hit", \
"new_normal_$disp.tsv" with lines title "Normal", \
"new_graze_$disp.tsv" with lines title "Grazing", \
"new_miss_$disp.tsv" with lines title "Miss"
unset multiplot
set terminal png size 600,600
set output "damage_$disp.png"
set title "Expected damage per shot, dispersion $disp arcmin"
set xlabel "Range (tiles)"
set ylabel "Damage (%)"
plot [1:] [0:] \
"stock_dmg_$disp.tsv" with lines title "Stock", \
"new_dmg_$disp.tsv" with lines title "New"
EOF
done
gnuplot -<<EOF
set terminal pngcairo size 600,600
set termoption dashed
set output "range_for_damage.png"
set title "Range for 50/100/200% expected damage per shot"
set xlabel "Dispersion (arcminutes)"
set ylabel "Range (tiles)"
plot [1:] [0:] \
"stock_50damage_range.tsv" with lines title "Stock 50%" lt 3 lc 1, \
"stock_100damage_range.tsv" with lines title "Stock 100%" lt 4 lc 1, \
"stock_200damage_range.tsv" with lines title "Stock 200%" lt 1 lc 1, \
"new_50damage_range.tsv" with lines title "New 50%" lt 3 lc 2, \
"new_100damage_range.tsv" with lines title "New 100%" lt 4 lc 2, \
"new_200damage_range.tsv" with lines title "New 200%" lt 1 lc 2
EOF
@mutability
Copy link
Author

mutability commented Oct 22, 2016

damage_150
damage_600
damage_1200

dispersion_150
dispersion_600
dispersion_1200

range_for_damage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment