Atree error validation and input combining
This commit is contained in:
parent
6e549e8325
commit
ffc0d4b9b4
3 changed files with 129 additions and 19 deletions
|
@ -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: 80vh; 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: 80vh; overflow-y: auto;" id="atree-rhs">
|
||||||
|
<div class="col mx-auto" style="height: 4em; 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>
|
||||||
|
|
138
js/atree.js
138
js/atree.js
|
@ -1,5 +1,3 @@
|
||||||
let abil_points_current;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
ATreeNode spec:
|
ATreeNode spec:
|
||||||
|
|
||||||
|
@ -49,9 +47,10 @@ 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.
|
||||||
|
// "all" means modify all parts.
|
||||||
"conversion": element_str
|
"conversion": element_str
|
||||||
}
|
}
|
||||||
raw_stat: {
|
raw_stat: {
|
||||||
|
@ -177,7 +176,7 @@ const atree_render = new (class extends ComputeNode {
|
||||||
|
|
||||||
//for some reason we have to cast to string
|
//for some reason we have to cast to string
|
||||||
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(document.getElementById("atree-ui"), document.getElementById("atree-header"), 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');
|
||||||
|
@ -216,7 +215,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'); }
|
||||||
|
@ -269,6 +268,115 @@ const atree_merge = new (class extends ComputeNode {
|
||||||
}
|
}
|
||||||
})().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_render, '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);
|
||||||
|
for (const node of atree_order) {
|
||||||
|
const abil = node.ability;
|
||||||
|
if (!atree_state.get(abil.id).active) { continue; }
|
||||||
|
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.name + '"');
|
||||||
|
errors.push(abil.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.name + '"');
|
||||||
|
errors.push(abil.name + ' is blocked by: ' + blockers_string.join(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
})().link_to(atree_node, 'atree').link_to(atree_render, 'atree-state');
|
||||||
|
|
||||||
|
function atree_dfs_mark(start, atree_state, mark) {
|
||||||
|
if (mark.get(start)) { 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 errors = input_map.get('atree-errors');
|
||||||
|
|
||||||
|
this.list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
|
||||||
|
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.
|
||||||
*
|
*
|
||||||
|
@ -404,7 +512,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');
|
||||||
|
|
||||||
|
@ -435,6 +543,7 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let abil_points_current;
|
||||||
/** The main function for rendering an ability tree.
|
/** The main function for rendering an ability tree.
|
||||||
*
|
*
|
||||||
* @param {Element} UI_elem - the DOM element to draw the atree within.
|
* @param {Element} UI_elem - the DOM element to draw the atree within.
|
||||||
|
@ -619,33 +728,30 @@ function render_AT(UI_elem, list_elem, tree) {
|
||||||
active_tooltip.appendChild(active_tooltip_desc);
|
active_tooltip.appendChild(active_tooltip_desc);
|
||||||
active_tooltip.appendChild(active_tooltip_cost);
|
active_tooltip.appendChild(active_tooltip_cost);
|
||||||
|
|
||||||
node_tooltip = active_tooltip.cloneNode(true);
|
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_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);
|
if (node_wrap.active) {
|
||||||
if (tooltip.style.display === "block") {
|
|
||||||
tooltip.style.display = "none";
|
|
||||||
this.classList.remove("atree-selected");
|
this.classList.remove("atree-selected");
|
||||||
abil_points_current -= ability.cost;
|
abil_points_current -= ability.cost;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tooltip.style.display = "block";
|
|
||||||
this.classList.add("atree-selected");
|
this.classList.add("atree-selected");
|
||||||
abil_points_current += ability.cost;
|
abil_points_current += ability.cost;
|
||||||
};
|
};
|
||||||
document.getElementById("active_AP_cost").textContent = abil_points_current;
|
document.getElementById("active_AP_cost").textContent = abil_points_current;
|
||||||
atree_toggle_state(atree_connectors_map, node_wrap);
|
atree_toggle_state(atree_connectors_map, node_wrap);
|
||||||
atree_merge.mark_dirty();
|
atree_merge.mark_dirty();
|
||||||
|
atree_validate.mark_dirty();
|
||||||
atree_merge.update();
|
atree_merge.update();
|
||||||
|
atree_validate.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
// add tooltip
|
// add tooltip
|
||||||
|
|
|
@ -749,7 +749,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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue