Last active
May 9, 2017 13:28
-
-
Save wsrast/35baa7755ef3ebd144ae38e90ba85d7d to your computer and use it in GitHub Desktop.
Overwatch Pharmercy Calculator
This file contains hidden or 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
{ | |
"shooters": { | |
"Bastion (recon)": { | |
"dmgFalloffStart": 26, | |
"dmgFalloffEnd": 50, | |
"magSize": 25, | |
"maxShotDmg": 20, | |
"minShotDmg": 6, | |
"reloadTime": 2.0, | |
"shotsPerSec": 8 | |
}, | |
"Bastion (turret)": { | |
"dmgFalloffStart": 35, | |
"dmgFalloffEnd": 55, | |
"magSize": 300, | |
"maxShotDmg": 15, | |
"minShotDmg": 4, | |
"reloadTime": 2.0, | |
"shotsPerSec": 35, | |
"noHeadshots": true | |
}, | |
"McCree": { | |
"dmgFalloffStart": 22, | |
"dmgFalloffEnd": 45, | |
"magSize": 6, | |
"maxShotDmg": 70, | |
"minShotDmg": 20, | |
"reloadTime": 1.5, | |
"shotsPerSec": 2 | |
}, | |
"Soldier": { | |
"dmgFalloffStart": 30, | |
"dmgFalloffEnd": 55, | |
"magSize": 25, | |
"maxShotDmg": 20, | |
"minShotDmg": 6, | |
"reloadTime": 1.5, | |
"shotsPerSec": 8.77 | |
} | |
}, | |
"healers": { | |
"Mercy": { | |
"healRate": 60 | |
} | |
}, | |
"targets": { | |
"Pharah": { | |
"health": 200 | |
} | |
}, | |
"ranges": [ | |
22, 25, 30, 35, 40, 45, 50, 55 | |
], | |
"players": { | |
"Perfect": { | |
"accuracy": { | |
"total": 1, | |
"head": 1 | |
}, | |
"desc": "Aimbot" | |
}, | |
"Perfect, Body Shots": { | |
"accuracy": { | |
"total": 1, | |
"head": 0 | |
}, | |
"desc": "Aimbot no headshots" | |
}, | |
"Pro": { | |
"accuracy": { | |
"total": 0.7, | |
"head": 0.2 | |
}, | |
"desc": "Top 0.5%" | |
}, | |
"Very Good": { | |
"accuracy": { | |
"total": 0.5, | |
"head": 0.1 | |
}, | |
"desc": "Top 10%" | |
} | |
} | |
} |
This file contains hidden or 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
/** | |
* Created by rastwe on 5/8/2017. | |
*/ | |
const data = require('./data.json'); | |
function calcTTK(shooterName, targetName, healerName, range, playerName, debug) { | |
let shooter = data.shooters[shooterName], | |
target = data.targets[targetName], | |
healer = data.healers[healerName], | |
player = data.players[playerName], | |
currHealth = target.health, | |
currTimeCount = 0.0, | |
reloadCount = 0, | |
shotTime = 1/shooter.shotsPerSec, | |
hitObj = {hits: 0, misses: 0, heads:0, total: 0}; | |
while (currHealth > 0 && currTimeCount < 60) { | |
//check for hit | |
let isHit = checkForHit(hitObj, shooter.shotsPerSec, player, shooter.noHeadshots); | |
//calc shot dmg | |
currHealth -= (isHit) ? getShotDmg(shooter, range, isHit) : 0; | |
hitObj.total++; | |
if (debug && currHealth > 0) reportResults(shooterName, targetName, range, currTimeCount, hitObj, reloadCount, currHealth); | |
//check for dead target | |
if (currHealth <= 0) { | |
break;//don't allow for healing dead target | |
} | |
//increment time | |
currTimeCount += shotTime; | |
//handle reload | |
if (checkReload(hitObj.total, shooter.magSize)) { | |
let addHealth = healer.healRate * shooter.reloadTime; | |
currHealth += addHealth; | |
reloadCount++; | |
currTimeCount += shooter.reloadTime; | |
if (debug) console.log(` Reload, +${shooter.reloadTime} sec, +${addHealth} health to target`); | |
} else { | |
//apply healing per shot time. First shot accepts no healing. | |
if (currTimeCount > 0) currHealth += healer.healRate * shotTime; | |
} | |
//cap health | |
if (currHealth > target.health) currHealth = target.health; | |
} | |
reportResults(shooterName, targetName, range, currTimeCount, hitObj, reloadCount, currHealth); | |
} | |
/** | |
* Check if this shot is a hit, based off player accuracy, then test whether it's a head or body shot | |
* @param {Object} hitObj | |
* @param {Number} shotsPerSec | |
* @param {Object} player | |
* @param {Boolean} noHeadshots | |
* @return {boolean|Object} | |
*/ | |
function checkForHit(hitObj, shotsPerSec, player, noHeadshots) { | |
let hitPercent = (hitObj.total) ? hitObj.hits/hitObj.total : 0, | |
headPercent = (hitObj.heads) ? hitObj.heads/hitObj.total: 0, | |
isHit = player.accuracy.total > 0 && hitPercent <= player.accuracy.total, | |
isHeadShot = !noHeadshots && player.accuracy.head > 0 && headPercent <= player.accuracy.head; | |
if (isHit) { | |
hitObj.hits++; | |
if (isHeadShot) hitObj.heads++; | |
} else { | |
hitObj.misses++ | |
} | |
return (isHit && {isHit: isHit, isHeadShot: isHeadShot}); | |
} | |
function checkReload(shotCount, magSize) { | |
return shotCount > 0 && shotCount % magSize === 0; | |
} | |
function formatNum(num, minChars) { | |
const str = (''+num); | |
return str.length < minChars ? ' '.repeat(minChars - str.length) + str : str; | |
} | |
function reportResults(shooterName, targetName, range, time, hitObj, reloads, currHealth) { | |
const formatTime = formatNum(time.toFixed(2), 5), | |
formatHits = formatNum(hitObj.hits, 3), | |
formatHeads = formatNum(hitObj.heads, 3), | |
formatMisses = formatNum(hitObj.misses, 3), | |
formatShots = formatNum(hitObj.total, 3), | |
formatReloads = formatNum(reloads, 2); | |
if (currHealth <= 0) { | |
console.log(` ${range}m -- Time: ${formatTime},`, | |
` hits: ${formatHits}, misses: ${formatMisses}, headshots: ${formatHeads}`, | |
` shots: ${formatShots}, Reloads: ${formatReloads}`); | |
} else { | |
console.log(` ${range}m -- Time: ${formatTime},`, | |
` hits: ${formatHits}, misses: ${formatMisses}, headshots: ${formatHeads}`, | |
` shots: ${formatShots}, Reloads: ${formatReloads}, final health: ${currHealth.toFixed(2)}`); | |
} | |
} | |
function getShotDmg(shooter, range, isHit) { | |
const {maxShotDmg, minShotDmg, dmgFalloffStart, dmgFalloffEnd} = shooter, | |
useFalloff = range > dmgFalloffStart, | |
useMin = range >= dmgFalloffEnd, | |
falloffDifference = range - dmgFalloffStart, | |
falloffFraction = falloffDifference / (dmgFalloffEnd - dmgFalloffStart), | |
calcShotDmg = maxShotDmg - (maxShotDmg * falloffFraction), | |
totalDamage = (useFalloff) ? (useMin) ? minShotDmg : calcShotDmg : maxShotDmg; | |
return (isHit.isHeadShot) ? totalDamage * 2 : totalDamage; | |
} | |
/* Loop through all the shooters, players, and ranges */ | |
for (let shooterName in data.shooters) { | |
if (data.shooters.hasOwnProperty(shooterName)) { | |
console.log(`\n***Shooter: ${shooterName}***\n`); | |
for (let playerName in data.players) { | |
if (data.players.hasOwnProperty(playerName)) { | |
const accy = data.players[playerName].accuracy, | |
total = `${Math.round(accy.total*100)}%`, | |
body = `${Math.round((accy.total-accy.head)*100)}%`, | |
head = `${Math.round(accy.head*100)}%`; | |
console.log(`Player Accuracy, **${playerName} (${data.players[playerName].desc})** : (body: ${body}/head: ${head}/total: ${total})\n`); | |
data.ranges.forEach(function (range) { | |
calcTTK(shooterName, 'Pharah', 'Mercy', range, playerName); | |
}); | |
console.log(`\n ---\n`); | |
} | |
} | |
} | |
} | |
/* Loop through just the ranges */ | |
/*data.ranges.forEach(function (range) { | |
calcTTK('Bastion (turret)', 'Pharah', 'Mercy', range, 'Very Good', false); | |
});*/ | |
/* One shooter, player, and range */ | |
//calcTTK('Soldier', 'Pharah', 'Mercy', 30, 'Very Good', true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment