pre-merge commit
This commit is contained in:
parent
b5e950b8a5
commit
9b8564261a
1 changed files with 340 additions and 0 deletions
340
js/builder/build_encode_decode.js
Normal file
340
js/builder/build_encode_decode.js
Normal file
|
@ -0,0 +1,340 @@
|
|||
let player_build;
|
||||
let build_powders;
|
||||
|
||||
function getItemNameFromID(id) { return idMap.get(id); }
|
||||
function getTomeNameFromID(id) { return tomeIDMap.get(id); }
|
||||
|
||||
function parsePowdering(powder_info) {
|
||||
// TODO: Make this run in linear instead of quadratic time... ew
|
||||
let powdering = [];
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let powders = "";
|
||||
let n_blocks = Base64.toInt(powder_info.charAt(0));
|
||||
// console.log(n_blocks + " blocks");
|
||||
powder_info = powder_info.slice(1);
|
||||
for (let j = 0; j < n_blocks; ++j) {
|
||||
let block = powder_info.slice(0,5);
|
||||
let six_powders = Base64.toInt(block);
|
||||
for (let k = 0; k < 6 && six_powders != 0; ++k) {
|
||||
powders += powderNames.get((six_powders & 0x1f) - 1);
|
||||
six_powders >>>= 5;
|
||||
}
|
||||
powder_info = powder_info.slice(5);
|
||||
}
|
||||
powdering[i] = powders;
|
||||
}
|
||||
return [powdering, powder_info];
|
||||
}
|
||||
|
||||
let atree_data = null;
|
||||
|
||||
/*
|
||||
* 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 <= 7) {
|
||||
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(equipment_inputs[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 <= 7){
|
||||
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 && version < 8) {
|
||||
//tome values do not appear in anything before v6.
|
||||
for (let i in tomes) {
|
||||
let tome_str = info[1].charAt(i);
|
||||
let tome_name = getTomeNameFromID(Base64.toInt(tome_str));
|
||||
setValue(tomeInputs[i], tome_name);
|
||||
}
|
||||
info[1] = info[1].slice(7);
|
||||
}
|
||||
|
||||
if (version == 7) {
|
||||
// ugly af. only works since its the last thing. will be fixed with binary decode
|
||||
atree_data = new BitVector(info[1]);
|
||||
}
|
||||
else {
|
||||
atree_data = null;
|
||||
}
|
||||
|
||||
for (let i in powder_inputs) {
|
||||
setValue(powder_inputs[i], powdering[i]);
|
||||
}
|
||||
for (let i in skillpoints) {
|
||||
setValue(skp_order[i] + "-skp", skillpoints[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
|
||||
*/
|
||||
function encodeBuild(build, powders, skillpoints, atree, atree_state) {
|
||||
//currently on version 8 - a unified version for all build types using bit-level encoding
|
||||
if (build) {
|
||||
//final link will be [build_vers]_[len_string]_[build_string]
|
||||
build_version = 8;
|
||||
let len_string = "";
|
||||
let build_string = "";
|
||||
let build_bits = new BitVector(0, 0);
|
||||
|
||||
//ITEMS
|
||||
for (const item of build.items) {
|
||||
if (item.statMap.get("NONE") && item.statMap.get("NONE") === true) {
|
||||
build_bits.append(0, 2); //00
|
||||
} else if (item.statMap.get("custom")) {
|
||||
build_bits.append(3, 2); //11
|
||||
//BitVector CI encoding TODO
|
||||
|
||||
// let custom = "CI-"+encodeCustom(item, true);
|
||||
// build_string += Base64.fromIntN(custom.length, 3) + custom;
|
||||
// build_version = Math.max(build_version, 5);
|
||||
} else if (item.statMap.get("crafted")) {
|
||||
build_bits.append(2, 2); //10
|
||||
//BitVector CR encoding TODO
|
||||
|
||||
// build_string += "CR-"+encodeCraft(item);
|
||||
} else {
|
||||
if (item.statMap.get("category") === "tome") {
|
||||
//we will encode tomes later
|
||||
continue;
|
||||
} else {
|
||||
build_bits.append(1, 2); //01
|
||||
build_bits.append(item.statMap.get("id"), 13);
|
||||
|
||||
//powderable
|
||||
if (powderable_keys.includes(item.statMap.get("type"))) {
|
||||
if (item.statMap.get("powders") && item.statMap.get("powders").length !== 0) {
|
||||
//has powders
|
||||
build_bits.append(1, 1);
|
||||
|
||||
//num of powders in 8 bits, then each powder (6 bits)
|
||||
//Having more than 256 powders on a vanilla item is NOT HANDLED.
|
||||
build_bits.append(item.statMap.get("powders").length, 8);
|
||||
for (const powder of item.statMap.get("powders")) {
|
||||
build_bits.append(powder, 6);
|
||||
}
|
||||
} else {
|
||||
//no powders
|
||||
build_bits.append(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//SKILL POINTS
|
||||
|
||||
//the original schema included a flag to indicate whether or not skill points are included.
|
||||
//any reason for having a flag isn't implemented yet, so for now every build will have the skill point flag set.
|
||||
build_bits.append(1, 1);
|
||||
|
||||
for (const skp of build.base_skillpoints) {
|
||||
build_bits.append(skp, 8); // Maximum skillpoints: 255 (allows for manual assign up to 150)
|
||||
}
|
||||
|
||||
//BUILD LEVEL
|
||||
|
||||
// [flag to indicate if level is not 106 (0/1)]
|
||||
// [else: level (7 bits, allows for lv 1->127)]
|
||||
if (player_build.level != 106) {
|
||||
build_bits.append(1, 1);
|
||||
build_bits.append(player_build.level, 7);
|
||||
} else {
|
||||
build_bits.append(0, 1);
|
||||
}
|
||||
|
||||
// TOMES
|
||||
|
||||
// [flag to indicate if tomes are included (0/1)]
|
||||
// [if set: 7 sequential tome IDs, each 6 bits unsigned]
|
||||
if (build.tomes.length > 0) {
|
||||
build_bits.append(1, 1);
|
||||
//decoding will assume that tomes has length of 7.
|
||||
for (const tome of build.tomes) {
|
||||
build_bits.append(tome.id, 6);
|
||||
}
|
||||
} else {
|
||||
build_bits.append(0, 1);
|
||||
}
|
||||
|
||||
// ATREE
|
||||
|
||||
// [flag to indicate if atree data is present]
|
||||
// [atree data: see existing encoding impl] //idk the impl
|
||||
if (atree.length > 0 && atree_state.get(atree[0].ability.id).active) {
|
||||
build_bits.append(1, 1);
|
||||
const atree_bitvec = encode_atree(atree, atree_state);
|
||||
build_bits.append(atree_bitvec);
|
||||
} else {
|
||||
build_bits.append(0, 1);
|
||||
}
|
||||
|
||||
//compute length and return final build hash
|
||||
return build_version.toString() + "_" + len_string + "_" + build_string;
|
||||
}
|
||||
}
|
||||
|
||||
function copyBuild() {
|
||||
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.items[0].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[1].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[2].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[3].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[4].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[5].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[6].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[7].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[15].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]";
|
||||
copyTextToClipboard(text);
|
||||
document.getElementById("share-button").textContent = "Copied!";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ability tree encode and decode functions
|
||||
*
|
||||
* Based on a traversal, basically only uses bits to represent the nodes that are on (and "dark" outgoing edges).
|
||||
* credit: SockMower
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return: BitVector
|
||||
*/
|
||||
function encode_atree(atree, atree_state) {
|
||||
let ret_vec = new BitVector(0, 0);
|
||||
|
||||
function traverse(head, atree_state, visited, ret) {
|
||||
for (const child of head.children) {
|
||||
if (visited.has(child.ability.id)) { continue; }
|
||||
visited.set(child.ability.id, true);
|
||||
if (atree_state.get(child.ability.id).active) {
|
||||
ret.append(1, 1);
|
||||
traverse(child, atree_state, visited, ret);
|
||||
}
|
||||
else {
|
||||
ret.append(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse(atree[0], atree_state, new Map(), ret_vec);
|
||||
return ret_vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return: List of active nodes
|
||||
*/
|
||||
function decode_atree(atree, bits) {
|
||||
let i = 0;
|
||||
let ret = [];
|
||||
ret.push(atree[0]);
|
||||
function traverse(head, visited, ret) {
|
||||
for (const child of head.children) {
|
||||
if (visited.has(child.ability.id)) { continue; }
|
||||
visited.set(child.ability.id, true);
|
||||
if (bits.read_bit(i)) {
|
||||
i += 1;
|
||||
ret.push(child);
|
||||
traverse(child, visited, ret);
|
||||
}
|
||||
else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
traverse(atree[0], new Map(), ret);
|
||||
return ret;
|
||||
}
|
Loading…
Reference in a new issue