Merge pull request #62 from hppeng-wynn/atree_encode

Atree encode
This commit is contained in:
hppeng-wynn 2022-06-30 20:12:32 -07:00 committed by GitHub
commit ad0d665306
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 791 additions and 317 deletions

View file

@ -429,7 +429,7 @@
</div> </div>
<div class = "col-3 py-2"> <div class = "col-3 py-2">
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')"> <button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')">
Show Ability Tree Show Tree
</button> </button>
</div> </div>
<div class = "col-3 py-2"> <div class = "col-3 py-2">
@ -618,10 +618,14 @@
</div> </div>
<div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;"> <div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;">
<div class="row row-cols-1 row-cols-xl-2"> <div class="row row-cols-1 row-cols-xl-2">
<div class="col border border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 50vh; overflow-y: auto;"> <div class="col border border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 90vh; overflow-y: auto;">
</div> </div>
<div class="col mx-auto" style="height: 50vh; overflow-y: auto;" id="atree-active"> <div class="col mx-auto" style="height: 90vh; overflow-y: auto;" id="atree-rhs">
<div class="col mx-auto" style="height: 2em; overflow-y: auto;" id="atree-header">
</div>
<div class="col mx-auto" style="overflow-y: auto;" id="atree-active">
</div>
</div> </div>
</div> </div>
</div> </div>
@ -1332,7 +1336,7 @@
<div class="col-12 dark-5 scaled-font"> <div class="col-12 dark-5 scaled-font">
<footer class="text-center"> <footer class="text-center">
<div id="header2"> <div id="header2">
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p> <p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p> <p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
</div> </div>
<div id="credits"> <div id="credits">

View file

@ -282,7 +282,7 @@
<div class="col dark-5 scaled-font"> <div class="col dark-5 scaled-font">
<footer class="text-center"> <footer class="text-center">
<div id="header2"> <div id="header2">
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p> <p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p> <p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
</div> </div>
<div id="credits"> <div id="credits">

20
credits.txt Normal file
View file

@ -0,0 +1,20 @@
Theme, formatting, and overall inspiration: Wynndata (Dukio)
- https://wynndata.tk
The game, of course
- wynncraft.com
Additional Contributors, in no particular order:
- Kiocifer (Icons!)
- IncinerateMe (helping transition to 1.20.3 / CI helper)
- puppy (wynn2 ability tree help)
- SockMower (ability tree encode/decode optimization)
- ITechnically (coding emotional support / misc)
- touhoku (best IM)
- HeyZeer0 (huge help in getting our damage formulas right)
- Lennon (Skill point formula reversing)
- Phanta (WynnAtlas custom expression parser / item search)
- nbcss (Crafted Item mechanics reverse engineering)
- dr_carlos (Hiding UI elements properly, fade animations, proper error handling)
- Atlas Inc discord (feedback, ideas, damage calc, etc)

View file

@ -1,5 +1,3 @@
let abil_points_current;
/** /**
ATreeNode spec: ATreeNode spec:
@ -49,14 +47,16 @@ add_spell_prop: {
} }
convert_spell_conv: { convert_spell_conv: {
"type": "convert_spell_conv", type: "convert_spell_conv"
"base_spell": int base_spell: int // spell identifier
"target_part": "all" | str, target_part: "all" | str // Part of the spell to modify. Can be not present/empty for ex. cost modifier.
"conversion": element_str // "all" means modify all parts.
conversion: element_str
} }
raw_stat: { raw_stat: {
"type": "raw_stat", type: "raw_stat"
"bonuses": list[stat_bonus] toggle: Optional[bool] // default: false
bonuses: List[stat_bonus]
} }
stat_bonus: { stat_bonus: {
"type": "stat" | "prop", "type": "stat" | "prop",
@ -70,7 +70,7 @@ stat_scaling: {
"slider_name": Optional[str], "slider_name": Optional[str],
"slider_step": Optional[float], "slider_step": Optional[float],
"inputs": Optional[list[scaling_target]], "inputs": Optional[list[scaling_target]],
"output": scaling_target, "output": scaling_target | List[scaling_target],
"scaling": list[float], "scaling": list[float],
"max": float "max": float
} }
@ -169,15 +169,23 @@ const atree_node = new (class extends ComputeNode {
* Signature: AbilityTreeRenderNode(atree: ATree) => RenderedATree ( Map[id, RenderedATNode] ) * Signature: AbilityTreeRenderNode(atree: ATree) => RenderedATree ( Map[id, RenderedATNode] )
*/ */
const atree_render = new (class extends ComputeNode { const atree_render = new (class extends ComputeNode {
constructor() { super('builder-atree-render'); this.fail_cb = true; } constructor() {
super('builder-atree-render');
this.fail_cb = true;
this.UI_elem = document.getElementById("atree-ui");
this.list_elem = document.getElementById("atree-header");
}
compute_func(input_map) { compute_func(input_map) {
if (input_map.size !== 1) { throw "AbilityTreeRenderNode accepts exactly one input (atree)"; } if (input_map.size !== 1) { throw "AbilityTreeRenderNode accepts exactly one input (atree)"; }
const [atree] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element const [atree] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
//for some reason we have to cast to string //for some reason we have to cast to string
this.list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
this.UI_elem.innerHTML = ""; //reset the atree in the DOM
let ret = null; let ret = null;
if (atree) { ret = render_AT(document.getElementById("atree-ui"), document.getElementById("atree-active"), atree); } if (atree) { ret = render_AT(this.UI_elem, this.list_elem, atree); }
//Toggle on, previously was toggled off //Toggle on, previously was toggled off
toggle_tab('atree-dropdown'); toggleButton('toggle-atree'); toggle_tab('atree-dropdown'); toggleButton('toggle-atree');
@ -186,6 +194,17 @@ const atree_render = new (class extends ComputeNode {
} }
})().link_to(atree_node); })().link_to(atree_node);
// This exists so i don't have to re-render the UI to push atree updates.
const atree_state_node = new (class extends ComputeNode {
constructor() { super('builder-atree-state'); }
compute_func(input_map) {
if (input_map.size !== 1) { throw "AbilityTreeStateNode accepts exactly one input (atree-rendered)"; }
const [rendered_atree] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
return rendered_atree;
}
})().link_to(atree_render, 'atree-render');
/** /**
* Create a reverse topological sort of the tree in the result list. * Create a reverse topological sort of the tree in the result list.
* *
@ -216,7 +235,7 @@ function topological_sort_tree(tree, res, mark_state) {
* I stg if wynn makes abils that modify multiple spells * I stg if wynn makes abils that modify multiple spells
* ... well we can extend this by making `base_abil` a list instead but annoy * ... well we can extend this by making `base_abil` a list instead but annoy
* *
* Signature: AbilityTreeMergeNode(atree: ATree, atree-state: RenderedATree) => Map[id, Ability] * Signature: AbilityTreeMergeNode(build: Build, atree: ATree, atree-state: RenderedATree) => Map[id, Ability]
*/ */
const atree_merge = new (class extends ComputeNode { const atree_merge = new (class extends ComputeNode {
constructor() { super('builder-atree-merge'); } constructor() { super('builder-atree-merge'); }
@ -267,7 +286,146 @@ const atree_merge = new (class extends ComputeNode {
} }
return abils_merged; return abils_merged;
} }
})().link_to(atree_node, 'atree').link_to(atree_render, 'atree-state'); // TODO: THIS IS WRONG!!!!! Need one "collect" node... })().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state'); // TODO: THIS IS WRONG!!!!! Need one "collect" node...
/**
* Validate ability tree.
* Return list of errors for rendering.
*
* Signature: AbilityTreeMergeNode(atree: ATree, atree-state: RenderedATree) => List[str]
*/
const atree_validate = new (class extends ComputeNode {
constructor() { super('atree-validator'); }
compute_func(input_map) {
const atree_state = input_map.get('atree-state');
const atree_order = input_map.get('atree');
let errors = [];
let reachable = new Map();
atree_dfs_mark(atree_order[0], atree_state, reachable);
let abil_points_total = 0;
let archetype_count = new Map();
for (const node of atree_order) {
const abil = node.ability;
if (!atree_state.get(abil.id).active) { continue; }
abil_points_total += abil.cost;
if (!reachable.get(abil.id)) { errors.push(abil.display_name + ' is not reachable!'); }
let failed_deps = [];
for (const dep_id of abil.dependencies) {
if (!atree_state.get(dep_id).active) { failed_deps.push(dep_id) }
}
if (failed_deps.length > 0) {
const dep_string = failed_deps.map(i => '"' + atree_state.get(i).ability.display_name + '"');
errors.push(abil.display_name + ' dependencies not satisfied: ' + dep_string.join(", "));
}
let blocking_ids = [];
for (const blocker_id of abil.blockers) {
if (atree_state.get(blocker_id).active) { blocking_ids.push(blocker_id); }
}
if (blocking_ids.length > 0) {
const blockers_string = blocking_ids.map(i => '"' + atree_state.get(i).ability.display_name + '"');
errors.push(abil.display_name+' is blocked by: '+blockers_string.join(", "));
}
if ('archetype' in abil && abil.archetype !== "") {
let val = 1;
if (archetype_count.has(abil.archetype)) {
val = archetype_count.get(abil.archetype) + 1;
}
archetype_count.set(abil.archetype, val);
}
}
// TODO: FIX THIS! ARCHETYPE REQ IS A PAIN IN THE ASS
// it doesn't follow topological order and theres some cases where "equip order" matters.
for (const node of atree_order) {
const abil = node.ability;
if (!atree_state.get(abil.id).active) { continue; }
if ('archetype_req' in abil && abil.archetype_req !== 0) {
const others = archetype_count.get(abil.archetype) - 1;
if (others < abil.archetype_req) {
errors.push(abil.display_name+' fails archetype: '+abil.archetype+': '+others+' < '+abil.archetype_req)
}
}
}
if (abil_points_total > 45) {
errors.push('too many ability points assigned! ('+abil_points_total+' > 45)');
}
return [abil_points_total, errors];
}
})().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
function atree_dfs_mark(start, atree_state, mark) {
if (mark.get(start.ability.id)) { return; }
mark.set(start.ability.id, true);
for (const child of start.children) {
if (atree_state.get(child.ability.id).active) {
atree_dfs_mark(child, atree_state, mark);
}
}
}
const atree_render_active = new (class extends ComputeNode {
constructor() {
super('atree-render-active');
this.list_elem = document.getElementById("atree-active");
}
compute_func(input_map) {
const merged_abils = input_map.get('atree-merged');
const atree_order = input_map.get('atree-order');
const [abil_points_total, errors] = input_map.get('atree-errors');
this.list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
// TODO: move to display?
document.getElementById("active_AP_cost").textContent = abil_points_total;
if (errors.length > 0) {
let errorbox = document.createElement('div');
errorbox.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow");
this.list_elem.appendChild(errorbox);
let error_title = document.createElement('b');
error_title.classList.add("warning", "scaled-font");
error_title.innerHTML = "ATree Error!";
errorbox.appendChild(error_title);
for (const error of errors) {
let atree_warning = document.createElement("p");
atree_warning.classList.add("warning", "small-text");
atree_warning.textContent = error;
errorbox.appendChild(atree_warning);
}
}
for (const node of atree_order) {
if (!merged_abils.has(node.ability.id)) {
continue;
}
const abil = merged_abils.get(node.ability.id);
let active_tooltip = document.createElement('div');
active_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow");
let active_tooltip_title = document.createElement('b');
active_tooltip_title.classList.add("scaled-font");
active_tooltip_title.innerHTML = abil.display_name;
active_tooltip.appendChild(active_tooltip_title);
for (const desc of abil.desc) {
let active_tooltip_desc = document.createElement('p');
active_tooltip_desc.classList.add("scaled-font-sm", "my-0", "mx-1", "text-wrap");
active_tooltip_desc.textContent = desc;
active_tooltip.appendChild(active_tooltip_desc);
}
this.list_elem.appendChild(active_tooltip);
}
}
})().link_to(atree_node, 'atree-order').link_to(atree_merge, 'atree-merged').link_to(atree_validate, 'atree-errors');
/** /**
* Collect spells from abilities. * Collect spells from abilities.
@ -284,7 +442,15 @@ const atree_collect_spells = new (class extends ComputeNode {
let ret_spells = new Map(); let ret_spells = new Map();
for (const [abil_id, abil] of atree_merged.entries()) { for (const [abil_id, abil] of atree_merged.entries()) {
// TODO: Possibly, make a better way for detecting "spell abilities"? // TODO: Possibly, make a better way for detecting "spell abilities"?
if (abil.effects.length == 0 || abil.effects[0].type !== 'replace_spell') { continue; } if (abil.effects.length == 0) { continue; }
let has_spell_def = false;
for (const effect of abil.effects) {
if (effect.type === 'replace_spell') {
has_spell_def = true;
break;
}
}
if (!has_spell_def) { continue; }
let ret_spell = deepcopy(abil.effects[0]); // NOTE: do not mutate results of previous steps! let ret_spell = deepcopy(abil.effects[0]); // NOTE: do not mutate results of previous steps!
const base_spell_id = ret_spell.base_spell; const base_spell_id = ret_spell.base_spell;
@ -404,7 +570,7 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
const spell_map = input_map.get('spells'); // TODO: is this gonna need more? idk... const spell_map = input_map.get('spells'); // TODO: is this gonna need more? idk...
// TODO shortcut update path for sliders // TODO shortcut update path for sliders
for (const [spell_id, spell] of spell_map.entries()) { for (const [spell_id, spell] of new Map([...spell_map].sort((a, b) => a[0] - b[0])).entries()) {
let spell_node = new SpellSelectNode(spell); let spell_node = new SpellSelectNode(spell);
spell_node.link_to(build_node, 'build'); spell_node.link_to(build_node, 'build');
@ -443,13 +609,9 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
*/ */
function render_AT(UI_elem, list_elem, tree) { function render_AT(UI_elem, list_elem, tree) {
console.log("constructing ability tree UI"); console.log("constructing ability tree UI");
list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
UI_elem.innerHTML = ""; //reset the atree in the DOM
// add in the "Active" title to atree // add in the "Active" title to atree
let active_row = document.createElement("div"); let active_row = document.createElement("div");
active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center"); active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center");
abil_points_current = 0;
let active_word = document.createElement("div"); let active_word = document.createElement("div");
active_word.classList.add("col-auto"); active_word.classList.add("col-auto");
active_word.textContent = "Active Abilities:"; active_word.textContent = "Active Abilities:";
@ -597,55 +759,47 @@ function render_AT(UI_elem, list_elem, tree) {
node_elem.classList.add("fake-button"); node_elem.classList.add("fake-button");
let active_tooltip = document.createElement('div'); let node_tooltip = document.createElement('div');
active_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow"); node_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow");
active_tooltip.style.display = "none"; node_tooltip.style.display = "none";
// tooltip text formatting // tooltip text formatting
let active_tooltip_title = document.createElement('b'); let node_tooltip_title = document.createElement('b');
active_tooltip_title.classList.add("scaled-font"); node_tooltip_title.classList.add("scaled-font");
active_tooltip_title.innerHTML = ability.display_name; node_tooltip_title.innerHTML = ability.display_name;
node_tooltip.appendChild(node_tooltip_title);
let active_tooltip_desc = document.createElement('p'); if ('archetype' in ability && ability.archetype !== "") {
active_tooltip_desc.classList.add("scaled-font-sm", "my-0", "mx-1", "text-wrap"); let node_tooltip_archetype = document.createElement('p');
active_tooltip_desc.textContent = ability.desc; node_tooltip_archetype.classList.add("scaled-font");
node_tooltip_archetype.innerHTML = "(Archetype: " + ability.archetype+")";
node_tooltip.appendChild(node_tooltip_archetype);
}
let active_tooltip_cost = document.createElement('p'); let node_tooltip_desc = document.createElement('p');
active_tooltip_cost.classList.add("scaled-font-sm", "my-0", "mx-1", "text-start"); node_tooltip_desc.classList.add("scaled-font-sm", "my-0", "mx-1", "text-wrap");
active_tooltip_cost.textContent = "Cost: " + ability.cost + " AP"; node_tooltip_desc.textContent = ability.desc;
node_tooltip.appendChild(node_tooltip_desc);
active_tooltip.appendChild(active_tooltip_title); let node_tooltip_cost = document.createElement('p');
active_tooltip.appendChild(active_tooltip_desc); node_tooltip_cost.classList.add("scaled-font-sm", "my-0", "mx-1", "text-start");
active_tooltip.appendChild(active_tooltip_cost); node_tooltip_cost.textContent = "Cost: " + ability.cost + " AP";
node_tooltip.appendChild(node_tooltip_cost);
node_tooltip = active_tooltip.cloneNode(true);
active_tooltip.id = "atree-ab-" + ability.id;
node_tooltip.style.position = "absolute"; node_tooltip.style.position = "absolute";
node_tooltip.style.zIndex = "100"; node_tooltip.style.zIndex = "100";
node_elem.appendChild(node_tooltip); node_elem.appendChild(node_tooltip);
list_elem.appendChild(active_tooltip); //list_elem.appendChild(active_tooltip); NOTE: moved to `atree_render_active`
node_wrap.elem = node_elem;
node_wrap.all_connectors_ref = atree_connectors_map;
node_elem.addEventListener('click', function(e) { node_elem.addEventListener('click', function(e) {
if (e.target !== this && e.target!== this.children[0]) {return;} if (e.target !== this && e.target!== this.children[0]) {return;}
let tooltip = document.getElementById("atree-ab-" + ability.id); atree_set_state(node_wrap, !node_wrap.active);
if (tooltip.style.display === "block") { atree_state_node.mark_dirty().update();
tooltip.style.display = "none";
this.classList.remove("atree-selected");
abil_points_current -= ability.cost;
}
else {
tooltip.style.display = "block";
this.classList.add("atree-selected");
abil_points_current += ability.cost;
};
document.getElementById("active_AP_cost").textContent = abil_points_current;
atree_toggle_state(atree_connectors_map, node_wrap);
atree_merge.mark_dirty();
atree_merge.update();
}); });
// add tooltip // add tooltip
@ -735,9 +889,16 @@ function atree_render_connection(atree_connectors_map) {
}; };
// toggle the state of a node. // toggle the state of a node.
function atree_toggle_state(atree_connectors_map, node_wrapper) { function atree_set_state(node_wrapper, new_state) {
const new_state = !node_wrapper.active; if (new_state) {
node_wrapper.active = new_state node_wrapper.active = true;
node_wrapper.elem.classList.add("atree-selected");
}
else {
node_wrapper.active = false;
node_wrapper.elem.classList.remove("atree-selected");
}
let atree_connectors_map = node_wrapper.all_connectors_ref;
for (const parent of node_wrapper.parents) { for (const parent of node_wrapper.parents) {
if (parent.active) { if (parent.active) {
atree_set_edge(atree_connectors_map, parent, node_wrapper, new_state); // self->parent state only changes if parent is on atree_set_edge(atree_connectors_map, parent, node_wrapper, new_state); // self->parent state only changes if parent is on

View file

@ -45,8 +45,6 @@ const atrees = {
{ {
"display_name": "Escape", "display_name": "Escape",
"desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",
"archetype": "",
"archetype_req": 0,
"parents": ["Heart Shatter"], "parents": ["Heart Shatter"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -141,8 +139,6 @@ const atrees = {
{ {
"display_name": "Fire Creep", "display_name": "Fire Creep",
"desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.", "desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",
"archetype": "",
"archetype_req": 0,
"parents": ["Phantom Ray", "Fire Mastery", "Bryophyte Roots"], "parents": ["Phantom Ray", "Fire Mastery", "Bryophyte Roots"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2175,8 +2171,6 @@ const atrees = {
{ {
"display_name": "Heavy Impact", "display_name": "Heavy Impact",
"desc": "After using Charge, violently crash down into the ground and deal damage", "desc": "After using Charge, violently crash down into the ground and deal damage",
"archetype": "",
"archetype_req": 0,
"parents": ["Uppercut"], "parents": ["Uppercut"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2263,7 +2257,7 @@ const atrees = {
{ {
"type": "stat", "type": "stat",
"name": "baseResist", "name": "baseResist",
"value": "5" "value": 5
} }
] ]
}, },
@ -2415,16 +2409,9 @@ const atrees = {
{ {
"type": "raw_stat", "type": "raw_stat",
"bonuses": [ "bonuses": [
{ { "type": "stat", "name": "eDamPct", "value": 20 },
"type": "stat", { "type": "stat", "name": "eDamAddMin", "value": 2 },
"name": "eDamPct", { "type": "stat", "name": "eDamAddMax", "value": 4 }
"value": 20
},
{
"type": "stat",
"name": "eDam",
"value": [2, 4]
}
] ]
} }
] ]
@ -2450,16 +2437,9 @@ const atrees = {
{ {
"type": "raw_stat", "type": "raw_stat",
"bonuses": [ "bonuses": [
{ { "type": "stat", "name": "tDamPct", "value": 10 },
"type": "stat", { "type": "stat", "name": "tDamAddMin", "value": 1 },
"name": "tDamPct", { "type": "stat", "name": "tDamAddMax", "value": 8 }
"value": 10
},
{
"type": "stat",
"name": "tDam",
"value": [1, 8]
}
] ]
} }
] ]
@ -2485,16 +2465,9 @@ const atrees = {
{ {
"type": "raw_stat", "type": "raw_stat",
"bonuses": [ "bonuses": [
{ { "type": "stat", "name": "wDamPct", "value": 15 },
"type": "stat", { "type": "stat", "name": "wDamAddMin", "value": 2 },
"name": "wDamPct", { "type": "stat", "name": "wDamAddMax", "value": 4 }
"value": 15
},
{
"type": "stat",
"name": "wDam",
"value": [2, 4]
}
] ]
} }
] ]
@ -2520,16 +2493,9 @@ const atrees = {
{ {
"type": "raw_stat", "type": "raw_stat",
"bonuses": [ "bonuses": [
{ { "type": "stat", "name": "aDamPct", "value": 15 },
"type": "stat", { "type": "stat", "name": "aDamAddMin", "value": 3 },
"name": "aDamPct", { "type": "stat", "name": "aDamAddMax", "value": 4 }
"value": 15
},
{
"type": "stat",
"name": "aDam",
"value": [3, 4]
}
] ]
} }
] ]
@ -2555,16 +2521,9 @@ const atrees = {
{ {
"type": "raw_stat", "type": "raw_stat",
"bonuses": [ "bonuses": [
{ { "type": "stat", "name": "fDamPct", "value": 15 },
"type": "stat", { "type": "stat", "name": "fDamAddMin", "value": 3 },
"name": "fDamPct", { "type": "stat", "name": "fDamAddMax", "value": 5 }
"value": 15
},
{
"type": "stat",
"name": "fDam",
"value": [3, 5]
}
] ]
} }
] ]
@ -2575,6 +2534,7 @@ const atrees = {
"desc": "Bash will hit 4 times at an even larger range", "desc": "Bash will hit 4 times at an even larger range",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Bash",
"parents": ["Earth Mastery", "Fireworks"], "parents": ["Earth Mastery", "Fireworks"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2610,6 +2570,7 @@ const atrees = {
"desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage", "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Uppercut",
"parents": ["Thunder Mastery", "Quadruple Bash"], "parents": ["Thunder Mastery", "Quadruple Bash"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2644,6 +2605,7 @@ const atrees = {
"desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water", "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",
"archetype": "Battle Monk", "archetype": "Battle Monk",
"archetype_req": 1, "archetype_req": 1,
"base_abil": "Uppercut",
"parents": ["Water Mastery"], "parents": ["Water Mastery"],
"dependencies": ["Uppercut"], "dependencies": ["Uppercut"],
"blockers": [], "blockers": [],
@ -2675,8 +2637,7 @@ const atrees = {
{ {
"display_name": "Flyby Jab", "display_name": "Flyby Jab",
"desc": "Damage enemies in your way when using Charge", "desc": "Damage enemies in your way when using Charge",
"archetype": "", "base_abil": "Charge",
"archetype_req": 0,
"parents": ["Air Mastery", "Flaming Uppercut"], "parents": ["Air Mastery", "Flaming Uppercut"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2704,6 +2665,7 @@ const atrees = {
"desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds", "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds",
"archetype": "Paladin", "archetype": "Paladin",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Uppercut",
"parents": ["Fire Mastery", "Flyby Jab"], "parents": ["Fire Mastery", "Flyby Jab"],
"dependencies": ["Uppercut"], "dependencies": ["Uppercut"],
"blockers": [], "blockers": [],
@ -2746,8 +2708,7 @@ const atrees = {
{ {
"display_name": "Iron Lungs", "display_name": "Iron Lungs",
"desc": "War Scream deals more damage", "desc": "War Scream deals more damage",
"archetype": "", "base_abil": "War Scream",
"archetype_req": 0,
"parents": ["Flyby Jab", "Flaming Uppercut"], "parents": ["Flyby Jab", "Flaming Uppercut"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2839,7 +2800,11 @@ const atrees = {
"mantle_charge": 3 "mantle_charge": 3
}, },
"effects": [ "effects": [
{
"type": "raw_stat",
"toggle": true,
"bonuses": [{ "type": "stat", "name": "defPct", "value": 70}]
}
] ]
}, },
@ -2870,7 +2835,7 @@ const atrees = {
"name": "raw" "name": "raw"
}, },
"scaling": [4], "scaling": [4],
"slider_step": 2, "slider_step": 1,
"max": 120 "max": 120
} }
] ]
@ -2879,8 +2844,7 @@ const atrees = {
{ {
"display_name": "Spear Proficiency 2", "display_name": "Spear Proficiency 2",
"desc": "Improve your Main Attack's damage and range w/ spear", "desc": "Improve your Main Attack's damage and range w/ spear",
"archetype": "", "base_abil": 999,
"archetype_req": 0,
"parents": ["Bak'al's Grasp", "Cheaper Uppercut"], "parents": ["Bak'al's Grasp", "Cheaper Uppercut"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2910,8 +2874,7 @@ const atrees = {
{ {
"display_name": "Cheaper Uppercut", "display_name": "Cheaper Uppercut",
"desc": "Reduce the Mana Cost of Uppercut", "desc": "Reduce the Mana Cost of Uppercut",
"archetype": "", "base_abil": "Uppercut",
"archetype_req": 0,
"parents": ["Spear Proficiency 2", "Aerodynamics", "Counter"], "parents": ["Spear Proficiency 2", "Aerodynamics", "Counter"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2937,6 +2900,7 @@ const atrees = {
"desc": "During Charge, you can steer and change direction", "desc": "During Charge, you can steer and change direction",
"archetype": "Battle Monk", "archetype": "Battle Monk",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Charge",
"parents": ["Cheaper Uppercut", "Provoke"], "parents": ["Cheaper Uppercut", "Provoke"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2946,11 +2910,8 @@ const atrees = {
"col": 5, "col": 5,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
}, "effects": []
"effects": [
]
}, },
{ {
@ -2958,6 +2919,7 @@ const atrees = {
"desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream", "desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",
"archetype": "Paladin", "archetype": "Paladin",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "War Scream",
"parents": ["Aerodynamics", "Mantle of the Bovemists"], "parents": ["Aerodynamics", "Mantle of the Bovemists"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -2967,8 +2929,7 @@ const atrees = {
"col": 7, "col": 7,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
@ -2981,8 +2942,6 @@ const atrees = {
{ {
"display_name": "Precise Strikes", "display_name": "Precise Strikes",
"desc": "+30% Critical Hit Damage", "desc": "+30% Critical Hit Damage",
"archetype": "",
"archetype_req": 0,
"parents": ["Cheaper Uppercut", "Spear Proficiency 2"], "parents": ["Cheaper Uppercut", "Spear Proficiency 2"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3000,7 +2959,7 @@ const atrees = {
"bonuses": [ "bonuses": [
{ {
"type": "stat", "type": "stat",
"name": "critDmg", "name": "critDamPct",
"value": 30 "value": 30
} }
] ]
@ -3011,8 +2970,7 @@ const atrees = {
{ {
"display_name": "Air Shout", "display_name": "Air Shout",
"desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times", "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times",
"archetype": "", "base_abil": "War Scream",
"archetype_req": 0,
"parents": ["Aerodynamics", "Provoke"], "parents": ["Aerodynamics", "Provoke"],
"dependencies": ["War Scream"], "dependencies": ["War Scream"],
"blockers": [], "blockers": [],
@ -3022,15 +2980,12 @@ const atrees = {
"col": 6, "col": 6,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 4, "base_spell": 4,
"target_part": "Air Shout", "target_part": "Air Shout",
"cost": 0,
"multipliers": [20, 0, 0, 0, 0, 5] "multipliers": [20, 0, 0, 0, 0, 5]
} }
] ]
@ -3038,7 +2993,7 @@ const atrees = {
{ {
"display_name": "Enraged Blow", "display_name": "Enraged Blow",
"desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)", "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +3% (Max 300%)",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 0, "archetype_req": 0,
"parents": ["Spear Proficiency 2"], "parents": ["Spear Proficiency 2"],
@ -3077,6 +3032,7 @@ const atrees = {
"desc": "When using Charge, mobs hit will halt your momentum and get knocked back", "desc": "When using Charge, mobs hit will halt your momentum and get knocked back",
"archetype": "Battle Monk", "archetype": "Battle Monk",
"archetype_req": 1, "archetype_req": 1,
"base_abil": "Charge",
"parents": ["Cheaper Uppercut", "Stronger Mantle"], "parents": ["Cheaper Uppercut", "Stronger Mantle"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3093,8 +3049,14 @@ const atrees = {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 2, "base_spell": 2,
"target_part": "Flying Kick", "target_part": "Flying Kick",
"cost": 0,
"multipliers": [120, 0, 0, 10, 0, 20] "multipliers": [120, 0, 0, 10, 0, 20]
},
{
"type": "add_spell_prop",
"base_spell": 2,
"target_part": "Flying Kick Max Damage",
"hits": { "Flying Kick": 1 },
"display": "Flying Kick Max Damage"
} }
] ]
}, },
@ -3113,11 +3075,19 @@ const atrees = {
"col": 6, "col": 6,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
"mantle_charge": 2
},
"effects": [ "effects": [
{
"type": "raw_stat",
"bonuses": [
{
"type": "prop",
"abil": "Mantle of the Bovemists",
"name": "mantle_charge",
"value": 2
}
]
}
] ]
}, },
@ -3138,9 +3108,7 @@ const atrees = {
"properties": { "properties": {
"cooldown": 1 "cooldown": 1
}, },
"effects": [ "effects": []
]
}, },
{ {
@ -3157,8 +3125,7 @@ const atrees = {
"col": 0, "col": 0,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
@ -3175,6 +3142,7 @@ const atrees = {
"desc": "War Scream become deafening, increasing its range and giving damage bonus to players", "desc": "War Scream become deafening, increasing its range and giving damage bonus to players",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "War Scream",
"parents": ["Boiling Blood", "Flying Kick"], "parents": ["Boiling Blood", "Flying Kick"],
"dependencies": ["War Scream"], "dependencies": ["War Scream"],
"blockers": [], "blockers": [],
@ -3185,7 +3153,6 @@ const atrees = {
"icon": "node_2" "icon": "node_2"
}, },
"properties": { "properties": {
"damage_bonus": 30,
"aoe": 2 "aoe": 2
}, },
"effects": [ "effects": [
@ -3193,15 +3160,18 @@ const atrees = {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 4, "base_spell": 4,
"cost": 10 "cost": 10
},
{
"type": "raw_stat",
"toggle": true,
"bonuses": [ {"type": "stat", "name": "damMult", "value": 30} ]
} }
] ]
}, },
{ {
"display_name": "Ambidextrous", "display_name": "Ambidextrous",
"desc": "Increase your chance to attack with Counter by +30%", "desc": "Increase your chance to attack with Counter by +30%",
"archetype": "", "base_abil": "Counter",
"archetype_req": 0,
"parents": ["Flying Kick", "Stronger Mantle", "Burning Heart"], "parents": ["Flying Kick", "Stronger Mantle", "Burning Heart"],
"dependencies": ["Counter"], "dependencies": ["Counter"],
"blockers": [], "blockers": [],
@ -3211,11 +3181,12 @@ const atrees = {
"col": 4, "col": 4,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
"chance": 30
},
"effects": [ "effects": [
{
"type": "raw_stat",
"bonuses": [ {"type": "prop", "abil": "Counter", "name": "chance", "value": 30} ]
}
] ]
}, },
@ -3250,8 +3221,7 @@ const atrees = {
"name": "fDamPct" "name": "fDamPct"
}, },
"scaling": [2], "scaling": [2],
"max": 100, "max": 100
"slider_step": 100
} }
] ]
}, },
@ -3259,8 +3229,7 @@ const atrees = {
{ {
"display_name": "Stronger Bash", "display_name": "Stronger Bash",
"desc": "Increase the damage of Bash", "desc": "Increase the damage of Bash",
"archetype": "", "base_abil": "Bash",
"archetype_req": 0,
"parents": ["Burning Heart", "Manachism"], "parents": ["Burning Heart", "Manachism"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3270,14 +3239,12 @@ const atrees = {
"col": 8, "col": 8,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 1, "base_spell": 1,
"target_part": "Single Hit", "target_part": "Single Hit",
"cost": 0,
"multipliers": [30, 0, 0, 0, 0, 0] "multipliers": [30, 0, 0, 0, 0, 0]
} }
] ]
@ -3288,6 +3255,7 @@ const atrees = {
"desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted", "desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 5, "archetype_req": 5,
"base_abil": "Bak'al's Grasp",
"parents": ["Ragnarokkr", "Boiling Blood"], "parents": ["Ragnarokkr", "Boiling Blood"],
"dependencies": ["Bak'al's Grasp"], "dependencies": ["Bak'al's Grasp"],
"blockers": [], "blockers": [],
@ -3297,11 +3265,8 @@ const atrees = {
"col": 1, "col": 1,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
}, "effects": []
"effects": [
]
}, },
{ {
@ -3309,6 +3274,7 @@ const atrees = {
"desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage", "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Uppercut",
"parents": ["Ragnarokkr"], "parents": ["Ragnarokkr"],
"dependencies": ["Fireworks"], "dependencies": ["Fireworks"],
"blockers": [], "blockers": [],
@ -3318,8 +3284,7 @@ const atrees = {
"col": 2, "col": 2,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
@ -3345,6 +3310,7 @@ const atrees = {
"desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage", "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage",
"archetype": "Battle Monk", "archetype": "Battle Monk",
"archetype_req": 4, "archetype_req": 4,
"base_abil": "Charge",
"parents": ["Ambidextrous", "Burning Heart"], "parents": ["Ambidextrous", "Burning Heart"],
"dependencies": ["Flying Kick"], "dependencies": ["Flying Kick"],
"blockers": [], "blockers": [],
@ -3364,6 +3330,12 @@ const atrees = {
"target_part": "Collide", "target_part": "Collide",
"cost": 0, "cost": 0,
"multipliers": [100, 0, 0, 0, 50, 0] "multipliers": [100, 0, 0, 0, 50, 0]
},
{
"type": "add_spell_prop",
"base_spell": 2,
"target_part": "Flying Kick Max Damage",
"hits": { "Collide": 1 }
} }
] ]
}, },
@ -3382,18 +3354,13 @@ const atrees = {
"col": 7, "col": 7,
"icon": "node_3" "icon": "node_3"
}, },
"properties": { "properties": {},
"effects": []
}, },
"effects": [
]
},
{ {
"display_name": "Uncontainable Corruption", "display_name": "Uncontainable Corruption",
"desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1", "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",
"archetype": "", "base_abil": "Bak'al's Grasp",
"archetype_req": 0,
"parents": ["Boiling Blood", "Radiant Devotee"], "parents": ["Boiling Blood", "Radiant Devotee"],
"dependencies": ["Bak'al's Grasp"], "dependencies": ["Bak'al's Grasp"],
"blockers": [], "blockers": [],
@ -3403,9 +3370,7 @@ const atrees = {
"col": 0, "col": 0,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
"cooldown": -5
},
"effects": [ "effects": [
{ {
"type": "stat_scaling", "type": "stat_scaling",
@ -3415,9 +3380,12 @@ const atrees = {
"type": "stat", "type": "stat",
"name": "raw" "name": "raw"
}, },
"scaling": [1], "scaling": [0.5],
"slider_step": 2,
"max": 50 "max": 50
},
{
"type": "raw_stat",
"bonuses": [ {"type": "prop", "abil": "Bak'al's Grasp", "name": "cooldown", "value": -5} ]
} }
] ]
}, },
@ -3436,8 +3404,7 @@ const atrees = {
"col": 2, "col": 2,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "stat_scaling", "type": "stat_scaling",
@ -3451,9 +3418,8 @@ const atrees = {
"type": "stat", "type": "stat",
"name": "mr" "name": "mr"
}, },
"scaling": [1], "scaling": [0.25],
"max": 10, "max": 10
"slider_step": 4
} }
] ]
}, },
@ -3463,6 +3429,7 @@ const atrees = {
"desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)", "desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",
"archetype": "Battle Monk", "archetype": "Battle Monk",
"archetype_req": 5, "archetype_req": 5,
"base_abil": "Uppercut",
"parents": ["Ambidextrous", "Radiant Devotee"], "parents": ["Ambidextrous", "Radiant Devotee"],
"dependencies": ["Uppercut"], "dependencies": ["Uppercut"],
"blockers": [], "blockers": [],
@ -3480,7 +3447,6 @@ const atrees = {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 3, "base_spell": 3,
"target_part": "Uppercut", "target_part": "Uppercut",
"cost": 0,
"multipliers": [0, 0, 0, 0, 0, 50] "multipliers": [0, 0, 0, 0, 0, 50]
} }
] ]
@ -3500,8 +3466,7 @@ const atrees = {
"col": 7, "col": 7,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "raw_stat", "type": "raw_stat",
@ -3521,6 +3486,7 @@ const atrees = {
"desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage", "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Uppercut",
"parents": ["Uncontainable Corruption", "Radiant Devotee"], "parents": ["Uncontainable Corruption", "Radiant Devotee"],
"dependencies": ["Bak'al's Grasp"], "dependencies": ["Bak'al's Grasp"],
"blockers": [], "blockers": [],
@ -3534,15 +3500,19 @@ const atrees = {
"duration": 5 "duration": 5
}, },
"effects": [ "effects": [
{
"type": "raw_stat",
"toggle": true,
"bonuses": [ {"type": "stat", "name": "damMult", "value": 30} ]
}
] ]
}, },
{ {
"display_name": "Shield Strike", "display_name": "Shield Strike",
"desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost", "desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",
"archetype": "Paladin", "archetype": "Paladin",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Mantle of the Bovemists",
"parents": ["Mythril Skin", "Sparkling Hope"], "parents": ["Mythril Skin", "Sparkling Hope"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3552,19 +3522,23 @@ const atrees = {
"col": 6, "col": 6,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "replace_spell",
"base_spell": 5, "name": "Shield Strike",
"target_part": "Shield Strike", "display_text": "Shield Strike",
"cost": 0, "base_spell": 6,
"display": "Damage per Shield",
"parts": [
{
"name": "Damage per Shield",
"multipliers": [60, 0, 20, 0, 0, 0] "multipliers": [60, 0, 20, 0, 0, 0]
} }
] ]
}
]
}, },
{ {
"display_name": "Sparkling Hope", "display_name": "Sparkling Hope",
"desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies", "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies",
@ -3584,21 +3558,26 @@ const atrees = {
}, },
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "replace_spell",
"base_spell": 5, "name": "Sparkling Hope",
"target_part": "Sparkling Hope", "display_text": "Sparkling Hope",
"cost": 0, "base_spell": 6,
"display": "Damage Tick",
"parts": [
{
"name": "Damage Tick",
"multipliers": [10, 0, 5, 0, 0, 0] "multipliers": [10, 0, 5, 0, 0, 0]
} }
] ]
}
]
}, },
{ {
"display_name": "Massive Bash", "display_name": "Massive Bash",
"desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)", "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 8, "archetype_req": 8,
"base_abil": "Bak'al's Grasp", "base_abil": "Bash",
"parents": ["Tempest", "Uncontainable Corruption"], "parents": ["Tempest", "Uncontainable Corruption"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3608,8 +3587,7 @@ const atrees = {
"col": 0, "col": 0,
"icon": "node_2" "icon": "node_2"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "stat_scaling", "type": "stat_scaling",
@ -3620,18 +3598,16 @@ const atrees = {
"abil": "Bash", "abil": "Bash",
"name": "aoe" "name": "aoe"
}, },
"scaling": [1], "scaling": [0.3333333333333333],
"max": 10, "max": 10
"slider_step": 3
} }
] ]
}, },{
{
"display_name": "Tempest", "display_name": "Tempest",
"desc": "War Scream will ripple the ground and deal damage 3 times in a large area", "desc": "War Scream will ripple the ground and deal damage 3 times in a large area",
"archetype": "Battle Monk", "archetype": "Battle Monk",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "War Scream",
"parents": ["Massive Bash", "Spirit of the Rabbit"], "parents": ["Massive Bash", "Spirit of the Rabbit"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3649,14 +3625,12 @@ const atrees = {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 4, "base_spell": 4,
"target_part": "Tempest", "target_part": "Tempest",
"cost": "0",
"multipliers": [30, 10, 0, 0, 0, 10] "multipliers": [30, 10, 0, 0, 0, 10]
}, },
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 4, "base_spell": 4,
"target_part": "Tempest Total Damage", "target_part": "Tempest Total Damage",
"cost": "0",
"hits": { "hits": {
"Tempest": 3 "Tempest": 3
} }
@ -3665,19 +3639,16 @@ const atrees = {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 4, "base_spell": 4,
"target_part": "Total Damage", "target_part": "Total Damage",
"cost": "0", "hits": { "Tempest": 3 }
"hits": {
"Tempest": 3
}
} }
] ]
}, },
{ {
"display_name": "Spirit of the Rabbit", "display_name": "Spirit of the Rabbit",
"desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%", "desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%",
"archetype": "Battle Monk", "archetype": "Battle Monk",
"archetype_req": 5, "archetype_req": 5,
"base_abil": "Charge",
"parents": ["Tempest", "Whirlwind Strike"], "parents": ["Tempest", "Whirlwind Strike"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3687,8 +3658,7 @@ const atrees = {
"col": 4, "col": 4,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
@ -3697,22 +3667,15 @@ const atrees = {
}, },
{ {
"type": "raw_stat", "type": "raw_stat",
"bonuses": [ "bonuses": [{ "type": "stat", "name": "spd", "value": 20 }]
{
"type": "stat",
"name": "spd",
"value": 20
} }
] ]
} },{
]
},
{
"display_name": "Massacre", "display_name": "Massacre",
"desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar", "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 5, "archetype_req": 5,
"base_abil": 999,
"parents": ["Tempest", "Massive Bash"], "parents": ["Tempest", "Massive Bash"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3722,18 +3685,13 @@ const atrees = {
"col": 1, "col": 1,
"icon": "node_1" "icon": "node_1"
}, },
"properties": { "properties": {},
"effects": []
}, },
"effects": [
]
},
{ {
"display_name": "Axe Kick", "display_name": "Axe Kick",
"desc": "Increase the damage of Uppercut, but also increase its mana cost", "desc": "Increase the damage of Uppercut, but also increase its mana cost",
"archetype": "", "base_abil": "Uppercut",
"archetype_req": 0,
"parents": ["Tempest", "Spirit of the Rabbit"], "parents": ["Tempest", "Spirit of the Rabbit"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3743,8 +3701,7 @@ const atrees = {
"col": 3, "col": 3,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
@ -3755,12 +3712,12 @@ const atrees = {
} }
] ]
}, },
{ {
"display_name": "Radiance", "display_name": "Radiance",
"desc": "Bash will buff your allies' positive IDs. (15s Cooldown)", "desc": "Bash will buff your allies' positive IDs. (15s Cooldown)",
"archetype": "Paladin", "archetype": "Paladin",
"archetype_req": 2, "archetype_req": 2,
"base_abil": "Bash",
"parents": ["Spirit of the Rabbit", "Cheaper Bash 2"], "parents": ["Spirit of the Rabbit", "Cheaper Bash 2"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3773,16 +3730,13 @@ const atrees = {
"properties": { "properties": {
"cooldown": 15 "cooldown": 15
}, },
"effects": [ "effects": []
]
}, },
{ {
"display_name": "Cheaper Bash 2", "display_name": "Cheaper Bash 2",
"desc": "Reduce the Mana cost of Bash", "desc": "Reduce the Mana cost of Bash",
"archetype": "", "base_abil": "Bash",
"archetype_req": 0,
"parents": ["Radiance", "Shield Strike", "Sparkling Hope"], "parents": ["Radiance", "Shield Strike", "Sparkling Hope"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3792,8 +3746,7 @@ const atrees = {
"col": 7, "col": 7,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
@ -3806,8 +3759,7 @@ const atrees = {
{ {
"display_name": "Cheaper War Scream", "display_name": "Cheaper War Scream",
"desc": "Reduce the Mana cost of War Scream", "desc": "Reduce the Mana cost of War Scream",
"archetype": "", "base_abil": "War Scream",
"archetype_req": 0,
"parents": ["Massive Bash"], "parents": ["Massive Bash"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -3817,8 +3769,7 @@ const atrees = {
"col": 0, "col": 0,
"icon": "node_0" "icon": "node_0"
}, },
"properties": { "properties": {},
},
"effects": [ "effects": [
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
@ -3849,10 +3800,13 @@ const atrees = {
"type": "stat_scaling", "type": "stat_scaling",
"slider": true, "slider": true,
"slider_name": "Hits dealt", "slider_name": "Hits dealt",
"output": { "output": [
"type": "stat", { "type": "stat", "name": "eDamAddMin" }, { "type": "stat", "name": "eDamAddMax" },
"name": "rainrawButDifferent" { "type": "stat", "name": "tDamAddMin" }, { "type": "stat", "name": "tDamAddMax" },
}, { "type": "stat", "name": "wDamAddMin" }, { "type": "stat", "name": "wDamAddMax" },
{ "type": "stat", "name": "fDamAddMin" }, { "type": "stat", "name": "fDamAddMax" },
{ "type": "stat", "name": "aDamAddMin" }, { "type": "stat", "name": "aDamAddMax" }
],
"scaling": [2], "scaling": [2],
"max": 50 "max": 50
} }
@ -3915,14 +3869,12 @@ const atrees = {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 4, "base_spell": 4,
"target_part": "Cyclone", "target_part": "Cyclone",
"cost": 0,
"multipliers": [10, 0, 0, 0, 5, 10] "multipliers": [10, 0, 0, 0, 5, 10]
}, },
{ {
"type": "add_spell_prop", "type": "add_spell_prop",
"base_spell": 4, "base_spell": 4,
"target_part": "Cyclone Total Damage", "target_part": "Cyclone Total Damage",
"cost": 0,
"hits": { "hits": {
"Cyclone": 40 "Cyclone": 40
} }
@ -3951,7 +3903,7 @@ const atrees = {
{ {
"display_name": "Blood Pact", "display_name": "Blood Pact",
"desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)", "desc": "If you do not have enough mana to cast a spell, spend health instead (0.6% health per mana)",
"archetype": "", "archetype": "",
"archetype_req": 10, "archetype_req": 10,
"parents": ["Cheaper War Scream"], "parents": ["Cheaper War Scream"],
@ -3963,15 +3915,18 @@ const atrees = {
"col": 1, "col": 1,
"icon": "node_3" "icon": "node_3"
}, },
"properties": {}, "properties": {
"health_cost": 0.6
},
"effects": [] "effects": []
}, },
{ {
"display_name": "Haemorrhage", "display_name": "Haemorrhage",
"desc": "Reduce Blood Pact's health cost. (0.5% health per mana)", "desc": "Reduce Blood Pact's health cost. (0.3% health per mana)",
"archetype": "Fallen", "archetype": "Fallen",
"archetype_req": 0, "archetype_req": 0,
"base_abil": "Blood Pact",
"parents": ["Blood Pact"], "parents": ["Blood Pact"],
"dependencies": ["Blood Pact"], "dependencies": ["Blood Pact"],
"blockers": [], "blockers": [],
@ -3982,14 +3937,15 @@ const atrees = {
"icon": "node_1" "icon": "node_1"
}, },
"properties": {}, "properties": {},
"effects": [] "effects": [{
"type": "raw_stat",
"bonuses": [{ "type": "prop", "abil": "Blood Pact", "name": "health_cost", "value": -0.3}]
}]
}, },
{ {
"display_name": "Brink of Madness", "display_name": "Brink of Madness",
"desc": "If your health is 25% full or less, gain +40% Resistance", "desc": "If your health is 25% full or less, gain +40% Resistance",
"archetype": "",
"archetype_req": 0,
"parents": ["Blood Pact", "Cheaper Uppercut 2"], "parents": ["Blood Pact", "Cheaper Uppercut 2"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],
@ -4006,8 +3962,7 @@ const atrees = {
{ {
"display_name": "Cheaper Uppercut 2", "display_name": "Cheaper Uppercut 2",
"desc": "Reduce the Mana cost of Uppercut", "desc": "Reduce the Mana cost of Uppercut",
"archetype": "", "base_abil": "Uppercut",
"archetype_req": 0,
"parents": ["Second Chance", "Brink of Madness"], "parents": ["Second Chance", "Brink of Madness"],
"dependencies": [], "dependencies": [],
"blockers": [], "blockers": [],

File diff suppressed because one or more lines are too long

View file

@ -20,6 +20,8 @@ function parsePowdering(powder_info) {
return [powdering, powder_info]; return [powdering, powder_info];
} }
let atree_data = null;
/* /*
* Populate fields based on url, and calculate build. * Populate fields based on url, and calculate build.
*/ */
@ -62,7 +64,7 @@ function decodeBuild(url_tag) {
} }
info[1] = info_str.slice(start_idx); info[1] = info_str.slice(start_idx);
} }
else if (version_number <= 6) { else if (version_number <= 7) {
let info_str = info[1]; let info_str = info[1];
let start_idx = 0; let start_idx = 0;
for (let i = 0; i < 9; ++i ) { for (let i = 0; i < 9; ++i ) {
@ -101,7 +103,7 @@ function decodeBuild(url_tag) {
let powder_info = info[1].slice(10); let powder_info = info[1].slice(10);
let res = parsePowdering(powder_info); let res = parsePowdering(powder_info);
powdering = res[0]; powdering = res[0];
} else if (version_number <= 6){ } else if (version_number <= 7){
level = Base64.toInt(info[1].slice(10,12)); level = Base64.toInt(info[1].slice(10,12));
setValue("level-choice",level); setValue("level-choice",level);
save_skp = true; save_skp = true;
@ -117,7 +119,7 @@ function decodeBuild(url_tag) {
info[1] = res[1]; info[1] = res[1];
} }
// Tomes. // Tomes.
if (version == 6) { if (version >= 6) {
//tome values do not appear in anything before v6. //tome values do not appear in anything before v6.
for (let i in tomes) { for (let i in tomes) {
let tome_str = info[1].charAt(i); let tome_str = info[1].charAt(i);
@ -128,6 +130,14 @@ function decodeBuild(url_tag) {
info[1] = info[1].slice(7); info[1] = info[1].slice(7);
} }
if (version >= 7) {
// ugly af. only works since its the last thing. will be fixed with binary decode
atree_data = new BitVector(info[1]);
}
else {
atree_data = null;
}
for (let i in powder_inputs) { for (let i in powder_inputs) {
setValue(powder_inputs[i], powdering[i]); setValue(powder_inputs[i], powdering[i]);
} }
@ -139,12 +149,13 @@ function decodeBuild(url_tag) {
/* Stores the entire build in a string using B64 encoding and adds it to the URL. /* Stores the entire build in a string using B64 encoding and adds it to the URL.
*/ */
function encodeBuild(build, powders, skillpoints) { function encodeBuild(build, powders, skillpoints, atree, atree_state) {
if (build) { if (build) {
let build_string; let build_string;
//V6 encoding - Tomes //V6 encoding - Tomes
//V7 encoding - ATree
build_version = 5; build_version = 5;
build_string = ""; build_string = "";
tome_string = ""; tome_string = "";
@ -189,6 +200,12 @@ function encodeBuild(build, powders, skillpoints) {
} }
build_string += tome_string; build_string += tome_string;
if (atree_state.get(atree[0].ability.id).active) {
build_version = Math.max(build_version, 7);
const bitvec = encode_atree(atree, atree_state);
build_string += bitvec.toB64();
}
return build_version.toString() + "_" + build_string; return build_version.toString() + "_" + build_string;
} }
} }
@ -216,3 +233,58 @@ function shareBuild(build) {
} }
} }
/**
* Ability tree encode and decode functions
*
* Based on a traversal, basically only uses bits to represent the nodes that are on (and "dark" outgoing edges).
* credit: SockMower
*/
/**
* Return: BitVector
*/
function encode_atree(atree, atree_state) {
let ret_vec = new BitVector(0, 0);
function traverse(head, atree_state, visited, ret) {
for (const child of head.children) {
if (visited.has(child.ability.id)) { continue; }
visited.set(child.ability.id, true);
if (atree_state.get(child.ability.id).active) {
ret.append(1, 1);
traverse(child, atree_state, visited, ret);
}
else {
ret.append(0, 1);
}
}
}
traverse(atree[0], atree_state, new Map(), ret_vec);
return ret_vec;
}
/**
* Return: List of active nodes
*/
function decode_atree(atree, bits) {
let i = 0;
let ret = [];
ret.push(atree[0]);
function traverse(head, visited, ret) {
for (const child of head.children) {
if (visited.has(child.ability.id)) { continue; }
visited.set(child.ability.id, true);
if (bits.read_bit(i)) {
i += 1;
ret.push(child);
traverse(child, visited, ret);
}
else {
i += 1;
}
}
}
traverse(atree[0], new Map(), ret);
return ret;
}

View file

@ -76,7 +76,8 @@ let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slot
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax", "aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element "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. /*"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 "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) : damageMultiplier, defMultiplier, poisonPct, activeMajorIDs
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ] let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ]

View file

@ -344,6 +344,8 @@ class BuildEncodeNode extends ComputeNode {
compute_func(input_map) { compute_func(input_map) {
const build = input_map.get('build'); const build = input_map.get('build');
const atree = input_map.get('atree');
const atree_state = input_map.get('atree-state');
let powders = [ let powders = [
input_map.get('helmet-powder'), input_map.get('helmet-powder'),
input_map.get('chestplate-powder'), input_map.get('chestplate-powder'),
@ -361,7 +363,7 @@ class BuildEncodeNode extends ComputeNode {
// TODO: grr global state for copy button.. // TODO: grr global state for copy button..
player_build = build; player_build = build;
build_powders = powders; build_powders = powders;
return encodeBuild(build, powders, skillpoints); return encodeBuild(build, powders, skillpoints, atree, atree_state);
} }
} }
@ -749,7 +751,7 @@ class DisplayBuildWarningsNode extends ComputeNode {
document.getElementById(skp_order[i]+"-warnings").textContent = '' document.getElementById(skp_order[i]+"-warnings").textContent = ''
if (assigned > 100) { if (assigned > 100) {
let skp_warning = document.createElement("p"); let skp_warning = document.createElement("p");
skp_warning.classList.add("warning"); skp_warning.classList.add("small-text"); skp_warning.classList.add("warning", "small-text");
skp_warning.textContent += "Cannot assign " + assigned + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually."; skp_warning.textContent += "Cannot assign " + assigned + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually.";
document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning); document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning);
} }
@ -1077,6 +1079,8 @@ function builder_graph_init() {
atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node) atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node)
.link_to(atree_collect_spells, 'spells'); .link_to(atree_collect_spells, 'spells');
build_encode_node.link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
// --------------------------------------------------------------- // ---------------------------------------------------------------
// Trigger the update cascade for build! // Trigger the update cascade for build!
// --------------------------------------------------------------- // ---------------------------------------------------------------
@ -1085,6 +1089,18 @@ function builder_graph_init() {
} }
level_input.update(); level_input.update();
// kinda janky, manually set atree and update. Some wasted compute here
if (atree_data !== null && atree_node.value !== null) { // janky check if atree is valid
const atree_state = atree_state_node.value;
if (atree_data.length > 0) {
const active_nodes = decode_atree(atree_node.value, atree_data);
for (const node of active_nodes) {
atree_set_state(atree_state.get(node.ability.id), true);
}
atree_state_node.mark_dirty().update();
}
}
// Powder specials. // Powder specials.
let powder_special_calc = new PowderSpecialCalcNode().link_to(powder_special_input, 'powder-specials'); let powder_special_calc = new PowderSpecialCalcNode().link_to(powder_special_input, 'powder-specials');
new PowderSpecialDisplayNode().link_to(powder_special_input, 'powder-specials') new PowderSpecialDisplayNode().link_to(powder_special_input, 'powder-specials')

View file

@ -1603,7 +1603,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
let third = document.createElement("span"); let third = document.createElement("span");
third.textContent = ") [Base: " + getBaseSpellCost(stats, spellIdx, spell.cost) + " ]"; third.textContent = ")";// [Base: " + getBaseSpellCost(stats, spellIdx, spell.cost) + " ]";
title_elem.appendChild(third); title_elem.appendChild(third);
let third_summary = document.createElement("span"); let third_summary = document.createElement("span");
third_summary.textContent = ")"; third_summary.textContent = ")";

View file

@ -74,6 +74,8 @@ function log(b, n) {
// https://stackoverflow.com/a/27696695 // https://stackoverflow.com/a/27696695
// Modified for fixed precision // Modified for fixed precision
// Base64.fromInt(-2147483648); // gives "200000"
// Base64.toInt("200000"); // gives -2147483648
Base64 = (function () { Base64 = (function () {
var digitsStr = var digitsStr =
// 0 8 16 24 32 40 48 56 63 // 0 8 16 24 32 40 48 56 63
@ -125,8 +127,246 @@ Base64 = (function () {
}; };
})(); })();
// Base64.fromInt(-2147483648); // gives "200000"
// Base64.toInt("200000"); // gives -2147483648 /** A class used to represent an arbitrary length bit vector. Very useful for encoding and decoding.
*
*/
class BitVector {
/** Constructs an arbitrary-length bit vector.
* @class
* @param {String | Number} data - The data to append.
* @param {Number} length - A set length for the data. Ignored if data is a string.
*
* The structure of the Uint32Array should be [[last, ..., first], ..., [last, ..., first], [empty space, last, ..., first]]
*/
constructor(data, length) {
let bit_vec = [];
if (typeof data === "string") {
let int = 0;
let bv_idx = 0;
length = data.length * 6;
for (let i = 0; i < data.length; i++) {
let char = Base64.toInt(data[i]);
let pre_pos = bv_idx % 32;
int |= (char << bv_idx);
bv_idx += 6;
let post_pos = bv_idx % 32;
if (post_pos < pre_pos) { //we have to have filled up the integer
bit_vec.push(int);
int = (char >>> (6 - post_pos));
}
if (i == data.length - 1 && post_pos != 0) {
bit_vec.push(int);
}
}
} else if (typeof data === "number") {
if (typeof length === "undefined")
if (length < 0) {
throw new RangeError("BitVector must have nonnegative length.");
}
//convert to int just in case
data = Math.round(data);
//range of numbers that won't fit in a uint32
if (data > 2**32 - 1 || data < -(2 ** 32 - 1)) {
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
}
bit_vec.push(data);
} else {
throw new TypeError("BitVector must be instantiated with a Number or a B64 String");
}
this.length = length;
this.bits = new Uint32Array(bit_vec);
}
/** Return value of bit at index idx.
*
* @param {Number} idx - The index to read
*
* @returns The bit value at position idx
*/
read_bit(idx) {
if (idx < 0 || idx >= this.length) {
throw new RangeError("Cannot read bit outside the range of the BitVector. ("+idx+" > "+this.length+")");
}
return ((this.bits[Math.floor(idx / 32)] & (1 << idx)) == 0 ? 0 : 1);
}
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
*
* @param {Number} start - The index to start slicing from. Inclusive.
* @param {Number} end - The index to end slicing at. Exclusive.
*
* @returns An integer representation of the sliced bits.
*/
slice(start, end) {
//TO NOTE: JS shifting is ALWAYS in mod 32. a << b will do a << (b mod 32) implicitly.
if (end < start) {
throw new RangeError("Cannot slice a range where the end is before the start.");
} else if (end == start) {
return 0;
} else if (end - start > 32) {
//requesting a slice of longer than 32 bits (safe integer "length")
throw new RangeError("Cannot slice a range of longer than 32 bits (unsafe to store in an integer).");
}
let res = 0;
if (Math.floor((end - 1) / 32) == Math.floor(start / 32)) {
//the range is within 1 uint32 section - do some relatively fast bit twiddling
res = (this.bits[Math.floor(start / 32)] & ~((((~0) << ((end - 1))) << 1) | ~((~0) << (start)))) >>> (start % 32);
} else {
//the number of bits in the uint32s
let start_pos = (start % 32);
let int_idx = Math.floor(start/32);
res = (this.bits[int_idx] & ((~0) << (start))) >>> (start_pos);
res |= (this.bits[int_idx + 1] & ~((~0) << (end))) << (32 - start_pos);
}
return res;
// General code - slow
// for (let i = start; i < end; i++) {
// res |= (get_bit(i) << (i - start));
// }
}
/** Assign bit at index idx to 1.
*
* @param {Number} idx - The index to set.
*/
set_bit(idx) {
if (idx < 0 || idx >= this.length) {
throw new RangeError("Cannot set bit outside the range of the BitVector.");
}
this.bits[Math.floor(idx / 32)] |= (1 << idx % 32);
}
/** Assign bit at index idx to 0.
*
* @param {Number} idx - The index to clear.
*/
clear_bit(idx) {
if (idx < 0 || idx >= this.length) {
throw new RangeError("Cannot clear bit outside the range of the BitVector.");
}
this.bits[Math.floor(idx / 32)] &= ~(1 << idx % 32);
}
/** Creates a string version of the bit vector in B64. Does not keep the order of elements a sensible human readable format.
*
* @returns A b64 string representation of the BitVector.
*/
toB64() {
if (this.length == 0) {
return "";
}
let b64_str = "";
let i = 0;
while (i < this.length) {
b64_str += Base64.fromIntV(this.slice(i, i + 6), 1);
i += 6;
}
return b64_str;
}
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from higher-indexed bits to lower-indexed bits. (n ... 0)
*/
toString() {
let ret_str = "";
for (let i = 0; i < this.length; i++) {
ret_str = (this.read_bit(i) == 0 ? "0": "1") + ret_str;
}
return ret_str;
}
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from lower-indexed bits to higher-indexed bits. (0 ... n)
*/
toStringR() {
let ret_str = "";
for (let i = 0; i < this.length; i++) {
ret_str += (this.read_bit(i) == 0 ? "0": "1");
}
return ret_str;
}
/** Appends data to the BitVector.
*
* @param {Number | String} data - The data to append.
* @param {Number} length - The length, in bits, of the new data. This is ignored if data is a string.
*/
append(data, length) {
if (length < 0) {
throw new RangeError("BitVector length must increase by a nonnegative number.");
}
let bit_vec = [];
for (const uint of this.bits) {
bit_vec.push(uint);
}
if (typeof data === "string") {
let int = bit_vec[bit_vec.length - 1];
let bv_idx = this.length;
length = data.length * 6;
let updated_curr = false;
for (let i = 0; i < data.length; i++) {
let char = Base64.toInt(data[i]);
let pre_pos = bv_idx % 32;
int |= (char << bv_idx);
bv_idx += 6;
let post_pos = bv_idx % 32;
if (post_pos < pre_pos) { //we have to have filled up the integer
if (bit_vec.length == this.bits.length && !updated_curr) {
bit_vec[bit_vec.length - 1] = int;
updated_curr = true;
} else {
bit_vec.push(int);
}
int = (char >>> (6 - post_pos));
}
if (i == data.length - 1) {
if (bit_vec.length == this.bits.length && !updated_curr) {
bit_vec[bit_vec.length - 1] = int;
} else if (post_pos != 0) {
bit_vec.push(int);
}
}
}
} else if (typeof data === "number") {
//convert to int just in case
let int = Math.round(data);
//range of numbers that "could" fit in a uint32 -> [0, 2^32) U [-2^31, 2^31)
if (data > 2**32 - 1 || data < -(2 ** 31)) {
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
}
//could be split between multiple new ints
//reminder that shifts implicitly mod 32
bit_vec[bit_vec.length - 1] |= ((int & ~((~0) << length)) << (this.length));
if (((this.length - 1) % 32 + 1) + length > 32) {
bit_vec.push(int >>> (32 - this.length));
}
} else {
throw new TypeError("BitVector must be appended with a Number or a B64 String");
}
this.bits = new Uint32Array(bit_vec);
this.length += length;
}
};
/* /*
Turns a raw stat and a % stat into a final stat on the basis that - raw and >= 100% becomes 0 and + raw and <=-100% becomes negative. Turns a raw stat and a % stat into a final stat on the basis that - raw and >= 100% becomes 0 and + raw and <=-100% becomes negative.

View file

@ -37,21 +37,26 @@ for _class, info in atree_data.items():
translate([abil], "base_abil") translate([abil], "base_abil")
if "effects" not in info[abil]: if "effects" not in info[abil]:
print("WARNING: abil missing 'effects' tag")
print(info[abil]) print(info[abil])
info[abil]["effects"] = [] info[abil]["effects"] = []
for effect in info[abil]["effects"]: for effect in info[abil]["effects"]:
if effect["type"] == "raw_stat": if effect["type"] == "raw_stat":
for bonus in effect["bonuses"]: for bonus in effect["bonuses"]:
if "abil" in bonus: if "abil" in bonus and bonus["abil"] in id_data[_class]:
bonus["abil"] = id_data[_class][bonus["abil"]] bonus["abil"] = id_data[_class][bonus["abil"]]
elif effect["type"] == "stat_scaling": elif effect["type"] == "stat_scaling":
if "inputs" in effect: # Might not exist for sliders if "inputs" in effect: # Might not exist for sliders
for _input in effect["inputs"]: for _input in effect["inputs"]:
if "abil" in _input: if "abil" in _input and _input["abil"] in id_data[_class]:
_input["abil"] = id_data[_class][_input["abil"]] _input["abil"] = id_data[_class][_input["abil"]]
if isinstance(effect["output"], list):
if "abil" in effect["output"]: 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"]] effect["output"]["abil"] = id_data[_class][effect["output"]["abil"]]