Merge pull request #142 from hppeng-wynn/builder-performance-testing
Builder performance improvements
This commit is contained in:
commit
8c050437fc
4 changed files with 288 additions and 194 deletions
|
@ -30,7 +30,6 @@ class Build{
|
||||||
this.level = level;
|
this.level = level;
|
||||||
} else if (typeof level === "string") {
|
} else if (typeof level === "string") {
|
||||||
this.level = level;
|
this.level = level;
|
||||||
errors.push(new IncorrectInput(level, "a number", "level-choice"));
|
|
||||||
} else {
|
} else {
|
||||||
errors.push("Level is not a string or number.");
|
errors.push("Level is not a string or number.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -407,7 +407,10 @@ class BuildAssembleNode extends ComputeNode {
|
||||||
input_map.get('guildTome1-input')
|
input_map.get('guildTome1-input')
|
||||||
];
|
];
|
||||||
let weapon = input_map.get('weapon-input');
|
let weapon = input_map.get('weapon-input');
|
||||||
let level = input_map.get('level-input');
|
let level = parseInt(input_map.get('level-input'));
|
||||||
|
if (isNaN(level)) {
|
||||||
|
level = 106;
|
||||||
|
}
|
||||||
|
|
||||||
let all_none = weapon.statMap.has('NONE');
|
let all_none = weapon.statMap.has('NONE');
|
||||||
for (const item of equipments) {
|
for (const item of equipments) {
|
||||||
|
|
|
@ -9,13 +9,13 @@ let iload_complete = false;
|
||||||
let ings;
|
let ings;
|
||||||
let recipes;
|
let recipes;
|
||||||
|
|
||||||
let ingMap;
|
let ingMap = new Map();
|
||||||
let ingList = [];
|
let ingList = [];
|
||||||
|
|
||||||
let recipeMap;
|
let recipeMap;
|
||||||
let recipeList = [];
|
let recipeList = [];
|
||||||
|
|
||||||
let ingIDMap;
|
let ingIDMap = new Map();
|
||||||
let recipeIDMap;
|
let recipeIDMap;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -163,39 +163,41 @@ async function load_ing_init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_ing_maps() {
|
function init_ing_maps() {
|
||||||
ingMap = new Map();
|
|
||||||
recipeMap = new Map();
|
recipeMap = new Map();
|
||||||
ingIDMap = new Map();
|
|
||||||
recipeIDMap = new Map();
|
recipeIDMap = new Map();
|
||||||
|
|
||||||
let ing = Object();
|
let ing = {
|
||||||
ing.name = "No Ingredient";
|
name: "No Ingredient",
|
||||||
ing.displayName = "No Ingredient";
|
displayName: "No Ingredient",
|
||||||
ing.tier = 0;
|
tier: 0,
|
||||||
ing.lvl = 0;
|
lvl: 0,
|
||||||
ing.skills = ["ARMOURING", "TAILORING", "WEAPONSMITHING", "WOODWORKING", "JEWELING", "COOKING", "ALCHEMISM", "SCRIBING"];
|
skills: ["ARMOURING", "TAILORING", "WEAPONSMITHING", "WOODWORKING", "JEWELING", "COOKING", "ALCHEMISM", "SCRIBING"],
|
||||||
ing.ids= {};
|
ids: {},
|
||||||
ing.itemIDs = {"dura": 0, "strReq": 0, "dexReq": 0,"intReq": 0,"defReq": 0,"agiReq": 0,};
|
itemIDs: {"dura": 0, "strReq": 0, "dexReq": 0,"intReq": 0,"defReq": 0,"agiReq": 0,},
|
||||||
ing.consumableIDs = {"dura": 0, "charges": 0};
|
consumableIDs: {"dura": 0, "charges": 0},
|
||||||
ing.posMods = {"left": 0, "right": 0, "above": 0, "under": 0, "touching": 0, "notTouching": 0};
|
posMods: {"left": 0, "right": 0, "above": 0, "under": 0, "touching": 0, "notTouching": 0},
|
||||||
ing.id = 4000;
|
id: 4000
|
||||||
ingMap.set(ing["displayName"], ing);
|
};
|
||||||
ingList.push(ing["displayName"]);
|
ingMap.set(ing.displayName, ing);
|
||||||
ingIDMap.set(ing["id"], ing["displayName"]);
|
ingList.push(ing.displayName);
|
||||||
|
ingIDMap.set(ing.id, ing.displayName);
|
||||||
let numerals = new Map([[1, "I"], [2, "II"], [3, "III"], [4, "IV"], [5, "V"], [6, "VI"]]);
|
let numerals = new Map([[1, "I"], [2, "II"], [3, "III"], [4, "IV"], [5, "V"], [6, "VI"]]);
|
||||||
for (let i = 0; i < 5; i ++) {
|
for (let i = 0; i < 5; i ++) {
|
||||||
for (const powderIng of powderIngreds) {
|
for (const powderIng of powderIngreds) {
|
||||||
let ing = Object();
|
let ing = {
|
||||||
ing.name = "" + damageClasses[i+1] + " Powder " + numerals.get(powderIngreds.indexOf(powderIng) + 1);
|
name: "" + damageClasses[i+1] + " Powder " + numerals.get(powderIngreds.indexOf(powderIng) + 1),
|
||||||
ing.displayName = ing.name
|
tier: 0,
|
||||||
ing.tier = 0;
|
lvl: 0,
|
||||||
ing.lvl = 0;
|
skills: ["ARMOURING", "TAILORING", "WEAPONSMITHING", "WOODWORKING", "JEWELING"],
|
||||||
ing.skills = ["ARMOURING", "TAILORING", "WEAPONSMITHING", "WOODWORKING", "JEWELING"];
|
ids: {},
|
||||||
ing.ids = {};
|
isPowder: true,
|
||||||
ing.isPowder = true;
|
pid: 6*i + powderIngreds.indexOf(powderIng),
|
||||||
ing.pid = 6*i + powderIngreds.indexOf(powderIng);
|
itemIDs: {"dura": powderIng["durability"], "strReq": 0, "dexReq": 0,"intReq": 0,"defReq": 0,"agiReq": 0},
|
||||||
|
consumableIDs: {"dura": 0, "charges": 0},
|
||||||
|
posMods: {"left": 0, "right": 0, "above": 0, "under": 0, "touching": 0, "notTouching": 0}
|
||||||
|
};
|
||||||
ing.id = 4001 + ing.pid;
|
ing.id = 4001 + ing.pid;
|
||||||
ing.itemIDs = {"dura": powderIng["durability"], "strReq": 0, "dexReq": 0,"intReq": 0,"defReq": 0,"agiReq": 0,};
|
ing.diplayName = ing.name;
|
||||||
switch(i) {
|
switch(i) {
|
||||||
case 0:
|
case 0:
|
||||||
ing.itemIDs["strReq"] = powderIng["skpReq"];
|
ing.itemIDs["strReq"] = powderIng["skpReq"];
|
||||||
|
@ -213,24 +215,21 @@ function init_ing_maps() {
|
||||||
ing.itemIDs["agiReq"] = powderIng["skpReq"];
|
ing.itemIDs["agiReq"] = powderIng["skpReq"];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ing.consumableIDs = {"dura": 0, "charges": 0};
|
ingMap.set(ing.displayName, ing);
|
||||||
ing.posMods = {"left": 0, "right": 0, "above": 0, "under": 0, "touching": 0, "notTouching": 0};
|
ingList.push(ing.displayName);
|
||||||
ingMap.set(ing["displayName"],ing);
|
ingIDMap.set(ing.id, ing.displayName);
|
||||||
ingList.push(ing["displayName"]);
|
|
||||||
ingIDMap.set(ing["id"], ing["displayName"]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (const ing of ings) {
|
for (const ing of ings) {
|
||||||
ingMap.set(ing["displayName"], ing);
|
ingMap.set(ing.displayName, ing);
|
||||||
ingList.push(ing["displayName"]);
|
ingList.push(ing.displayName);
|
||||||
ingIDMap.set(ing["id"], ing["displayName"]);
|
ingIDMap.set(ing.id, ing.displayName);
|
||||||
}
|
}
|
||||||
for (const recipe of recipes) {
|
for (const recipe of recipes) {
|
||||||
recipeMap.set(recipe["name"], recipe);
|
recipeMap.set(recipe.name, recipe);
|
||||||
recipeList.push(recipe["name"]);
|
recipeList.push(recipe.name);
|
||||||
recipeIDMap.set(recipe["id"],recipe["name"]);
|
recipeIDMap.set(recipe.id, recipe.name);
|
||||||
}
|
}
|
||||||
console.log(ingMap);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,98 +1,116 @@
|
||||||
|
/**
|
||||||
|
* Apply skillpoint bonuses from an item.
|
||||||
|
* Also applies set deltas.
|
||||||
|
* Modifies the skillpoints array.
|
||||||
|
*/
|
||||||
|
function apply_skillpoints(skillpoints, item, activeSetCounts) {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
skillpoints[i] += item.skillpoints[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const setName = item.set;
|
||||||
|
if (setName) { // undefined/null means no set.
|
||||||
|
let setCount = activeSetCounts.get(setName);
|
||||||
|
let old_bonus = {};
|
||||||
|
if (setCount) {
|
||||||
|
old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||||
|
activeSetCounts.set(setName, setCount + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setCount = 0;
|
||||||
|
activeSetCounts.set(setName, 1);
|
||||||
|
}
|
||||||
|
const new_bonus = sets.get(setName).bonuses[setCount];
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply skillpoints until this item can be worn.
|
||||||
|
* Also applies set deltas.
|
||||||
|
* Confusingly, does not modify the skillpoints array.
|
||||||
|
* Instead, return an array of deltas.
|
||||||
|
*/
|
||||||
|
function apply_to_fit(skillpoints, item, skillpoint_min, activeSetCounts) {
|
||||||
|
let applied = [0, 0, 0, 0, 0];
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
if (item.skillpoints[i] < 0 && skillpoint_min[i]) {
|
||||||
|
const unadjusted = skillpoints[i] + item.skillpoints[i];
|
||||||
|
const delta = skillpoint_min[i] - unadjusted;
|
||||||
|
if (delta > 0) {
|
||||||
|
applied[i] += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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];
|
||||||
|
const cur = skillpoints[i];
|
||||||
|
if (req > cur) {
|
||||||
|
const diff = req - cur;
|
||||||
|
applied[i] += diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setName = item.set;
|
||||||
|
if (setName) { // undefined/null means no set.
|
||||||
|
const setCount = activeSetCounts.get(setName);
|
||||||
|
if (setCount) {
|
||||||
|
const old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||||
|
const new_bonus = sets.get(setName).bonuses[setCount];
|
||||||
|
//let skp_order = ["str","dex","int","def","agi"];
|
||||||
|
for (const i in skp_order) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return applied;
|
||||||
|
}
|
||||||
|
|
||||||
function calculate_skillpoints(equipment, weapon) {
|
function calculate_skillpoints(equipment, weapon) {
|
||||||
|
// const start = performance.now();
|
||||||
// Calculate equipment equipping order and required skillpoints.
|
// Calculate equipment equipping order and required skillpoints.
|
||||||
// Return value: [equip_order, best_skillpoints, final_skillpoints, best_total];
|
// Return value: [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||||
let fixed = [];
|
let fixed = [];
|
||||||
let consider = [];
|
let consider = [];
|
||||||
let noboost = [];
|
let noboost = [];
|
||||||
let crafted = [];
|
let crafted = [];
|
||||||
|
weapon.skillpoints = weapon.get('skillpoints');
|
||||||
|
weapon.reqs = weapon.get('reqs');
|
||||||
|
weapon.set = weapon.get('set');
|
||||||
for (const item of equipment) {
|
for (const item of equipment) {
|
||||||
|
item.skillpoints = item.get('skillpoints');
|
||||||
|
item.reqs = item.get('reqs');
|
||||||
|
item.set = item.get('set');
|
||||||
if (item.get("crafted")) {
|
if (item.get("crafted")) {
|
||||||
crafted.push(item);
|
crafted.push(item);
|
||||||
}
|
}
|
||||||
else if (item.get("reqs").every(x => x === 0) && item.get("skillpoints").every(x => x >= 0)) {
|
// TODO hack: We will treat ALL set items as unsafe :(
|
||||||
|
else if (item.set !== null) {
|
||||||
|
consider.push(item);
|
||||||
|
}
|
||||||
|
else if (item.get("reqs").every(x => x === 0) && item.skillpoints.every(x => x >= 0)) {
|
||||||
// All reqless item without -skillpoints.
|
// All reqless item without -skillpoints.
|
||||||
fixed.push(item);
|
fixed.push(item);
|
||||||
}
|
}
|
||||||
// TODO hack: We will treat ALL set items as unsafe :(
|
else if (item.skillpoints.every(x => x <= 0)) {
|
||||||
else if (item.get("skillpoints").every(x => x === 0) && item.get("set") === null) {
|
|
||||||
noboost.push(item);
|
noboost.push(item);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
consider.push(item);
|
consider.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function apply_skillpoints(skillpoints, item, activeSetCounts) {
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
skillpoints[i] += item.get("skillpoints")[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
const setName = item.get("set");
|
|
||||||
if (setName) { // undefined/null means no set.
|
|
||||||
let setCount = activeSetCounts.get(setName);
|
|
||||||
let old_bonus = {};
|
|
||||||
if (setCount) {
|
|
||||||
old_bonus = sets.get(setName).bonuses[setCount-1];
|
|
||||||
activeSetCounts.set(setName, setCount + 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setCount = 0;
|
|
||||||
activeSetCounts.set(setName, 1);
|
|
||||||
}
|
|
||||||
const new_bonus = sets.get(setName).bonuses[setCount];
|
|
||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function apply_to_fit(skillpoints, item, skillpoint_min, activeSetCounts) {
|
|
||||||
let applied = [0, 0, 0, 0, 0];
|
|
||||||
let total = 0;
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
if (item.get("skillpoints")[i] < 0 && skillpoint_min[i]) {
|
|
||||||
const unadjusted = skillpoints[i] + item.get("skillpoints")[i];
|
|
||||||
const delta = skillpoint_min[i] - unadjusted;
|
|
||||||
if (delta > 0) {
|
|
||||||
applied[i] += delta;
|
|
||||||
total += delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item.get("reqs")[i] == 0) continue;
|
|
||||||
skillpoint_min[i] = Math.max(skillpoint_min[i], item.get("reqs")[i] + item.get("skillpoints")[i]);
|
|
||||||
const req = item.get("reqs")[i];
|
|
||||||
const cur = skillpoints[i];
|
|
||||||
if (req > cur) {
|
|
||||||
const diff = req - cur;
|
|
||||||
applied[i] += diff;
|
|
||||||
total += diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setName = item.get("set");
|
|
||||||
if (setName) { // undefined/null means no set.
|
|
||||||
const setCount = activeSetCounts.get(setName);
|
|
||||||
if (setCount) {
|
|
||||||
const old_bonus = sets.get(setName).bonuses[setCount-1];
|
|
||||||
const new_bonus = sets.get(setName).bonuses[setCount];
|
|
||||||
//let skp_order = ["str","dex","int","def","agi"];
|
|
||||||
for (const i in skp_order) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [applied, total];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate out the no req items and add them to the static skillpoint base.
|
// Separate out the no req items and add them to the static skillpoint base.
|
||||||
let static_skillpoints_base = [0, 0, 0, 0, 0]
|
let static_skillpoints_base = [0, 0, 0, 0, 0]
|
||||||
|
@ -101,7 +119,7 @@ function calculate_skillpoints(equipment, weapon) {
|
||||||
apply_skillpoints(static_skillpoints_base, item, static_activeSetCounts);
|
apply_skillpoints(static_skillpoints_base, item, static_activeSetCounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
let best = consider.concat(noboost);
|
let best = consider;
|
||||||
let final_skillpoints = static_skillpoints_base.slice();
|
let final_skillpoints = static_skillpoints_base.slice();
|
||||||
let best_skillpoints = [0, 0, 0, 0, 0];
|
let best_skillpoints = [0, 0, 0, 0, 0];
|
||||||
let best_total = Infinity;
|
let best_total = Infinity;
|
||||||
|
@ -110,100 +128,175 @@ function calculate_skillpoints(equipment, weapon) {
|
||||||
let allFalse = [0, 0, 0, 0, 0];
|
let allFalse = [0, 0, 0, 0, 0];
|
||||||
if (consider.length > 0 || noboost.length > 0 || crafted.length > 0) {
|
if (consider.length > 0 || noboost.length > 0 || crafted.length > 0) {
|
||||||
// Try every combination and pick the best one.
|
// Try every combination and pick the best one.
|
||||||
for (let permutation of perm(consider)) {
|
const [root, terminal, sccs] = construct_scc_graph(consider);
|
||||||
let activeSetCounts = new Map(static_activeSetCounts);
|
const end_checks = crafted.concat(noboost);
|
||||||
|
end_checks.push(weapon);
|
||||||
|
|
||||||
let has_skillpoint = allFalse.slice();
|
function check_end(skillpoints_applied, skillpoints, activeSetCounts, total_applied) {
|
||||||
|
|
||||||
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) {
|
|
||||||
result = apply_to_fit(skillpoints, item, has_skillpoint, activeSetCounts);
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
apply_skillpoints(skillpoints, item, activeSetCounts);
|
|
||||||
total_applied += total_diff;
|
|
||||||
if (total_applied >= best_total) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crafted skillpoint does not count initially.
|
// Crafted skillpoint does not count initially.
|
||||||
for (const item of crafted) {
|
for (const item of end_checks) {
|
||||||
//console.log(item)
|
const needed_skillpoints = apply_to_fit(skillpoints, item,
|
||||||
result = apply_to_fit(skillpoints, item, allFalse.slice(), activeSetCounts);
|
[false, false, false, false, false], activeSetCounts);
|
||||||
//console.log(result)
|
|
||||||
needed_skillpoints = result[0];
|
|
||||||
total_diff = result[1];
|
|
||||||
|
|
||||||
for (let i = 0; i < 5; ++i) {
|
for (let i = 0; i < 5; ++i) {
|
||||||
skillpoints_applied[i] += needed_skillpoints[i];
|
const skp = needed_skillpoints[i]
|
||||||
skillpoints[i] += needed_skillpoints[i];
|
skillpoints_applied[i] += skp;
|
||||||
|
skillpoints[i] += skp;
|
||||||
|
total_applied += skp;
|
||||||
}
|
}
|
||||||
total_applied += total_diff;
|
if (best_total < total_applied) { return -1; }
|
||||||
}
|
|
||||||
|
|
||||||
if (total_applied >= best_total) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pre = skillpoints.slice();
|
|
||||||
result = apply_to_fit(skillpoints, weapon, allFalse.slice(), activeSetCounts);
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_skillpoints(skillpoints, weapon, activeSetCounts);
|
|
||||||
total_applied += total_diff;
|
|
||||||
|
|
||||||
// Applying crafted item skill points last.
|
|
||||||
for (const item of crafted) {
|
|
||||||
apply_skillpoints(skillpoints, item, activeSetCounts);
|
|
||||||
//total_applied += total_diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total_applied < best_total) {
|
|
||||||
best = permutation;
|
|
||||||
final_skillpoints = skillpoints;
|
|
||||||
best_skillpoints = skillpoints_applied;
|
|
||||||
best_total = total_applied;
|
|
||||||
best_activeSetCounts = activeSetCounts;
|
|
||||||
}
|
}
|
||||||
|
return total_applied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function permute_check(idx, _applied, _skillpoints, _sets, _has, _total_applied, order) {
|
||||||
|
const {nodes, children} = sccs[idx];
|
||||||
|
if (nodes[0] === terminal) {
|
||||||
|
const total = check_end(_applied, _skillpoints, _sets, _total_applied);
|
||||||
|
if (total !== -1 && total < best_total) {
|
||||||
|
final_skillpoints = _skillpoints;
|
||||||
|
best_skillpoints = _applied;
|
||||||
|
best_total = total;
|
||||||
|
best_activeSetCounts = _sets;
|
||||||
|
best = order;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let permutation of perm(nodes)) {
|
||||||
|
const skillpoints_applied = _applied.slice();
|
||||||
|
const skillpoints = _skillpoints.slice();
|
||||||
|
const activeSetCounts = new Map(_sets);
|
||||||
|
const has_skillpoint = _has.slice();
|
||||||
|
let total_applied = _total_applied;
|
||||||
|
let short_circuit = false;
|
||||||
|
for (const {item} of permutation) {
|
||||||
|
needed_skillpoints = apply_to_fit(skillpoints, item, has_skillpoint, activeSetCounts);
|
||||||
|
for (let i = 0; i < 5; ++i) {
|
||||||
|
skp = needed_skillpoints[i];
|
||||||
|
skillpoints_applied[i] += skp;
|
||||||
|
skillpoints[i] += skp;
|
||||||
|
total_applied += skp;
|
||||||
|
}
|
||||||
|
if (total_applied >= best_total) {
|
||||||
|
short_circuit = true;
|
||||||
|
break; // short circuit failure
|
||||||
|
}
|
||||||
|
apply_skillpoints(skillpoints, item, activeSetCounts);
|
||||||
|
}
|
||||||
|
if (short_circuit) { continue; }
|
||||||
|
permute_check(idx+1, skillpoints_applied, skillpoints, activeSetCounts, has_skillpoint, total_applied, order.concat(permutation.map(x => x.item)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// skip root.
|
||||||
|
permute_check(1, best_skillpoints, final_skillpoints, best_activeSetCounts, allFalse.slice(), 0, []);
|
||||||
|
|
||||||
|
// add extra sp bonus
|
||||||
|
apply_skillpoints(final_skillpoints, weapon, best_activeSetCounts);
|
||||||
|
// Applying crafted item skill points last.
|
||||||
|
for (const item of crafted) {
|
||||||
|
apply_skillpoints(final_skillpoints, item, best_activeSetCounts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
best_total = 0;
|
best_total = 0;
|
||||||
result = apply_to_fit(final_skillpoints, weapon, allFalse.slice(), best_activeSetCounts);
|
needed_skillpoints = apply_to_fit(final_skillpoints, weapon, allFalse.slice(), best_activeSetCounts);
|
||||||
needed_skillpoints = result[0];
|
|
||||||
total_diff = result[1];
|
|
||||||
for (let i = 0; i < 5; ++i) {
|
for (let i = 0; i < 5; ++i) {
|
||||||
best_skillpoints[i] += needed_skillpoints[i];
|
const skp = needed_skillpoints[i];
|
||||||
final_skillpoints[i] += needed_skillpoints[i];
|
best_skillpoints[i] += skp;
|
||||||
|
final_skillpoints[i] += skp;
|
||||||
|
best_skillpoints += skp;
|
||||||
}
|
}
|
||||||
apply_skillpoints(final_skillpoints, weapon, best_activeSetCounts);
|
apply_skillpoints(final_skillpoints, weapon, best_activeSetCounts);
|
||||||
best_total += total_diff;
|
|
||||||
}
|
}
|
||||||
let equip_order = fixed.concat(best).concat(crafted);
|
let equip_order = fixed.concat(best).concat(noboost).concat(crafted);
|
||||||
// best_skillpoints: manually assigned (before any gear)
|
// best_skillpoints: manually assigned (before any gear)
|
||||||
// final_skillpoints: final totals (5 individ)
|
// final_skillpoints: final totals (5 individ)
|
||||||
// best_total: total skillpoints assigned (number)
|
// best_total: total skillpoints assigned (number)
|
||||||
|
// const end = performance.now();
|
||||||
|
// const output_msg = `skillpoint calculation took ${(end-start)/ 1000} seconds.`;
|
||||||
|
// console.log(output_msg);
|
||||||
return [equip_order, best_skillpoints, final_skillpoints, best_total, best_activeSetCounts];
|
return [equip_order, best_skillpoints, final_skillpoints, best_total, best_activeSetCounts];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function construct_scc_graph(items_to_consider) {
|
||||||
|
let nodes = [];
|
||||||
|
let terminal_node = {
|
||||||
|
item: null,
|
||||||
|
children: [],
|
||||||
|
parents: nodes,
|
||||||
|
visited: false,
|
||||||
|
assigned: false,
|
||||||
|
scc: null
|
||||||
|
};
|
||||||
|
let root_node = {
|
||||||
|
item: null,
|
||||||
|
children: nodes,
|
||||||
|
parents: [],
|
||||||
|
visited: false,
|
||||||
|
assigned: false,
|
||||||
|
scc: null
|
||||||
|
};
|
||||||
|
for (const item of items_to_consider) {
|
||||||
|
nodes.push({item: item, children: [terminal_node], parents: [root_node], visited: false, assigned: false, scc: null});
|
||||||
|
}
|
||||||
|
// 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 (a.skillpoints[i] > 0 && (a.reqs[i] < b.reqs[i] || b.skillpoints[i] < 0)) {
|
||||||
|
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();
|
||||||
|
const sccs = [];
|
||||||
|
function assign(node, cur_scc) {
|
||||||
|
if (node.assigned) { return; }
|
||||||
|
cur_scc.nodes.push(node);
|
||||||
|
node.scc = cur_scc;
|
||||||
|
node.assigned = true;
|
||||||
|
for (const parent of node.parents) {
|
||||||
|
assign(parent, cur_scc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const node of res) {
|
||||||
|
if (node.assigned) { continue; }
|
||||||
|
const cur_scc = {
|
||||||
|
nodes: [],
|
||||||
|
children: new Set(),
|
||||||
|
parents: new Set()
|
||||||
|
};
|
||||||
|
assign(node, cur_scc);
|
||||||
|
sccs.push(cur_scc);
|
||||||
|
}
|
||||||
|
for (const scc of sccs) {
|
||||||
|
for (const node of scc.nodes) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
scc.children.add(child.scc);
|
||||||
|
}
|
||||||
|
for (const parent of node.parents) {
|
||||||
|
scc.parents.add(parent.scc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [root_node, terminal_node, sccs];
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue