Add spell damage

This commit is contained in:
b 2021-01-09 02:52:58 -06:00
parent 30c142f497
commit f2a482036d
7 changed files with 305 additions and 46 deletions

View file

@ -47,7 +47,13 @@ const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY
*/
class Build{
/*Construct a build.
/*
* Construct a build.
* @param level : Level of the player.
* @param equipment : List of equipment names that make up the build.
* In order: Helmet, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Weapon.
* @param powders : Powder application. List of lists of integers (powder IDs).
* In order: Helmet, Chestplate, Leggings, Boots, Weapon.
*/
constructor(level,equipment, powders){
// NOTE: powders is just an array of arrays of powder IDs. Not powder objects.
@ -157,6 +163,12 @@ class Build{
}
}
getSpellCost(spellIdx, cost) {
cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
cost += this.statMap.get("spRaw"+spellIdx);
return Math.max(1, Math.floor(cost * (1 - this.statMap.get("spPct"+spellIdx))))
}
/* Get melee stats for build.
Returns an array in the order:

View file

@ -9,7 +9,6 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
}
// Applying powder.
let neutralRemaining = spellConversions[0];
let neutralBase = damages[0].slice();
let neutralRemainingRaw = damages[0];
for (let i = 0; i < 5; ++i) {
@ -38,9 +37,6 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
damages[element+1][0] += powder.min;
damages[element+1][1] += powder.max;
}
damages[0][0] *= neutralRemaining / 100;
damages[0][1] *= neutralRemaining / 100;
console.log(damages);
let damageMult = 1;
// If we are doing melee calculations:
@ -50,6 +46,8 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
else {
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(stats.get("atkSpd"))];
}
console.log(damages);
console.log(damageMult);
rawModifier *= spellMultiplier;
@ -79,5 +77,94 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
}
damages_results[0][0] += rawModifier;
damages_results[0][1] += rawModifier;
damages_results[0][2] += rawModifier;
damages_results[0][3] += rawModifier;
return [totalDamNorm, totalDamCrit, damages_results];
}
const spell_table = {
"wand": [
{ title: "Heal", cost: 6, parts: [
{ subtitle: "First Pulse", type: "heal", strength: 0.2 },
{ subtitle: "Second and Third Pulses", type: "heal", strength: 0.05 },
{ subtitle: "Total Heal", type: "heal", strength: 0.3 }
] },
{ title: "Teleport", cost: 4, parts: [
{ subtitle: "", type: "damage", multiplier: 100, conversion: [60, 0, 40, 0, 0, 0] },
] },
{ title: "Meteor", cost: 8, parts: [
{ subtitle: "Blast Damage", type: "damage", multiplier: 500, conversion: [40, 30, 0, 0, 30, 0] },
{ subtitle: "Burn Damage", type: "damage", multiplier: 125, conversion: [40, 30, 0, 0, 30, 0] },
] },
{ title: "Ice Snake", cost: 4, parts: [
{ subtitle: "", type: "damage", multiplier: 70, conversion: [50, 0, 0, 50, 0, 0] },
] },
],
"spear": [
{ title: "Bash", cost: 6, parts: [
{ subtitle: "First Damage", type: "damage", multiplier: 130, conversion: [60, 40, 0, 0, 0, 0]},
{ subtitle: "Explosion Damage", type: "damage", multiplier: 130, conversion: [100, 0, 0, 0, 0, 0]},
] },
{ title: "Charge", cost: 4, parts: [
{ subtitle: "", type: "damage", multiplier: 150, conversion: [60, 0, 0, 0, 40, 0] },
] },
{ title: "Uppercut", cost: 10, parts: [
{ subtitle: "First Damage", type: "damage", multiplier: 300, conversion: [70, 20, 10, 0, 0, 0] },
{ subtitle: "Fireworks Damage", type: "damage", multiplier: 50, conversion: [60, 0, 40, 0, 0, 0] },
{ subtitle: "Crash Damage", type: "damage", multiplier: 50, conversion: [80, 0, 20, 0, 0, 0] },
] },
{ title: "War Scream", cost: 6, parts: [
{ subtitle: "Area Damage", type: "damage", multiplier: 50, conversion: [0, 0, 0, 0, 75, 25] },
{ subtitle: "Air Shout (Per Hit)", type: "damage", multiplier: 30, conversion: [0, 0, 0, 0, 75, 25] },
] },
],
"bow": [
{ title: "Arrow Storm", cost: 6, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [60, 0, 25, 0, 15, 0]},
{ subtitle: "Per Arrow", type: "damage", multiplier: 10, conversion: [60, 0, 25, 0, 15, 0]},
] },
{ title: "Escape", cost: 3, parts: [
{ subtitle: "Landing Damage", type: "damage", multiplier: 100, conversion: [50, 0, 0, 0, 0, 50] },
] },
{ title: "Bomb Arrow", cost: 8, parts: [
{ subtitle: "", type: "damage", multiplier: 250, conversion: [60, 25, 0, 0, 15, 0] },
] },
{ title: "Arrow Shield", cost: 10, parts: [
{ subtitle: "Shield Damage", type: "damage", multiplier: 100, conversion: [70, 0, 0, 0, 0, 30] },
{ subtitle: "Arrow Rain Damage", type: "damage", multiplier: 200, conversion: [70, 0, 0, 0, 0, 30] },
] },
],
"dagger": [
{ title: "Spin Attack", cost: 6, parts: [
{ subtitle: "", type: "damage", multiplier: 150, conversion: [70, 0, 30, 0, 0, 0]},
] },
{ title: "Vanish", cost: 1, parts: [
{ subtitle: "No Damage", type: "none" }
] },
{ title: "Multihit", cost: 8, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 380, conversion: [60, 25, 0, 0, 15, 0] },
{ subtitle: "1st to 10th Hit", type: "damage", multiplier: 28, conversion: [60, 25, 0, 0, 15, 0] },
{ subtitle: "Fatality", type: "damage", multiplier: 100, conversion: [20, 0, 30, 50, 0, 0] },
] },
{ title: "Smoke Bomb", cost: 8, parts: [
{ subtitle: "Tick Damage", type: "damage", multiplier: 60, conversion: [45, 25, 0, 0, 0, 30] },
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [45, 25, 0, 0, 0, 30] },
] },
],
"relik": [
{ title: "Totem", cost: 4, parts: [
{ subtitle: "Smash Damage", type: "damage", multiplier: 100, conversion: [80, 0, 0, 0, 20, 0]},
{ subtitle: "Damage Tick", type: "damage", multiplier: 100, conversion: [80, 0, 0, 0, 0, 20]},
{ subtitle: "Heal Tick", type: "heal", strength: 0.04 },
] },
{ title: "Haul", cost: 1, parts: [
{ subtitle: "", type: "damage", multiplier: 100, conversion: [80, 0, 20, 0, 0, 0] },
] },
{ title: "Aura", cost: 8, parts: [
{ subtitle: "One Wave", type: "damage", multiplier: 200, conversion: [70, 0, 0, 30, 0, 0] },
] },
{ title: "Uproot", cost: 6, parts: [
{ subtitle: "", type: "damage", multiplier: 50, conversion: [70, 30, 0, 0, 0, 0] },
] },
]
}

View file

@ -244,3 +244,83 @@ function displayExpandedItem(item, parent_id){
item_desc_elem.textContent = item.get("tier")+" "+item.get("type");
parent_div.append(item_desc_elem);
}
function displaySpellDamage(parent_elem, build, spell, spellIdx) {
parent_elem.textContent = "";
const stats = build.statMap;
let title_elem = document.createElement("p");
title_elem.classList.add('center');
if (spellIdx != 0) {
title_elem.textContent = spell.title + " (" + build.getSpellCost(spellIdx, spell.cost) + ")";
}
else {
title_elem.textContent = spell.title;
}
parent_elem.append(title_elem);
let critChance = skillPointsToPercentage(build.total_skillpoints[1]);
for (const part of spell.parts) {
parent_elem.append(document.createElement("br"));
let part_div = document.createElement("p");
parent_elem.append(part_div);
let subtitle_elem = document.createElement("p");
subtitle_elem.textContent = part.subtitle;
part_div.append(subtitle_elem);
if (part.type === "damage") {
let _results = calculateSpellDamage(stats, part.conversion,
stats.get("sdRaw"), stats.get("sdPct"),
part.multiplier / 100, build.weapon, build.total_skillpoints);
let totalDamNormal = _results[0];
let totalDamCrit = _results[1];
let results = _results[2];
for (let i = 0; i < 6; ++i) {
for (let j in results[i]) {
results[i][j] = Math.round(results[i][j]);
}
}
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2;
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2;
let averageDamage = document.createElement("p");
averageDamage.textContent = "Average: "+Math.round((1-critChance)*nonCritAverage+critChance*critAverage);
averageDamage.classList.add("damageSubtitle");
part_div.append(averageDamage);
let nonCritLabel = document.createElement("p");
nonCritLabel.textContent = "Non-Crit Average: "+Math.round(nonCritAverage);
nonCritLabel.classList.add("damageSubtitle");
part_div.append(nonCritLabel);
let damageClasses = ["Neutral","Earth","Thunder","Water","Fire","Air"];
console.log(results);
for (let i = 0; i < 6; i++){
if (results[i][0] > 0){
let p = document.createElement("p");
p.classList.add("damagep");
p.classList.add(damageClasses[i]);
p.textContent = results[i][0]+"-"+results[i][1];
part_div.append(p);
}
}
//part_div.append(document.createElement("br"));
let critLabel = document.createElement("p");
critLabel.textContent = "Crit Average: "+Math.round(critAverage);
critLabel.classList.add("damageSubtitle");
part_div.append(critLabel);
for (let i = 0; i < 6; i++){
if (results[i][0] > 0){
let p = document.createElement("p");
p.classList.add("damagep");
p.classList.add(damageClasses[i]);
p.textContent = results[i][2]+"-"+results[i][3];
part_div.append(p);
}
}
}
}
}

View file

@ -122,7 +122,7 @@
</div>
<div class="center" style="grid-column:3;grid-row:3">
<button class = "button" id = "calc-button" onclick = "calculateBuild()">
Calculate
Update Items (Resets stats)
</button>
</div>
<div class="center" style="grid-column:4;grid-row:3">
@ -206,6 +206,11 @@
</div>
</div>
</div>
<div class="center">
<button class = "button" id = "update-button" onclick = "updateStats()">
Update Stats
</button>
</div>
<div class = "build">
<div class = "center build-helmet" id = "build-helmet" style = "grid-column:1;grid-row:1">
<div class = "center" id = "build-helmet-stats"></div>
@ -242,21 +247,21 @@
</div>
</div>
<div class = "spells">
<div class = "center spell-info" id = "spell1" style = "grid-column:1;grid-row:1">
<div class = "center" id = "spell1-name">Spell 1</div>
<div class = "center" id = "spell1-info"></div>
<div class = "center spell-info" id = "spell0" style = "grid-column:1;grid-row:1">
<!--div class = "center" id = "spell0-name">Spell 1</div-->
<div class = "center" id = "spell0-info">Spell 1</div>
</div>
<div class = "center spell-info" id = "spell2" style = "grid-column:2;grid-row:1">
<div class = "center" id = "spell2-name">Spell 2</div>
<div class = "center" id = "spell2-info"></div>
<div class = "center spell-info" id = "spell1" style = "grid-column:2;grid-row:1">
<!--div class = "center" id = "spell1-name">Spell 2</div-->
<div class = "center" id = "spell1-info">Spell 2</div>
</div>
<div class = "center spell-info" id = "spell3" style = "grid-column:3;grid-row:1">
<div class = "center" id = "spell3-name">Spell 3</div>
<div class = "center" id = "spell3-info"></div>
<div class = "center spell-info" id = "spell2" style = "grid-column:3;grid-row:1">
<!--div class = "center" id = "spell2-name">Spell 3</div-->
<div class = "center" id = "spell2-info">Spell 3</div>
</div>
<div class = "center spell-info" id = "spell4" style = "grid-column:4;grid-row:1">
<div class = "center" id = "spell4-name">Spell 4</div>
<div class = "center" id = "spell4-info"></div>
<div class = "center spell-info" id = "spell3" style = "grid-column:4;grid-row:1">
<!--div class = "center" id = "spell3-name">Spell 4</div-->
<div class = "center" id = "spell3-info">Spell 4</div>
</div>
</div>

View file

@ -46,6 +46,8 @@
}
.spell-info {
color: #aaa;
background: #110110;
border: 2px solid black;
border-radius: 3px;
width: 100%;
@ -75,11 +77,19 @@
margin-bottom: 16px;
}
.itemp {
.itemp, .damagep {
margin: 2px 2%;
padding: 0;
}
.damageSubtitle {
text-align: center;
margin: 12px 2% 4px;
/*margin-bottom: 16px;*/
/*margin-top: 16px;*/
}
.positive {
color: #5f5;
/*text-shadow: 2px 2px 0 #153f15;*/
@ -134,6 +144,9 @@
color: #fa0;
/*text-shadow: 2px 2px 0 #2a2a00;*/
}
.Neutral:before {
content: "\2724" ' ';
}
.Health {
color: #a00;

111
test.js
View file

@ -22,7 +22,9 @@ let accessoryTypes = [ "ring", "bracelet", "necklace" ];
let weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ];
// THIS IS SUPER DANGEROUS, WE SHOULD NOT BE KEEPING THIS IN SO MANY PLACES
let item_fields = [ "name", "displayName", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "exploding", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id" ];
let skpReqs = ["strReq", "dexReq", "intReq", "defReq", "agiReq"];
let skp_order = ["str","dex","int","def","agi"];
let skpReqs = skp_order.map(x => x + "Req");
let equipment_fields = [
"helmet",
@ -218,7 +220,9 @@ function populateFromURL() {
let powdering = ["", "", "", "", ""];
let info = url_tag.split("_");
let version = info[0];
if (version === "0" || version === "1") {
let save_skp = false;
let skillpoints = [0, 0, 0, 0, 0];
if (version === "0" || version === "1" || version === "2") {
let equipments = info[1];
for (let i = 0; i < 9; ++i ) {
equipment[i] = idMap.get(Base64.toInt(equipments.slice(i*3,i*3+3)));
@ -246,6 +250,34 @@ function populateFromURL() {
powdering[i] = powders;
}
}
if (version === "2") {
save_skp = true;
let skillpoint_info = info[1].slice(27, 37);
for (let i = 0; i < 5; ++i ) {
skillpoints[i] = Base64.toInt(skillpoint_info.slice(i*2,i*2+2));
}
let powder_info = info[1].slice(37);
console.log(powder_info);
// TODO: Make this run in linear instead of quadratic time...
for (let i = 0; i < 5; ++i) {
let powders = "";
let n_blocks = Base64.toInt(powder_info.charAt(0));
console.log(n_blocks + " blocks");
powder_info = powder_info.slice(1);
for (let j = 0; j < n_blocks; ++j) {
let block = powder_info.slice(0,5);
console.log(block);
let six_powders = Base64.toInt(block);
for (let k = 0; k < 6 && six_powders != 0; ++k) {
powders += powderNames.get((six_powders & 0x1f) - 1);
six_powders >>>= 5;
}
powder_info = powder_info.slice(5);
}
powdering[i] = powders;
}
}
for (let i in powderInputs) {
setValue(powderInputs[i], powdering[i]);
@ -253,27 +285,13 @@ function populateFromURL() {
for (let i in equipment) {
setValue(equipmentInputs[i], equipment[i]);
}
setValue("str-skp", "0");
setValue("dex-skp", "0");
setValue("int-skp", "0");
setValue("def-skp", "0");
setValue("agi-skp", "0");
calculateBuild();
calculateBuild(save_skp, skillpoints);
}
}
function encodeBuild() {
if (player_build) {
// let build_string = "0_" + Base64.fromIntN(player_build.helmet.id, 3) +
// Base64.fromIntN(player_build.chestplate.id, 3) +
// Base64.fromIntN(player_build.leggings.id, 3) +
// Base64.fromIntN(player_build.boots.id, 3) +
// Base64.fromIntN(player_build.ring1.id, 3) +
// Base64.fromIntN(player_build.ring2.id, 3) +
// Base64.fromIntN(player_build.bracelet.id, 3) +
// Base64.fromIntN(player_build.necklace.id, 3) +
// Base64.fromIntN(player_build.weapon.id, 3);
let build_string = "1_" + Base64.fromIntN(player_build.helmet.get("id"), 3) +
let build_string = "2_" + Base64.fromIntN(player_build.helmet.get("id"), 3) +
Base64.fromIntN(player_build.chestplate.get("id"), 3) +
Base64.fromIntN(player_build.leggings.get("id"), 3) +
Base64.fromIntN(player_build.boots.get("id"), 3) +
@ -283,6 +301,10 @@ function encodeBuild() {
Base64.fromIntN(player_build.necklace.get("id"), 3) +
Base64.fromIntN(player_build.weapon.get("id"), 3);
for (const skp of skp_order) {
build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 4096
}
for (const _powderset of player_build.powders) {
let n_bits = Math.ceil(_powderset.length / 6);
build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders.
@ -304,7 +326,8 @@ function encodeBuild() {
return "";
}
function calculateBuild(){
function calculateBuild(save_skp, skp){
save_skp = (typeof save_skp !== 'undefined') ? save_skp : false;
/* TODO: implement level changing
Make this entire function prettier
*/
@ -336,14 +359,45 @@ function calculateBuild(){
equip_order_text += item.get("displayName") + "<br>";
}
setHTML("build-order", equip_order_text);
if (save_skp) {
// TODO: reduce duplicated code, @updateStats
let skillpoints = player_build.total_skillpoints;
let delta_total = 0;
for (let i in skp_order) {
let manual_assigned = skp[i];
let delta = manual_assigned - skillpoints[i];
skillpoints[i] = manual_assigned;
player_build.base_skillpoints[i] += delta;
delta_total += delta;
}
player_build.assigned_skillpoints += delta_total;
}
calculateBuildStats();
}
function updateStats() {
let skillpoints = player_build.total_skillpoints;
let delta_total = 0;
for (let i in skp_order) {
let manual_assigned = getValue(skp_order[i] + "-skp");
let delta = manual_assigned - skillpoints[i];
skillpoints[i] = manual_assigned;
player_build.base_skillpoints[i] += delta;
delta_total += delta;
}
player_build.assigned_skillpoints += delta_total;
calculateBuildStats();
}
function calculateBuildStats() {
const assigned = player_build.base_skillpoints;
const skillpoints = player_build.total_skillpoints;
let skp_order = ["str","dex","int","def","agi"];
let skp_effects = ["% more damage dealt.","% chance to crit.","% spell cost reduction.","% less damage taken.","% chance to dodge."];
for (let i in skp_order){ //big bren
setText(skp_order[i] + "-skp-assign", "Before Boosts: " + assigned[i]);
setText(skp_order[i] + "-skp-assign", "Base assigned: " + assigned[i]);
setValue(skp_order[i] + "-skp", skillpoints[i]);
if(assigned[i] <= 100){
setText(skp_order[i] + "-skp-base", "Original Value: " + skillpoints[i]);
@ -352,6 +406,7 @@ function calculateBuild(){
}
setText(skp_order[i] + "-skp-pct", (skillPointsToPercentage(skillpoints[i])*100).toFixed(1).concat(skp_effects[i]));
}
if(player_build.assigned_skillpoints > levelToSkillPoints(player_build.level)){
setHTML("summary-box", "Summary: Assigned "+player_build.assigned_skillpoints+" skillpoints.<br>" + "WARNING: Too many skillpoints need to be assigned!<br> For level " + player_build.level + ", there are only " + levelToSkillPoints(player_build.level) + " skill points available.");
}else{
@ -361,10 +416,7 @@ function calculateBuild(){
for (let i in player_build.items) {
displayExpandedItem(player_build.items[i], buildFields[i]);
}
calculateBuildStats();
}
function calculateBuildStats() {
let meleeStats = player_build.getMeleeStats();
//nDamAdj,eDamAdj,tDamAdj,wDamAdj,fDamAdj,aDamAdj,totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS
for (let i = 0; i < 6; ++i) {
@ -403,6 +455,13 @@ function calculateBuildStats() {
let defenseStats = "";
setHTML("build-defense-stats", "".concat(defenseStats));
let spells = spell_table[player_build.weapon.get("type")];
for (let i = 0; i < 4; ++i) {
let parent_elem = document.getElementById("spell"+i+"-info");
displaySpellDamage(parent_elem, player_build, spells[i], i+1);
}
location.hash = encodeBuild();
}
@ -410,7 +469,7 @@ function resetFields(){
for (let i in powderInputs) {
setValue(powderInputs[i], "");
}
for (let i in equipment) {
for (let i in equipmentInputs) {
setValue(equipmentInputs[i], "");
}
setValue("str-skp", "0");

3
test_regress.txt Normal file
View file

@ -0,0 +1,3 @@
Version 0: http://localhost:8000/#0_0K30oY09X2SJ2SK2SL2SM2SN0QQ
Version 1: http://localhost:8000/#1_0690px0CE0QR0050050K40BR0Qk00001004fI
Version 2: http://localhost:8000/#2_2SG2SH2SI2SJ2SK0K22SM2SN05n000t210t0000000