Working atree encoder and decoder
This commit is contained in:
parent
7e40d6a926
commit
0976fff43a
9 changed files with 218 additions and 68 deletions
|
@ -429,7 +429,7 @@
|
|||
</div>
|
||||
<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')">
|
||||
Show Ability Tree
|
||||
Show Tree
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-3 py-2">
|
||||
|
@ -618,11 +618,11 @@
|
|||
</div>
|
||||
<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="col border border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 80vh; 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 class="col mx-auto" style="height: 80vh; overflow-y: auto;" id="atree-rhs">
|
||||
<div class="col mx-auto" style="height: 4em; overflow-y: auto;" id="atree-header">
|
||||
<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>
|
||||
|
@ -1336,7 +1336,7 @@
|
|||
<div class="col-12 dark-5 scaled-font">
|
||||
<footer class="text-center">
|
||||
<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>
|
||||
</div>
|
||||
<div id="credits">
|
||||
|
|
|
@ -282,7 +282,7 @@
|
|||
<div class="col dark-5 scaled-font">
|
||||
<footer class="text-center">
|
||||
<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>
|
||||
</div>
|
||||
<div id="credits">
|
||||
|
|
20
credits.txt
Normal file
20
credits.txt
Normal 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)
|
146
js/atree.js
146
js/atree.js
|
@ -168,15 +168,23 @@ const atree_node = new (class extends ComputeNode {
|
|||
* Signature: AbilityTreeRenderNode(atree: ATree) => RenderedATree ( Map[id, RenderedATNode] )
|
||||
*/
|
||||
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) {
|
||||
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
|
||||
|
||||
//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;
|
||||
if (atree) { ret = render_AT(document.getElementById("atree-ui"), document.getElementById("atree-header"), atree); }
|
||||
if (atree) { ret = render_AT(this.UI_elem, this.list_elem, atree); }
|
||||
|
||||
//Toggle on, previously was toggled off
|
||||
toggle_tab('atree-dropdown'); toggleButton('toggle-atree');
|
||||
|
@ -185,6 +193,17 @@ const atree_render = new (class extends ComputeNode {
|
|||
}
|
||||
})().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.
|
||||
*
|
||||
|
@ -266,7 +285,7 @@ const atree_merge = new (class extends ComputeNode {
|
|||
}
|
||||
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.
|
||||
|
@ -284,9 +303,12 @@ const atree_validate = new (class extends ComputeNode {
|
|||
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 = [];
|
||||
|
@ -294,8 +316,8 @@ const atree_validate = new (class extends ComputeNode {
|
|||
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.name + '"');
|
||||
errors.push(abil.name + ' dependencies not satisfied: ' + dep_string.join(", "));
|
||||
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 = [];
|
||||
|
@ -303,17 +325,41 @@ const atree_validate = new (class extends ComputeNode {
|
|||
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.name + '"');
|
||||
errors.push(abil.name + ' is blocked by: ' + blockers_string.join(", "));
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
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_render, 'atree-state');
|
||||
})().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
|
||||
|
||||
function atree_dfs_mark(start, atree_state, mark) {
|
||||
if (mark.get(start)) { return; }
|
||||
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) {
|
||||
|
@ -331,9 +377,12 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
compute_func(input_map) {
|
||||
const merged_abils = input_map.get('atree-merged');
|
||||
const atree_order = input_map.get('atree-order');
|
||||
const errors = input_map.get('atree-errors');
|
||||
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");
|
||||
|
@ -543,7 +592,6 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
|
|||
}
|
||||
}
|
||||
|
||||
let abil_points_current;
|
||||
/** The main function for rendering an ability tree.
|
||||
*
|
||||
* @param {Element} UI_elem - the DOM element to draw the atree within.
|
||||
|
@ -552,13 +600,9 @@ let abil_points_current;
|
|||
*/
|
||||
function render_AT(UI_elem, list_elem, tree) {
|
||||
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
|
||||
let active_row = document.createElement("div");
|
||||
active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center");
|
||||
abil_points_current = 0;
|
||||
let active_word = document.createElement("div");
|
||||
active_word.classList.add("col-auto");
|
||||
active_word.textContent = "Active Abilities:";
|
||||
|
@ -706,29 +750,33 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
|
||||
node_elem.classList.add("fake-button");
|
||||
|
||||
let active_tooltip = document.createElement('div');
|
||||
active_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow");
|
||||
active_tooltip.style.display = "none";
|
||||
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 active_tooltip_title = document.createElement('b');
|
||||
active_tooltip_title.classList.add("scaled-font");
|
||||
active_tooltip_title.innerHTML = ability.display_name;
|
||||
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 active_tooltip_desc = document.createElement('p');
|
||||
active_tooltip_desc.classList.add("scaled-font-sm", "my-0", "mx-1", "text-wrap");
|
||||
active_tooltip_desc.textContent = ability.desc;
|
||||
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);
|
||||
}
|
||||
|
||||
let active_tooltip_cost = document.createElement('p');
|
||||
active_tooltip_cost.classList.add("scaled-font-sm", "my-0", "mx-1", "text-start");
|
||||
active_tooltip_cost.textContent = "Cost: " + ability.cost + " AP";
|
||||
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);
|
||||
|
||||
active_tooltip.appendChild(active_tooltip_title);
|
||||
active_tooltip.appendChild(active_tooltip_desc);
|
||||
active_tooltip.appendChild(active_tooltip_cost);
|
||||
|
||||
node_tooltip = active_tooltip;//.cloneNode(true);
|
||||
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);
|
||||
|
||||
node_tooltip.style.position = "absolute";
|
||||
node_tooltip.style.zIndex = "100";
|
||||
|
@ -736,22 +784,13 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
node_elem.appendChild(node_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) {
|
||||
if (e.target !== this && e.target!== this.children[0]) {return;}
|
||||
if (node_wrap.active) {
|
||||
this.classList.remove("atree-selected");
|
||||
abil_points_current -= ability.cost;
|
||||
}
|
||||
else {
|
||||
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_validate.mark_dirty();
|
||||
atree_merge.update();
|
||||
atree_validate.update();
|
||||
atree_set_state(node_wrap, !node_wrap.active);
|
||||
atree_state_node.mark_dirty().update();
|
||||
});
|
||||
|
||||
// add tooltip
|
||||
|
@ -841,9 +880,16 @@ function atree_render_connection(atree_connectors_map) {
|
|||
};
|
||||
|
||||
// toggle the state of a node.
|
||||
function atree_toggle_state(atree_connectors_map, node_wrapper) {
|
||||
const new_state = !node_wrapper.active;
|
||||
node_wrapper.active = new_state
|
||||
function atree_set_state(node_wrapper, new_state) {
|
||||
if (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) {
|
||||
if (parent.active) {
|
||||
atree_set_edge(atree_connectors_map, parent, node_wrapper, new_state); // self->parent state only changes if parent is on
|
||||
|
|
|
@ -45,8 +45,6 @@ const atrees = {
|
|||
{
|
||||
"display_name": "Escape",
|
||||
"desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",
|
||||
"archetype": "",
|
||||
"archetype_req": 0,
|
||||
"parents": ["Heart Shatter"],
|
||||
"dependencies": [],
|
||||
"blockers": [],
|
||||
|
@ -141,8 +139,6 @@ const atrees = {
|
|||
{
|
||||
"display_name": "Fire Creep",
|
||||
"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"],
|
||||
"dependencies": [],
|
||||
"blockers": [],
|
||||
|
|
|
@ -20,6 +20,8 @@ function parsePowdering(powder_info) {
|
|||
return [powdering, powder_info];
|
||||
}
|
||||
|
||||
let atree_data = null;
|
||||
|
||||
/*
|
||||
* Populate fields based on url, and calculate build.
|
||||
*/
|
||||
|
@ -62,7 +64,7 @@ function decodeBuild(url_tag) {
|
|||
}
|
||||
info[1] = info_str.slice(start_idx);
|
||||
}
|
||||
else if (version_number <= 6) {
|
||||
else if (version_number <= 7) {
|
||||
let info_str = info[1];
|
||||
let start_idx = 0;
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
|
@ -101,7 +103,7 @@ function decodeBuild(url_tag) {
|
|||
let powder_info = info[1].slice(10);
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
} else if (version_number <= 6){
|
||||
} else if (version_number <= 7){
|
||||
level = Base64.toInt(info[1].slice(10,12));
|
||||
setValue("level-choice",level);
|
||||
save_skp = true;
|
||||
|
@ -117,7 +119,7 @@ function decodeBuild(url_tag) {
|
|||
info[1] = res[1];
|
||||
}
|
||||
// Tomes.
|
||||
if (version == 6) {
|
||||
if (version >= 6) {
|
||||
//tome values do not appear in anything before v6.
|
||||
for (let i in tomes) {
|
||||
let tome_str = info[1].charAt(i);
|
||||
|
@ -128,6 +130,14 @@ function decodeBuild(url_tag) {
|
|||
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) {
|
||||
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.
|
||||
*/
|
||||
function encodeBuild(build, powders, skillpoints) {
|
||||
function encodeBuild(build, powders, skillpoints, atree, atree_state) {
|
||||
|
||||
if (build) {
|
||||
let build_string;
|
||||
|
||||
//V6 encoding - Tomes
|
||||
//V7 encoding - ATree
|
||||
build_version = 5;
|
||||
build_string = "";
|
||||
tome_string = "";
|
||||
|
@ -189,6 +200,12 @@ function encodeBuild(build, powders, skillpoints) {
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -344,6 +344,8 @@ class BuildEncodeNode extends ComputeNode {
|
|||
|
||||
compute_func(input_map) {
|
||||
const build = input_map.get('build');
|
||||
const atree = input_map.get('atree');
|
||||
const atree_state = input_map.get('atree-state');
|
||||
let powders = [
|
||||
input_map.get('helmet-powder'),
|
||||
input_map.get('chestplate-powder'),
|
||||
|
@ -361,7 +363,7 @@ class BuildEncodeNode extends ComputeNode {
|
|||
// TODO: grr global state for copy button..
|
||||
player_build = build;
|
||||
build_powders = powders;
|
||||
return encodeBuild(build, powders, skillpoints);
|
||||
return encodeBuild(build, powders, skillpoints, atree, atree_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1077,6 +1079,8 @@ function builder_graph_init() {
|
|||
atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node)
|
||||
.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!
|
||||
// ---------------------------------------------------------------
|
||||
|
@ -1085,6 +1089,18 @@ function builder_graph_init() {
|
|||
}
|
||||
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.
|
||||
let powder_special_calc = new PowderSpecialCalcNode().link_to(powder_special_input, 'powder-specials');
|
||||
new PowderSpecialDisplayNode().link_to(powder_special_input, 'powder-specials')
|
||||
|
|
|
@ -1603,7 +1603,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
|||
|
||||
|
||||
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);
|
||||
let third_summary = document.createElement("span");
|
||||
third_summary.textContent = ")";
|
||||
|
|
|
@ -193,9 +193,9 @@ Base64 = (function () {
|
|||
*/
|
||||
read_bit(idx) {
|
||||
if (idx < 0 || idx >= this.length) {
|
||||
throw new RangeError("Cannot read bit outside the range of the BitVector.");
|
||||
throw new RangeError("Cannot read bit outside the range of the BitVector. ("+idx+" > "+this.length+")");
|
||||
}
|
||||
return ((this.bits[Math.floor(idx / 32)] & (1 << (idx % 32))) == 0 ? 0 : 1);
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue