Pull info from abil tree; construct spell objects and render them
This commit is contained in:
parent
a42f79afb5
commit
25102100ae
9 changed files with 324 additions and 163 deletions
|
@ -1264,21 +1264,23 @@
|
||||||
<div class = "col">
|
<div class = "col">
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-poison-stats">poison</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-poison-stats">poison</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div id="all-spells-display" class="row row-cols-1 gy-3 text-center scaled-font pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell0-infoAvg">spell1</div>
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell0-infoAvg">spell1</div>
|
||||||
</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</div>
|
||||||
<div class = "col">
|
</div>
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell1-infoAvg">spell2</div>
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell1-infoAvg">spell2</div>
|
||||||
</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</div>
|
||||||
<div class = "col">
|
</div>
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell2-infoAvg">spell3</div>
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell2-infoAvg">spell3</div>
|
||||||
</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</div>
|
||||||
<div class = "col">
|
</div>
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell3-infoAvg">spell4</div>
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell3-infoAvg">spell4</div>
|
||||||
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col">
|
||||||
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
||||||
|
|
181
js/atree.js
181
js/atree.js
|
@ -50,6 +50,7 @@ add_spell_prop: {
|
||||||
|
|
||||||
convert_spell_conv: {
|
convert_spell_conv: {
|
||||||
"type": "convert_spell_conv",
|
"type": "convert_spell_conv",
|
||||||
|
"base_spell": int
|
||||||
"target_part": "all" | str,
|
"target_part": "all" | str,
|
||||||
"conversion": element_str
|
"conversion": element_str
|
||||||
}
|
}
|
||||||
|
@ -87,35 +88,35 @@ const default_abils = {
|
||||||
id: 999,
|
id: 999,
|
||||||
desc: "Mage basic attack.",
|
desc: "Mage basic attack.",
|
||||||
properties: {range: 5000},
|
properties: {range: 5000},
|
||||||
effects: [default_spells.wand]
|
effects: [default_spells.wand[0]]
|
||||||
}],
|
}],
|
||||||
spear: [{
|
spear: [{
|
||||||
display_name: "Warrior Melee",
|
display_name: "Warrior Melee",
|
||||||
id: 999,
|
id: 999,
|
||||||
desc: "Warrior basic attack.",
|
desc: "Warrior basic attack.",
|
||||||
properties: {range: 2},
|
properties: {range: 2},
|
||||||
effects: [default_spells.spear]
|
effects: [default_spells.spear[0]]
|
||||||
}],
|
}],
|
||||||
bow: [{
|
bow: [{
|
||||||
display_name: "Archer Melee",
|
display_name: "Archer Melee",
|
||||||
id: 999,
|
id: 999,
|
||||||
desc: "Archer basic attack.",
|
desc: "Archer basic attack.",
|
||||||
properties: {range: 20},
|
properties: {range: 20},
|
||||||
effects: [default_spells.bow]
|
effects: [default_spells.bow[0]]
|
||||||
}],
|
}],
|
||||||
dagger: [{
|
dagger: [{
|
||||||
display_name: "Assassin Melee",
|
display_name: "Assassin Melee",
|
||||||
id: 999,
|
id: 999,
|
||||||
desc: "Assassin basic attack.",
|
desc: "Assassin basic attack.",
|
||||||
properties: {range: 2},
|
properties: {range: 2},
|
||||||
effects: [default_spells.dagger]
|
effects: [default_spells.dagger[0]]
|
||||||
}],
|
}],
|
||||||
relik: [{
|
relik: [{
|
||||||
display_name: "Shaman Melee",
|
display_name: "Shaman Melee",
|
||||||
id: 999,
|
id: 999,
|
||||||
desc: "Shaman basic attack.",
|
desc: "Shaman basic attack.",
|
||||||
properties: {range: 15, speed: 0},
|
properties: {range: 15, speed: 0},
|
||||||
effects: [default_spells.relik]
|
effects: [default_spells.relik[0]]
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -216,6 +217,11 @@ function topological_sort_tree(tree, res, mark_state) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect abilities and condense them into a list of "final abils".
|
* Collect abilities and condense them into a list of "final abils".
|
||||||
|
* This is just for rendering purposes, and for collecting things that modify spells into one chunk.
|
||||||
|
* I stg if wynn makes abils that modify multiple spells
|
||||||
|
* ... well we can extend this by making `base_abil` a list instead but annoy
|
||||||
|
*
|
||||||
|
* Signature: AbilityTreeMergeNode(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'); }
|
||||||
|
@ -246,7 +252,6 @@ const atree_merge = new (class extends ComputeNode {
|
||||||
// Merge abilities.
|
// Merge abilities.
|
||||||
// TODO: What if there is more than one base abil?
|
// TODO: What if there is more than one base abil?
|
||||||
let base_abil = abils_merged.get(abil.base_abil);
|
let base_abil = abils_merged.get(abil.base_abil);
|
||||||
console.log(base_abil);
|
|
||||||
if (Array.isArray(abil.desc)) { base_abil.desc = base_abil.desc.concat(abil.desc); }
|
if (Array.isArray(abil.desc)) { base_abil.desc = base_abil.desc.concat(abil.desc); }
|
||||||
else { base_abil.desc.push(abil.desc); }
|
else { base_abil.desc.push(abil.desc); }
|
||||||
|
|
||||||
|
@ -269,7 +274,171 @@ 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...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect spells from abilities.
|
||||||
|
*
|
||||||
|
* Signature: AbilityCollectSpellsNode(atree-merged: Map[id, Ability]) => List[Spell]
|
||||||
|
*/
|
||||||
|
const atree_collect_spells = new (class extends ComputeNode {
|
||||||
|
constructor() { super('atree-spell-collector'); }
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
if (input_map.size !== 1) { throw "AbilityTreeCollectSpellsNode accepts exactly one input (atree-merged)"; }
|
||||||
|
const [atree_merged] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
|
||||||
|
|
||||||
|
let ret_spells = new Map();
|
||||||
|
for (const [abil_id, abil] of atree_merged.entries()) {
|
||||||
|
// TODO: Possibly, make a better way for detecting "spell abilities"?
|
||||||
|
if (abil.effects.length == 0 || abil.effects[0].type !== 'replace_spell') { continue; }
|
||||||
|
|
||||||
|
let ret_spell = deepcopy(abil.effects[0]); // NOTE: do not mutate results of previous steps!
|
||||||
|
const base_spell_id = ret_spell.base_spell;
|
||||||
|
for (const effect of abil.effects) {
|
||||||
|
switch (effect.type) {
|
||||||
|
case 'replace_spell':
|
||||||
|
// replace_spell just replaces all (defined) aspects.
|
||||||
|
for (const key in effect) {
|
||||||
|
ret_spell[key] = effect[key];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
case 'add_spell_prop': {
|
||||||
|
const { base_spell, target_part = null, cost = 0} = effect;
|
||||||
|
if (base_spell !== base_spell_id) { continue; } // TODO: redundant? if we assume abils only affect one spell
|
||||||
|
ret_spell.cost += cost;
|
||||||
|
|
||||||
|
if (target_part === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let found_part = false;
|
||||||
|
for (let part of ret_spell.parts) { // TODO: replace with Map? to avoid this linear search... idk prolly good since its not more verbose to type in json
|
||||||
|
if (part.name === target_part) {
|
||||||
|
if ('display' in effect) {
|
||||||
|
part.display = effect.display;
|
||||||
|
}
|
||||||
|
if ('multipliers' in effect) {
|
||||||
|
for (const [idx, v] of effect.multipliers.entries()) { // python: enumerate()
|
||||||
|
part.multipliers[idx] += v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ('power' in effect) {
|
||||||
|
part.power += effect.power;
|
||||||
|
}
|
||||||
|
else if ('hits' in effect) {
|
||||||
|
for (const [idx, v] of Object.entries(effect.hits)) { // looks kinda similar to multipliers case... hmm... can we unify all of these three? (make healpower a list)
|
||||||
|
part.hits[idx] += v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw "uhh invalid spell add effect";
|
||||||
|
}
|
||||||
|
found_part = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found_part) { // add part.
|
||||||
|
let spell_part = deepcopy(effect);
|
||||||
|
spell_part.name = target_part; // has some extra fields but whatever
|
||||||
|
ret_spell.parts.push(spell_part);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case 'convert_spell_conv':
|
||||||
|
const { base_spell, target_part, conversion } = effect;
|
||||||
|
if (base_spell !== base_spell_id) { continue; } // TODO: redundant? if we assume abils only affect one spell
|
||||||
|
const elem_idx = damageClasses.indexOf(conversion);
|
||||||
|
let filter = target_part === 'all';
|
||||||
|
for (let part of ret_spell.parts) { // TODO: replace with Map? to avoid this linear search... idk prolly good since its not more verbose to type in json
|
||||||
|
if (filter || part.name === target_part) {
|
||||||
|
if ('multipliers' in part) {
|
||||||
|
let total_conv = 0;
|
||||||
|
for (let i = 1; i < 6; ++i) { // skip neutral
|
||||||
|
total_conv += part.multipliers[i];
|
||||||
|
}
|
||||||
|
let new_conv = [part.multipliers[0], 0, 0, 0, 0, 0];
|
||||||
|
new_conv[elem_idx] = total_conv;
|
||||||
|
part.multipliers = new_conv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret_spells.set(base_spell_id, ret_spell);
|
||||||
|
}
|
||||||
|
return ret_spells;
|
||||||
|
}
|
||||||
|
})().link_to(atree_merge, 'atree-merged');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct compute nodes to link builder items and edit IDs to the appropriate display outputs.
|
||||||
|
* To make things a bit cleaner, the compute graph structure goes like
|
||||||
|
* [builder, build stats] -> [one agg node that is just a passthrough] -> all the spell calc nodes
|
||||||
|
* This way, when things have to be deleted i can just delete one node from the dependencies of builder/build stats...
|
||||||
|
* thats the idea anyway.
|
||||||
|
*
|
||||||
|
* Whenever this is updated, it forces an update of all the newly created spell nodes (if the build is clean).
|
||||||
|
*
|
||||||
|
* Signature: AbilityEnsureSpellsNodes(spells: Map[id, Spell]) => null
|
||||||
|
*/
|
||||||
|
class AbilityTreeEnsureNodesNode extends ComputeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kinda "hyper-node": Constructor takes nodes that should be linked to (build node and stat agg node)
|
||||||
|
*/
|
||||||
|
constructor(build_node, stat_agg_node) {
|
||||||
|
super('atree-make-nodes');
|
||||||
|
this.build_node = build_node;
|
||||||
|
this.stat_agg_node = stat_agg_node;
|
||||||
|
// Slight amount of wasted compute to keep internal state non-changing.
|
||||||
|
this.passthrough = new PassThroughNode('atree-make-nodes_internal').link_to(this.build_node, 'build').link_to(this.stat_agg_node, 'stats');
|
||||||
|
this.spelldmg_nodes = []; // debugging use
|
||||||
|
this.spell_display_elem = document.getElementById("all-spells-display");
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
console.log('atree make nodes');
|
||||||
|
this.passthrough.remove_link(this.build_node);
|
||||||
|
this.passthrough.remove_link(this.stat_agg_node);
|
||||||
|
this.passthrough = new PassThroughNode('atree-make-nodes_internal').link_to(this.build_node, 'build').link_to(this.stat_agg_node, 'stats');
|
||||||
|
this.spell_display_elem.textContent = "";
|
||||||
|
const build_node = this.passthrough.get_node('build'); // aaaaaaaaa performance... savings... help....
|
||||||
|
const stat_agg_node = this.passthrough.get_node('stats');
|
||||||
|
|
||||||
|
const spell_map = input_map.get('spells'); // TODO: is this gonna need more? idk...
|
||||||
|
// TODO shortcut update path for sliders
|
||||||
|
|
||||||
|
for (const [spell_id, spell] of spell_map.entries()) {
|
||||||
|
let spell_node = new SpellSelectNode(spell);
|
||||||
|
spell_node.link_to(build_node, 'build');
|
||||||
|
|
||||||
|
let calc_node = new SpellDamageCalcNode(spell.base_spell);
|
||||||
|
calc_node.link_to(build_node, 'build').link_to(stat_agg_node, 'stats')
|
||||||
|
.link_to(spell_node, 'spell-info');
|
||||||
|
this.spelldmg_nodes.push(calc_node);
|
||||||
|
|
||||||
|
let display_elem = document.createElement('div');
|
||||||
|
display_elem.classList.add("col", "pe-0");
|
||||||
|
// TODO: just pass these elements into the display node instead of juggling the raw IDs...
|
||||||
|
let spell_summary = document.createElement('div'); spell_summary.setAttribute('id', "spell"+spell.base_spell+"-infoAvg");
|
||||||
|
spell_summary.classList.add("col", "spell-display", "spell-expand", "dark-5", "rounded", "dark-shadow", "pt-2", "border", "border-dark");
|
||||||
|
let spell_detail = document.createElement('div'); spell_detail.setAttribute('id', "spell"+spell.base_spell+"-info");
|
||||||
|
spell_detail.classList.add("col", "spell-display", "dark-5", "rounded", "dark-shadow", "py-2");
|
||||||
|
spell_detail.style.display = "none";
|
||||||
|
|
||||||
|
display_elem.appendChild(spell_summary); display_elem.appendChild(spell_detail);
|
||||||
|
|
||||||
|
let display_node = new SpellDisplayNode(spell.base_spell);
|
||||||
|
display_node.link_to(stat_agg_node, 'stats');
|
||||||
|
display_node.link_to(spell_node, 'spell-info');
|
||||||
|
display_node.link_to(calc_node, 'spell-damage');
|
||||||
|
|
||||||
|
this.spell_display_elem.appendChild(display_elem);
|
||||||
|
}
|
||||||
|
this.passthrough.mark_dirty().update(); // Force update once.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The main function for rendering an ability tree.
|
/** The main function for rendering an ability tree.
|
||||||
*
|
*
|
||||||
|
|
|
@ -623,7 +623,7 @@ const atrees = {
|
||||||
{
|
{
|
||||||
"type": "convert_spell_conv",
|
"type": "convert_spell_conv",
|
||||||
"target_part": "all",
|
"target_part": "all",
|
||||||
"conversion": "thunder"
|
"conversion": "Thunder"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -808,7 +808,7 @@ const atrees = {
|
||||||
"base_spell": 5,
|
"base_spell": 5,
|
||||||
"spell_type": "damage",
|
"spell_type": "damage",
|
||||||
"scaling": "spell",
|
"scaling": "spell",
|
||||||
"display": "One Focus",
|
"display": "DPS",
|
||||||
"cost": 0,
|
"cost": 0,
|
||||||
|
|
||||||
"parts": [
|
"parts": [
|
||||||
|
@ -818,7 +818,7 @@ const atrees = {
|
||||||
"multipliers": [10, 0, 0, 5, 0, 0]
|
"multipliers": [10, 0, 0, 5, 0, 0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "One Focus",
|
"name": "DPS",
|
||||||
"type": "total",
|
"type": "total",
|
||||||
"hits": {
|
"hits": {
|
||||||
"Single Arrow": 20
|
"Single Arrow": 20
|
||||||
|
@ -828,7 +828,7 @@ const atrees = {
|
||||||
"name": "Total Damage",
|
"name": "Total Damage",
|
||||||
"type": "total",
|
"type": "total",
|
||||||
"hits": {
|
"hits": {
|
||||||
"One Focus": 7
|
"DPS": 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2128,8 +2128,7 @@ const atrees = {
|
||||||
"target_part": "Total Damage",
|
"target_part": "Total Damage",
|
||||||
"cost": 0,
|
"cost": 0,
|
||||||
"hits": {
|
"hits": {
|
||||||
"name": "Single Hit",
|
"Single Hit": 1
|
||||||
"value": 1
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2145,8 +2144,6 @@ const atrees = {
|
||||||
{
|
{
|
||||||
"display_name": "Charge",
|
"display_name": "Charge",
|
||||||
"desc": "Charge forward at high speed (hold shift to cancel)",
|
"desc": "Charge forward at high speed (hold shift to cancel)",
|
||||||
"archetype": "",
|
|
||||||
"archetype_req": 0,
|
|
||||||
"parents": ["Double Bash"],
|
"parents": ["Double Bash"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"blockers": [],
|
"blockers": [],
|
||||||
|
@ -2156,34 +2153,23 @@ const atrees = {
|
||||||
"col": 4,
|
"col": 4,
|
||||||
"icon": "node_4"
|
"icon": "node_4"
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {},
|
||||||
},
|
"effects": [{
|
||||||
"effects": [
|
"type": "replace_spell",
|
||||||
{
|
"name": "Charge",
|
||||||
"type": "replace_spell",
|
"cost": 25,
|
||||||
"name": "Charge",
|
"display_text": "Total Damage Average",
|
||||||
"cost": 25,
|
"base_spell": 2,
|
||||||
"display_text": "Total Damage Average",
|
"spell_type": "damage",
|
||||||
"base_spell": 2,
|
"scaling": "spell",
|
||||||
"spell_type": "damage",
|
"display": "Total Damage",
|
||||||
"scaling": "spell",
|
"parts": [
|
||||||
"display": "Total Damage",
|
{
|
||||||
"parts": [
|
"name": "Total Damage",
|
||||||
{
|
"hits": {}
|
||||||
"name": "None",
|
}
|
||||||
"type": "damage",
|
]
|
||||||
"multipliers": [0, 0, 0, 0, 0, 0]
|
}]
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Total Damage",
|
|
||||||
"type": "total",
|
|
||||||
"hits": {
|
|
||||||
"None": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -2307,9 +2293,7 @@ const atrees = {
|
||||||
{
|
{
|
||||||
"display_name": "Uppercut",
|
"display_name": "Uppercut",
|
||||||
"desc": "Rocket enemies in the air and deal massive damage",
|
"desc": "Rocket enemies in the air and deal massive damage",
|
||||||
"archetype": "",
|
"parents": ["Vehement", "Cheaper Charge"],
|
||||||
"archetype_req": 0,
|
|
||||||
"parents": ["Vehement"],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"blockers": [],
|
"blockers": [],
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
|
@ -2331,19 +2315,15 @@ const atrees = {
|
||||||
"base_spell": 3,
|
"base_spell": 3,
|
||||||
"spell_type": "damage",
|
"spell_type": "damage",
|
||||||
"scaling": "spell",
|
"scaling": "spell",
|
||||||
"display": "total",
|
"display": "Total Damage",
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"name": "Uppercut",
|
"name": "Uppercut",
|
||||||
"type": "damage",
|
|
||||||
"multipliers": [150, 50, 50, 0, 0, 0]
|
"multipliers": [150, 50, 50, 0, 0, 0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Total Damage",
|
"name": "Total Damage",
|
||||||
"type": "total",
|
"hits": { "Uppercut": 1 }
|
||||||
"hits": {
|
|
||||||
"Uppercut": 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2353,8 +2333,7 @@ const atrees = {
|
||||||
{
|
{
|
||||||
"display_name": "Cheaper Charge",
|
"display_name": "Cheaper Charge",
|
||||||
"desc": "Reduce the Mana cost of Charge",
|
"desc": "Reduce the Mana cost of Charge",
|
||||||
"archetype": "",
|
"base_abil": "Charge",
|
||||||
"archetype_req": 0,
|
|
||||||
"parents": ["Uppercut", "War Scream"],
|
"parents": ["Uppercut", "War Scream"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"blockers": [],
|
"blockers": [],
|
||||||
|
@ -2378,9 +2357,7 @@ const atrees = {
|
||||||
{
|
{
|
||||||
"display_name": "War Scream",
|
"display_name": "War Scream",
|
||||||
"desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",
|
"desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",
|
||||||
"archetype": "",
|
"parents": ["Tougher Skin", "Cheaper Charge"],
|
||||||
"archetype_req": 0,
|
|
||||||
"parents": ["Tougher Skin"],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"blockers": [],
|
"blockers": [],
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
|
@ -2407,8 +2384,11 @@ const atrees = {
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"name": "War Scream",
|
"name": "War Scream",
|
||||||
"type": "damage",
|
|
||||||
"multipliers": [50, 0, 0, 0, 50, 0]
|
"multipliers": [50, 0, 0, 0, 50, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Total Damage Average",
|
||||||
|
"hits": { "War Scream": 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2687,7 +2667,7 @@ const atrees = {
|
||||||
{
|
{
|
||||||
"type": "convert_spell_conv",
|
"type": "convert_spell_conv",
|
||||||
"target_part": "all",
|
"target_part": "all",
|
||||||
"conversion": "water"
|
"conversion": "Water"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2826,11 +2806,17 @@ const atrees = {
|
||||||
},
|
},
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
"type": "add_spell_prop",
|
"type": "replace_spell",
|
||||||
|
"name": "Counter",
|
||||||
|
"display_text": "Counter",
|
||||||
"base_spell": 5,
|
"base_spell": 5,
|
||||||
"target_part": "Counter",
|
"display": "Counter Damage",
|
||||||
"cost": 0,
|
"parts": [
|
||||||
"multipliers": [60, 0, 20, 0, 0, 20]
|
{
|
||||||
|
"name": "Counter Damage",
|
||||||
|
"multipliers": [60, 0, 20, 0, 0, 20]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3892,7 +3878,7 @@ const atrees = {
|
||||||
{
|
{
|
||||||
"type": "convert_spell_conv",
|
"type": "convert_spell_conv",
|
||||||
"target_part": "all",
|
"target_part": "all",
|
||||||
"conversion": "thunder"
|
"conversion": "Thunder"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "raw_stat",
|
"type": "raw_stat",
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -149,17 +149,6 @@ function toggle_tab(tab) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggle spell arrow
|
|
||||||
function toggle_spell_tab(tab) {
|
|
||||||
let arrow_img = document.querySelector("#" + "arrow_" + tab + "Avg");
|
|
||||||
if (document.querySelector("#"+tab).style.display == "none") {
|
|
||||||
document.querySelector("#"+tab).style.display = "";
|
|
||||||
arrow_img.src = arrow_img.src.replace("down", "up");
|
|
||||||
} else {
|
|
||||||
document.querySelector("#"+tab).style.display = "none";
|
|
||||||
arrow_img.src = arrow_img.src.replace("up", "down");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_boost_tab(tab) {
|
function toggle_boost_tab(tab) {
|
||||||
for (const i of skp_order) {
|
for (const i of skp_order) {
|
||||||
|
@ -396,16 +385,12 @@ function collapse_element(elmnt) {
|
||||||
document.querySelector(elmnt).style.removeProperty('display');
|
document.querySelector(elmnt).style.removeProperty('display');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Learn and use await
|
|
||||||
function init() {
|
function init() {
|
||||||
console.log("builder.js init");
|
console.log("builder.js init");
|
||||||
init_autocomplete();
|
init_autocomplete();
|
||||||
|
|
||||||
// Other "main" stuff
|
// Other "main" stuff
|
||||||
// Spell dropdowns
|
// Spell dropdowns
|
||||||
for (const i of spell_disp) {
|
|
||||||
document.querySelector("#"+i+"Avg").addEventListener("click", () => toggle_spell_tab(i));
|
|
||||||
}
|
|
||||||
for (const eq of equipment_keys) {
|
for (const eq of equipment_keys) {
|
||||||
document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip'));
|
document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -468,41 +468,17 @@ class PowderInputNode extends InputNode {
|
||||||
* Signature: SpellSelectNode<int>(build: Build) => [Spell, SpellParts]
|
* Signature: SpellSelectNode<int>(build: Build) => [Spell, SpellParts]
|
||||||
*/
|
*/
|
||||||
class SpellSelectNode extends ComputeNode {
|
class SpellSelectNode extends ComputeNode {
|
||||||
// TODO: rewrite me entirely...
|
constructor(spell) {
|
||||||
constructor(spell_num) {
|
super("builder-spell"+spell.base_spell+"-select");
|
||||||
super("builder-spell"+spell_num+"-select");
|
this.spell = spell;
|
||||||
this.spell_idx = spell_num;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
const build = input_map.get('build');
|
const build = input_map.get('build');
|
||||||
let stats = build.statMap;
|
let stats = build.statMap;
|
||||||
|
// TODO: apply major ids... DOOM.....
|
||||||
|
|
||||||
const i = this.spell_idx;
|
return [this.spell, this.spell.parts];
|
||||||
const spells = default_spells[build.weapon.statMap.get("type")];
|
|
||||||
let spell;
|
|
||||||
for (const _spell of spells) {
|
|
||||||
if (_spell.base_spell === i) {
|
|
||||||
spell = _spell;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (spell === undefined) { return null; }
|
|
||||||
|
|
||||||
let spell_parts;
|
|
||||||
if (spell.parts) {
|
|
||||||
spell_parts = spell.parts;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
spell_parts = spell.variants.DEFAULT;
|
|
||||||
for (const majorID of stats.get("activeMajorIDs")) {
|
|
||||||
if (majorID in spell.variants) {
|
|
||||||
spell_parts = spell.variants[majorID];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [spell, spell_parts];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -995,15 +971,15 @@ class SumNumberInputNode extends InputNode {
|
||||||
|
|
||||||
let item_nodes = [];
|
let item_nodes = [];
|
||||||
let powder_nodes = [];
|
let powder_nodes = [];
|
||||||
let spelldmg_nodes = [];
|
|
||||||
let edit_input_nodes = [];
|
let edit_input_nodes = [];
|
||||||
let skp_inputs = [];
|
let skp_inputs = [];
|
||||||
let build_node;
|
let build_node;
|
||||||
let stat_agg_node;
|
let stat_agg_node;
|
||||||
let edit_agg_node;
|
let edit_agg_node;
|
||||||
|
let atree_graph_creator;
|
||||||
|
|
||||||
function builder_graph_init() {
|
function builder_graph_init() {
|
||||||
// Phase 1/2: Set up item input, propagate updates, etc.
|
// Phase 1/3: Set up item input, propagate updates, etc.
|
||||||
|
|
||||||
// Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff).
|
// Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff).
|
||||||
for (const [eq, display_elem, none_item] of zip3(equipment_fields, build_fields, none_items)) {
|
for (const [eq, display_elem, none_item] of zip3(equipment_fields, build_fields, none_items)) {
|
||||||
|
@ -1060,7 +1036,7 @@ function builder_graph_init() {
|
||||||
item_nodes[3].link_to(powder_nodes[3], 'powdering');
|
item_nodes[3].link_to(powder_nodes[3], 'powdering');
|
||||||
item_nodes[8].link_to(powder_nodes[4], 'powdering');
|
item_nodes[8].link_to(powder_nodes[4], 'powdering');
|
||||||
|
|
||||||
// Phase 2/2: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage
|
// Phase 2/3: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage
|
||||||
|
|
||||||
let build_disp_node = new BuildDisplayNode()
|
let build_disp_node = new BuildDisplayNode()
|
||||||
build_disp_node.link_to(build_node, 'build');
|
build_disp_node.link_to(build_node, 'build');
|
||||||
|
@ -1092,9 +1068,18 @@ function builder_graph_init() {
|
||||||
}
|
}
|
||||||
stat_agg_node.link_to(edit_agg_node);
|
stat_agg_node.link_to(edit_agg_node);
|
||||||
build_disp_node.link_to(stat_agg_node, 'stats');
|
build_disp_node.link_to(stat_agg_node, 'stats');
|
||||||
|
|
||||||
|
// Phase 3/3: Set up atree stuff.
|
||||||
|
|
||||||
|
// These two are defined in `atree.js`
|
||||||
atree_node.link_to(build_node, 'build');
|
atree_node.link_to(build_node, 'build');
|
||||||
atree_merge.link_to(build_node, 'build');
|
atree_merge.link_to(build_node, 'build');
|
||||||
|
atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node)
|
||||||
|
.link_to(atree_collect_spells, 'spells');
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// Trigger the update cascade for build!
|
||||||
|
// ---------------------------------------------------------------
|
||||||
for (const input_node of item_nodes.concat(powder_nodes)) {
|
for (const input_node of item_nodes.concat(powder_nodes)) {
|
||||||
input_node.update();
|
input_node.update();
|
||||||
}
|
}
|
||||||
|
@ -1113,23 +1098,6 @@ function builder_graph_init() {
|
||||||
|
|
||||||
// Also do something similar for skill points
|
// Also do something similar for skill points
|
||||||
|
|
||||||
//for (let i = 0; i < 4; ++i) { TODO: testing code
|
|
||||||
for (let i = 0; i < 1; ++i) {
|
|
||||||
let spell_node = new SpellSelectNode(i);
|
|
||||||
spell_node.link_to(build_node, 'build');
|
|
||||||
// TODO: link and rewrite spell_node to the stat agg node
|
|
||||||
spell_node.link_to(stat_agg_node, 'stats')
|
|
||||||
|
|
||||||
let calc_node = new SpellDamageCalcNode(i);
|
|
||||||
calc_node.link_to(build_node, 'build').link_to(stat_agg_node, 'stats')
|
|
||||||
.link_to(spell_node, 'spell-info');
|
|
||||||
spelldmg_nodes.push(calc_node);
|
|
||||||
|
|
||||||
let display_node = new SpellDisplayNode(i);
|
|
||||||
display_node.link_to(stat_agg_node, 'stats'); // TODO: same here..
|
|
||||||
display_node.link_to(spell_node, 'spell-info');
|
|
||||||
display_node.link_to(calc_node, 'spell-damage');
|
|
||||||
}
|
|
||||||
for (const node of edit_input_nodes) {
|
for (const node of edit_input_nodes) {
|
||||||
node.update();
|
node.update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ class ComputeNode {
|
||||||
const idx = this.inputs.indexOf(parent_node); // Get idx
|
const idx = this.inputs.indexOf(parent_node); // Get idx
|
||||||
this.inputs.splice(idx, 1); // remove element
|
this.inputs.splice(idx, 1); // remove element
|
||||||
|
|
||||||
this.input_translations.delete(parent_node.name);
|
this.input_translation.delete(parent_node.name);
|
||||||
const was_dirty = this.inputs_dirty.get(parent_node.name);
|
const was_dirty = this.inputs_dirty.get(parent_node.name);
|
||||||
this.inputs_dirty.delete(parent_node.name);
|
this.inputs_dirty.delete(parent_node.name);
|
||||||
if (was_dirty) {
|
if (was_dirty) {
|
||||||
|
@ -173,3 +173,42 @@ class InputNode extends ComputeNode {
|
||||||
return this.input_field.value;
|
return this.input_field.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passthrough node for simple aggregation.
|
||||||
|
* Unfortunately if you use this too much you get layers and layers of maps...
|
||||||
|
*
|
||||||
|
* Signature: PassThroughNode(**kwargs) => Map[...]
|
||||||
|
*/
|
||||||
|
class PassThroughNode extends ComputeNode {
|
||||||
|
constructor(name) {
|
||||||
|
super(name);
|
||||||
|
this.breakout_nodes = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
return input_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a ComputeNode that will "break out" one part of this aggregation input.
|
||||||
|
* There is some overhead to this operation because ComputeNode is not exactly a free abstraction... oof
|
||||||
|
* Also you will recv updates whenever any input that is part of the aggregation changes even
|
||||||
|
* if the specific sub-input didn't change.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* sub-input: The key to listen to
|
||||||
|
*/
|
||||||
|
get_node(sub_input) {
|
||||||
|
if (this.breakout_nodes.has(sub_input)) {
|
||||||
|
return this.breakout_nodes.get(sub_input);
|
||||||
|
}
|
||||||
|
const _name = this.name;
|
||||||
|
const ret = new (class extends ComputeNode {
|
||||||
|
constructor() { super('passthrough-'+_name+'-'+sub_input); }
|
||||||
|
compute_func(input_map) { return input_map.get(_name).get(sub_input); }
|
||||||
|
})().link_to(this);
|
||||||
|
this.breakout_nodes.set(sub_input, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
||||||
if (conversions[i] > 0) {
|
if (conversions[i] > 0) {
|
||||||
const conv_frac = conversions[i]/100;
|
const conv_frac = conversions[i]/100;
|
||||||
damages[i][0] += conv_frac * weapon_min;
|
damages[i][0] += conv_frac * weapon_min;
|
||||||
damages[i][1] += conf_frac * weapon_max;
|
damages[i][1] += conv_frac * weapon_max;
|
||||||
present[i] = true;
|
present[i] = true;
|
||||||
total_convert += conv_frac
|
total_convert += conv_frac
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,8 @@ function displaySetBonuses(parent_id,build) {
|
||||||
set_summary_elem.append(set_elem);
|
set_summary_elem.append(set_elem);
|
||||||
|
|
||||||
const bonus = active_set.bonuses[count-1];
|
const bonus = active_set.bonuses[count-1];
|
||||||
let mock_item = new Map();
|
let mock_item = new Map([["fixID", true],
|
||||||
mock_item.set("fixID", true);
|
["displayName", setName+" Set: "+count+"/"+sets.get(setName).items.length]]);
|
||||||
mock_item.set("displayName", setName+" Set: "+count+"/"+sets.get(setName).items.length);
|
|
||||||
let mock_minRolls = new Map();
|
let mock_minRolls = new Map();
|
||||||
let mock_maxRolls = new Map();
|
let mock_maxRolls = new Map();
|
||||||
mock_item.set("minRolls", mock_minRolls);
|
mock_item.set("minRolls", mock_minRolls);
|
||||||
|
@ -1216,7 +1215,7 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) {
|
||||||
critStats.append(critChance);
|
critStats.append(critChance);
|
||||||
|
|
||||||
parent_elem.append(critStats);
|
parent_elem.append(critStats);
|
||||||
addClickableArrow(overallparent_elem);
|
addClickableArrow(overallparent_elem, parent_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayDefenseStats(parent_elem, statMap, insertSummary){
|
function displayDefenseStats(parent_elem, statMap, insertSummary){
|
||||||
|
@ -1567,15 +1566,15 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpellCost(stats, spellIdx, cost) {
|
function getSpellCost(stats, spell) {
|
||||||
return Math.max(1, getBaseSpellCost(stats, spellIdx, cost));
|
return Math.max(1, getBaseSpellCost(stats, spell));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBaseSpellCost(stats, spellIdx, cost) {
|
function getBaseSpellCost(stats, spell) {
|
||||||
// old intelligence:
|
// old intelligence:
|
||||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(stats.get('int'))));
|
let cost = spell.cost; //Math.ceil(spell.cost * (1 - skillPointsToPercentage(stats.get('int'))));
|
||||||
cost += stats.get("spRaw"+spellIdx);
|
cost += stats.get("spRaw"+spell.base_spell);
|
||||||
return Math.floor(cost * (1 + stats.get("spPct"+spellIdx) / 100));
|
return Math.floor(cost * (1 + stats.get("spPct"+spell.base_spell) / 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1591,12 +1590,12 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
||||||
|
|
||||||
if ('cost' in spell) {
|
if ('cost' in spell) {
|
||||||
let first = document.createElement("span");
|
let first = document.createElement("span");
|
||||||
first.textContent = spell.title + " (";
|
first.textContent = spell.name + " (";
|
||||||
title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here.
|
title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here.
|
||||||
title_elemavg.appendChild(first);
|
title_elemavg.appendChild(first);
|
||||||
|
|
||||||
let second = document.createElement("span");
|
let second = document.createElement("span");
|
||||||
second.textContent = getSpellCost(stats, spellIdx, spell.cost);
|
second.textContent = getSpellCost(stats, spell);
|
||||||
second.classList.add("Mana");
|
second.classList.add("Mana");
|
||||||
|
|
||||||
title_elem.appendChild(second.cloneNode(true));
|
title_elem.appendChild(second.cloneNode(true));
|
||||||
|
@ -1618,9 +1617,10 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
||||||
parent_elem.append(title_elem);
|
parent_elem.append(title_elem);
|
||||||
overallparent_elem.append(title_elemavg);
|
overallparent_elem.append(title_elemavg);
|
||||||
|
|
||||||
if ('cost' in spell) {
|
// if ('cost' in spell) {
|
||||||
overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
// :( ...... ?
|
||||||
}
|
// overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
||||||
|
// }
|
||||||
|
|
||||||
let critChance = skillPointsToPercentage(stats.get('dex'));
|
let critChance = skillPointsToPercentage(stats.get('dex'));
|
||||||
|
|
||||||
|
@ -1695,7 +1695,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addClickableArrow(overallparent_elem);
|
addClickableArrow(overallparent_elem, parent_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays the ID costs of an item
|
/** Displays the ID costs of an item
|
||||||
|
@ -2108,11 +2108,23 @@ function stringCDF(id,val,base,amp) {
|
||||||
document.getElementById(id + "-cdf").appendChild(b3);
|
document.getElementById(id + "-cdf").appendChild(b3);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addClickableArrow(elem) {
|
function addClickableArrow(elem, target) {
|
||||||
//up and down arrow - done ugly
|
//up and down arrow - done ugly
|
||||||
let arrow = document.createElement("img");
|
let arrow = document.createElement("img");
|
||||||
arrow.id = "arrow_" + elem.id;
|
arrow.id = "arrow_" + elem.id;
|
||||||
arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem";
|
arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem";
|
||||||
arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png";
|
arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png";
|
||||||
elem.appendChild(arrow);
|
elem.appendChild(arrow);
|
||||||
|
arrow.addEventListener("click", () => toggle_spell_tab(arrow, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggle arrow thinger
|
||||||
|
function toggle_spell_tab(arrow_img, target) {
|
||||||
|
if (target.style.display == "none") {
|
||||||
|
target.style.display = "";
|
||||||
|
arrow_img.src = arrow_img.src.replace("down", "up");
|
||||||
|
} else {
|
||||||
|
target.style.display = "none";
|
||||||
|
arrow_img.src = arrow_img.src.replace("up", "down");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue