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 class="row align-items-center">
|
||||
<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 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>
|
||||
|
@ -1399,8 +1399,9 @@
|
|||
<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/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_encode_decode.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/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).
|
||||
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
||||
* @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=[]){
|
||||
|
||||
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)
|
||||
constructor(level, items, tomes, weapon){
|
||||
|
||||
if (level < 1) { //Should these be constants?
|
||||
this.level = 1;
|
||||
|
@ -348,10 +122,12 @@ class Build{
|
|||
document.getElementById("level-choice").value = 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.items = this.equipment.concat([this.weapon]);
|
||||
this.equipment = items;
|
||||
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];
|
||||
let result = calculate_skillpoints(this.equipment, this.weapon);
|
||||
let result = calculate_skillpoints(this.equipment.concat(this.tomes), this.weapon);
|
||||
console.log(result);
|
||||
this.equip_order = result[0];
|
||||
// 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)
|
||||
this.assigned_skillpoints = result[3];
|
||||
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();
|
||||
|
||||
// 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
|
||||
*/
|
||||
toString(){
|
||||
return [this.equipment,this.weapon].flat();
|
||||
return [this.equipment,this.weapon,this.tomes].flat();
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
|
@ -392,7 +154,7 @@ class Build{
|
|||
}
|
||||
|
||||
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);
|
||||
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];
|
||||
}
|
||||
|
||||
/*
|
||||
* 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() {
|
||||
const buildList = document.getElementById("build-choice");
|
||||
const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
|
||||
|
@ -250,7 +50,7 @@ function saveBuild() {
|
|||
if (player_build) {
|
||||
const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
|
||||
const saveName = document.getElementById("build-name").value;
|
||||
const encodedBuild = encodeBuild();
|
||||
const encodedBuild = encodeBuild(player_build);
|
||||
if ((!Object.keys(savedBuilds).includes(saveName)
|
||||
|| document.getElementById("saved-error").textContent !== "") && encodedBuild !== "") {
|
||||
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
|
||||
function 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 = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
@ -16,6 +63,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
}
|
||||
let weapon_image = document.getElementById("weapon-img");
|
||||
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");
|
||||
|
||||
});
|
||||
|
@ -98,6 +153,7 @@ function init_autocomplete() {
|
|||
if (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).
|
||||
*/
|
||||
constructor(name) {
|
||||
this.inputs = [];
|
||||
this.inputs = []; // parent nodes
|
||||
this.children = [];
|
||||
this.value = 0;
|
||||
this.name = name;
|
||||
this.update_task = null;
|
||||
this.update_time = Date.now();
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
if (value || this.fail_cb) {
|
||||
this.calc_inputs.set(input_name, value)
|
||||
if (this.calc_inputs.size === this.inputs.length) {
|
||||
this.update(timestamp)
|
||||
mark_input_clean(input_name, value, timestamp) {
|
||||
if (value !== null || this.fail_cb) {
|
||||
if (this.inputs_dirty.get(input_name)) {
|
||||
this.inputs_dirty.set(input_name, false);
|
||||
this.inputs_dirty_count -= 1;
|
||||
}
|
||||
if (this.inputs_dirty_count === 0) {
|
||||
this.update(timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cached input values to this calculation.
|
||||
*/
|
||||
clear_cache() {
|
||||
this.calc_inputs = new Map();
|
||||
mark_input_dirty(input_name) {
|
||||
if (!this.inputs_dirty.get(input_name)) {
|
||||
this.inputs_dirty.set(input_name, true);
|
||||
this.inputs_dirty_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +118,7 @@ function calcSchedule(node) {
|
|||
if (node.update_task !== null) {
|
||||
clearTimeout(node.update_task);
|
||||
}
|
||||
node.mark_dirty();
|
||||
node.update_task = setTimeout(function() {
|
||||
const timestamp = Date.now();
|
||||
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.
|
||||
*/
|
||||
class ItemInputNode extends ComputeNode {
|
||||
class ItemInputNode extends InputNode {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
constructor(name, item_input_field, none_item) {
|
||||
super(name);
|
||||
this.input_field = item_input_field;
|
||||
this.input_field.addEventListener("input", () => calcSchedule(this));
|
||||
super(name, item_input_field);
|
||||
this.none_item = new Item(none_item);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue