2022-05-21 04:34:24 +00:00
|
|
|
function calculate_skillpoints(equipment, weapon) {
|
2022-07-17 21:34:54 +00:00
|
|
|
const start = performance.now();
|
2021-01-07 06:41:41 +00:00
|
|
|
// Calculate equipment equipping order and required skillpoints.
|
|
|
|
// Return value: [equip_order, best_skillpoints, final_skillpoints, best_total];
|
|
|
|
let fixed = [];
|
|
|
|
let consider = [];
|
|
|
|
let noboost = [];
|
2021-02-13 00:43:21 +00:00
|
|
|
let crafted = [];
|
2022-07-17 08:38:12 +00:00
|
|
|
weapon.skillpoints = weapon.get('skillpoints');
|
|
|
|
weapon.reqs = weapon.get('reqs');
|
|
|
|
weapon.set = weapon.get('set');
|
2021-01-07 06:41:41 +00:00
|
|
|
for (const item of equipment) {
|
2022-07-17 08:38:12 +00:00
|
|
|
item.skillpoints = item.get('skillpoints');
|
|
|
|
item.reqs = item.get('reqs');
|
|
|
|
item.set = item.get('set');
|
2021-02-14 23:33:37 +00:00
|
|
|
if (item.get("crafted")) {
|
2021-02-13 00:43:21 +00:00
|
|
|
crafted.push(item);
|
|
|
|
}
|
2022-07-17 08:38:12 +00:00
|
|
|
else if (item.get("reqs").every(x => x === 0) && item.skillpoints.every(x => x >= 0)) {
|
2021-03-21 04:00:34 +00:00
|
|
|
// All reqless item without -skillpoints.
|
2021-02-14 23:33:37 +00:00
|
|
|
fixed.push(item);
|
|
|
|
}
|
2021-01-10 04:29:07 +00:00
|
|
|
// TODO hack: We will treat ALL set items as unsafe :(
|
2022-07-17 08:38:12 +00:00
|
|
|
else if (item.skillpoints.every(x => x === 0) && item.set === null) {
|
2021-01-07 06:41:41 +00:00
|
|
|
noboost.push(item);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
consider.push(item);
|
|
|
|
}
|
|
|
|
}
|
2021-01-10 04:29:07 +00:00
|
|
|
function apply_skillpoints(skillpoints, item, activeSetCounts) {
|
2021-01-07 06:41:41 +00:00
|
|
|
for (let i = 0; i < 5; i++) {
|
2022-07-17 08:38:12 +00:00
|
|
|
skillpoints[i] += item.skillpoints[i];
|
2021-01-07 06:41:41 +00:00
|
|
|
}
|
|
|
|
|
2022-07-17 08:38:12 +00:00
|
|
|
const setName = item.set;
|
2021-01-10 04:29:07 +00:00
|
|
|
if (setName) { // undefined/null means no set.
|
|
|
|
let setCount = activeSetCounts.get(setName);
|
|
|
|
let old_bonus = {};
|
|
|
|
if (setCount) {
|
2022-06-19 16:49:04 +00:00
|
|
|
old_bonus = sets.get(setName).bonuses[setCount-1];
|
2021-01-10 04:29:07 +00:00
|
|
|
activeSetCounts.set(setName, setCount + 1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
setCount = 0;
|
|
|
|
activeSetCounts.set(setName, 1);
|
|
|
|
}
|
2022-06-19 16:49:04 +00:00
|
|
|
const new_bonus = sets.get(setName).bonuses[setCount];
|
2021-01-10 04:29:07 +00:00
|
|
|
//let skp_order = ["str","dex","int","def","agi"];
|
|
|
|
for (const i in skp_order) {
|
|
|
|
const delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
|
|
|
skillpoints[i] += delta;
|
|
|
|
}
|
2021-01-07 06:41:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 20:14:25 +00:00
|
|
|
function apply_to_fit(skillpoints, item, skillpoint_min, activeSetCounts) {
|
2021-01-07 06:41:41 +00:00
|
|
|
let applied = [0, 0, 0, 0, 0];
|
|
|
|
let total = 0;
|
|
|
|
for (let i = 0; i < 5; i++) {
|
2022-07-17 08:38:12 +00:00
|
|
|
if (item.skillpoints[i] < 0 && skillpoint_min[i]) {
|
|
|
|
const unadjusted = skillpoints[i] + item.skillpoints[i];
|
2021-03-19 10:00:01 +00:00
|
|
|
const delta = skillpoint_min[i] - unadjusted;
|
2021-03-05 20:14:25 +00:00
|
|
|
if (delta > 0) {
|
|
|
|
applied[i] += delta;
|
|
|
|
total += delta;
|
|
|
|
}
|
2021-01-07 08:34:31 +00:00
|
|
|
}
|
2022-07-17 08:38:12 +00:00
|
|
|
if (item.reqs[i] == 0) continue;
|
|
|
|
skillpoint_min[i] = Math.max(skillpoint_min[i], item.reqs[i] + item.skillpoints[i]);
|
|
|
|
const req = item.reqs[i];
|
2021-01-07 06:41:41 +00:00
|
|
|
const cur = skillpoints[i];
|
|
|
|
if (req > cur) {
|
|
|
|
const diff = req - cur;
|
|
|
|
applied[i] += diff;
|
|
|
|
total += diff;
|
|
|
|
}
|
|
|
|
}
|
2021-01-10 04:29:07 +00:00
|
|
|
|
2022-07-17 08:38:12 +00:00
|
|
|
const setName = item.set;
|
2021-01-10 04:29:07 +00:00
|
|
|
if (setName) { // undefined/null means no set.
|
|
|
|
const setCount = activeSetCounts.get(setName);
|
|
|
|
if (setCount) {
|
2022-06-19 16:49:04 +00:00
|
|
|
const old_bonus = sets.get(setName).bonuses[setCount-1];
|
|
|
|
const new_bonus = sets.get(setName).bonuses[setCount];
|
2021-01-10 04:29:07 +00:00
|
|
|
//let skp_order = ["str","dex","int","def","agi"];
|
|
|
|
for (const i in skp_order) {
|
2021-03-19 10:00:01 +00:00
|
|
|
const set_delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
|
|
|
if (set_delta < 0 && skillpoint_min[i]) {
|
|
|
|
const unadjusted = skillpoints[i] + set_delta;
|
|
|
|
const delta = skillpoint_min[i] - unadjusted;
|
|
|
|
if (delta > 0) {
|
|
|
|
applied[i] += delta;
|
|
|
|
total += delta;
|
|
|
|
}
|
2021-01-10 04:29:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-07 06:41:41 +00:00
|
|
|
return [applied, total];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Separate out the no req items and add them to the static skillpoint base.
|
|
|
|
let static_skillpoints_base = [0, 0, 0, 0, 0]
|
2021-01-10 04:29:07 +00:00
|
|
|
let static_activeSetCounts = new Map()
|
2021-01-07 06:41:41 +00:00
|
|
|
for (const item of fixed) {
|
2021-01-10 04:29:07 +00:00
|
|
|
apply_skillpoints(static_skillpoints_base, item, static_activeSetCounts);
|
2021-01-07 06:41:41 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 20:36:03 +00:00
|
|
|
let best = consider.concat(noboost);
|
|
|
|
let final_skillpoints = static_skillpoints_base.slice();
|
2021-01-07 06:41:41 +00:00
|
|
|
let best_skillpoints = [0, 0, 0, 0, 0];
|
|
|
|
let best_total = Infinity;
|
2021-01-10 04:29:07 +00:00
|
|
|
let best_activeSetCounts = static_activeSetCounts;
|
2021-01-07 06:41:41 +00:00
|
|
|
|
2021-02-13 00:43:21 +00:00
|
|
|
let allFalse = [0, 0, 0, 0, 0];
|
|
|
|
if (consider.length > 0 || noboost.length > 0 || crafted.length > 0) {
|
2021-01-07 06:41:41 +00:00
|
|
|
// Try every combination and pick the best one.
|
2022-07-17 19:40:37 +00:00
|
|
|
construct_scc_graph(consider);
|
2021-01-07 06:41:41 +00:00
|
|
|
for (let permutation of perm(consider)) {
|
2021-01-10 04:29:07 +00:00
|
|
|
let activeSetCounts = new Map(static_activeSetCounts);
|
2021-01-09 13:15:30 +00:00
|
|
|
let has_skillpoint = allFalse.slice();
|
2021-01-07 06:41:41 +00:00
|
|
|
permutation = permutation.concat(noboost);
|
|
|
|
|
|
|
|
let skillpoints_applied = [0, 0, 0, 0, 0];
|
|
|
|
// Complete slice is a shallow copy.
|
|
|
|
let skillpoints = static_skillpoints_base.slice();
|
|
|
|
|
|
|
|
let total_applied = 0;
|
|
|
|
|
|
|
|
let result;
|
|
|
|
let needed_skillpoints;
|
|
|
|
let total_diff;
|
|
|
|
for (const item of permutation) {
|
2021-01-10 04:29:07 +00:00
|
|
|
result = apply_to_fit(skillpoints, item, has_skillpoint, activeSetCounts);
|
2021-01-07 06:41:41 +00:00
|
|
|
needed_skillpoints = result[0];
|
|
|
|
total_diff = result[1];
|
|
|
|
|
|
|
|
for (let i = 0; i < 5; ++i) {
|
|
|
|
skillpoints_applied[i] += needed_skillpoints[i];
|
|
|
|
skillpoints[i] += needed_skillpoints[i];
|
|
|
|
}
|
2021-01-10 04:29:07 +00:00
|
|
|
apply_skillpoints(skillpoints, item, activeSetCounts);
|
2021-01-07 06:41:41 +00:00
|
|
|
total_applied += total_diff;
|
|
|
|
if (total_applied >= best_total) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-02-13 00:43:21 +00:00
|
|
|
|
|
|
|
// Crafted skillpoint does not count initially.
|
|
|
|
for (const item of crafted) {
|
2021-03-14 07:55:08 +00:00
|
|
|
//console.log(item)
|
2021-02-22 07:23:00 +00:00
|
|
|
result = apply_to_fit(skillpoints, item, allFalse.slice(), activeSetCounts);
|
2021-03-14 07:55:08 +00:00
|
|
|
//console.log(result)
|
2021-02-13 00:43:21 +00:00
|
|
|
needed_skillpoints = result[0];
|
|
|
|
total_diff = result[1];
|
|
|
|
|
|
|
|
for (let i = 0; i < 5; ++i) {
|
|
|
|
skillpoints_applied[i] += needed_skillpoints[i];
|
|
|
|
skillpoints[i] += needed_skillpoints[i];
|
|
|
|
}
|
2021-02-22 07:23:00 +00:00
|
|
|
total_applied += total_diff;
|
2021-02-13 00:43:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (total_applied >= best_total) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-01-09 23:31:14 +00:00
|
|
|
let pre = skillpoints.slice();
|
2021-01-10 04:29:07 +00:00
|
|
|
result = apply_to_fit(skillpoints, weapon, allFalse.slice(), activeSetCounts);
|
2021-01-07 06:41:41 +00:00
|
|
|
needed_skillpoints = result[0];
|
|
|
|
total_diff = result[1];
|
|
|
|
for (let i = 0; i < 5; ++i) {
|
|
|
|
skillpoints_applied[i] += needed_skillpoints[i];
|
|
|
|
skillpoints[i] += needed_skillpoints[i];
|
|
|
|
}
|
|
|
|
|
2021-01-10 04:29:07 +00:00
|
|
|
apply_skillpoints(skillpoints, weapon, activeSetCounts);
|
2021-01-07 06:41:41 +00:00
|
|
|
total_applied += total_diff;
|
|
|
|
|
2021-02-13 00:43:21 +00:00
|
|
|
// Applying crafted item skill points last.
|
|
|
|
for (const item of crafted) {
|
|
|
|
apply_skillpoints(skillpoints, item, activeSetCounts);
|
2021-02-20 05:22:19 +00:00
|
|
|
//total_applied += total_diff;
|
2021-02-13 00:43:21 +00:00
|
|
|
}
|
|
|
|
|
2021-01-07 06:41:41 +00:00
|
|
|
if (total_applied < best_total) {
|
|
|
|
best = permutation;
|
|
|
|
final_skillpoints = skillpoints;
|
|
|
|
best_skillpoints = skillpoints_applied;
|
|
|
|
best_total = total_applied;
|
2021-01-10 04:29:07 +00:00
|
|
|
best_activeSetCounts = activeSetCounts;
|
2021-01-07 06:41:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else {
|
2021-01-08 20:36:03 +00:00
|
|
|
best_total = 0;
|
2021-01-10 04:29:07 +00:00
|
|
|
result = apply_to_fit(final_skillpoints, weapon, allFalse.slice(), best_activeSetCounts);
|
2021-01-08 20:14:23 +00:00
|
|
|
needed_skillpoints = result[0];
|
|
|
|
total_diff = result[1];
|
|
|
|
for (let i = 0; i < 5; ++i) {
|
2021-01-08 20:36:03 +00:00
|
|
|
best_skillpoints[i] += needed_skillpoints[i];
|
|
|
|
final_skillpoints[i] += needed_skillpoints[i];
|
2021-01-08 20:14:23 +00:00
|
|
|
}
|
2021-01-10 07:48:40 +00:00
|
|
|
apply_skillpoints(final_skillpoints, weapon, best_activeSetCounts);
|
2021-01-08 20:36:03 +00:00
|
|
|
best_total += total_diff;
|
2021-01-07 06:41:41 +00:00
|
|
|
}
|
2021-02-14 23:33:37 +00:00
|
|
|
let equip_order = fixed.concat(best).concat(crafted);
|
2021-07-27 10:04:12 +00:00
|
|
|
// best_skillpoints: manually assigned (before any gear)
|
|
|
|
// final_skillpoints: final totals (5 individ)
|
|
|
|
// best_total: total skillpoints assigned (number)
|
2022-07-17 21:34:54 +00:00
|
|
|
const end = performance.now();
|
2022-07-17 05:48:28 +00:00
|
|
|
const output_msg = `skillpoint calculation took ${(end-start)/ 1000} seconds.`;
|
|
|
|
console.log(output_msg);
|
2022-07-17 20:10:50 +00:00
|
|
|
document.getElementById('stack-box').textContent += output_msg;
|
2021-01-10 04:29:07 +00:00
|
|
|
return [equip_order, best_skillpoints, final_skillpoints, best_total, best_activeSetCounts];
|
2021-01-07 06:41:41 +00:00
|
|
|
}
|
2022-07-17 19:40:37 +00:00
|
|
|
|
|
|
|
function construct_scc_graph(items_to_consider) {
|
|
|
|
let nodes = [];
|
|
|
|
for (const item of items_to_consider) {
|
|
|
|
nodes.push({item: item, children: [], parents: [], visited: false});
|
|
|
|
}
|
|
|
|
let root_node = {
|
|
|
|
children: nodes,
|
|
|
|
parents: [],
|
|
|
|
visited: false
|
|
|
|
};
|
|
|
|
// Dependency graph construction.
|
|
|
|
for (const node_a of nodes) {
|
|
|
|
const {item: a, children: a_children} = node_a;
|
|
|
|
for (const node_b of nodes) {
|
|
|
|
const {item: b, parents: b_parents} = node_b;
|
|
|
|
for (let i = 0; i < 5; ++i) {
|
|
|
|
if (b.reqs[i] < a.reqs[i] && b.skillpoints[i]) {
|
|
|
|
a_children.push(node_b);
|
|
|
|
b_parents.push(node_a);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const res = []
|
|
|
|
/*
|
|
|
|
* SCC graph construction.
|
|
|
|
* https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
|
|
|
|
*/
|
|
|
|
function visit(u, res) {
|
|
|
|
if (u.visited) { return; }
|
|
|
|
u.visited = true;
|
|
|
|
for (const child of u.children) {
|
|
|
|
if (!child.visited) { visit(child, res); }
|
|
|
|
}
|
|
|
|
res.push(u);
|
|
|
|
}
|
|
|
|
visit(root_node, res);
|
|
|
|
res.reverse();
|
|
|
|
console.log(res);
|
|
|
|
}
|