Merge pull request #87 from hppeng-wynn/atree_active_box

Atree active box
This commit is contained in:
hppeng-wynn 2022-07-08 17:49:54 -07:00 committed by GitHub
commit 3af9f868c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 459 additions and 328 deletions

View file

@ -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>

View file

@ -8,7 +8,7 @@ The game, of course
Additional Contributors, in no particular order:
- Kiocifer (Icons!)
- IncinerateMe (helping transition to 1.20.3 / CI helper)
- dog (wynn2 ability tree help)
- puppy (dog)
- SockMower (ability tree encode/decode optimization)
- ITechnically (coding emotional support / misc)
- touhoku (best IM)

View file

@ -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;
}
}

View file

@ -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);
@ -378,6 +386,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');
@ -410,6 +424,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;
@ -430,9 +445,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');
@ -543,12 +560,84 @@ const atree_collect_spells = new (class extends ComputeNode {
}
})().link_to(atree_merge, 'atree-merged');
/**
* 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()) {
@ -557,7 +646,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...
@ -565,8 +692,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;
@ -583,10 +709,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');
/**
@ -667,42 +795,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();
@ -791,7 +898,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';
@ -799,7 +906,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];
@ -814,39 +921,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`
@ -878,7 +998,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;

View file

@ -1409,7 +1409,7 @@ const atrees = {
"type": "raw_stat",
"bonuses": [{
"type": "stat",
"name": "mdCritPct",
"name": "critDamPct",
"value": 30
}]
}]
@ -1692,10 +1692,10 @@ const atrees = {
"slider_name": "Focus",
"output": {
"type": "stat",
"name": "damMult"
"name": "damMult.Focus"
},
"scaling": [40],
"max": 3
"slider_max": 3
}]
},
{
@ -1703,6 +1703,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": [],
@ -1717,12 +1718,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]
}]
},
{
@ -1730,6 +1731,7 @@ const atrees = {
"desc": "Add +2 max Focus",
"archetype": "Sharpshooter",
"archetype_req": 0,
"base_abil": "Focus",
"parents": ["Crepuscular Ray", "Snow Storm"],
"dependencies": ["Focus"],
"blockers": [],
@ -1744,12 +1746,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]
}]
},
{
@ -1802,13 +1804,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]
}]
},
{
@ -1832,12 +1834,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",
@ -1882,6 +1879,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"],
@ -1970,12 +1968,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
}]
}
],
@ -2213,7 +2211,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"],
@ -2233,7 +2231,7 @@ const atrees = {
"bonuses": [
{
"type": "stat",
"name": "baseResist",
"name": "defMult.Base",
"value": 5
}
]
@ -2781,7 +2779,7 @@ const atrees = {
{
"type": "raw_stat",
"toggle": true,
"bonuses": [{ "type": "stat", "name": "defPct", "value": 70}]
"bonuses": [{ "type": "stat", "name": "defMult.Mantle", "value": 70}]
}
]
},
@ -2808,13 +2806,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]
}
]
},
@ -2989,19 +2987,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]
}
]
},
@ -3143,7 +3134,7 @@ const atrees = {
{
"type": "raw_stat",
"toggle": true,
"bonuses": [ {"type": "stat", "name": "damMult", "value": 30} ]
"bonuses": [ {"type": "stat", "name": "damMult.Ragnarokkr", "value": 30} ]
}
]
},
@ -3199,7 +3190,7 @@ const atrees = {
"type": "stat",
"name": "fDamPct"
},
"scaling": [2],
"scaling": [0.02],
"max": 100
}
]
@ -3359,8 +3350,7 @@ const atrees = {
"type": "stat",
"name": "damRaw"
},
"scaling": [0.5],
"max": 50
"scaling": [0.5]
},
{
"type": "raw_stat",
@ -3452,7 +3442,7 @@ const atrees = {
"bonuses": [
{
"type": "stat",
"name": "baseResist",
"name": "defMult.Base",
"value": 5
}
]
@ -3482,7 +3472,7 @@ const atrees = {
{
"type": "raw_stat",
"toggle": true,
"bonuses": [ {"type": "stat", "name": "damMult", "value": 30} ]
"bonuses": [ {"type": "stat", "name": "damMult.ArmorBreaker", "value": 30} ]
}
]
},
@ -3758,7 +3748,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"],
@ -3777,6 +3767,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" },
@ -3934,7 +3925,13 @@ const atrees = {
"icon": "node_2"
},
"properties": {},
"effects": []
"effects": [
{
"type": "raw_stat",
"toggle": true,
"bonuses": [{ "type": "stat", "name": "defMult.Brink", "value": 40}]
}
]
},
{

File diff suppressed because one or more lines are too long

View file

@ -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];

View file

@ -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); }
}

View file

@ -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({

View file

@ -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

View file

@ -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 = [

View file

@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -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: