Merge branch 'atree' into merge-mastery
This commit is contained in:
commit
e0c91ffabb
4 changed files with 143 additions and 104 deletions
239
js/atree.js
239
js/atree.js
|
@ -323,76 +323,88 @@ const atree_validate = new (class extends ComputeNode {
|
||||||
const atree_state = input_map.get('atree-state');
|
const atree_state = input_map.get('atree-state');
|
||||||
const atree_order = input_map.get('atree');
|
const atree_order = input_map.get('atree');
|
||||||
|
|
||||||
if (atree_order.length == 0) { return [0, ['no atree data']]; }
|
if (atree_order.length == 0) { return [0, false, ['no atree data']]; }
|
||||||
|
|
||||||
let errors = [];
|
let atree_to_add = [];
|
||||||
let reachable = new Map();
|
for (const node of atree_order) {
|
||||||
atree_dfs_mark(atree_order[0], atree_state, reachable);
|
const abil = node.ability;
|
||||||
|
if (atree_state.get(abil.id).active) { atree_to_add.push([node, 'not reachable', false]); }
|
||||||
|
}
|
||||||
|
|
||||||
|
let reachable = new Set();
|
||||||
let abil_points_total = 0;
|
let abil_points_total = 0;
|
||||||
let archetype_count = new Map();
|
let archetype_count = new Map();
|
||||||
for (const node of atree_order) {
|
while (true) {
|
||||||
const abil = node.ability;
|
let _add = [];
|
||||||
if (!atree_state.get(abil.id).active) { continue; }
|
for (const [node, fail_reason, fail_hardness] of atree_to_add) {
|
||||||
abil_points_total += abil.cost;
|
const {parents, ability} = node;
|
||||||
if (!reachable.get(abil.id)) { errors.push(abil.display_name + ' is not reachable!'); }
|
if (parents.length === 0) {
|
||||||
|
reachable.add(ability.id);
|
||||||
let failed_deps = [];
|
// root abil has no archetype.
|
||||||
for (const dep_id of abil.dependencies) {
|
abil_points_total += ability.cost;
|
||||||
if (!atree_state.get(dep_id).active) { failed_deps.push(dep_id) }
|
continue;
|
||||||
}
|
|
||||||
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);
|
let failed_deps = [];
|
||||||
}
|
for (const dep_id of ability.dependencies) {
|
||||||
}
|
if (!atree_state.get(dep_id).active) { failed_deps.push(dep_id) }
|
||||||
// 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 (failed_deps.length > 0) {
|
||||||
|
const dep_strings = failed_deps.map(i => '"' + atree_state.get(i).ability.display_name + '"');
|
||||||
|
_add.push([node, 'missing dep: ' + dep_strings.join(", "), true]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let blocking_ids = [];
|
||||||
|
for (const blocker_id of ability.blockers) {
|
||||||
|
if (atree_state.get(blocker_id).active) { blocking_ids.push(blocker_id); }
|
||||||
|
}
|
||||||
|
if (blocking_ids.length > 0) {
|
||||||
|
const blockers_strings = blocking_ids.map(i => '"' + atree_state.get(i).ability.display_name + '"');
|
||||||
|
_add.push([node, 'blocked by: '+blockers_strings.join(", "), true]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let node_reachable = false;
|
||||||
|
for (const parent of parents) {
|
||||||
|
if (reachable.has(parent.ability.id)) {
|
||||||
|
node_reachable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!node_reachable) {
|
||||||
|
_add.push([node, 'not reachable', false])
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ('archetype' in ability && ability.archetype !== "") {
|
||||||
|
if ('archetype_req' in ability && ability.archetype_req !== 0) {
|
||||||
|
const others = archetype_count.get(ability.archetype);
|
||||||
|
if (others < ability.archetype_req) {
|
||||||
|
_add.push([node, abil.archetype+': '+others+' < '+abil.archetype_req, false])
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let val = 1;
|
||||||
|
if (archetype_count.has(ability.archetype)) {
|
||||||
|
val = archetype_count.get(ability.archetype) + 1;
|
||||||
|
}
|
||||||
|
archetype_count.set(ability.archetype, val);
|
||||||
|
}
|
||||||
|
abil_points_total += ability.cost;
|
||||||
|
reachable.add(ability.id);
|
||||||
}
|
}
|
||||||
|
if (atree_to_add.length == _add.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
atree_to_add = _add;
|
||||||
}
|
}
|
||||||
|
let hard_error = false;
|
||||||
if (abil_points_total > 45) {
|
let errors = [];
|
||||||
errors.push('too many ability points assigned! ('+abil_points_total+' > 45)');
|
for (const [node, fail_reason, fail_hardness] of atree_to_add) {
|
||||||
|
if (fail_hardness) { hard_error = true; }
|
||||||
|
errors.push(node.ability.display_name + ": " + fail_reason);
|
||||||
}
|
}
|
||||||
|
return [abil_points_total, hard_error, errors];
|
||||||
return [abil_points_total, errors];
|
|
||||||
}
|
}
|
||||||
})().link_to(atree_node, 'atree').link_to(atree_state_node, '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.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render ability tree.
|
* Render ability tree.
|
||||||
* Return map of id -> corresponding html element.
|
* Return map of id -> corresponding html element.
|
||||||
|
@ -408,7 +420,7 @@ const atree_render_active = new (class extends ComputeNode {
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
const merged_abils = input_map.get('atree-merged');
|
const merged_abils = input_map.get('atree-merged');
|
||||||
const atree_order = input_map.get('atree-order');
|
const atree_order = input_map.get('atree-order');
|
||||||
const [abil_points_total, errors] = input_map.get('atree-errors');
|
const [abil_points_total, hard_error, errors] = input_map.get('atree-errors');
|
||||||
|
|
||||||
this.list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
|
this.list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
|
||||||
// TODO: move to display?
|
// TODO: move to display?
|
||||||
|
@ -424,10 +436,19 @@ const atree_render_active = new (class extends ComputeNode {
|
||||||
error_title.innerHTML = "ATree Error!";
|
error_title.innerHTML = "ATree Error!";
|
||||||
errorbox.appendChild(error_title);
|
errorbox.appendChild(error_title);
|
||||||
|
|
||||||
for (const error of errors) {
|
for (let i = 0; i < 5 && i < errors.length; ++i) {
|
||||||
let atree_warning = document.createElement("p");
|
const error = errors[i];
|
||||||
atree_warning.classList.add("warning", "small-text");
|
const atree_warning = make_elem("p", ["warning", "small-text"], {textContent: error});
|
||||||
atree_warning.textContent = error;
|
errorbox.appendChild(atree_warning);
|
||||||
|
}
|
||||||
|
if (errors.length > 5) {
|
||||||
|
const error = '... ' + errors.length-5 + ' errors not shown';
|
||||||
|
const atree_warning = make_elem("p", ["warning", "small-text"], {textContent: error});
|
||||||
|
errorbox.appendChild(atree_warning);
|
||||||
|
}
|
||||||
|
if (abil_points_total > 45) {
|
||||||
|
const error = 'too many ability points assigned! ('+abil_points_total+' > 45)';
|
||||||
|
const atree_warning = make_elem("p", ["warning", "small-text"], {textContent: error});
|
||||||
errorbox.appendChild(atree_warning);
|
errorbox.appendChild(atree_warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -473,8 +494,9 @@ const atree_collect_spells = new (class extends ComputeNode {
|
||||||
constructor() { super('atree-spell-collector'); }
|
constructor() { super('atree-spell-collector'); }
|
||||||
|
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
if (input_map.size !== 1) { throw "AbilityTreeCollectSpellsNode accepts exactly one input (atree-merged)"; }
|
const atree_merged = input_map.get('atree-merged');
|
||||||
const [atree_merged] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
|
const [abil_points_total, hard_error, errors] = input_map.get('atree-errors');
|
||||||
|
if (hard_error) { return []; }
|
||||||
|
|
||||||
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()) {
|
||||||
|
@ -569,7 +591,7 @@ const atree_collect_spells = new (class extends ComputeNode {
|
||||||
}
|
}
|
||||||
return ret_spells;
|
return ret_spells;
|
||||||
}
|
}
|
||||||
})().link_to(atree_merge, 'atree-merged');
|
})().link_to(atree_merge, 'atree-merged').link_to(atree_validate, 'atree-errors');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -660,7 +682,8 @@ const atree_stats = new (class extends ComputeNode {
|
||||||
if (effect.slider) {
|
if (effect.slider) {
|
||||||
// TODO: handle
|
// TODO: handle
|
||||||
const slider_val = interactive_map.get(effect.slider_name).slider.value;
|
const slider_val = interactive_map.get(effect.slider_name).slider.value;
|
||||||
const total = parseInt(slider_val) * effect.scaling[0];
|
let total = parseInt(slider_val) * effect.scaling[0];
|
||||||
|
if ('max' in effect && total > effect.max) { total = effect.max; }
|
||||||
if (Array.isArray(effect.output)) {
|
if (Array.isArray(effect.output)) {
|
||||||
for (const output of effect.output) {
|
for (const output of effect.output) {
|
||||||
if (output.type === 'stat') {
|
if (output.type === 'stat') {
|
||||||
|
@ -675,13 +698,12 @@ const atree_stats = new (class extends ComputeNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const cap = effect.max;
|
|
||||||
// TODO: type: prop?
|
// TODO: type: prop?
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (const [scaling, input] of zip2(effect.scaling, effect.inputs)) {
|
for (const [scaling, input] of zip2(effect.scaling, effect.inputs)) {
|
||||||
total += scaling * item_stats.get(input.name);
|
total += scaling * item_stats.get(input.name);
|
||||||
}
|
}
|
||||||
if (total > cap) { total = cap; }
|
if ('max' in effect && total > effect.max) { total = effect.max; }
|
||||||
// TODO: output (list...)
|
// TODO: output (list...)
|
||||||
if (Array.isArray(effect.output)) {
|
if (Array.isArray(effect.output)) {
|
||||||
for (const output of effect.output) {
|
for (const output of effect.output) {
|
||||||
|
@ -1029,30 +1051,33 @@ function resolve_connector(atree_connectors_map, pos, new_connector) {
|
||||||
function set_connector_type(connector_info) { // left right up down
|
function set_connector_type(connector_info) { // left right up down
|
||||||
const connections = connector_info.connections;
|
const connections = connector_info.connections;
|
||||||
const connector_elem = connector_info.connector;
|
const connector_elem = connector_info.connector;
|
||||||
if (connections[2]) {
|
let connector_dict = {
|
||||||
if (connections[0]) {
|
"1100": {type: "line", rotate: 90},
|
||||||
connector_info.type = 'c'; // cross
|
"1010": {type: "angle", rotate: 0},
|
||||||
return;
|
"1001": {type: "angle", rotate: 270},
|
||||||
}
|
"0110": {type: "angle", rotate: 90},
|
||||||
connector_info.type = 'line'; // vert line
|
"0101": {type: "angle", rotate: 180},
|
||||||
return;
|
"0011": {type: "line", rotate: 0},
|
||||||
|
"1110": {type: "t", rotate: 180},
|
||||||
|
"1101": {type: "t", rotate: 0},
|
||||||
|
"1011": {type: "t", rotate: 90},
|
||||||
|
"0111": {type: "t", rotate: 270},
|
||||||
|
"1111": {type: "c", rotate: 0}
|
||||||
}
|
}
|
||||||
if (connections[3]) { // if down:
|
|
||||||
if (connections[0] && connections[1]) {
|
let lookup_str = "";
|
||||||
connector_info.type = 't'; // all 3 t
|
for (let i of connections) {
|
||||||
return;
|
if (i != 0) {
|
||||||
|
lookup_str += 1;
|
||||||
|
} else {
|
||||||
|
lookup_str += 0;
|
||||||
}
|
}
|
||||||
connector_info.type = 'angle'; // elbow
|
|
||||||
if (connections[1]) {
|
|
||||||
connector_elem.classList.add("rotate-180");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
connector_elem.classList.add("rotate-270");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
connector_info.type = 'line'; // horiz line
|
|
||||||
connector_elem.classList.add("rotate-90");
|
connector_info.type = connector_dict[lookup_str].type;
|
||||||
|
connector_info.rotate = connector_dict[lookup_str].rotate;
|
||||||
|
connector_elem.classList.add("rotate-" + connector_dict[lookup_str].rotate);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw the connector onto the screen
|
// draw the connector onto the screen
|
||||||
|
@ -1130,6 +1155,7 @@ function atree_set_edge(atree_connectors_map, parent, child, state) {
|
||||||
let connector_img_elem = document.createElement("img");
|
let connector_img_elem = document.createElement("img");
|
||||||
connector_img_elem.style = "width: 100%; height: 100%;";
|
connector_img_elem.style = "width: 100%; height: 100%;";
|
||||||
const ctype = connector_info.type;
|
const ctype = connector_info.type;
|
||||||
|
const rotate = connector_info.rotate;
|
||||||
if (ctype === 't' || ctype === 'c') {
|
if (ctype === 't' || ctype === 'c') {
|
||||||
// c, t
|
// c, t
|
||||||
const [connector_row, connector_col] = connector_label.split(',').map(x => parseInt(x));
|
const [connector_row, connector_col] = connector_label.split(',').map(x => parseInt(x));
|
||||||
|
@ -1149,7 +1175,7 @@ function atree_set_edge(atree_connectors_map, parent, child, state) {
|
||||||
|
|
||||||
let render_state = highlight_state.map(x => (x > 0 ? 1 : 0));
|
let render_state = highlight_state.map(x => (x > 0 ? 1 : 0));
|
||||||
|
|
||||||
let connector_img = atree_parse_connector(render_state, ctype);
|
let connector_img = atree_parse_connector(render_state, ctype, rotate);
|
||||||
connector_img_elem.src = connector_img.img
|
connector_img_elem.src = connector_img.img
|
||||||
connector_elem.className = "";
|
connector_elem.className = "";
|
||||||
connector_elem.classList.add("rotate-" + connector_img.rotate);
|
connector_elem.classList.add("rotate-" + connector_img.rotate);
|
||||||
|
@ -1170,7 +1196,7 @@ function atree_set_edge(atree_connectors_map, parent, child, state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse a sequence of left, right, up, down to appropriate connector image
|
// parse a sequence of left, right, up, down to appropriate connector image
|
||||||
function atree_parse_connector(orient, type) {
|
function atree_parse_connector(orient, type, rotate) {
|
||||||
// left, right, up, down
|
// left, right, up, down
|
||||||
|
|
||||||
let c_connector_dict = {
|
let c_connector_dict = {
|
||||||
|
@ -1188,10 +1214,24 @@ function atree_parse_connector(orient, type) {
|
||||||
};
|
};
|
||||||
|
|
||||||
let t_connector_dict = {
|
let t_connector_dict = {
|
||||||
"1100": {attrib: "_2_l", rotate: 0},
|
0: {
|
||||||
"1001": {attrib: "_2_a", rotate: "flip"},
|
"1100": {attrib: "_2_l"},
|
||||||
"0101": {attrib: "_2_a", rotate: 0},
|
"1001": {attrib: "_2_a_f"},
|
||||||
"1101": {attrib: "_3", rotate: 0}
|
"0101": {attrib: "_2_a"},
|
||||||
|
"1101": {attrib: "_3"},
|
||||||
|
},
|
||||||
|
90: {
|
||||||
|
"1010": {attrib: "_2_a_f"},
|
||||||
|
"1001": {attrib: "_2_a"},
|
||||||
|
"0011": {attrib: "_2_l"},
|
||||||
|
"1011": {attrib: "_3"}
|
||||||
|
},
|
||||||
|
270: {
|
||||||
|
"0110": {attrib: "_2_a"},
|
||||||
|
"0101": {attrib: "_2_a_f"},
|
||||||
|
"0011": {attrib: "_2_l"},
|
||||||
|
"0111": {attrib: "_3"}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = "";
|
let res = "";
|
||||||
|
@ -1199,14 +1239,15 @@ function atree_parse_connector(orient, type) {
|
||||||
res += i;
|
res += i;
|
||||||
}
|
}
|
||||||
if (res === "0000") {
|
if (res === "0000") {
|
||||||
return {img: "../media/atree/connect_" + type + ".png", rotate: 0};
|
return {img: "../media/atree/connect_" + type + ".png", rotate: rotate};
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret;
|
let ret;
|
||||||
if (type == "c") {
|
if (type == "c") {
|
||||||
ret = c_connector_dict[res];
|
ret = c_connector_dict[res];
|
||||||
} else {
|
} else {
|
||||||
ret = t_connector_dict[res];
|
ret = t_connector_dict[rotate][res];
|
||||||
|
ret.rotate = rotate;
|
||||||
};
|
};
|
||||||
ret.img = "../media/atree/highlight_" + type + ret.attrib + ".png";
|
ret.img = "../media/atree/highlight_" + type + ret.attrib + ".png";
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -2905,14 +2905,12 @@ const atrees = {
|
||||||
|
|
||||||
{
|
{
|
||||||
"display_name": "Provoke",
|
"display_name": "Provoke",
|
||||||
"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. Reduce the Mana cost of War Scream",
|
||||||
"archetype": "Paladin",
|
|
||||||
"archetype_req": 0,
|
|
||||||
"base_abil": "War Scream",
|
"base_abil": "War Scream",
|
||||||
"parents": ["Aerodynamics", "Mantle of the Bovemists"],
|
"parents": ["Aerodynamics", "Mantle of the Bovemists"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"blockers": [],
|
"blockers": [],
|
||||||
"cost": 1,
|
"cost": 2,
|
||||||
"display": {
|
"display": {
|
||||||
"row": 17,
|
"row": 17,
|
||||||
"col": 7,
|
"col": 7,
|
||||||
|
|
File diff suppressed because one or more lines are too long
BIN
media/atree/highlight_t_2_a_f.png
Normal file
BIN
media/atree/highlight_t_2_a_f.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
Loading…
Add table
Reference in a new issue