fixed merge conflict
This commit is contained in:
commit
1ed723f9b5
9 changed files with 360 additions and 183 deletions
|
@ -618,10 +618,10 @@
|
|||
</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: 500px; overflow-y: auto;">
|
||||
<div class="col border border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 50vh; overflow-y: auto;">
|
||||
|
||||
</div>
|
||||
<div class="col mx-auto" style="height: 500px; overflow-y: auto;" id="atree-active">
|
||||
<div class="col mx-auto" style="height: 50vh; overflow-y: auto;" id="atree-active">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
43
js/atree.js
43
js/atree.js
|
@ -53,12 +53,6 @@ const atree_render = new (class extends ComputeNode {
|
|||
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
|
||||
//as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff.
|
||||
// TODO: FIXME! this is a side effect of `px` based rendering.
|
||||
if (!document.getElementById("toggle-atree").classList.contains("toggleOn")) {
|
||||
toggle_tab('atree-dropdown');
|
||||
toggleButton('toggle-atree');
|
||||
}
|
||||
|
||||
//for some reason we have to cast to string
|
||||
if (atree) { render_AT(document.getElementById("atree-ui"), document.getElementById("atree-active"), atree); }
|
||||
|
@ -175,14 +169,10 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
let row = document.createElement('div');
|
||||
row.classList.add("row");
|
||||
row.id = "atree-row-" + j;
|
||||
// TODO: do this more dynamically
|
||||
row.style.minHeight = UI_elem.scrollWidth / 9 + "px";
|
||||
//row.style.minHeight = UI_elem.getBoundingClientRect().width / 9 + "px";
|
||||
|
||||
for (let k = 0; k < 9; k++) {
|
||||
col = document.createElement('div');
|
||||
col.classList.add('col', 'px-0');
|
||||
col.style.minHeight = UI_elem.scrollWidth / 9 + "px";
|
||||
row.appendChild(col);
|
||||
}
|
||||
UI_elem.appendChild(row);
|
||||
|
@ -237,8 +227,11 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
if (icon === undefined) {
|
||||
icon = "node_0";
|
||||
}
|
||||
node_elem.style = "background-image: url('../media/atree/"+icon+".png'); background-size: cover; width: 100%; height: 100%;";
|
||||
node_elem.classList.add("atree-circle", "fake-button");
|
||||
let node_img = document.createElement('img');
|
||||
node_img.src = '../media/atree/'+icon+'.png';
|
||||
node_img.style = "width: 100%; height: 100%;";
|
||||
node_elem.appendChild(node_img);
|
||||
node_elem.classList.add("atree-circle");
|
||||
|
||||
// add node tooltip
|
||||
node_elem.addEventListener('mouseover', function(e) {
|
||||
|
@ -323,6 +316,7 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
let tooltip = this.children[this.children.length - 1];
|
||||
tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px";
|
||||
tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .2 / 2) + "px";
|
||||
tooltip.style.maxWidth = UI_elem.getBoundingClientRect().width * .95 + "px";
|
||||
tooltip.style.display = "block";
|
||||
});
|
||||
|
||||
|
@ -384,8 +378,11 @@ function atree_render_connection(atree_connectors_map) {
|
|||
for (let i of atree_connectors_map.keys()) {
|
||||
let connector_info = atree_connectors_map.get(i);
|
||||
let connector_elem = connector_info.connector;
|
||||
let connector_img = document.createElement('img');
|
||||
set_connector_type(connector_info);
|
||||
connector_elem.style.backgroundImage = "url('../media/atree/connect_"+connector_info.type+".png')";
|
||||
connector_img.src = '../media/atree/connect_'+connector_info.type+'.png';
|
||||
connector_img.style = "width: 100%; height: 100%;"
|
||||
connector_elem.replaceChildren(connector_img);
|
||||
connector_info.highlight = [0, 0, 0, 0];
|
||||
let target_elem = document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]];
|
||||
if (target_elem.children.length != 0) {
|
||||
|
@ -417,7 +414,10 @@ function atree_toggle_state(atree_connectors_map, node_wrapper) {
|
|||
function atree_update_connector() {
|
||||
atree_connectors_map.forEach((v) => {
|
||||
if (v.length != 0) {
|
||||
v[0].connector.style.backgroundImage = "url('../media/atree/connect_" + v[0].type + ".png')";
|
||||
let connector_elem = document.createElement("img");
|
||||
connector_elem.style = "width: 100%; height: 100%;";
|
||||
connector_elem.src = '../media/atree/connect_' + v[0].type + '.png'
|
||||
v[0].replaceChildren(connector_elem);
|
||||
}
|
||||
});
|
||||
atree_map.forEach((v) => {
|
||||
|
@ -439,6 +439,8 @@ function atree_set_edge(atree_connectors_map, parent, child, state) {
|
|||
let connector_info = atree_connectors_map.get(connector_label);
|
||||
let connector_elem = connector_info.connector;
|
||||
let highlight_state = connector_info.highlight; // left right up down
|
||||
let connector_img_elem = document.createElement("img");
|
||||
connector_img_elem.style = "width: 100%; height: 100%;";
|
||||
const ctype = connector_info.type;
|
||||
if (ctype === 't' || ctype === 'c') {
|
||||
// c, t
|
||||
|
@ -460,18 +462,21 @@ function atree_set_edge(atree_connectors_map, parent, child, state) {
|
|||
let render_state = highlight_state.map(x => (x > 0 ? 1 : 0));
|
||||
|
||||
let connector_img = atree_parse_connector(render_state, ctype);
|
||||
connector_img_elem.src = connector_img.img
|
||||
connector_elem.className = "";
|
||||
connector_elem.classList.add("rotate-" + connector_img.rotate);
|
||||
connector_elem.style.backgroundImage = connector_img.img;
|
||||
connector_elem.replaceChildren(connector_img_elem);
|
||||
continue;
|
||||
}
|
||||
// lol bad overloading, [0] is just the whole state
|
||||
highlight_state[0] += state_delta;
|
||||
if (highlight_state[0] > 0) {
|
||||
connector_elem.style.backgroundImage = "url('../media/atree/highlight_"+ctype+".png')";
|
||||
connector_img_elem.src = '../media/atree/highlight_'+ctype+'.png';
|
||||
connector_elem.replaceChildren(connector_img_elem);
|
||||
}
|
||||
else {
|
||||
connector_elem.style.backgroundImage = "url('../media/atree/connect_"+ctype+".png')";
|
||||
connector_img_elem.src = '../media/atree/connect_'+ctype+'.png';
|
||||
connector_elem.replaceChildren(connector_img_elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -506,7 +511,7 @@ function atree_parse_connector(orient, type) {
|
|||
res += i;
|
||||
}
|
||||
if (res === "0000") {
|
||||
return {img: "url('../media/atree/connect_" + type + ".png')", rotate: 0};
|
||||
return {img: "../media/atree/connect_" + type + ".png", rotate: 0};
|
||||
}
|
||||
|
||||
let ret;
|
||||
|
@ -515,6 +520,6 @@ function atree_parse_connector(orient, type) {
|
|||
} else {
|
||||
ret = t_connector_dict[res];
|
||||
};
|
||||
ret.img = "url('../media/atree/highlight_" + type + ret.attrib + ".png')";
|
||||
ret.img = "../media/atree/highlight_" + type + ret.attrib + ".png";
|
||||
return ret;
|
||||
};
|
||||
|
|
|
@ -225,7 +225,7 @@ const atrees = {
|
|||
},
|
||||
{
|
||||
"display_name": "Nimble String",
|
||||
"desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",
|
||||
"desc": "Arrow Storm throw out +6 arrows per stream and shoot twice as fast.",
|
||||
"archetype": "",
|
||||
"archetype_req": 0,
|
||||
"parents": ["Thunder Mastery", "Arrow Rain"],
|
||||
|
@ -253,7 +253,7 @@ const atrees = {
|
|||
"target_part": "Single Stream",
|
||||
"cost": 0,
|
||||
"hits": {
|
||||
"Single Arrow": 8
|
||||
"Single Arrow": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -302,7 +302,7 @@ const atrees = {
|
|||
"name": "Total Damage",
|
||||
"type": "total",
|
||||
"hits": {
|
||||
"Single Stream": 2
|
||||
"Single Stream": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -329,37 +329,37 @@ const atrees = {
|
|||
"count": 2
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"type": "replace_spell",
|
||||
"name": "Guardian Angels",
|
||||
"cost": 30,
|
||||
"display_text": "Total Damage Average",
|
||||
"base_spell": 4,
|
||||
"spell_type": "damage",
|
||||
"scaling": "spell",
|
||||
"display": "Total Damage",
|
||||
"parts": [
|
||||
{
|
||||
"name": "Single Arrow",
|
||||
"type": "damage",
|
||||
"multipliers": [30, 0, 0, 0, 0, 10]
|
||||
},
|
||||
{
|
||||
"name": "Single Bow",
|
||||
"type": "total",
|
||||
"hits": {
|
||||
"Single Arrow": 8
|
||||
{
|
||||
"type": "replace_spell",
|
||||
"name": "Guardian Angels",
|
||||
"cost": 30,
|
||||
"display_text": "Total Damage Average",
|
||||
"base_spell": 4,
|
||||
"spell_type": "damage",
|
||||
"scaling": "spell",
|
||||
"display": "Total Damage",
|
||||
"parts": [
|
||||
{
|
||||
"name": "Single Arrow",
|
||||
"type": "damage",
|
||||
"multipliers": [30, 0, 0, 0, 0, 10]
|
||||
},
|
||||
{
|
||||
"name": "Single Bow",
|
||||
"type": "total",
|
||||
"hits": {
|
||||
"Single Arrow": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Total Damage",
|
||||
"type": "total",
|
||||
"hits": {
|
||||
"Single Bow": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Total Damage",
|
||||
"type": "total",
|
||||
"hits": {
|
||||
"Single Bow": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -436,7 +436,7 @@ const atrees = {
|
|||
"base_spell": 1,
|
||||
"target_part": "Single Arrow",
|
||||
"cost": 0,
|
||||
"multipliers": [-11, 0, -7, 0, 0, 3]
|
||||
"multipliers": [-10, 0, -2, 0, 0, 2]
|
||||
},
|
||||
{
|
||||
"type": "add_spell_prop",
|
||||
|
@ -446,6 +446,15 @@ const atrees = {
|
|||
"hits": {
|
||||
"Single Stream": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_spell_prop",
|
||||
"base_spell": 1,
|
||||
"target_part": "Single Stream",
|
||||
"cost": 0,
|
||||
"hits": {
|
||||
"Single Arrow": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -928,7 +937,7 @@ const atrees = {
|
|||
"col": 1
|
||||
},
|
||||
"properties": {
|
||||
"range": 10,
|
||||
"range": 8,
|
||||
"shots": 5
|
||||
},
|
||||
"effects": [
|
||||
|
@ -937,7 +946,7 @@ const atrees = {
|
|||
"base_spell": 4,
|
||||
"target_part": "Single Arrow",
|
||||
"cost": 0,
|
||||
"multipliers": [0, 0, 0, 0, 20, 0]
|
||||
"multipliers": [0, 0, 0, 0, 10, 0]
|
||||
},
|
||||
{
|
||||
"type": "add_spell_prop",
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -468,6 +468,7 @@ class PowderInputNode extends InputNode {
|
|||
* Signature: SpellSelectNode<int>(build: Build) => [Spell, SpellParts]
|
||||
*/
|
||||
class SpellSelectNode extends ComputeNode {
|
||||
// TODO: rewrite me entirely...
|
||||
constructor(spell_num) {
|
||||
super("builder-spell"+spell_num+"-select");
|
||||
this.spell_idx = spell_num;
|
||||
|
@ -475,10 +476,18 @@ class SpellSelectNode extends ComputeNode {
|
|||
|
||||
compute_func(input_map) {
|
||||
const build = input_map.get('build');
|
||||
let stats = build.statMap;
|
||||
|
||||
const i = this.spell_idx;
|
||||
let spell = spell_table[build.weapon.statMap.get("type")][i];
|
||||
let stats = build.statMap;
|
||||
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) {
|
||||
|
@ -551,6 +560,7 @@ class SpellDamageCalcNode extends ComputeNode {
|
|||
compute_func(input_map) {
|
||||
const weapon = input_map.get('build').weapon.statMap;
|
||||
const spell_info = input_map.get('spell-info');
|
||||
const spell = spell_info[0];
|
||||
const spell_parts = spell_info[1];
|
||||
const stats = input_map.get('stats');
|
||||
const damage_mult = stats.get('damageMultiplier');
|
||||
|
@ -562,23 +572,67 @@ class SpellDamageCalcNode extends ComputeNode {
|
|||
stats.get('agi')
|
||||
];
|
||||
let spell_results = []
|
||||
let spell_result_map = new Map();
|
||||
const use_speed = (('use_atkspd' in spell) ? spell.use_atkspd : true);
|
||||
const use_spell = (('scaling' in spell) ? spell.scaling === 'spell' : true);
|
||||
|
||||
// TODO: move preprocessing to separate node/node chain
|
||||
for (const part of spell_parts) {
|
||||
if (part.type === "damage") {
|
||||
let tmp_conv = [];
|
||||
for (let i in part.conversion) {
|
||||
tmp_conv.push(part.conversion[i] * part.multiplier/100);
|
||||
let spell_result;
|
||||
if ('multipliers' in part) { // damage type spell
|
||||
let results = calculateSpellDamage(stats, weapon, part.multipliers, use_spell, !use_speed);
|
||||
spell_result = {
|
||||
type: "damage",
|
||||
normal_min: results[2].map(x => x[0]),
|
||||
normal_max: results[2].map(x => x[1]),
|
||||
normal_total: results[0],
|
||||
crit_min: results[2].map(x => x[2]),
|
||||
crit_max: results[2].map(x => x[3]),
|
||||
crit_total: results[1],
|
||||
}
|
||||
let results = calculateSpellDamage(stats, weapon, tmp_conv, true);
|
||||
spell_results.push(results);
|
||||
} else if (part.type === "heal") {
|
||||
} else if ('power' in part) {
|
||||
// TODO: wynn2 formula
|
||||
let heal_amount = (part.strength * getDefenseStats(stats)[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2);
|
||||
spell_results.push(heal_amount);
|
||||
} else if (part.type === "total") {
|
||||
// TODO: remove "total" type
|
||||
spell_results.push(null);
|
||||
let _heal_amount = (part.power * getDefenseStats(stats)[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100)));
|
||||
spell_result = {
|
||||
type: "heal",
|
||||
heal_amount: _heal_amount
|
||||
}
|
||||
} else if ('hits' in part) {
|
||||
spell_result = {
|
||||
normal_min: [0, 0, 0, 0, 0, 0],
|
||||
normal_max: [0, 0, 0, 0, 0, 0],
|
||||
normal_total: [0, 0],
|
||||
crit_min: [0, 0, 0, 0, 0, 0],
|
||||
crit_max: [0, 0, 0, 0, 0, 0],
|
||||
crit_total: [0, 0],
|
||||
heal_amount: 0
|
||||
}
|
||||
const dam_res_keys = ['normal_min', 'normal_max', 'normal_total', 'crit_min', 'crit_max', 'crit_total'];
|
||||
for (const [subpart_name, hits] of Object.entries(part.hits)) {
|
||||
const subpart = spell_result_map.get(subpart_name);
|
||||
if (spell_result.type) {
|
||||
if (subpart.type !== spell_result.type) {
|
||||
throw "SpellCalc total subpart type mismatch";
|
||||
}
|
||||
}
|
||||
else {
|
||||
spell_result.type = subpart.type;
|
||||
}
|
||||
if (spell_result.type === 'damage') {
|
||||
for (const key of dam_res_keys) {
|
||||
for (let i in spell_result.normal_min) {
|
||||
spell_result[key][i] += subpart[key][i] * hits;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
spell_result.heal_amount += subpart.heal_amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
spell_result.name = part.name;
|
||||
spell_results.push(spell_result);
|
||||
spell_result_map.set(part.name, spell_result);
|
||||
}
|
||||
return spell_results;
|
||||
}
|
||||
|
@ -604,12 +658,11 @@ class SpellDisplayNode extends ComputeNode {
|
|||
const spell_info = input_map.get('spell-info');
|
||||
const damages = input_map.get('spell-damage');
|
||||
const spell = spell_info[0];
|
||||
const spell_parts = spell_info[1];
|
||||
|
||||
const i = this.spell_idx;
|
||||
let parent_elem = document.getElementById("spell"+i+"-info");
|
||||
let overallparent_elem = document.getElementById("spell"+i+"-infoAvg");
|
||||
displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, spell_parts, damages);
|
||||
displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, damages);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -833,14 +886,13 @@ class AggregateEditableIDNode extends ComputeNode {
|
|||
|
||||
compute_func(input_map) {
|
||||
const build = input_map.get('build'); input_map.delete('build');
|
||||
const weapon = input_map.get('weapon'); input_map.delete('weapon');
|
||||
|
||||
const output_stats = new Map(build.statMap);
|
||||
for (const [k, v] of input_map.entries()) {
|
||||
output_stats.set(k, v);
|
||||
}
|
||||
|
||||
output_stats.set('classDef', classDefenseMultipliers.get(weapon.statMap.get("type")));
|
||||
output_stats.set('classDef', classDefenseMultipliers.get(build.weapon.statMap.get("type")));
|
||||
return output_stats;
|
||||
}
|
||||
}
|
||||
|
@ -946,6 +998,9 @@ let powder_nodes = [];
|
|||
let spelldmg_nodes = [];
|
||||
let edit_input_nodes = [];
|
||||
let skp_inputs = [];
|
||||
let build_node;
|
||||
let stat_agg_node;
|
||||
let edit_agg_node;
|
||||
|
||||
function builder_graph_init() {
|
||||
// Phase 1/2: Set up item input, propagate updates, etc.
|
||||
|
@ -980,7 +1035,7 @@ function builder_graph_init() {
|
|||
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
|
||||
|
||||
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
|
||||
let build_node = new BuildAssembleNode();
|
||||
build_node = new BuildAssembleNode();
|
||||
for (const input of item_nodes) {
|
||||
build_node.link_to(input);
|
||||
}
|
||||
|
@ -1011,9 +1066,9 @@ function builder_graph_init() {
|
|||
build_disp_node.link_to(build_node, 'build');
|
||||
|
||||
// Create one node that will be the "aggregator node" (listen to all the editable id nodes, as well as the build_node (for non editable stats) and collect them into one statmap)
|
||||
let stat_agg_node = new AggregateStatsNode();
|
||||
let edit_agg_node = new AggregateEditableIDNode();
|
||||
edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon');
|
||||
stat_agg_node = new AggregateStatsNode();
|
||||
edit_agg_node = new AggregateEditableIDNode();
|
||||
edit_agg_node.link_to(build_node, 'build');
|
||||
for (const field of editable_item_fields) {
|
||||
// Create nodes that listens to each editable id input, the node name should match the "id"
|
||||
const elem = document.getElementById(field);
|
||||
|
@ -1057,7 +1112,8 @@ function builder_graph_init() {
|
|||
|
||||
// Also do something similar for skill points
|
||||
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
//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
|
||||
|
@ -1080,8 +1136,6 @@ function builder_graph_init() {
|
|||
let skp_output = new SkillPointSetterNode(edit_input_nodes);
|
||||
skp_output.link_to(build_node);
|
||||
|
||||
edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon');
|
||||
|
||||
let build_warnings_node = new DisplayBuildWarningsNode();
|
||||
build_warnings_node.link_to(build_node, 'build');
|
||||
for (const [skp_input, skp] of zip2(skp_inputs, skp_order)) {
|
||||
|
|
|
@ -89,6 +89,9 @@ class ComputeNode {
|
|||
throw "no compute func specified";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add link to a parent compute node, optionally with an alias.
|
||||
*/
|
||||
link_to(parent_node, link_name) {
|
||||
this.inputs.push(parent_node)
|
||||
link_name = (link_name !== undefined) ? link_name : parent_node.name;
|
||||
|
@ -100,6 +103,26 @@ class ComputeNode {
|
|||
parent_node.children.push(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a link to a parent node.
|
||||
* TODO: time complexity of list deletion (not super relevant but it hurts my soul)
|
||||
*/
|
||||
remove_link(parent_node) {
|
||||
const idx = this.inputs.indexOf(parent_node); // Get idx
|
||||
this.inputs.splice(idx, 1); // remove element
|
||||
|
||||
this.input_translations.delete(parent_node.name);
|
||||
const was_dirty = this.inputs_dirty.get(parent_node.name);
|
||||
this.inputs_dirty.delete(parent_node.name);
|
||||
if (was_dirty) {
|
||||
this.inputs_dirty_count -= 1;
|
||||
}
|
||||
|
||||
const idx2 = parent_node.children.indexOf(this);
|
||||
parent_node.children.splice(idx2, 1);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,11 +56,14 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
|
||||
// 2.2. Next, apply elemental conversions using damage computed in step 1.1.
|
||||
// Also, track which elements are present. (Add onto those present in the weapon itself.)
|
||||
let total_convert = 0; //TODO get confirmation that this is how raw works.
|
||||
for (let i = 1; i <= 5; ++i) {
|
||||
if (conversions[i] > 0) {
|
||||
damages[i][0] += conversions[i]/100 * weapon_min;
|
||||
damages[i][1] += conversions[i]/100 * weapon_max;
|
||||
const conv_frac = conversions[i]/100;
|
||||
damages[i][0] += conv_frac * weapon_min;
|
||||
damages[i][1] += conf_frac * weapon_max;
|
||||
present[i] = true;
|
||||
total_convert += conv_frac
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,22 +128,22 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw');
|
||||
}
|
||||
// Next, rainraw and propRaw
|
||||
let new_min = damages_obj[0] + raw_boost;
|
||||
let new_max = damages_obj[1] + raw_boost;
|
||||
let min_boost = raw_boost;
|
||||
let max_boost = raw_boost;
|
||||
if (total_max > 0) { // TODO: what about total negative all raw?
|
||||
if (total_elem_min > 0) {
|
||||
new_min += (damages_obj[0] / total_min) * prop_raw;
|
||||
min_boost += (damages_obj[0] / total_min) * prop_raw;
|
||||
}
|
||||
new_max += (damages_obj[1] / total_max) * prop_raw;
|
||||
max_boost += (damages_obj[1] / total_max) * prop_raw;
|
||||
}
|
||||
if (i != 0 && total_elem_max > 0) { // rainraw TODO above
|
||||
if (total_elem_min > 0) {
|
||||
new_min += (damages_obj[0] / total_elem_min) * rainbow_raw;
|
||||
min_boost += (damages_obj[0] / total_elem_min) * rainbow_raw;
|
||||
}
|
||||
new_max += (damages_obj[1] / total_elem_max) * rainbow_raw;
|
||||
max_boost += (damages_obj[1] / total_elem_max) * rainbow_raw;
|
||||
}
|
||||
damages_obj[0] = new_min;
|
||||
damages_obj[1] = new_max;
|
||||
damages_obj[0] += min_boost * total_convert;
|
||||
damages_obj[1] += max_boost * total_convert;
|
||||
}
|
||||
|
||||
// 6. Strength boosters
|
||||
|
@ -173,6 +176,125 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
return [total_dam_norm, total_dam_crit, damages_results];
|
||||
}
|
||||
|
||||
/*
|
||||
Spell schema:
|
||||
|
||||
spell: {
|
||||
name: str internal string name for the spell. Unique identifier
|
||||
cost: Optional[int] ignored for spells that are not id 1-4
|
||||
display_text: str short description of the spell, ex. Bash, Meteor, Arrow Shield
|
||||
base_spell: int spell index. 0-4 are reserved (0 is melee, 1-4 is common 4 spells)
|
||||
spell_type: str [TODO: DEPRECATED/REMOVE] "healing" or "damage"
|
||||
scaling: Optional[str] [DEFAULT: "spell"] "melee" or "spell"
|
||||
use_atkspd: Optional[bool] [DEFAULT: true] true to factor attack speed, false otherwise.
|
||||
display: Optional[str] [DEFAULT: "total"] "total" to sum all parts. Or, the name of a spell part
|
||||
parts: List[part] Parts of this spell (different stuff the spell does basically)
|
||||
}
|
||||
|
||||
NOTE: when using `replace_spell` on an existing spell, all fields become optional.
|
||||
Specified fields overwrite existing fields; unspecified fields are left unchanged.
|
||||
|
||||
|
||||
There are three possible spell "part" types: damage, heal, and total.
|
||||
|
||||
part: spell_damage | spell_heal | spell_total
|
||||
|
||||
spell_damage: {
|
||||
name: str != "total" Name of the part.
|
||||
type: "damage" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||
multipliers: array[num, 6] floating point spellmults (though supposedly wynn only supports integer mults)
|
||||
}
|
||||
spell_heal: {
|
||||
name: str != "total" Name of the part.
|
||||
type: "heal" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||
power: num floating point healing power (1 is 100% of max hp).
|
||||
}
|
||||
spell_total: {
|
||||
name: str != "total" Name of the part.
|
||||
type: "total" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||
hits: Map[str, num] Keys are other part names, numbers are the multipliers. Undefined behavior if subparts
|
||||
are not the same type of spell. Can only pull from spells defined before it.
|
||||
}
|
||||
|
||||
|
||||
Before passing to display, use the following structs.
|
||||
NOTE: total is collapsed into damage or healing.
|
||||
|
||||
spell_damage: {
|
||||
type: "damage" Internal use
|
||||
name: str Display name of part. Should be human readable
|
||||
normal_min: array[num, 6] floating point damages (no crit, min), can be less than zero. Order: NETWFA
|
||||
normal_max: array[num, 6] floating point damages (no crit, max)
|
||||
normal_total: array[num, 2] (min, max) noncrit total damage (not negative)
|
||||
crit_min: array[num, 6] floating point damages (crit, min), can be less than zero. Order: NETWFA
|
||||
crit_max: array[num, 6] floating point damages (crit, max)
|
||||
crit_total: array[num, 2] (min, max) crit total damage (not negative)
|
||||
}
|
||||
spell_heal: {
|
||||
type: "heal" Internal use
|
||||
name: str Display name of part. Should be human readable
|
||||
heal_amount: num floating point HP healed (self)
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
const default_spells = {
|
||||
wand: [{
|
||||
name: "Wand Melee", // TODO: name for melee attacks?
|
||||
display_text: "Mage basic attack",
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Melee",
|
||||
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}, {
|
||||
name: "Heal", // TODO: name for melee attacks?
|
||||
display_text: "Heal spell!",
|
||||
base_spell: 1,
|
||||
display: "Total Heal",
|
||||
parts: [
|
||||
{ name: "First Pulse", power: 0.12 },
|
||||
{ name: "Second and Third Pulses", power: 0.06 },
|
||||
{ name: "Total Heal", hits: { "First Pulse": 1, "Second and Third Pulses": 2 } }
|
||||
]
|
||||
}],
|
||||
spear: [{
|
||||
name: "Melee", // TODO: name for melee attacks?
|
||||
display_text: "Warrior basic attack",
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Melee",
|
||||
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}],
|
||||
bow: [{
|
||||
name: "Bow Shot", // TODO: name for melee attacks?
|
||||
display_text: "Archer basic attack",
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Melee",
|
||||
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}],
|
||||
dagger: [{
|
||||
name: "Melee", // TODO: name for melee attacks?
|
||||
display_text: "Assassin basic attack",
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Melee",
|
||||
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}],
|
||||
relik: [{
|
||||
name: "Relik Melee", // TODO: name for melee attacks?
|
||||
display_text: "Shaman basic attack",
|
||||
base_spell: 0,
|
||||
spell_type: "damage",
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Total",
|
||||
parts: [
|
||||
{ name: "Single Beam", multipliers: [33, 0, 0, 0, 0, 0] },
|
||||
{ name: "Total", hits: { "Single Beam": 3 } }
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
const spell_table = {
|
||||
"wand": [
|
||||
{ title: "Heal", cost: 6, parts: [
|
||||
|
|
121
js/display.js
121
js/display.js
|
@ -1476,9 +1476,10 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
|
|||
|
||||
let tmp_conv = [];
|
||||
for (let i in part.conversion) {
|
||||
tmp_conv.push(part.conversion[i] * part.multiplier[power-1]);
|
||||
tmp_conv.push(part.conversion[i] * part.multiplier[power-1] / 100);
|
||||
}
|
||||
let _results = calculateSpellDamage(stats, weapon, tmp_conv, false);
|
||||
console.log(tmp_conv);
|
||||
let _results = calculateSpellDamage(stats, weapon, tmp_conv, false, true);
|
||||
|
||||
let critChance = skillPointsToPercentage(skillpoints[1]);
|
||||
let save_damages = [];
|
||||
|
@ -1578,7 +1579,7 @@ function getBaseSpellCost(stats, spellIdx, cost) {
|
|||
}
|
||||
|
||||
|
||||
function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spellIdx, spell_parts, damages) {
|
||||
function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spellIdx, spell_results) {
|
||||
// TODO: remove spellIdx (just used to flag melee and cost)
|
||||
// TODO: move cost calc out
|
||||
parent_elem.textContent = "";
|
||||
|
@ -1588,7 +1589,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
|||
overallparent_elem.textContent = "";
|
||||
let title_elemavg = document.createElement("b");
|
||||
|
||||
if (spellIdx != 0) {
|
||||
if ('cost' in spell) {
|
||||
let first = document.createElement("span");
|
||||
first.textContent = spell.title + " (";
|
||||
title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here.
|
||||
|
@ -1610,44 +1611,48 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
|||
title_elemavg.appendChild(third_summary);
|
||||
}
|
||||
else {
|
||||
title_elem.textContent = spell.title;
|
||||
title_elemavg.textContent = spell.title;
|
||||
title_elem.textContent = spell.name;
|
||||
title_elemavg.textContent = spell.name;
|
||||
}
|
||||
|
||||
parent_elem.append(title_elem);
|
||||
overallparent_elem.append(title_elemavg);
|
||||
|
||||
overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
||||
if ('cost' in spell) {
|
||||
overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
||||
}
|
||||
|
||||
let critChance = skillPointsToPercentage(stats.get('dex'));
|
||||
|
||||
let save_damages = [];
|
||||
|
||||
let part_divavg = document.createElement("p");
|
||||
overallparent_elem.append(part_divavg);
|
||||
|
||||
for (let i = 0; i < spell_parts.length; ++i) {
|
||||
const part = spell_parts[i];
|
||||
const damage = damages[i];
|
||||
function _summary(text, val, fmt) {
|
||||
let overallaverageLabel = document.createElement("p");
|
||||
let first = document.createElement("span");
|
||||
let second = document.createElement("span");
|
||||
first.textContent = text;
|
||||
second.textContent = val.toFixed(2);
|
||||
overallaverageLabel.appendChild(first);
|
||||
overallaverageLabel.appendChild(second);
|
||||
second.classList.add(fmt);
|
||||
part_divavg.append(overallaverageLabel);
|
||||
}
|
||||
|
||||
for (let i = 0; i < spell_results.length; ++i) {
|
||||
const spell_info = spell_results[i];
|
||||
|
||||
let part_div = document.createElement("p");
|
||||
parent_elem.append(part_div);
|
||||
|
||||
let subtitle_elem = document.createElement("p");
|
||||
subtitle_elem.textContent = part.subtitle;
|
||||
subtitle_elem.textContent = spell_info.name
|
||||
part_div.append(subtitle_elem);
|
||||
|
||||
if (part.type === "damage") {
|
||||
let _results = damage;
|
||||
let totalDamNormal = _results[0];
|
||||
let totalDamCrit = _results[1];
|
||||
let results = _results[2];
|
||||
if (spell_info.type === "damage") {
|
||||
let totalDamNormal = spell_info.normal_total;
|
||||
let totalDamCrit = spell_info.crit_total;
|
||||
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
for (let j in results[i]) {
|
||||
results[i][j] = results[i][j].toFixed(2);
|
||||
}
|
||||
}
|
||||
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
|
||||
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
|
||||
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
|
||||
|
@ -1658,83 +1663,35 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
|||
part_div.append(averageLabel);
|
||||
|
||||
|
||||
if (part.summary == true) {
|
||||
let overallaverageLabel = document.createElement("p");
|
||||
let first = document.createElement("span");
|
||||
let second = document.createElement("span");
|
||||
first.textContent = part.subtitle + " Average: ";
|
||||
second.textContent = averageDamage.toFixed(2);
|
||||
overallaverageLabel.appendChild(first);
|
||||
overallaverageLabel.appendChild(second);
|
||||
second.classList.add("Damage");
|
||||
part_divavg.append(overallaverageLabel);
|
||||
if (spell_info.name === spell.display) {
|
||||
_summary(spell_info.name+ " Average: ", averageDamage, "Damage");
|
||||
}
|
||||
|
||||
function _damage_display(label_text, average, result_idx) {
|
||||
function _damage_display(label_text, average, dmg_min, dmg_max) {
|
||||
let label = document.createElement("p");
|
||||
label.textContent = label_text+average.toFixed(2);
|
||||
part_div.append(label);
|
||||
|
||||
let arrmin = [];
|
||||
let arrmax = [];
|
||||
for (let i = 0; i < 6; i++){
|
||||
if (results[i][1] != 0){
|
||||
if (dmg_max[i] != 0){
|
||||
let p = document.createElement("p");
|
||||
p.classList.add(damageClasses[i]);
|
||||
p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1];
|
||||
arrmin.push(results[i][result_idx]);
|
||||
arrmax.push(results[i][result_idx + 1]);
|
||||
p.textContent = dmg_min[i].toFixed(2)+" \u2013 "+dmg_max[i].toFixed(2);
|
||||
part_div.append(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
_damage_display("Non-Crit Average: ", nonCritAverage, 0);
|
||||
_damage_display("Crit Average: ", critAverage, 2);
|
||||
|
||||
save_damages.push(averageDamage);
|
||||
} else if (part.type === "heal") {
|
||||
let heal_amount = damage;
|
||||
_damage_display("Non-Crit Average: ", nonCritAverage, spell_info.normal_min, spell_info.normal_max);
|
||||
_damage_display("Crit Average: ", critAverage, spell_info.crit_min, spell_info.crit_max);
|
||||
} else if (spell_info.type === "heal") {
|
||||
let heal_amount = spell_info.heal_amount;
|
||||
let healLabel = document.createElement("p");
|
||||
healLabel.textContent = heal_amount;
|
||||
// healLabel.classList.add("damagep");
|
||||
part_div.append(healLabel);
|
||||
if (part.summary == true) {
|
||||
let overallhealLabel = document.createElement("p");
|
||||
let first = document.createElement("span");
|
||||
let second = document.createElement("span");
|
||||
first.textContent = part.subtitle + ": ";
|
||||
second.textContent = heal_amount;
|
||||
overallhealLabel.appendChild(first);
|
||||
second.classList.add("Set");
|
||||
overallhealLabel.appendChild(second);
|
||||
part_divavg.append(overallhealLabel);
|
||||
if (spell_info.name === spell.display) {
|
||||
_summary(spell_info.name+ ": ", heal_amount, "Set");
|
||||
}
|
||||
} else if (part.type === "total") {
|
||||
let total_damage = 0;
|
||||
for (let i in part.factors) {
|
||||
total_damage += save_damages[i] * part.factors[i];
|
||||
}
|
||||
|
||||
let dmgarr = part.factors.slice();
|
||||
dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")");
|
||||
|
||||
|
||||
let averageLabel = document.createElement("p");
|
||||
averageLabel.textContent = "Average: "+total_damage.toFixed(2);
|
||||
averageLabel.classList.add("damageSubtitle");
|
||||
part_div.append(averageLabel);
|
||||
|
||||
let overallaverageLabel = document.createElement("p");
|
||||
let overallaverageLabelFirst = document.createElement("span");
|
||||
let overallaverageLabelSecond = document.createElement("span");
|
||||
overallaverageLabelFirst.textContent = "Average: ";
|
||||
overallaverageLabelSecond.textContent = total_damage.toFixed(2);
|
||||
overallaverageLabelSecond.classList.add("Damage");
|
||||
|
||||
|
||||
overallaverageLabel.appendChild(overallaverageLabelFirst);
|
||||
overallaverageLabel.appendChild(overallaverageLabelSecond);
|
||||
part_divavg.append(overallaverageLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
"""
|
||||
Generate a JSON Ability Tree [atree_constants_id.json] with:
|
||||
Generate a minified JSON Ability Tree [atree_constants_min.json] AND a minified .js form [atree_constants_min.js] of the Ability Tree with:
|
||||
- All references replaced by numerical IDs
|
||||
- Extra JSON File with Class: [Original name as key and Assigned IDs as value].
|
||||
given a JSON Ability Tree with reference as string.
|
||||
given [atree_constants.js] .js form of the Ability Tree with reference as string.
|
||||
"""
|
||||
import json
|
||||
|
||||
abilDict = {}
|
||||
with open("atree_constants.json") as f:
|
||||
data = json.loads(f.read())
|
||||
with open("atree_constants.js") as f:
|
||||
data = f.read()
|
||||
data = data.replace("const atrees = ", "")
|
||||
data = json.loads(data)
|
||||
for classType, info in data.items():
|
||||
_id = 0
|
||||
abilDict[classType] = {}
|
||||
|
@ -31,5 +33,10 @@ with open("atree_constants.json") as f:
|
|||
for ref in range(len(info[abil]["blockers"])):
|
||||
info[abil]["blockers"][ref] = abilDict[classType][info[abil]["blockers"][ref]]
|
||||
|
||||
with open('atree_constants_id.json', 'w', encoding='utf-8') as abil_dest:
|
||||
json.dump(data, abil_dest, ensure_ascii=False, indent=4)
|
||||
data_str = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
|
||||
data_str = "const atrees=" + data_str
|
||||
with open('atree_constants_min.js', 'w', encoding='utf-8') as abil_dest:
|
||||
abil_dest.write(data_str)
|
||||
|
||||
with open('atree_constants_min.json', 'w', encoding='utf-8') as json_dest:
|
||||
json.dump(data, json_dest, ensure_ascii=False, separators=(',', ':'))
|
||||
|
|
Loading…
Reference in a new issue