Compute graph cleanup, prepping for full build calc (currently broke)
This commit is contained in:
parent
4f7f0f9cfc
commit
62a9a4f0c2
6 changed files with 346 additions and 472 deletions
|
@ -313,10 +313,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-auto px-1 text-nowrap scaled-font">
|
<div class="col-auto px-1 text-nowrap scaled-font">
|
||||||
<button class="border-dark text-light dark-5 scaled-font rounded" id=copy-button onclick="copyBuild()">Copy short</button>
|
<button class="border-dark text-light dark-5 scaled-font rounded" id=copy-button onclick="copyBuild(player_build)">Copy short</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto px-1 text-nowrap scaled-font">
|
<div class="col-auto px-1 text-nowrap scaled-font">
|
||||||
<button class="border-dark text-light dark-5 scaled-font rounded" id=share-button onclick="shareBuild()">Copy for sharing</button>
|
<button class="border-dark text-light dark-5 scaled-font rounded" id=share-button onclick="shareBuild(player_build)">Copy for sharing</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1399,8 +1399,9 @@
|
||||||
<script type="text/javascript" src="../js/load_tome.js"></script>
|
<script type="text/javascript" src="../js/load_tome.js"></script>
|
||||||
<script type="text/javascript" src="../js/custom.js"></script>
|
<script type="text/javascript" src="../js/custom.js"></script>
|
||||||
<script type="text/javascript" src="../js/craft.js"></script>
|
<script type="text/javascript" src="../js/craft.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2build.js"></script>
|
<script type="text/javascript" src="../js/build.js"></script>
|
||||||
<script type="text/javascript" src="../js/build_constants.js"></script>
|
<script type="text/javascript" src="../js/build_constants.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/build_encode_decode.js"></script>
|
||||||
<script type="text/javascript" src="../js/builder.js"></script>
|
<script type="text/javascript" src="../js/builder.js"></script>
|
||||||
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
||||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||||
|
|
262
js/build.js
262
js/build.js
|
@ -100,238 +100,12 @@ class Build{
|
||||||
* @param {Number[]} powders : Powder application. List of lists of integers (powder IDs).
|
* @param {Number[]} powders : Powder application. List of lists of integers (powder IDs).
|
||||||
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
||||||
* @param {Object[]} inputerrors : List of instances of error-like classes.
|
* @param {Object[]} inputerrors : List of instances of error-like classes.
|
||||||
|
*
|
||||||
|
* @param {Object[]} tomes: List of tomes.
|
||||||
|
* In order: 2x Weapon Mastery Tome, 4x Armor Mastery Tome, 1x Guild Tome.
|
||||||
|
* 2x Slaying Mastery Tome, 2x Dungeoneering Mastery Tome, 2x Gathering Mastery Tome are in game, but do not have "useful" stats (those that affect damage calculations or building)
|
||||||
*/
|
*/
|
||||||
constructor(level,equipment, powders, externalStats, inputerrors=[]){
|
constructor(level, items, tomes, weapon){
|
||||||
|
|
||||||
let errors = inputerrors;
|
|
||||||
//this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem).
|
|
||||||
this.craftedItems = [];
|
|
||||||
this.customItems = [];
|
|
||||||
// NOTE: powders is just an array of arrays of powder IDs. Not powder objects.
|
|
||||||
this.powders = powders;
|
|
||||||
if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") {
|
|
||||||
const helmet = itemMap.get(equipment[0]);
|
|
||||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
|
||||||
this.helmet = expandItem(helmet, this.powders[0]);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined);
|
|
||||||
if (helmet.statMap.get("type") !== "helmet") {
|
|
||||||
throw new Error("Not a helmet");
|
|
||||||
}
|
|
||||||
this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots"));
|
|
||||||
helmet.statMap.set("powders",this.powders[0].slice());
|
|
||||||
this.helmet = helmet.statMap;
|
|
||||||
applyArmorPowders(this.helmet, this.powders[0]);
|
|
||||||
if (this.helmet.get("custom")) {
|
|
||||||
this.customItems.push(helmet);
|
|
||||||
} else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(helmet);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const helmet = itemMap.get("No Helmet");
|
|
||||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
|
||||||
this.helmet = expandItem(helmet, this.powders[0]);
|
|
||||||
errors.push(new ItemNotFound(equipment[0], "helmet", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") {
|
|
||||||
const chestplate = itemMap.get(equipment[1]);
|
|
||||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
|
||||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined);
|
|
||||||
if (chestplate.statMap.get("type") !== "chestplate") {
|
|
||||||
throw new Error("Not a chestplate");
|
|
||||||
}
|
|
||||||
this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots"));
|
|
||||||
chestplate.statMap.set("powders",this.powders[1].slice());
|
|
||||||
this.chestplate = chestplate.statMap;
|
|
||||||
applyArmorPowders(this.chestplate, this.powders[1]);
|
|
||||||
if (this.chestplate.get("custom")) {
|
|
||||||
this.customItems.push(chestplate);
|
|
||||||
} else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(chestplate);
|
|
||||||
}
|
|
||||||
} catch (Error) {
|
|
||||||
console.log(Error);
|
|
||||||
const chestplate = itemMap.get("No Chestplate");
|
|
||||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
|
||||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
|
||||||
errors.push(new ItemNotFound(equipment[1], "chestplate", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") {
|
|
||||||
const leggings = itemMap.get(equipment[2]);
|
|
||||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
|
||||||
this.leggings = expandItem(leggings, this.powders[2]);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined);
|
|
||||||
if (leggings.statMap.get("type") !== "leggings") {
|
|
||||||
throw new Error("Not a leggings");
|
|
||||||
}
|
|
||||||
this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots"));
|
|
||||||
leggings.statMap.set("powders",this.powders[2].slice());
|
|
||||||
this.leggings = leggings.statMap;
|
|
||||||
applyArmorPowders(this.leggings, this.powders[2]);
|
|
||||||
if (this.leggings.get("custom")) {
|
|
||||||
this.customItems.push(leggings);
|
|
||||||
} else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(leggings);
|
|
||||||
}
|
|
||||||
} catch (Error) {
|
|
||||||
const leggings = itemMap.get("No Leggings");
|
|
||||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
|
||||||
this.leggings = expandItem(leggings, this.powders[2]);
|
|
||||||
errors.push(new ItemNotFound(equipment[2], "leggings", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") {
|
|
||||||
const boots = itemMap.get(equipment[3]);
|
|
||||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
|
||||||
this.boots = expandItem(boots, this.powders[3]);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined);
|
|
||||||
if (boots.statMap.get("type") !== "boots") {
|
|
||||||
throw new Error("Not a boots");
|
|
||||||
}
|
|
||||||
this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots"));
|
|
||||||
boots.statMap.set("powders",this.powders[3].slice());
|
|
||||||
this.boots = boots.statMap;
|
|
||||||
applyArmorPowders(this.boots, this.powders[3]);
|
|
||||||
if (this.boots.get("custom")) {
|
|
||||||
this.customItems.push(boots);
|
|
||||||
} else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(boots);
|
|
||||||
}
|
|
||||||
} catch (Error) {
|
|
||||||
const boots = itemMap.get("No Boots");
|
|
||||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
|
||||||
this.boots = expandItem(boots, this.powders[3]);
|
|
||||||
errors.push(new ItemNotFound(equipment[3], "boots", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") {
|
|
||||||
const ring = itemMap.get(equipment[4]);
|
|
||||||
this.ring1 = expandItem(ring, []);
|
|
||||||
}else{
|
|
||||||
try {
|
|
||||||
let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined);
|
|
||||||
if (ring.statMap.get("type") !== "ring") {
|
|
||||||
throw new Error("Not a ring");
|
|
||||||
}
|
|
||||||
this.ring1 = ring.statMap;
|
|
||||||
if (this.ring1.get("custom")) {
|
|
||||||
this.customItems.push(ring);
|
|
||||||
} else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(ring);
|
|
||||||
}
|
|
||||||
} catch (Error) {
|
|
||||||
const ring = itemMap.get("No Ring 1");
|
|
||||||
this.ring1 = expandItem(ring, []);
|
|
||||||
errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") {
|
|
||||||
const ring = itemMap.get(equipment[5]);
|
|
||||||
this.ring2 = expandItem(ring, []);
|
|
||||||
}else{
|
|
||||||
try {
|
|
||||||
let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined);
|
|
||||||
if (ring.statMap.get("type") !== "ring") {
|
|
||||||
throw new Error("Not a ring");
|
|
||||||
}
|
|
||||||
this.ring2 = ring.statMap;
|
|
||||||
if (this.ring2.get("custom")) {
|
|
||||||
this.customItems.push(ring);
|
|
||||||
} else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(ring);
|
|
||||||
}
|
|
||||||
} catch (Error) {
|
|
||||||
const ring = itemMap.get("No Ring 2");
|
|
||||||
this.ring2 = expandItem(ring, []);
|
|
||||||
errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") {
|
|
||||||
const bracelet = itemMap.get(equipment[6]);
|
|
||||||
this.bracelet = expandItem(bracelet, []);
|
|
||||||
}else{
|
|
||||||
try {
|
|
||||||
let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined);
|
|
||||||
if (bracelet.statMap.get("type") !== "bracelet") {
|
|
||||||
throw new Error("Not a bracelet");
|
|
||||||
}
|
|
||||||
this.bracelet = bracelet.statMap;
|
|
||||||
if (this.bracelet.get("custom")) {
|
|
||||||
this.customItems.push(bracelet);
|
|
||||||
} else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(bracelet);
|
|
||||||
}
|
|
||||||
} catch (Error) {
|
|
||||||
const bracelet = itemMap.get("No Bracelet");
|
|
||||||
this.bracelet = expandItem(bracelet, []);
|
|
||||||
errors.push(new ItemNotFound(equipment[6], "bracelet", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") {
|
|
||||||
const necklace = itemMap.get(equipment[7]);
|
|
||||||
this.necklace = expandItem(necklace, []);
|
|
||||||
}else{
|
|
||||||
try {
|
|
||||||
let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined);
|
|
||||||
if (necklace.statMap.get("type") !== "necklace") {
|
|
||||||
throw new Error("Not a necklace");
|
|
||||||
}
|
|
||||||
this.necklace = necklace.statMap;
|
|
||||||
if (this.necklace.get("custom")) {
|
|
||||||
this.customItems.push(necklace);
|
|
||||||
} else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(necklace);
|
|
||||||
}
|
|
||||||
} catch (Error) {
|
|
||||||
const necklace = itemMap.get("No Necklace");
|
|
||||||
this.necklace = expandItem(necklace, []);
|
|
||||||
errors.push(new ItemNotFound(equipment[7], "necklace", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") {
|
|
||||||
const weapon = itemMap.get(equipment[8]);
|
|
||||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
|
||||||
this.weapon = expandItem(weapon, this.powders[4]);
|
|
||||||
if (equipment[8] !== "No Weapon") {
|
|
||||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
|
||||||
} else {
|
|
||||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
try {
|
|
||||||
let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined);
|
|
||||||
if (weapon.statMap.get("category") !== "weapon") {
|
|
||||||
throw new Error("Not a weapon");
|
|
||||||
}
|
|
||||||
this.weapon = weapon.statMap;
|
|
||||||
if (this.weapon.get("custom")) {
|
|
||||||
this.customItems.push(weapon);
|
|
||||||
} else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
|
||||||
this.craftedItems.push(weapon);
|
|
||||||
}
|
|
||||||
this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots"));
|
|
||||||
this.weapon.set("powders",this.powders[4].slice());
|
|
||||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
|
||||||
} catch (Error) {
|
|
||||||
const weapon = itemMap.get("No Weapon");
|
|
||||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
|
||||||
this.weapon = expandItem(weapon, this.powders[4]);
|
|
||||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
|
||||||
errors.push(new ItemNotFound(equipment[8], "weapon", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//console.log(this.craftedItems)
|
|
||||||
|
|
||||||
if (level < 1) { //Should these be constants?
|
if (level < 1) { //Should these be constants?
|
||||||
this.level = 1;
|
this.level = 1;
|
||||||
|
@ -348,10 +122,12 @@ class Build{
|
||||||
document.getElementById("level-choice").value = this.level;
|
document.getElementById("level-choice").value = this.level;
|
||||||
|
|
||||||
this.availableSkillpoints = levelToSkillPoints(this.level);
|
this.availableSkillpoints = levelToSkillPoints(this.level);
|
||||||
this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
|
this.equipment = items;
|
||||||
this.items = this.equipment.concat([this.weapon]);
|
this.tomes = tomes;
|
||||||
|
this.weapon = weapon;
|
||||||
|
this.items = this.equipment.concat([this.weapon]).concat(this.tomes);
|
||||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||||
let result = calculate_skillpoints(this.equipment, this.weapon);
|
let result = calculate_skillpoints(this.equipment.concat(this.tomes), this.weapon);
|
||||||
console.log(result);
|
console.log(result);
|
||||||
this.equip_order = result[0];
|
this.equip_order = result[0];
|
||||||
// How many skillpoints the player had to assign (5 number)
|
// How many skillpoints the player had to assign (5 number)
|
||||||
|
@ -361,28 +137,14 @@ class Build{
|
||||||
// How many skillpoints assigned (1 number, sum of base_skillpoints)
|
// How many skillpoints assigned (1 number, sum of base_skillpoints)
|
||||||
this.assigned_skillpoints = result[3];
|
this.assigned_skillpoints = result[3];
|
||||||
this.activeSetCounts = result[4];
|
this.activeSetCounts = result[4];
|
||||||
|
|
||||||
// For strength boosts like warscream, vanish, etc.
|
|
||||||
this.damageMultiplier = 1.0;
|
|
||||||
this.defenseMultiplier = 1.0;
|
|
||||||
|
|
||||||
// For other external boosts ;-;
|
|
||||||
this.externalStats = externalStats;
|
|
||||||
|
|
||||||
this.initBuildStats();
|
this.initBuildStats();
|
||||||
|
|
||||||
// Remove every error before adding specific ones
|
|
||||||
for (let i of document.getElementsByClassName("error")) {
|
|
||||||
i.textContent = "";
|
|
||||||
}
|
|
||||||
this.errors = errors;
|
|
||||||
if (errors.length > 0) this.errored = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Returns build in string format
|
/*Returns build in string format
|
||||||
*/
|
*/
|
||||||
toString(){
|
toString(){
|
||||||
return [this.equipment,this.weapon].flat();
|
return [this.equipment,this.weapon,this.tomes].flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getters */
|
/* Getters */
|
||||||
|
@ -392,7 +154,7 @@ class Build{
|
||||||
}
|
}
|
||||||
|
|
||||||
getBaseSpellCost(spellIdx, cost) {
|
getBaseSpellCost(spellIdx, cost) {
|
||||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
|
// old intelligence: cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
|
||||||
cost += this.statMap.get("spRaw"+spellIdx);
|
cost += this.statMap.get("spRaw"+spellIdx);
|
||||||
return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100));
|
return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100));
|
||||||
}
|
}
|
||||||
|
|
201
js/build_encode_decode.js
Normal file
201
js/build_encode_decode.js
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Populate fields based on url, and calculate build.
|
||||||
|
*/
|
||||||
|
function decodeBuild(url_tag) {
|
||||||
|
if (url_tag) {
|
||||||
|
//default values
|
||||||
|
let equipment = [null, null, null, null, null, null, null, null, null];
|
||||||
|
let tomes = [null, null, null, null, null, null, null];
|
||||||
|
let powdering = ["", "", "", "", ""];
|
||||||
|
let info = url_tag.split("_");
|
||||||
|
let version = info[0];
|
||||||
|
let save_skp = false;
|
||||||
|
let skillpoints = [0, 0, 0, 0, 0];
|
||||||
|
let level = 106;
|
||||||
|
|
||||||
|
let version_number = parseInt(version)
|
||||||
|
//equipment (items)
|
||||||
|
// TODO: use filters
|
||||||
|
if (version_number < 4) {
|
||||||
|
let equipments = info[1];
|
||||||
|
for (let i = 0; i < 9; ++i ) {
|
||||||
|
let equipment_str = equipments.slice(i*3,i*3+3);
|
||||||
|
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||||
|
}
|
||||||
|
info[1] = equipments.slice(27);
|
||||||
|
}
|
||||||
|
else if (version_number == 4) {
|
||||||
|
let info_str = info[1];
|
||||||
|
let start_idx = 0;
|
||||||
|
for (let i = 0; i < 9; ++i ) {
|
||||||
|
if (info_str.charAt(start_idx) === "-") {
|
||||||
|
equipment[i] = "CR-"+info_str.slice(start_idx+1, start_idx+18);
|
||||||
|
start_idx += 18;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
||||||
|
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||||
|
start_idx += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info[1] = info_str.slice(start_idx);
|
||||||
|
}
|
||||||
|
else if (version_number <= 6) {
|
||||||
|
let info_str = info[1];
|
||||||
|
let start_idx = 0;
|
||||||
|
for (let i = 0; i < 9; ++i ) {
|
||||||
|
if (info_str.slice(start_idx,start_idx+3) === "CR-") {
|
||||||
|
equipment[i] = info_str.slice(start_idx, start_idx+20);
|
||||||
|
start_idx += 20;
|
||||||
|
} else if (info_str.slice(start_idx+3,start_idx+6) === "CI-") {
|
||||||
|
let len = Base64.toInt(info_str.slice(start_idx,start_idx+3));
|
||||||
|
equipment[i] = info_str.slice(start_idx+3,start_idx+3+len);
|
||||||
|
start_idx += (3+len);
|
||||||
|
} else {
|
||||||
|
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
||||||
|
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||||
|
start_idx += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info[1] = info_str.slice(start_idx);
|
||||||
|
}
|
||||||
|
//constant in all versions
|
||||||
|
for (let i in equipment) {
|
||||||
|
setValue(equipmentInputs[i], equipment[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//level, skill point assignments, and powdering
|
||||||
|
if (version_number == 1) {
|
||||||
|
let powder_info = info[1];
|
||||||
|
let res = parsePowdering(powder_info);
|
||||||
|
powdering = res[0];
|
||||||
|
} else if (version_number == 2) {
|
||||||
|
save_skp = true;
|
||||||
|
let skillpoint_info = info[1].slice(0, 10);
|
||||||
|
for (let i = 0; i < 5; ++i ) {
|
||||||
|
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
||||||
|
}
|
||||||
|
|
||||||
|
let powder_info = info[1].slice(10);
|
||||||
|
let res = parsePowdering(powder_info);
|
||||||
|
powdering = res[0];
|
||||||
|
} else if (version_number <= 6){
|
||||||
|
level = Base64.toInt(info[1].slice(10,12));
|
||||||
|
setValue("level-choice",level);
|
||||||
|
save_skp = true;
|
||||||
|
let skillpoint_info = info[1].slice(0, 10);
|
||||||
|
for (let i = 0; i < 5; ++i ) {
|
||||||
|
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
||||||
|
}
|
||||||
|
|
||||||
|
let powder_info = info[1].slice(12);
|
||||||
|
|
||||||
|
let res = parsePowdering(powder_info);
|
||||||
|
powdering = res[0];
|
||||||
|
info[1] = res[1];
|
||||||
|
}
|
||||||
|
// Tomes.
|
||||||
|
if (version == 6) {
|
||||||
|
//tome values do not appear in anything before v6.
|
||||||
|
for (let i = 0; i < 7; ++i) {
|
||||||
|
let tome_str = info[1].charAt(i);
|
||||||
|
for (let i in tomes) {
|
||||||
|
setValue(tomeInputs[i], getTomeNameFromID(Base64.toInt(tome_str)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info[1] = info[1].slice(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i in powderInputs) {
|
||||||
|
setValue(powderInputs[i], powdering[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
|
||||||
|
*/
|
||||||
|
function encodeBuild(build) {
|
||||||
|
|
||||||
|
if (build) {
|
||||||
|
let build_string;
|
||||||
|
|
||||||
|
//V6 encoding - Tomes
|
||||||
|
build_version = 4;
|
||||||
|
build_string = "";
|
||||||
|
tome_string = "";
|
||||||
|
|
||||||
|
let crafted_idx = 0;
|
||||||
|
let custom_idx = 0;
|
||||||
|
for (const item of build.items) {
|
||||||
|
|
||||||
|
if (item.get("custom")) {
|
||||||
|
let custom = "CI-"+encodeCustom(build.customItems[custom_idx],true);
|
||||||
|
build_string += Base64.fromIntN(custom.length, 3) + custom;
|
||||||
|
custom_idx += 1;
|
||||||
|
build_version = Math.max(build_version, 5);
|
||||||
|
} else if (item.get("crafted")) {
|
||||||
|
build_string += "CR-"+encodeCraft(build.craftedItems[crafted_idx]);
|
||||||
|
crafted_idx += 1;
|
||||||
|
} else if (item.get("category") === "tome") {
|
||||||
|
let tome_id = item.get("id");
|
||||||
|
if (tome_id <= 60) {
|
||||||
|
// valid normal tome. ID 61-63 is for NONE tomes.
|
||||||
|
build_version = Math.max(build_version, 6);
|
||||||
|
}
|
||||||
|
tome_string += Base64.fromIntN(tome_id, 1);
|
||||||
|
} else {
|
||||||
|
build_string += Base64.fromIntN(item.get("id"), 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const skp of skp_order) {
|
||||||
|
build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048
|
||||||
|
}
|
||||||
|
build_string += Base64.fromIntN(build.level, 2);
|
||||||
|
for (const _powderset of build.powders) {
|
||||||
|
let n_bits = Math.ceil(_powderset.length / 6);
|
||||||
|
build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders.
|
||||||
|
// Slice copy.
|
||||||
|
let powderset = _powderset.slice();
|
||||||
|
while (powderset.length != 0) {
|
||||||
|
let firstSix = powderset.slice(0,6).reverse();
|
||||||
|
let powder_hash = 0;
|
||||||
|
for (const powder of firstSix) {
|
||||||
|
powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first.
|
||||||
|
}
|
||||||
|
build_string += Base64.fromIntN(powder_hash, 5);
|
||||||
|
powderset = powderset.slice(6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
build_string += tome_string;
|
||||||
|
|
||||||
|
return build_version.toString() + "_" + build_string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyBuild(build) {
|
||||||
|
if (build) {
|
||||||
|
copyTextToClipboard(url_base+location.hash);
|
||||||
|
document.getElementById("copy-button").textContent = "Copied!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shareBuild(build) {
|
||||||
|
if (build) {
|
||||||
|
let text = url_base+location.hash+"\n"+
|
||||||
|
"WynnBuilder build:\n"+
|
||||||
|
"> "+build.helmet.get("displayName")+"\n"+
|
||||||
|
"> "+build.chestplate.get("displayName")+"\n"+
|
||||||
|
"> "+build.leggings.get("displayName")+"\n"+
|
||||||
|
"> "+build.boots.get("displayName")+"\n"+
|
||||||
|
"> "+build.ring1.get("displayName")+"\n"+
|
||||||
|
"> "+build.ring2.get("displayName")+"\n"+
|
||||||
|
"> "+build.bracelet.get("displayName")+"\n"+
|
||||||
|
"> "+build.necklace.get("displayName")+"\n"+
|
||||||
|
"> "+build.weapon.get("displayName")+" ["+build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]";
|
||||||
|
copyTextToClipboard(text);
|
||||||
|
document.getElementById("share-button").textContent = "Copied!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
211
js/builder.js
211
js/builder.js
|
@ -35,206 +35,6 @@ function parsePowdering(powder_info) {
|
||||||
return [powdering, powder_info];
|
return [powdering, powder_info];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate fields based on url, and calculate build.
|
|
||||||
*/
|
|
||||||
function decodeBuild(url_tag) {
|
|
||||||
if (url_tag) {
|
|
||||||
//default values
|
|
||||||
let equipment = [null, null, null, null, null, null, null, null, null];
|
|
||||||
let tomes = [null, null, null, null, null, null, null];
|
|
||||||
let powdering = ["", "", "", "", ""];
|
|
||||||
let info = url_tag.split("_");
|
|
||||||
let version = info[0];
|
|
||||||
let save_skp = false;
|
|
||||||
let skillpoints = [0, 0, 0, 0, 0];
|
|
||||||
let level = 106;
|
|
||||||
|
|
||||||
let version_number = parseInt(version)
|
|
||||||
//equipment (items)
|
|
||||||
// TODO: use filters
|
|
||||||
if (version_number < 4) {
|
|
||||||
let equipments = info[1];
|
|
||||||
for (let i = 0; i < 9; ++i ) {
|
|
||||||
let equipment_str = equipments.slice(i*3,i*3+3);
|
|
||||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
|
||||||
}
|
|
||||||
info[1] = equipments.slice(27);
|
|
||||||
}
|
|
||||||
else if (version_number == 4) {
|
|
||||||
let info_str = info[1];
|
|
||||||
let start_idx = 0;
|
|
||||||
for (let i = 0; i < 9; ++i ) {
|
|
||||||
if (info_str.charAt(start_idx) === "-") {
|
|
||||||
equipment[i] = "CR-"+info_str.slice(start_idx+1, start_idx+18);
|
|
||||||
start_idx += 18;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
|
||||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
|
||||||
start_idx += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info[1] = info_str.slice(start_idx);
|
|
||||||
}
|
|
||||||
else if (version_number <= 6) {
|
|
||||||
let info_str = info[1];
|
|
||||||
let start_idx = 0;
|
|
||||||
for (let i = 0; i < 9; ++i ) {
|
|
||||||
if (info_str.slice(start_idx,start_idx+3) === "CR-") {
|
|
||||||
equipment[i] = info_str.slice(start_idx, start_idx+20);
|
|
||||||
start_idx += 20;
|
|
||||||
} else if (info_str.slice(start_idx+3,start_idx+6) === "CI-") {
|
|
||||||
let len = Base64.toInt(info_str.slice(start_idx,start_idx+3));
|
|
||||||
equipment[i] = info_str.slice(start_idx+3,start_idx+3+len);
|
|
||||||
start_idx += (3+len);
|
|
||||||
} else {
|
|
||||||
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
|
||||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
|
||||||
start_idx += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info[1] = info_str.slice(start_idx);
|
|
||||||
}
|
|
||||||
//constant in all versions
|
|
||||||
for (let i in equipment) {
|
|
||||||
setValue(equipmentInputs[i], equipment[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//level, skill point assignments, and powdering
|
|
||||||
if (version_number == 1) {
|
|
||||||
let powder_info = info[1];
|
|
||||||
let res = parsePowdering(powder_info);
|
|
||||||
powdering = res[0];
|
|
||||||
} else if (version_number == 2) {
|
|
||||||
save_skp = true;
|
|
||||||
let skillpoint_info = info[1].slice(0, 10);
|
|
||||||
for (let i = 0; i < 5; ++i ) {
|
|
||||||
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
|
||||||
}
|
|
||||||
|
|
||||||
let powder_info = info[1].slice(10);
|
|
||||||
let res = parsePowdering(powder_info);
|
|
||||||
powdering = res[0];
|
|
||||||
} else if (version_number <= 6){
|
|
||||||
level = Base64.toInt(info[1].slice(10,12));
|
|
||||||
setValue("level-choice",level);
|
|
||||||
save_skp = true;
|
|
||||||
let skillpoint_info = info[1].slice(0, 10);
|
|
||||||
for (let i = 0; i < 5; ++i ) {
|
|
||||||
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
|
||||||
}
|
|
||||||
|
|
||||||
let powder_info = info[1].slice(12);
|
|
||||||
|
|
||||||
let res = parsePowdering(powder_info);
|
|
||||||
powdering = res[0];
|
|
||||||
info[1] = res[1];
|
|
||||||
}
|
|
||||||
// Tomes.
|
|
||||||
if (version == 6) {
|
|
||||||
//tome values do not appear in anything before v6.
|
|
||||||
for (let i = 0; i < 7; ++i) {
|
|
||||||
let tome_str = info[1].charAt(i);
|
|
||||||
for (let i in tomes) {
|
|
||||||
setValue(tomeInputs[i], getTomeNameFromID(Base64.toInt(tome_str)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info[1] = info[1].slice(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i in powderInputs) {
|
|
||||||
setValue(powderInputs[i], powdering[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
|
|
||||||
*/
|
|
||||||
function encodeBuild() {
|
|
||||||
|
|
||||||
if (player_build) {
|
|
||||||
let build_string;
|
|
||||||
|
|
||||||
//V6 encoding - Tomes
|
|
||||||
build_version = 4;
|
|
||||||
build_string = "";
|
|
||||||
tome_string = "";
|
|
||||||
|
|
||||||
let crafted_idx = 0;
|
|
||||||
let custom_idx = 0;
|
|
||||||
for (const item of player_build.items) {
|
|
||||||
|
|
||||||
if (item.get("custom")) {
|
|
||||||
let custom = "CI-"+encodeCustom(player_build.customItems[custom_idx],true);
|
|
||||||
build_string += Base64.fromIntN(custom.length, 3) + custom;
|
|
||||||
custom_idx += 1;
|
|
||||||
build_version = Math.max(build_version, 5);
|
|
||||||
} else if (item.get("crafted")) {
|
|
||||||
build_string += "CR-"+encodeCraft(player_build.craftedItems[crafted_idx]);
|
|
||||||
crafted_idx += 1;
|
|
||||||
} else if (item.get("category") === "tome") {
|
|
||||||
let tome_id = item.get("id");
|
|
||||||
if (tome_id <= 60) {
|
|
||||||
// valid normal tome. ID 61-63 is for NONE tomes.
|
|
||||||
build_version = Math.max(build_version, 6);
|
|
||||||
}
|
|
||||||
tome_string += Base64.fromIntN(tome_id, 1);
|
|
||||||
} else {
|
|
||||||
build_string += Base64.fromIntN(item.get("id"), 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const skp of skp_order) {
|
|
||||||
build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048
|
|
||||||
}
|
|
||||||
build_string += Base64.fromIntN(player_build.level, 2);
|
|
||||||
for (const _powderset of player_build.powders) {
|
|
||||||
let n_bits = Math.ceil(_powderset.length / 6);
|
|
||||||
build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders.
|
|
||||||
// Slice copy.
|
|
||||||
let powderset = _powderset.slice();
|
|
||||||
while (powderset.length != 0) {
|
|
||||||
let firstSix = powderset.slice(0,6).reverse();
|
|
||||||
let powder_hash = 0;
|
|
||||||
for (const powder of firstSix) {
|
|
||||||
powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first.
|
|
||||||
}
|
|
||||||
build_string += Base64.fromIntN(powder_hash, 5);
|
|
||||||
powderset = powderset.slice(6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
build_string += tome_string;
|
|
||||||
|
|
||||||
return build_version.toString() + "_" + build_string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyBuild() {
|
|
||||||
if (player_build) {
|
|
||||||
copyTextToClipboard(url_base+location.hash);
|
|
||||||
document.getElementById("copy-button").textContent = "Copied!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shareBuild() {
|
|
||||||
if (player_build) {
|
|
||||||
let text = url_base+location.hash+"\n"+
|
|
||||||
"WynnBuilder build:\n"+
|
|
||||||
"> "+player_build.helmet.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.chestplate.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.leggings.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.boots.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.ring1.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.ring2.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.bracelet.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.necklace.get("displayName")+"\n"+
|
|
||||||
"> "+player_build.weapon.get("displayName")+" ["+player_build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]";
|
|
||||||
copyTextToClipboard(text);
|
|
||||||
document.getElementById("share-button").textContent = "Copied!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateBuildList() {
|
function populateBuildList() {
|
||||||
const buildList = document.getElementById("build-choice");
|
const buildList = document.getElementById("build-choice");
|
||||||
const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
|
const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
|
||||||
|
@ -250,7 +50,7 @@ function saveBuild() {
|
||||||
if (player_build) {
|
if (player_build) {
|
||||||
const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
|
const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
|
||||||
const saveName = document.getElementById("build-name").value;
|
const saveName = document.getElementById("build-name").value;
|
||||||
const encodedBuild = encodeBuild();
|
const encodedBuild = encodeBuild(player_build);
|
||||||
if ((!Object.keys(savedBuilds).includes(saveName)
|
if ((!Object.keys(savedBuilds).includes(saveName)
|
||||||
|| document.getElementById("saved-error").textContent !== "") && encodedBuild !== "") {
|
|| document.getElementById("saved-error").textContent !== "") && encodedBuild !== "") {
|
||||||
savedBuilds[saveName] = encodedBuild.replace("#", "");
|
savedBuilds[saveName] = encodedBuild.replace("#", "");
|
||||||
|
@ -330,6 +130,15 @@ function toggleButton(button_id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toggle tab
|
||||||
|
function toggle_tab(tab) {
|
||||||
|
if (document.querySelector("#"+tab).style.display == "none") {
|
||||||
|
document.querySelector("#"+tab).style.display = "";
|
||||||
|
} else {
|
||||||
|
document.querySelector("#"+tab).style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Learn and use await
|
// TODO: Learn and use await
|
||||||
function init() {
|
function init() {
|
||||||
console.log("builder.js init");
|
console.log("builder.js init");
|
||||||
|
|
|
@ -1,5 +1,52 @@
|
||||||
|
|
||||||
|
|
||||||
|
class BuildEncodeNode extends ComputeNode {
|
||||||
|
constructor() {
|
||||||
|
super("builder-encode");
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
if (input_map.size !== 1) { throw "BuildEncodeNode accepts exactly one input (build)"; }
|
||||||
|
const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
|
||||||
|
return encodeBuild(build);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class URLUpdateNode extends ComputeNode {
|
||||||
|
constructor() {
|
||||||
|
super("builder-url-update");
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
if (input_map.size !== 1) { throw "URLUpdateNode accepts exactly one input (build_str)"; }
|
||||||
|
const [build_str] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
|
||||||
|
location.hash = build_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BuildAssembleNode extends ComputeNode {
|
||||||
|
constructor() {
|
||||||
|
super("builder-make-build");
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
let equipments = [
|
||||||
|
input_map.get('helmet-input'),
|
||||||
|
input_map.get('chestplate-input'),
|
||||||
|
input_map.get('leggings-input'),
|
||||||
|
input_map.get('boots-input'),
|
||||||
|
input_map.get('ring1-input'),
|
||||||
|
input_map.get('ring2-input'),
|
||||||
|
input_map.get('bracelet-input'),
|
||||||
|
input_map.get('necklace-input')
|
||||||
|
];
|
||||||
|
let weapon = input_map.get('weapon-input');
|
||||||
|
let level = input_map.get('level-input');
|
||||||
|
console.log('build node run');
|
||||||
|
return new Build(level, equipments, [], weapon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let item_nodes = [];
|
let item_nodes = [];
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
@ -16,6 +63,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
}
|
}
|
||||||
let weapon_image = document.getElementById("weapon-img");
|
let weapon_image = document.getElementById("weapon-img");
|
||||||
new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]);
|
new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]);
|
||||||
|
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
|
||||||
|
new PrintNode('lvl-debug').link_to(level_input);
|
||||||
|
|
||||||
|
let build_node = new BuildAssembleNode();
|
||||||
|
for (const input of item_nodes) {
|
||||||
|
build_node.link_to(input);
|
||||||
|
}
|
||||||
|
build_node.link_to(level_input);
|
||||||
console.log("Set up graph");
|
console.log("Set up graph");
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -98,6 +153,7 @@ function init_autocomplete() {
|
||||||
if (event.detail.selection.value) {
|
if (event.detail.selection.value) {
|
||||||
event.target.value = event.detail.selection.value;
|
event.target.value = event.detail.selection.value;
|
||||||
}
|
}
|
||||||
|
event.target.dispatchEvent(new Event('input'));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,16 @@ class ComputeNode {
|
||||||
* @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes).
|
* @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes).
|
||||||
*/
|
*/
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.inputs = [];
|
this.inputs = []; // parent nodes
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.update_task = null;
|
this.update_task = null;
|
||||||
this.update_time = Date.now();
|
this.update_time = Date.now();
|
||||||
this.fail_cb = false; // Set to true to force updates even if parent failed.
|
this.fail_cb = false; // Set to true to force updates even if parent failed.
|
||||||
this.calc_inputs = new Map();
|
this.dirty = false;
|
||||||
|
this.inputs_dirty = new Map();
|
||||||
|
this.inputs_dirty_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +26,19 @@ class ComputeNode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.update_time = timestamp;
|
this.update_time = timestamp;
|
||||||
this.set_value(this.compute_func(this.calc_inputs));
|
|
||||||
|
if (this.inputs_dirty_count != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let calc_inputs = new Map();
|
||||||
|
for (const input of this.inputs) {
|
||||||
|
calc_inputs.set(input.name, input.value);
|
||||||
|
}
|
||||||
|
this.value = this.compute_func(calc_inputs);
|
||||||
|
this.dirty = false;
|
||||||
|
for (const child of this.children) {
|
||||||
|
child.mark_input_clean(this.name, this.value, timestamp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,22 +54,35 @@ class ComputeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set an input value. Propagates calculation if all inputs are present.
|
* Mark parent as not dirty. Propagates calculation if all inputs are present.
|
||||||
*/
|
*/
|
||||||
set_input(input_name, value, timestamp) {
|
mark_input_clean(input_name, value, timestamp) {
|
||||||
if (value || this.fail_cb) {
|
if (value !== null || this.fail_cb) {
|
||||||
this.calc_inputs.set(input_name, value)
|
if (this.inputs_dirty.get(input_name)) {
|
||||||
if (this.calc_inputs.size === this.inputs.length) {
|
this.inputs_dirty.set(input_name, false);
|
||||||
this.update(timestamp)
|
this.inputs_dirty_count -= 1;
|
||||||
|
}
|
||||||
|
if (this.inputs_dirty_count === 0) {
|
||||||
|
this.update(timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
mark_input_dirty(input_name) {
|
||||||
* Remove cached input values to this calculation.
|
if (!this.inputs_dirty.get(input_name)) {
|
||||||
*/
|
this.inputs_dirty.set(input_name, true);
|
||||||
clear_cache() {
|
this.inputs_dirty_count += 1;
|
||||||
this.calc_inputs = new Map();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_dirty() {
|
||||||
|
if (!this.dirty) {
|
||||||
|
this.dirty = true;
|
||||||
|
for (const child of this.children) {
|
||||||
|
child.mark_input_dirty(this.name);
|
||||||
|
child.mark_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,6 +101,10 @@ class ComputeNode {
|
||||||
|
|
||||||
link_to(parent_node) {
|
link_to(parent_node) {
|
||||||
this.inputs.push(parent_node)
|
this.inputs.push(parent_node)
|
||||||
|
this.inputs_dirty.set(parent_node.name, parent_node.dirty);
|
||||||
|
if (parent_node.dirty) {
|
||||||
|
this.inputs_dirty_count += 1;
|
||||||
|
}
|
||||||
parent_node.children.push(this);
|
parent_node.children.push(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +118,7 @@ function calcSchedule(node) {
|
||||||
if (node.update_task !== null) {
|
if (node.update_task !== null) {
|
||||||
clearTimeout(node.update_task);
|
clearTimeout(node.update_task);
|
||||||
}
|
}
|
||||||
|
node.mark_dirty();
|
||||||
node.update_task = setTimeout(function() {
|
node.update_task = setTimeout(function() {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
node.update(timestamp);
|
node.update(timestamp);
|
||||||
|
@ -107,10 +139,25 @@ class PrintNode extends ComputeNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node for getting an input from an input field.
|
||||||
|
*/
|
||||||
|
class InputNode extends ComputeNode {
|
||||||
|
constructor(name, input_field) {
|
||||||
|
super(name);
|
||||||
|
this.input_field = input_field;
|
||||||
|
this.input_field.addEventListener("input", () => calcSchedule(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
return this.input_field.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Node for getting an item's stats from an item input field.
|
* Node for getting an item's stats from an item input field.
|
||||||
*/
|
*/
|
||||||
class ItemInputNode extends ComputeNode {
|
class ItemInputNode extends InputNode {
|
||||||
/**
|
/**
|
||||||
* Make an item stat pulling compute node.
|
* Make an item stat pulling compute node.
|
||||||
*
|
*
|
||||||
|
@ -119,9 +166,7 @@ class ItemInputNode extends ComputeNode {
|
||||||
* @param none_item: Item object to use as the "none" for this field.
|
* @param none_item: Item object to use as the "none" for this field.
|
||||||
*/
|
*/
|
||||||
constructor(name, item_input_field, none_item) {
|
constructor(name, item_input_field, none_item) {
|
||||||
super(name);
|
super(name, item_input_field);
|
||||||
this.input_field = item_input_field;
|
|
||||||
this.input_field.addEventListener("input", () => calcSchedule(this));
|
|
||||||
this.none_item = new Item(none_item);
|
this.none_item = new Item(none_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue