Merge branch 'atree_active_box' into atree-validation-fix
|
@ -1055,11 +1055,6 @@
|
|||
<div class="col eDam">
|
||||
Rage (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "e_slider" id = "str_boost_armor" name = "str-boost-armor" autocomplete = "off" min = '0' max = '400' value = '0' step = '1' onchange = "update_armor_powder_specials('str_boost_armor')">
|
||||
<input type="text" id="str_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "str_boost_armor_label" for="str-boost-armor">% Earth Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="dex-boost" style="display: none;">
|
||||
|
@ -1099,11 +1094,6 @@
|
|||
<div class="col tDam">
|
||||
Kill Streak (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "t_slider" id = "dex_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('dex_boost_armor')">
|
||||
<input type="text" id="dex_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "dex_boost_armor_label" for="dex-boost-armor">% Thunder Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="int-boost">
|
||||
|
@ -1143,11 +1133,6 @@
|
|||
<div class="col wDam">
|
||||
Concentration (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "w_slider" id = "int_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('int_boost_armor')">
|
||||
<input type="text" id="int_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "int_boost_armor_label" for="dex-boost-armor">% Water Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="def-boost" style="display: none;">
|
||||
|
@ -1187,11 +1172,6 @@
|
|||
<div class="col fDam">
|
||||
Endurance (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "f_slider" id = "def_boost_armor" name = "def-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('def_boost_armor')">
|
||||
<input type="text" id="def_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "def_boost_armor_label" for="def-boost-armor">% Fire Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="agi-boost" style="display: none;">
|
||||
|
@ -1231,11 +1211,6 @@
|
|||
<div class="col aDam">
|
||||
Dodge (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "a_slider" id = "agi_boost_armor" name = "agi-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('agi_boost_armor')">
|
||||
<input type="text" id="agi_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "agi_boost_armor_label" for="agi-boost-armor">% Air Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
/* builder containers */
|
||||
|
||||
|
||||
.e_slider, .t_slider, .w_slider, .f_slider, .a_slider {
|
||||
.slider {
|
||||
-webkit-appearance: none;
|
||||
background: #AAAAAA;
|
||||
border-radius: 30px;
|
||||
|
@ -16,15 +16,14 @@
|
|||
}
|
||||
|
||||
/***** Chrome, Safari, Opera, and Edge Chromium *****/
|
||||
.e_slider::-webkit-slider-runnable-track, .t_slider::-webkit-slider-runnable-track, .w_slider::-webkit-slider-runnable-track, .f_slider::-webkit-slider-runnable-track, .a_slider::-webkit-slider-runnable-track {
|
||||
.slider::-webkit-slider-runnable-track{
|
||||
-webkit-appeareance: none;
|
||||
background:transparent;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
/******** Firefox **** **/
|
||||
.e_slider::-moz-range-track, .t_slider::-moz-range-track, .w_slider::-moz-range-track, .f_slider::-moz-range-track, .a_slider::-moz-range-track {
|
||||
.slider::-moz-range-track {
|
||||
-webkit-appearance: none;
|
||||
background-color: transparent;
|
||||
border-radius: 30px;
|
||||
|
@ -32,7 +31,7 @@
|
|||
}
|
||||
|
||||
|
||||
.e_slider::-webkit-slider-thumb, .t_slider::-webkit-slider-thumb, .w_slider::-webkit-slider-thumb, .f_slider::-webkit-slider-thumb, .a_slider::-webkit-slider-thumb {
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 0.75rem;
|
||||
|
@ -504,4 +503,4 @@ a:hover {
|
|||
}
|
||||
.ferricles{
|
||||
color: #5be553;
|
||||
}
|
||||
}
|
||||
|
|
315
js/atree.js
|
@ -76,6 +76,10 @@ stat_scaling: {
|
|||
"slider": bool,
|
||||
"slider_name": Optional[str],
|
||||
"slider_step": Optional[float],
|
||||
slider_behavior: Optional[str] // One of: "merge", "modify". default: merge
|
||||
// merge: add if exist, make new part if not exist
|
||||
// modify: change existing part. do nothing if not exist
|
||||
slider_max: Optional[float] // affected by slider_behavior
|
||||
"inputs": Optional[list[scaling_target]],
|
||||
"output": scaling_target | List[scaling_target],
|
||||
"scaling": list[float],
|
||||
|
@ -170,6 +174,31 @@ const atree_node = new (class extends ComputeNode {
|
|||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Create a reverse topological sort of the tree in the result list.
|
||||
* NOTE: our structure isn't a tree... it isn't even acyclic... but do it anyway i guess...
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Topological_sorting
|
||||
* @param tree: Root of tree to sort
|
||||
* @param res: Result list (reverse topological order)
|
||||
* @param mark_state: Bookkeeping. Call with empty Map()
|
||||
*/
|
||||
function topological_sort_tree(tree, res, mark_state) {
|
||||
const state = mark_state.get(tree);
|
||||
if (state === undefined) {
|
||||
// unmarked.
|
||||
mark_state.set(tree, false); // temporary mark
|
||||
for (const child of tree.children) {
|
||||
topological_sort_tree(child, res, mark_state);
|
||||
}
|
||||
mark_state.set(tree, true); // permanent mark
|
||||
res.push(tree);
|
||||
}
|
||||
// these cases are not needed. Case 1 does nothing, case 2 should never happen.
|
||||
// else if (state === true) { return; } // permanent mark.
|
||||
// else if (state === false) { throw "not a DAG"; } // temporary mark.
|
||||
}
|
||||
|
||||
/**
|
||||
* Display ability tree from topologically sorted list.
|
||||
*
|
||||
|
@ -212,30 +241,6 @@ const atree_state_node = new (class extends ComputeNode {
|
|||
}
|
||||
})().link_to(atree_render, 'atree-render');
|
||||
|
||||
/**
|
||||
* Create a reverse topological sort of the tree in the result list.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Topological_sorting
|
||||
* @param tree: Root of tree to sort
|
||||
* @param res: Result list (reverse topological order)
|
||||
* @param mark_state: Bookkeeping. Call with empty Map()
|
||||
*/
|
||||
function topological_sort_tree(tree, res, mark_state) {
|
||||
const state = mark_state.get(tree);
|
||||
if (state === undefined) {
|
||||
// unmarked.
|
||||
mark_state.set(tree, false); // temporary mark
|
||||
for (const child of tree.children) {
|
||||
topological_sort_tree(child, res, mark_state);
|
||||
}
|
||||
mark_state.set(tree, true); // permanent mark
|
||||
res.push(tree);
|
||||
}
|
||||
// these cases are not needed. Case 1 does nothing, case 2 should never happen.
|
||||
// else if (state === true) { return; } // permanent mark.
|
||||
// else if (state === false) { throw "not a DAG"; } // temporary mark.
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect abilities and condense them into a list of "final abils".
|
||||
* This is just for rendering purposes, and for collecting things that modify spells into one chunk.
|
||||
|
@ -269,18 +274,21 @@ const atree_merge = new (class extends ComputeNode {
|
|||
}
|
||||
const abil = node.ability;
|
||||
|
||||
if (abils_merged.has(abil.base_abil)) {
|
||||
// Merge abilities.
|
||||
// TODO: What if there is more than one base abil?
|
||||
let base_abil = abils_merged.get(abil.base_abil);
|
||||
if (Array.isArray(abil.desc)) { base_abil.desc = base_abil.desc.concat(abil.desc); }
|
||||
else { base_abil.desc.push(abil.desc); }
|
||||
if ('base_abil' in abil) {
|
||||
if (abils_merged.has(abil.base_abil)) {
|
||||
// Merge abilities.
|
||||
// TODO: What if there is more than one base abil?
|
||||
let base_abil = abils_merged.get(abil.base_abil);
|
||||
if (Array.isArray(abil.desc)) { base_abil.desc = base_abil.desc.concat(abil.desc); }
|
||||
else { base_abil.desc.push(abil.desc); }
|
||||
|
||||
base_abil.subparts.push(abil.id);
|
||||
base_abil.effects = base_abil.effects.concat(abil.effects);
|
||||
for (let propname in abil.properties) {
|
||||
base_abil[propname] = abil[propname];
|
||||
base_abil.subparts.push(abil.id);
|
||||
base_abil.effects = base_abil.effects.concat(abil.effects);
|
||||
for (let propname in abil.properties) {
|
||||
base_abil[propname] = abil[propname];
|
||||
}
|
||||
}
|
||||
// do nothing otherwise.
|
||||
}
|
||||
else {
|
||||
let tmp_abil = deepcopy(abil);
|
||||
|
@ -381,6 +389,12 @@ function atree_dfs_mark(start, atree_state, mark) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ability tree.
|
||||
* Return map of id -> corresponding html element.
|
||||
*
|
||||
* Signature: AbilityTreeRenderActiveNode(atree-merged: MergedATree, atree-order: ATree, atree-errors: List[str]) => Map[int, ATreeNode]
|
||||
*/
|
||||
const atree_render_active = new (class extends ComputeNode {
|
||||
constructor() {
|
||||
super('atree-render-active');
|
||||
|
@ -413,6 +427,7 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
errorbox.appendChild(atree_warning);
|
||||
}
|
||||
}
|
||||
const ret_map = new Map();
|
||||
for (const node of atree_order) {
|
||||
if (!merged_abils.has(node.ability.id)) {
|
||||
continue;
|
||||
|
@ -433,9 +448,11 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
active_tooltip_desc.textContent = desc;
|
||||
active_tooltip.appendChild(active_tooltip_desc);
|
||||
}
|
||||
ret_map.set(abil.id, active_tooltip);
|
||||
|
||||
this.list_elem.appendChild(active_tooltip);
|
||||
}
|
||||
return ret_map;
|
||||
}
|
||||
})().link_to(atree_node, 'atree-order').link_to(atree_merge, 'atree-merged').link_to(atree_validate, 'atree-errors');
|
||||
|
||||
|
@ -546,12 +563,84 @@ const atree_collect_spells = new (class extends ComputeNode {
|
|||
}
|
||||
})().link_to(atree_merge, 'atree-merged').link_to(atree_validate, 'atree-errors');
|
||||
|
||||
|
||||
/**
|
||||
* Make interactive elements (sliders, buttons)
|
||||
*
|
||||
* Signature: AbilityActiveUINode(atree-merged: MergedATree) => Map<str, slider_info>
|
||||
*
|
||||
* ElemState: {
|
||||
* value: int // value for sliders; 0-1 for toggles
|
||||
* }
|
||||
*/
|
||||
const atree_make_interactives = new (class extends ComputeNode {
|
||||
constructor() { super('atree-make-interactives'); }
|
||||
|
||||
compute_func(input_map) {
|
||||
const merged_abils = input_map.get('atree-merged');
|
||||
const atree_order = input_map.get('atree-order');
|
||||
const atree_html = input_map.get('atree-elements');
|
||||
|
||||
/**
|
||||
* slider_info
|
||||
* label_name: str,
|
||||
* max: int,
|
||||
* step: int,
|
||||
* id: str,
|
||||
* abil: atree_node
|
||||
* slider: html element
|
||||
* }
|
||||
*/
|
||||
// Map<str, slider_info>
|
||||
const slider_map = new Map();
|
||||
|
||||
// first, pull out all the sliders.
|
||||
for (const [abil_id, ability] of merged_abils.entries()) {
|
||||
for (const effect of ability.effects) {
|
||||
if (effect['type'] === "stat_scaling" && effect['slider'] === true) {
|
||||
const { slider_name, slider_behavior = 'merge', slider_max, slider_step } = effect;
|
||||
if (slider_map.has(slider_name)) {
|
||||
const slider_info = slider_map.get(slider_name);
|
||||
slider_info.max += slider_max;
|
||||
}
|
||||
else if (slider_behavior === 'merge') {
|
||||
slider_map.set(slider_name, {
|
||||
label_name: slider_name,
|
||||
max: slider_max,
|
||||
step: slider_step,
|
||||
id: "ability-slider"+ability.id,
|
||||
//color: effect['slider_color'] TODO: add colors to json
|
||||
abil: ability
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// next, render the sliders onto the abilities.
|
||||
for (const [slider_name, slider_info] of slider_map.entries()) {
|
||||
let slider_container = gen_slider_labeled(slider_info);
|
||||
atree_html.get(slider_info.abil.id).appendChild(slider_container);
|
||||
slider_info.slider = document.getElementById(slider_info.id);
|
||||
slider_info.slider.addEventListener("change", (e) => atree_stats.mark_dirty().update());
|
||||
}
|
||||
return slider_map;
|
||||
}
|
||||
})().link_to(atree_node, 'atree-order').link_to(atree_merge, 'atree-merged').link_to(atree_render_active, 'atree-elements');
|
||||
|
||||
|
||||
/**
|
||||
* Collect stats from ability tree.
|
||||
* Return StatMap of added stats (incl. cost modifications as raw cost)
|
||||
*
|
||||
* Signature: AbilityTreeStatsNode(atree-merged: MergedATree, build: Build, atree-interactive: Map<str, slider_info>) => StatMap
|
||||
*/
|
||||
const atree_stats = new (class extends ComputeNode {
|
||||
constructor() { super('atree-stats-collector'); }
|
||||
|
||||
compute_func(input_map) {
|
||||
if (input_map.size !== 1) { throw "AbilityTreeCollectStats accepts exactly one input (atree-merged)"; }
|
||||
const [atree_merged] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
|
||||
const atree_merged = input_map.get('atree-merged');
|
||||
const item_stats = input_map.get('build').statMap;
|
||||
const interactive_map = input_map.get('atree-interactive');
|
||||
|
||||
let ret_effects = new Map();
|
||||
for (const [abil_id, abil] of atree_merged.entries()) {
|
||||
|
@ -560,7 +649,45 @@ const atree_stats = new (class extends ComputeNode {
|
|||
for (const effect of abil.effects) {
|
||||
switch (effect.type) {
|
||||
case 'stat_scaling':
|
||||
// TODO: handle
|
||||
if (effect.slider) {
|
||||
// TODO: handle
|
||||
const slider_val = interactive_map.get(effect.slider_name).slider.value;
|
||||
const total = parseInt(slider_val) * effect.scaling[0];
|
||||
if (Array.isArray(effect.output)) {
|
||||
for (const output of effect.output) {
|
||||
if (output.type === 'stat') {
|
||||
merge_stat(ret_effects, output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (effect.output.type === 'stat') {
|
||||
merge_stat(ret_effects, effect.output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const cap = effect.max;
|
||||
// TODO: type: prop?
|
||||
let total = 0;
|
||||
for (const [scaling, input] of zip2(effect.scaling, effect.inputs)) {
|
||||
total += scaling * item_stats.get(input.name);
|
||||
}
|
||||
if (total > cap) { total = cap; }
|
||||
// TODO: output (list...)
|
||||
if (Array.isArray(effect.output)) {
|
||||
for (const output of effect.output) {
|
||||
if (output.type === 'stat') {
|
||||
merge_stat(ret_effects, output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (effect.output.type === 'stat') {
|
||||
merge_stat(ret_effects, effect.output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case 'raw_stat':
|
||||
// TODO: toggles...
|
||||
|
@ -568,8 +695,7 @@ const atree_stats = new (class extends ComputeNode {
|
|||
const { type, name, abil = "", value } = bonus;
|
||||
// TODO: prop
|
||||
if (type === "stat") {
|
||||
if (ret_effects.has(name)) { ret_effects.set(name, ret_effects.get(name) + value); }
|
||||
else { ret_effects.set(name, value); }
|
||||
merge_stat(ret_effects, name, value);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
|
@ -586,10 +712,12 @@ const atree_stats = new (class extends ComputeNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
console.log(ret_effects);
|
||||
if (ret_effects.has('baseResist')) {
|
||||
merge_stat(ret_effects, "defMult", 1 - (ret_effects.get('baseResist') / 100));
|
||||
}
|
||||
return ret_effects;
|
||||
}
|
||||
})().link_to(atree_merge, 'atree-merged');
|
||||
})().link_to(atree_merge, 'atree-merged').link_to(atree_make_interactives, 'atree-interactive');
|
||||
|
||||
|
||||
/**
|
||||
|
@ -670,42 +798,21 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
|
|||
function render_AT(UI_elem, list_elem, tree) {
|
||||
console.log("constructing ability tree UI");
|
||||
// add in the "Active" title to atree
|
||||
let active_row = document.createElement("div");
|
||||
active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center");
|
||||
let active_word = document.createElement("div");
|
||||
active_word.classList.add("col-auto");
|
||||
active_word.textContent = "Active Abilities:";
|
||||
let active_row = make_elem("div", ["row", "item-title", "mx-auto", "justify-content-center"]);
|
||||
let active_word = make_elem("div", ["col-auto"], {textContent: "Active Abilities:"});
|
||||
|
||||
let active_AP_container = document.createElement("div");
|
||||
active_AP_container.classList.add("col-auto");
|
||||
let active_AP_container = make_elem("div", ["col-auto"]);
|
||||
let active_AP_subcontainer = make_elem("div", ["row"]);
|
||||
let active_AP_cost = make_elem("div", ["col-auto", "mx-0", "px-0"], {id: "active_AP_cost", textContent: "0"});
|
||||
|
||||
let active_AP_subcontainer = document.createElement("div");
|
||||
active_AP_subcontainer.classList.add("row");
|
||||
let active_AP_slash = make_elem("div", ["col-auto", "mx-0", "px-0"], {textContent: "/"});
|
||||
let active_AP_cap = make_elem("div", ["col-auto", "mx-0", "px-0"], {id: "active_AP_cap", textContent: "45"});
|
||||
let active_AP_end = make_elem("div", ["col-auto", "mx-0", "px-0"], {textContent: " AP"});
|
||||
|
||||
let active_AP_cost = document.createElement("div");
|
||||
active_AP_cost.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_cost.id = "active_AP_cost";
|
||||
active_AP_cost.textContent = "0";
|
||||
let active_AP_slash = document.createElement("div");
|
||||
active_AP_slash.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_slash.textContent = "/";
|
||||
let active_AP_cap = document.createElement("div");
|
||||
active_AP_cap.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_cap.id = "active_AP_cap";
|
||||
active_AP_cap.textContent = "45";
|
||||
let active_AP_end = document.createElement("div");
|
||||
active_AP_end.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_end.textContent = " AP";
|
||||
|
||||
//I can't believe we can't pass in multiple children at once
|
||||
active_AP_subcontainer.appendChild(active_AP_cost);
|
||||
active_AP_subcontainer.appendChild(active_AP_slash);
|
||||
active_AP_subcontainer.appendChild(active_AP_cap);
|
||||
active_AP_subcontainer.appendChild(active_AP_end);
|
||||
active_AP_container.appendChild(active_AP_subcontainer);
|
||||
active_AP_subcontainer.append(active_AP_cost, active_AP_slash, active_AP_cap, active_AP_end);
|
||||
|
||||
active_row.appendChild(active_word);
|
||||
active_row.appendChild(active_AP_container);
|
||||
active_row.append(active_word, active_AP_container);
|
||||
list_elem.appendChild(active_row);
|
||||
|
||||
let atree_map = new Map();
|
||||
|
@ -794,7 +901,7 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
let node_elem = document.createElement('div');
|
||||
let icon = ability.display.icon;
|
||||
if (icon === undefined) {
|
||||
icon = "node";
|
||||
icon = "node_0";
|
||||
}
|
||||
let node_img = document.createElement('img');
|
||||
node_img.src = '../media/atree/'+icon+'.png';
|
||||
|
@ -802,7 +909,7 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
node_elem.appendChild(node_img);
|
||||
node_elem.classList.add("atree-circle");
|
||||
|
||||
// add tooltip
|
||||
// add node tooltip
|
||||
node_elem.addEventListener('mouseover', function(e) {
|
||||
if (e.target !== this) {return;}
|
||||
let tooltip = this.children[0];
|
||||
|
@ -817,39 +924,52 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
tooltip.style.display = "none";
|
||||
});
|
||||
|
||||
node_elem.classList.add("fake-button");
|
||||
//node tooltip and active tooltip have common parts - let's make them first
|
||||
|
||||
let node_tooltip = document.createElement('div');
|
||||
node_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow");
|
||||
node_tooltip.style.display = "none";
|
||||
|
||||
// tooltip text formatting
|
||||
|
||||
let node_tooltip_title = document.createElement('b');
|
||||
node_tooltip_title.classList.add("scaled-font");
|
||||
node_tooltip_title.innerHTML = ability.display_name;
|
||||
node_tooltip.appendChild(node_tooltip_title);
|
||||
let tooltip_title = document.createElement('div');
|
||||
tooltip_title.classList.add("row", "justify-content-center", "fw-bold");
|
||||
tooltip_title.textContent = ability.display_name;
|
||||
|
||||
let tooltip_archetype;
|
||||
if ('archetype' in ability && ability.archetype !== "") {
|
||||
let node_tooltip_archetype = document.createElement('p');
|
||||
node_tooltip_archetype.classList.add("scaled-font");
|
||||
node_tooltip_archetype.innerHTML = "(Archetype: " + ability.archetype+")";
|
||||
node_tooltip.appendChild(node_tooltip_archetype);
|
||||
tooltip_archetype = document.createElement('div');
|
||||
tooltip_archetype.classList.add("row", "mx-1", "text-start");
|
||||
tooltip_archetype.textContent = "(Archetype: " + ability.archetype+")";
|
||||
}
|
||||
|
||||
let node_tooltip_desc = document.createElement('p');
|
||||
node_tooltip_desc.classList.add("scaled-font-sm", "my-0", "mx-1", "text-wrap");
|
||||
node_tooltip_desc.textContent = ability.desc;
|
||||
node_tooltip.appendChild(node_tooltip_desc);
|
||||
let tooltip_desc = document.createElement('div');
|
||||
tooltip_desc.classList.add("row", "mx-1", "text-wrap");
|
||||
tooltip_desc.textContent = ability.desc;
|
||||
|
||||
let node_tooltip_cost = document.createElement('p');
|
||||
node_tooltip_cost.classList.add("scaled-font-sm", "my-0", "mx-1", "text-start");
|
||||
node_tooltip_cost.textContent = "Cost: " + ability.cost + " AP";
|
||||
node_tooltip.appendChild(node_tooltip_cost);
|
||||
let tooltip_cost = document.createElement('div');
|
||||
tooltip_cost.classList.add("row", "mx-1", "text-start");
|
||||
tooltip_cost.textContent = "Cost: " + ability.cost + " AP";
|
||||
|
||||
//create node tooltip
|
||||
let node_tooltip = document.createElement('div');
|
||||
node_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "my-1", "dark-shadow", "scaled-font", "container");
|
||||
node_tooltip.style.display = "none";
|
||||
node_tooltip.append(tooltip_title, tooltip_archetype ? tooltip_archetype : "", tooltip_desc);
|
||||
|
||||
//active = copy of node
|
||||
let active_tooltip = node_tooltip.cloneNode(true);
|
||||
|
||||
//node tooltip specific stuff that we don't want to be copied
|
||||
node_tooltip.style.position = "absolute";
|
||||
node_tooltip.style.zIndex = "100";
|
||||
node_tooltip.appendChild(tooltip_cost);
|
||||
|
||||
//add in anything new for active tooltips
|
||||
active_tooltip.id = "atree-ab-" + ability.id;
|
||||
|
||||
if (ability.blockers.length > 0) {
|
||||
let active_tooltip_blockers = document.createElement("div");
|
||||
active_tooltip_blockers.classList.add("row", "mx-1", "text-start");
|
||||
active_tooltip_blockers.textContent = "Blockers: " + ability.blockers.join(", ");
|
||||
active_tooltip.append(active_tooltip_blockers);
|
||||
}
|
||||
|
||||
//append node and active tooltips to corresponding parent elems
|
||||
node_elem.appendChild(node_tooltip);
|
||||
//list_elem.appendChild(active_tooltip); NOTE: moved to `atree_render_active`
|
||||
|
||||
|
@ -881,7 +1001,6 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
|
||||
document.getElementById("atree-row-" + ability.display.row).children[ability.display.col].appendChild(node_elem);
|
||||
};
|
||||
console.log(atree_connectors_map);
|
||||
atree_render_connection(atree_connectors_map);
|
||||
|
||||
return atree_map;
|
||||
|
|
|
@ -1405,7 +1405,7 @@ const atrees = {
|
|||
"type": "raw_stat",
|
||||
"bonuses": [{
|
||||
"type": "stat",
|
||||
"name": "mdCritPct",
|
||||
"name": "critDamPct",
|
||||
"value": 30
|
||||
}]
|
||||
}]
|
||||
|
@ -1688,10 +1688,10 @@ const atrees = {
|
|||
"slider_name": "Focus",
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damMult"
|
||||
"name": "damMult.Focus"
|
||||
},
|
||||
"scaling": [40],
|
||||
"max": 3
|
||||
"slider_max": 3
|
||||
}]
|
||||
},
|
||||
{
|
||||
|
@ -1699,6 +1699,7 @@ const atrees = {
|
|||
"desc": "Add +2 max Focus",
|
||||
"archetype": "Sharpshooter",
|
||||
"archetype_req": 0,
|
||||
"base_abil": "Focus",
|
||||
"parents": ["Cheaper Arrow Storm", "Grappling Hook"],
|
||||
"dependencies": ["Focus"],
|
||||
"blockers": [],
|
||||
|
@ -1713,12 +1714,12 @@ const atrees = {
|
|||
"type": "stat_scaling",
|
||||
"slider": true,
|
||||
"slider_name": "Focus",
|
||||
"slider_max": 2,
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damMult"
|
||||
"name": "damMult.Focus"
|
||||
},
|
||||
"scaling": [30],
|
||||
"max": 5
|
||||
"scaling": [-5]
|
||||
}]
|
||||
},
|
||||
{
|
||||
|
@ -1726,6 +1727,7 @@ const atrees = {
|
|||
"desc": "Add +2 max Focus",
|
||||
"archetype": "Sharpshooter",
|
||||
"archetype_req": 0,
|
||||
"base_abil": "Focus",
|
||||
"parents": ["Crepuscular Ray", "Snow Storm"],
|
||||
"dependencies": ["Focus"],
|
||||
"blockers": [],
|
||||
|
@ -1740,12 +1742,12 @@ const atrees = {
|
|||
"type": "stat_scaling",
|
||||
"slider": true,
|
||||
"slider_name": "Focus",
|
||||
"slider_max": 2,
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damMult"
|
||||
"name": "damMult.Focus"
|
||||
},
|
||||
"scaling": [25],
|
||||
"max": 7
|
||||
"scaling": [-5]
|
||||
}]
|
||||
},
|
||||
{
|
||||
|
@ -1798,13 +1800,13 @@ const atrees = {
|
|||
"type": "stat_scaling",
|
||||
"slider": true,
|
||||
"slider_name": "Trap Wait Time",
|
||||
"slider_max": 4,
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damMult:Basaltic Trap"
|
||||
"name": "damMult.Basaltic:Basaltic Trap"
|
||||
},
|
||||
"slider_step": 1,
|
||||
"scaling": [20],
|
||||
"max": 80
|
||||
"scaling": [20]
|
||||
}]
|
||||
},
|
||||
{
|
||||
|
@ -1828,12 +1830,7 @@ const atrees = {
|
|||
"type": "stat_scaling",
|
||||
"slider": true,
|
||||
"slider_name": "Trap Wait Time",
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damMult:Basaltic Trap"
|
||||
},
|
||||
"scaling": [20],
|
||||
"max": 80
|
||||
"slider_max": 4
|
||||
},
|
||||
{
|
||||
"type": "raw_stat",
|
||||
|
@ -1878,6 +1875,7 @@ const atrees = {
|
|||
"desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second",
|
||||
"archetype": "Sharpshooter",
|
||||
"archetype_req": 0,
|
||||
"base_abil": "Arrow Storm",
|
||||
"parents": ["Water Mastery", "Fire Creep"],
|
||||
"dependencies": ["Arrow Storm"],
|
||||
"blockers": ["Windstorm", "Nimble String", "Arrow Hurricane"],
|
||||
|
@ -1966,12 +1964,12 @@ const atrees = {
|
|||
"type": "stat_scaling",
|
||||
"slider": true,
|
||||
"slider_name": "Phantom Ray hits",
|
||||
"slider_max": 7,
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damMult:Single Arrow"
|
||||
"name": "damMult.Decimator:Single Arrow"
|
||||
},
|
||||
"scaling": 10,
|
||||
"max": 70
|
||||
"scaling": 10
|
||||
}]
|
||||
}
|
||||
],
|
||||
|
@ -2209,7 +2207,7 @@ const atrees = {
|
|||
|
||||
{
|
||||
"display_name": "Tougher Skin",
|
||||
"desc": "Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",
|
||||
"desc": "Harden your skin and become permanently +5% more resistant. For every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",
|
||||
"archetype": "Paladin",
|
||||
"archetype_req": 0,
|
||||
"parents": ["Charge"],
|
||||
|
@ -2229,7 +2227,7 @@ const atrees = {
|
|||
"bonuses": [
|
||||
{
|
||||
"type": "stat",
|
||||
"name": "baseResist",
|
||||
"name": "defMult.Base",
|
||||
"value": 5
|
||||
}
|
||||
]
|
||||
|
@ -2777,7 +2775,7 @@ const atrees = {
|
|||
{
|
||||
"type": "raw_stat",
|
||||
"toggle": true,
|
||||
"bonuses": [{ "type": "stat", "name": "defPct", "value": 70}]
|
||||
"bonuses": [{ "type": "stat", "name": "defMult.Mantle", "value": 70}]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2804,13 +2802,13 @@ const atrees = {
|
|||
"type": "stat_scaling",
|
||||
"slider": true,
|
||||
"slider_name": "Corrupted",
|
||||
"slider_max": 100,
|
||||
"slider_step": 1,
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damRaw"
|
||||
},
|
||||
"scaling": [4],
|
||||
"slider_step": 1,
|
||||
"max": 120
|
||||
"scaling": [2]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2985,19 +2983,12 @@ const atrees = {
|
|||
"effects": [
|
||||
{
|
||||
"type": "stat_scaling",
|
||||
"slider": false,
|
||||
"inputs": [
|
||||
{
|
||||
"type": "stat",
|
||||
"name": "hpBonus"
|
||||
}
|
||||
],
|
||||
"slider_name": "Corrupted",
|
||||
"output": {
|
||||
"type": "stat",
|
||||
"name": "damMult"
|
||||
"name": "damMult.Enraged"
|
||||
},
|
||||
"scaling": [3],
|
||||
"max": 300
|
||||
"scaling": [3]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3139,7 +3130,7 @@ const atrees = {
|
|||
{
|
||||
"type": "raw_stat",
|
||||
"toggle": true,
|
||||
"bonuses": [ {"type": "stat", "name": "damMult", "value": 30} ]
|
||||
"bonuses": [ {"type": "stat", "name": "damMult.Ragnarokkr", "value": 30} ]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3195,7 +3186,7 @@ const atrees = {
|
|||
"type": "stat",
|
||||
"name": "fDamPct"
|
||||
},
|
||||
"scaling": [2],
|
||||
"scaling": [0.02],
|
||||
"max": 100
|
||||
}
|
||||
]
|
||||
|
@ -3355,8 +3346,7 @@ const atrees = {
|
|||
"type": "stat",
|
||||
"name": "damRaw"
|
||||
},
|
||||
"scaling": [0.5],
|
||||
"max": 50
|
||||
"scaling": [0.5]
|
||||
},
|
||||
{
|
||||
"type": "raw_stat",
|
||||
|
@ -3448,7 +3438,7 @@ const atrees = {
|
|||
"bonuses": [
|
||||
{
|
||||
"type": "stat",
|
||||
"name": "baseResist",
|
||||
"name": "defMult.Base",
|
||||
"value": 5
|
||||
}
|
||||
]
|
||||
|
@ -3478,7 +3468,7 @@ const atrees = {
|
|||
{
|
||||
"type": "raw_stat",
|
||||
"toggle": true,
|
||||
"bonuses": [ {"type": "stat", "name": "damMult", "value": 30} ]
|
||||
"bonuses": [ {"type": "stat", "name": "damMult.ArmorBreaker", "value": 30} ]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3754,7 +3744,7 @@ const atrees = {
|
|||
|
||||
{
|
||||
"display_name": "Discombobulate",
|
||||
"desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",
|
||||
"desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +3 (Additive, Max +80). This bonus decays -5 every second",
|
||||
"archetype": "Battle Monk",
|
||||
"archetype_req": 11,
|
||||
"parents": ["Cyclone"],
|
||||
|
@ -3773,6 +3763,7 @@ const atrees = {
|
|||
"type": "stat_scaling",
|
||||
"slider": true,
|
||||
"slider_name": "Hits dealt",
|
||||
"slider_max": 27,
|
||||
"output": [
|
||||
{ "type": "stat", "name": "eDamAddMin" }, { "type": "stat", "name": "eDamAddMax" },
|
||||
{ "type": "stat", "name": "tDamAddMin" }, { "type": "stat", "name": "tDamAddMax" },
|
||||
|
@ -3929,7 +3920,13 @@ const atrees = {
|
|||
"icon": "node_2"
|
||||
},
|
||||
"properties": {},
|
||||
"effects": []
|
||||
"effects": [
|
||||
{
|
||||
"type": "raw_stat",
|
||||
"toggle": true,
|
||||
"bonuses": [{ "type": "stat", "name": "defMult.Brink", "value": 40}]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
|
|
96
js/build.js
|
@ -2,94 +2,9 @@
|
|||
|
||||
const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.20], ["sword", 1.10]]);
|
||||
|
||||
/**
|
||||
* @description Error to catch items that don't exist.
|
||||
* @module ItemNotFound
|
||||
/*
|
||||
* Class that represents a wynn player's build.
|
||||
*/
|
||||
class ItemNotFound {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} item the item name entered
|
||||
* @param {String} type the type of item
|
||||
* @param {Boolean} genElement whether to generate an element from inputs
|
||||
* @param {String} override override for item type
|
||||
*/
|
||||
constructor(item, type, genElement, override) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `Cannot find ${override||type} named ${item}`;
|
||||
if (genElement)
|
||||
/**
|
||||
* @public
|
||||
* @type {Element}
|
||||
*/
|
||||
this.element = document.getElementById(`${type}-choice`).parentElement.querySelectorAll("p.error")[0];
|
||||
else
|
||||
this.element = document.createElement("div");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error to catch incorrect input.
|
||||
* @module IncorrectInput
|
||||
*/
|
||||
class IncorrectInput {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} input the inputted text
|
||||
* @param {String} format the correct format
|
||||
* @param {String} sibling the id of the error node's sibling
|
||||
*/
|
||||
constructor(input, format, sibling) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `${input} is incorrect. Example: ${format}`;
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.id = sibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error that inputs an array of items to generate errors of.
|
||||
* @module ListError
|
||||
* @extends Error
|
||||
*/
|
||||
class ListError extends Error {
|
||||
/**
|
||||
* @class
|
||||
* @param {Array} errors array of errors
|
||||
*/
|
||||
constructor(errors) {
|
||||
let ret = [];
|
||||
if (typeof errors[0] == "string") {
|
||||
super(errors[0]);
|
||||
} else {
|
||||
super(errors[0].message);
|
||||
}
|
||||
for (let i of errors) {
|
||||
if (typeof i == "string") {
|
||||
ret.push(new Error(i));
|
||||
} else {
|
||||
ret.push(i);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
* @type {Object[]}
|
||||
*/
|
||||
this.errors = ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*Class that represents a wynn player's build.
|
||||
*/
|
||||
class Build{
|
||||
|
||||
/**
|
||||
|
@ -168,7 +83,6 @@ class Build{
|
|||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
statMap.set("defMultiplier", 1);
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
|
@ -198,8 +112,10 @@ class Build{
|
|||
}
|
||||
}
|
||||
}
|
||||
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
|
||||
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
|
||||
statMap.set('damMult', new Map());
|
||||
statMap.set('defMult', new Map());
|
||||
statMap.get('damMult').set('tome', statMap.get('damMobs'))
|
||||
statMap.get('defMult').set('tome', statMap.get('defMobs'))
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets.get(setName).bonuses[count-1];
|
||||
|
|
|
@ -16,8 +16,8 @@ function skillPointsToPercentage(skp){
|
|||
}
|
||||
|
||||
// WYNN2: Skillpoint max scaling. Intel is cost reduction
|
||||
const skillpoint_final_mult = [1, 1, 0.5, 0.867, 0.951];
|
||||
// intel damage and water%
|
||||
const skillpoint_final_mult = [1, 1, 0.5/skillPointsToPercentage(150), 0.867, 0.951];
|
||||
// intel water%
|
||||
const skillpoint_damage_mult = [1, 1, 1, 0.867, 0.951];
|
||||
|
||||
/*Turns the input amount of levels into skillpoints available.
|
||||
|
@ -74,17 +74,17 @@ let skpReqs = skp_order.map(x => x + "Req");
|
|||
let item_fields = [ "name", "displayName", "lore", "color", "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", "expd", "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", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
|
||||
|
||||
// wynn2 damages.
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct,"*/"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct,"*/"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct,"*/"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct,"*/"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
"critDamPct"
|
||||
];
|
||||
// Extra fake IDs (reserved for use in spell damage calculation) : damageMultiplier, defMultiplier, poisonPct, activeMajorIDs
|
||||
// Extra fake IDs (reserved for use in spell damage calculation) : damMult, defMult, poisonPct, activeMajorIDs
|
||||
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ]
|
||||
|
||||
//File reading for ID translations for JSON purposes
|
||||
|
@ -169,11 +169,11 @@ let rolledIDs = [
|
|||
"gXp",
|
||||
"gSpd",
|
||||
// wynn2 damages.
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct,"*/"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct,"*/"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct,"*/"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct,"*/"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
|
@ -300,3 +300,28 @@ function idRound(id){
|
|||
return rounded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stupid stupid multiplicative stats
|
||||
*/
|
||||
function merge_stat(stats, name, value) {
|
||||
const start = name.slice(0, 7);
|
||||
if (start === 'damMult' || start === 'defMult') {
|
||||
if (!stats.has(start)) {
|
||||
stats.set(start, new Map());
|
||||
}
|
||||
const map = stats.get(start);
|
||||
if (value instanceof Map) {
|
||||
for (const [k, v] of value.entries()) {
|
||||
merge_stat(map, k, v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
merge_stat(map, name.slice(8), value);
|
||||
return;
|
||||
}
|
||||
if (stats.has(name)) {
|
||||
stats.set(name, stats.get(name) + value);
|
||||
}
|
||||
else { stats.set(name, value); }
|
||||
}
|
||||
|
|
|
@ -394,6 +394,18 @@ function init() {
|
|||
for (const eq of equipment_keys) {
|
||||
document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip'));
|
||||
}
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
const powder_special = powderSpecialStats[i];
|
||||
const elem_name = damageClasses[i+1]; // skip neutral
|
||||
const elem_char = skp_elements[i]; // TODO: merge?
|
||||
const skp_name = skp_order[i]; // TODO: merge?
|
||||
const boost_parent = document.getElementById(skp_name+'-boost');
|
||||
const slider_id = skp_name+'_boost_armor';
|
||||
const label_name = "% " + elem_name + " Dmg Boost";
|
||||
const slider_container = gen_slider_labeled({label_name: label_name, max: powder_special.cap, id: slider_id, color: elem_colors[i]});
|
||||
boost_parent.appendChild(slider_container);
|
||||
document.getElementById(slider_id).addEventListener("change", (_) => armor_powder_node.mark_dirty().update() );
|
||||
}
|
||||
|
||||
// Masonry setup
|
||||
let masonry = Macy({
|
||||
|
|
|
@ -11,29 +11,7 @@ let armor_powder_node = new (class extends ComputeNode {
|
|||
}
|
||||
return statMap;
|
||||
}
|
||||
})().update();
|
||||
|
||||
/* Updates PASSIVE powder special boosts (armors)
|
||||
*/
|
||||
function update_armor_powder_specials(elem_id) {
|
||||
//we only update the powder special + external stats if the player has a build
|
||||
let wynn_elem = elem_id.split("_")[0]; //str, dex, int, def, agi
|
||||
|
||||
//update the label associated w/ the slider
|
||||
let elem = document.getElementById(elem_id);
|
||||
let label = document.getElementById(elem_id + "_label");
|
||||
let value = elem.value;
|
||||
|
||||
label.textContent = label.textContent.split(":")[0] + ": " + value
|
||||
|
||||
//update the slider's graphics
|
||||
let bg_color = elem_colors[skp_order.indexOf(wynn_elem)];
|
||||
let pct = Math.round(100 * value / powderSpecialStats[skp_order.indexOf(wynn_elem)].cap);
|
||||
elem.style.background = `linear-gradient(to right, ${bg_color}, ${bg_color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
|
||||
|
||||
armor_powder_node.mark_dirty().update();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
let boosts_node = new (class extends ComputeNode {
|
||||
constructor() { super('builder-boost-input'); }
|
||||
|
@ -50,8 +28,8 @@ let boosts_node = new (class extends ComputeNode {
|
|||
}
|
||||
}
|
||||
let res = new Map();
|
||||
res.set('damageMultiplier', 1+damage_boost);
|
||||
res.set('defMultiplier', 1-def_boost);
|
||||
res.set('damMult.Potion', 100*damage_boost);
|
||||
res.set('defMult.Potion', 100*def_boost);
|
||||
return res;
|
||||
}
|
||||
})().update();
|
||||
|
@ -511,7 +489,10 @@ function getDefenseStats(stats) {
|
|||
defenseStats.push(totalHp);
|
||||
//EHP
|
||||
let ehp = [totalHp, totalHp];
|
||||
let defMult = (2 - stats.get("classDef")) * stats.get("defMultiplier");
|
||||
let defMult = (2 - stats.get("classDef"));
|
||||
for (const [k, v] of stats.get("defMult").entries()) {
|
||||
defMult *= (1 - v/100);
|
||||
}
|
||||
// newehp = oldehp / [0.1 * A(x) + (1 - A(x)) * (1 - D(x))]
|
||||
ehp[0] = ehp[0] / (0.1*agi_pct + (1-agi_pct) * (1-def_pct));
|
||||
ehp[0] /= defMult;
|
||||
|
@ -558,7 +539,6 @@ class SpellDamageCalcNode extends ComputeNode {
|
|||
const spell = spell_info[0];
|
||||
const spell_parts = spell_info[1];
|
||||
const stats = input_map.get('stats');
|
||||
const damage_mult = stats.get('damageMultiplier');
|
||||
const skillpoints = [
|
||||
stats.get('str'),
|
||||
stats.get('dex'),
|
||||
|
@ -695,7 +675,7 @@ function getMeleeStats(stats, weapon) {
|
|||
}
|
||||
|
||||
if (weapon_stats.get("type") === "relik") {
|
||||
stats.set('damageMultiplier', 0.99); // CURSE YOU WYNNCRAFT
|
||||
merge_stat(stats, 'damMult.ShamanMelee', 0.99); // CURSE YOU WYNNCRAFT
|
||||
//One day we will create WynnWynn and no longer have shaman 99% melee injustice.
|
||||
//In all seriousness 99% is because wynn uses 0.33 to estimate dividing the damage by 3 to split damage between 3 beams.
|
||||
}
|
||||
|
@ -863,18 +843,7 @@ class AggregateStatsNode extends ComputeNode {
|
|||
const output_stats = new Map();
|
||||
for (const [k, v] of input_map.entries()) {
|
||||
for (const [k2, v2] of v.entries()) {
|
||||
if (output_stats.has(k2)) {
|
||||
// TODO: ugly AF
|
||||
if (k2 === 'damageMultiplier' || k2 === 'defMultiplier') {
|
||||
output_stats.set(k2, v2 * output_stats.get(k2));
|
||||
}
|
||||
else {
|
||||
output_stats.set(k2, v2 + output_stats.get(k2));
|
||||
}
|
||||
}
|
||||
else {
|
||||
output_stats.set(k2, v2);
|
||||
}
|
||||
merge_stat(output_stats, k2, v2);
|
||||
}
|
||||
}
|
||||
return output_stats;
|
||||
|
@ -1106,6 +1075,7 @@ function builder_graph_init() {
|
|||
atree_merge.link_to(build_node, 'build');
|
||||
atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node)
|
||||
.link_to(atree_collect_spells, 'spells');
|
||||
atree_stats.link_to(build_node, 'build');
|
||||
stat_agg_node.link_to(atree_stats, 'atree-stats');
|
||||
|
||||
build_encode_node.link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
|
||||
|
@ -1116,6 +1086,7 @@ function builder_graph_init() {
|
|||
for (const input_node of item_nodes.concat(powder_nodes)) {
|
||||
input_node.update();
|
||||
}
|
||||
armor_powder_node.update();
|
||||
level_input.update();
|
||||
|
||||
// kinda janky, manually set atree and update. Some wasted compute here
|
||||
|
|
|
@ -26,7 +26,7 @@ function get_base_dps(item) {
|
|||
}
|
||||
|
||||
|
||||
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
|
||||
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false, part=undefined) {
|
||||
// TODO: Roll all the loops together maybe
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
|
@ -154,7 +154,14 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
let total_dam_norm = [0, 0];
|
||||
let total_dam_crit = [0, 0];
|
||||
let damages_results = [];
|
||||
const damage_mult = stats.get("damageMultiplier");
|
||||
const mult_map = stats.get("damMult");
|
||||
console.log(mult_map);
|
||||
let damage_mult = 1;
|
||||
for (const [k, v] of mult_map.entries()) {
|
||||
damage_mult *= (1 + v/100);
|
||||
}
|
||||
console.log(damage_mult);
|
||||
|
||||
|
||||
for (const damage of damages) {
|
||||
const res = [
|
||||
|
|
109
js/utils.js
|
@ -753,3 +753,112 @@ function deepcopy(obj) {
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function gen_slider_labeled({label_name, label_classlist = [], min = 0, max = 100, step = 1, default_val = min, id = undefined, color = "#FFFFFF", classlist = []}) {
|
||||
let slider_container = document.createElement("div");
|
||||
slider_container.classList.add("row");
|
||||
|
||||
let buf_col = document.createElement("div");
|
||||
buf_col.classList.add("col", "mx-1");
|
||||
|
||||
let label = document.createElement("div");
|
||||
label.classList.add("col");
|
||||
label.classList.add(...label_classlist);
|
||||
label.textContent = label_name + ": " + default_val;
|
||||
|
||||
let slider = gen_slider(min, max, step, default_val, id, color, classlist, label);
|
||||
|
||||
//we set IDs here because the slider's id is potentially only meaningful after gen_slider() is called
|
||||
label.id = slider.id + "-label";
|
||||
slider_container.id = slider.id + "-container";
|
||||
|
||||
buf_col.append(slider, label);
|
||||
slider_container.appendChild(buf_col);
|
||||
|
||||
return slider_container;
|
||||
}
|
||||
|
||||
/** Creates a slider input (input type = range) given styling parameters
|
||||
*
|
||||
* @param {Number | String} min - The minimum value for the slider. defaults to 0
|
||||
* @param {Number | String} max - The maximum value for the slider. defaults to 100
|
||||
* @param {Number | String} step - The granularity between possible values. defaults to 1
|
||||
* @param {Number | String} default_val - The default value to set the slider to.
|
||||
* @param {String} id - The element ID to use for the slider. defaults to the current date time
|
||||
* @param {String} color - The hex color to use for the slider. Needs the # character.
|
||||
* @param {Array<String>} classlist - A list of classes to add to the slider.
|
||||
* @returns
|
||||
*/
|
||||
function gen_slider(min = 0, max = 100, step = 1, default_val = min, id = undefined, color = "#FFFFFF", classlist = [], label = undefined) {
|
||||
//simple attribute vals
|
||||
let slider = document.createElement("input");
|
||||
slider.type = "range";
|
||||
slider.min = min;
|
||||
slider.max = max;
|
||||
slider.step = step;
|
||||
slider.value = default_val;
|
||||
slider.autocomplete = "off";
|
||||
if (id) {
|
||||
if (document.getElementById(id)) {
|
||||
throw new Error("ID " + id + " already exists within the DOM.")
|
||||
} else {
|
||||
slider.id = id;
|
||||
}
|
||||
} else {
|
||||
slider.id = new Date().toLocaleTimeString();
|
||||
}
|
||||
slider.color = color;
|
||||
slider.classList.add(...classlist); //special spread operator -
|
||||
//necessary for display purposes
|
||||
slider.style.webkitAppearance = "none";
|
||||
slider.style.borderRadius = "30px";
|
||||
slider.style.height = "0.5rem";
|
||||
slider.classList.add("px-0", "slider");
|
||||
|
||||
//set up recoloring
|
||||
slider.addEventListener("change", function(e) {
|
||||
recolor_slider(slider, label);
|
||||
});
|
||||
//do recoloring for the default val
|
||||
let pct = Math.round(100 * (parseInt(slider.value) - parseInt(slider.min)) / (parseInt(slider.max) - parseInt(slider.min)));
|
||||
slider.style.background = `rgba(0, 0, 0, 0) linear-gradient(to right, ${color}, ${color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
|
||||
|
||||
//return slider
|
||||
return slider;
|
||||
}
|
||||
|
||||
/** Recolors a slider. If the corresponding label exists, also update that.
|
||||
*
|
||||
* @param {slider} slider - the slider element
|
||||
* @param {label} label - the label element
|
||||
*/
|
||||
function recolor_slider(slider, label) {
|
||||
let color = slider.color;
|
||||
let pct = Math.round(100 * (parseInt(slider.value) - parseInt(slider.min)) / (parseInt(slider.max) - parseInt(slider.min)));
|
||||
slider.style.background = `rgba(0, 0, 0, 0) linear-gradient(to right, ${color}, ${color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
|
||||
|
||||
if (label) {
|
||||
//convention is that the number goes at the end... I parse by separating it at ':'
|
||||
label.textContent = label.textContent.split(":")[0] + ": " + slider.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for making an element in html.
|
||||
*
|
||||
* @param {String} type : type of element
|
||||
* @param {List[String]} classlist : css classes for element
|
||||
* @param {Map[String, String]} args : Properties for the element
|
||||
*/
|
||||
function make_elem(type, classlist = [], args = {}) {
|
||||
const ret_elem = document.createElement(type);
|
||||
ret_elem.classList.add(...classlist);
|
||||
for (const i in args) {
|
||||
ret_elem[i] = args[i];
|
||||
}
|
||||
return ret_elem;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
@ -45,13 +45,14 @@ def translate_id(id_data, atree_data):
|
|||
for _input in effect["inputs"]:
|
||||
if "abil" in _input and _input["abil"] in id_data[_class]:
|
||||
_input["abil"] = id_data[_class][_input["abil"]]
|
||||
if isinstance(effect["output"], list):
|
||||
for output in effect["output"]:
|
||||
if "abil" in output and output["abil"] in id_data[_class]:
|
||||
output["abil"] = id_data[_class][output["abil"]]
|
||||
else:
|
||||
if "abil" in effect["output"] and effect["output"]["abil"] in id_data[_class]:
|
||||
effect["output"]["abil"] = id_data[_class][effect["output"]["abil"]]
|
||||
if "output" in effect:
|
||||
if isinstance(effect["output"], list):
|
||||
for output in effect["output"]:
|
||||
if "abil" in output and output["abil"] in id_data[_class]:
|
||||
output["abil"] = id_data[_class][output["abil"]]
|
||||
else:
|
||||
if "abil" in effect["output"] and effect["output"]["abil"] in id_data[_class]:
|
||||
effect["output"]["abil"] = id_data[_class][effect["output"]["abil"]]
|
||||
|
||||
abilDict = {}
|
||||
with open("atree_constants.js") as f:
|
||||
|
|