Last active
May 26, 2019 19:59
-
-
Save mutability/625b8edb72aceb6abfa161e0acc1c681 to your computer and use it in GitHub Desktop.
cdda ranged damage simulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Author
mutability
commented
Oct 22, 2016
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment