Skip to content

Instantly share code, notes, and snippets.

@wsrast
Last active May 9, 2017 13:28
Show Gist options
  • Save wsrast/35baa7755ef3ebd144ae38e90ba85d7d to your computer and use it in GitHub Desktop.
Save wsrast/35baa7755ef3ebd144ae38e90ba85d7d to your computer and use it in GitHub Desktop.
Overwatch Pharmercy Calculator
{
"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%"
}
}
}
/**
* 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