fixed merge issues
This commit is contained in:
commit
48ccac6022
15 changed files with 1241 additions and 124 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
*.swp
|
||||
*.bat
|
||||
sets/
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
|
|
46
build.js
46
build.js
|
@ -113,9 +113,12 @@ class Build{
|
|||
const helmet = itemMap.get(equipment[0]);
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
}else{
|
||||
} else {
|
||||
try {
|
||||
let helmet = getCraftFromHash(equipment[0]);
|
||||
if (helmet.statMap.get("type") !== "helmet") {
|
||||
throw new Error("Not a helmet");
|
||||
}
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.statMap.slots);
|
||||
helmet.statMap.set("powders",this.powders[0].slice());
|
||||
helmet.applyPowders();
|
||||
|
@ -133,9 +136,12 @@ class Build{
|
|||
const chestplate = itemMap.get(equipment[1]);
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
}else{
|
||||
} else {
|
||||
try {
|
||||
let chestplate = getCraftFromHash(equipment[1]);
|
||||
if (chestplate.statMap.get("type") !== "chestplate") {
|
||||
throw new Error("Not a chestplate");
|
||||
}
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.statMap.slots);
|
||||
chestplate.statMap.set("powders",this.powders[1].slice());
|
||||
chestplate.applyPowders();
|
||||
|
@ -148,13 +154,16 @@ class Build{
|
|||
errors.push(new ItemNotFound(equipment[1], "chestplate", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") {
|
||||
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{
|
||||
} else {
|
||||
try {
|
||||
let leggings = getCraftFromHash(equipment[2]);
|
||||
if (leggings.statMap.get("type") !== "leggings") {
|
||||
throw new Error("Not a leggings");
|
||||
}
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.statMap.slots);
|
||||
leggings.statMap.set("powders",this.powders[2].slice());
|
||||
leggings.applyPowders();
|
||||
|
@ -167,13 +176,16 @@ class Build{
|
|||
errors.push(new ItemNotFound(equipment[2], "leggings", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") {
|
||||
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{
|
||||
} else {
|
||||
try {
|
||||
let boots = getCraftFromHash(equipment[3]);
|
||||
if (boots.statMap.get("type") !== "boots") {
|
||||
throw new Error("Not a boots");
|
||||
}
|
||||
this.powders[3] = this.powders[3].slice(0,boots.statMap.slots);
|
||||
boots.statMap.set("powders",this.powders[3].slice());
|
||||
boots.applyPowders();
|
||||
|
@ -192,6 +204,9 @@ class Build{
|
|||
}else{
|
||||
try {
|
||||
let ring = getCraftFromHash(equipment[4]);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring1 = ring.statMap;
|
||||
this.craftedItems.push(ring);
|
||||
} catch (Error) {
|
||||
|
@ -206,6 +221,9 @@ class Build{
|
|||
}else{
|
||||
try {
|
||||
let ring = getCraftFromHash(equipment[5]);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring2 = ring.statMap;
|
||||
this.craftedItems.push(ring);
|
||||
} catch (Error) {
|
||||
|
@ -220,6 +238,9 @@ class Build{
|
|||
}else{
|
||||
try {
|
||||
let bracelet = getCraftFromHash(equipment[6]);
|
||||
if (bracelet.statMap.get("type") !== "bracelet") {
|
||||
throw new Error("Not a bracelet");
|
||||
}
|
||||
this.bracelet = bracelet.statMap;
|
||||
this.craftedItems.push(bracelet);
|
||||
} catch (Error) {
|
||||
|
@ -234,6 +255,9 @@ class Build{
|
|||
}else{
|
||||
try {
|
||||
let necklace = getCraftFromHash(equipment[7]);
|
||||
if (necklace.statMap.get("type") !== "necklace") {
|
||||
throw new Error("Not a necklace");
|
||||
}
|
||||
this.necklace = necklace.statMap;
|
||||
this.craftedItems.push(necklace);
|
||||
} catch (Error) {
|
||||
|
@ -254,6 +278,9 @@ class Build{
|
|||
}else{
|
||||
try {
|
||||
let weapon = getCraftFromHash(equipment[8]);
|
||||
if (weapon.statMap.get("category") !== "weapon") {
|
||||
throw new Error("Not a weapon");
|
||||
}
|
||||
this.weapon = weapon.statMap;
|
||||
this.craftedItems.push(weapon);
|
||||
this.powders[4] = this.powders[4].slice(0,this.weapon.slots);
|
||||
|
@ -417,6 +444,7 @@ class Build{
|
|||
}
|
||||
statMap.set("hp", levelToHPBase(this.level));
|
||||
|
||||
let major_ids = new Set();
|
||||
for (const item of this.items){
|
||||
for (let [id, value] of item.get("maxRolls")) {
|
||||
statMap.set(id,(statMap.get(id) || 0)+value);
|
||||
|
@ -426,7 +454,13 @@ class Build{
|
|||
statMap.set(staticID, statMap.get(staticID) + item.get(staticID));
|
||||
}
|
||||
}
|
||||
if (item.get("majorIds")) {
|
||||
for (const majorID of item.get("majorIds")) {
|
||||
major_ids.add(majorID);
|
||||
}
|
||||
}
|
||||
}
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets[setName].bonuses[count-1];
|
||||
for (const id in bonus) {
|
||||
|
|
155
builder.js
155
builder.js
|
@ -3,7 +3,7 @@ const url_tag = location.hash.slice(1);
|
|||
console.log(url_base);
|
||||
console.log(url_tag);
|
||||
|
||||
const BUILD_VERSION = "6.9.20";
|
||||
const BUILD_VERSION = "6.9.22";
|
||||
|
||||
function setTitle() {
|
||||
let text;
|
||||
|
@ -220,6 +220,29 @@ function getItemNameFromID(id) {
|
|||
return idMap.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);
|
||||
console.log(block);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Populate fields based on url, and calculate build.
|
||||
*/
|
||||
|
@ -235,90 +258,54 @@ function decodeBuild(url_tag) {
|
|||
if (version === "0" || version === "1" || version === "2" || version === "3") {
|
||||
let equipments = info[1];
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipments.slice(i*3,i*3+3)));
|
||||
let equipment_str = equipments.slice(i*3,i*3+3);
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||
}
|
||||
info[1] = equipments.slice(27);
|
||||
}
|
||||
if (version === "1") {
|
||||
let powder_info = info[1].slice(27);
|
||||
console.log(powder_info);
|
||||
// TODO: Make this run in linear instead of quadratic time... ew
|
||||
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);
|
||||
console.log(block);
|
||||
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);
|
||||
if (version === "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;
|
||||
}
|
||||
powdering[i] = powders;
|
||||
}
|
||||
info[1] = info_str.slice(start_idx);
|
||||
}
|
||||
if (version === "2") {
|
||||
|
||||
if (version === "1") {
|
||||
let powder_info = info[1];
|
||||
powdering = parsePowdering(powder_info);
|
||||
} else if (version === "2") {
|
||||
save_skp = true;
|
||||
let skillpoint_info = info[1].slice(27, 37);
|
||||
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(37);
|
||||
console.log(powder_info);
|
||||
// TODO: Make this run in linear instead of quadratic time...
|
||||
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);
|
||||
console.log(block);
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (version === "3"){
|
||||
level = Base64.toInt(info[1].slice(37,39));
|
||||
let powder_info = info[1].slice(10);
|
||||
powdering = parsePowdering(powder_info);
|
||||
} else if (version === "3" || version === "4"){
|
||||
level = Base64.toInt(info[1].slice(10,12));
|
||||
setValue("level-choice",level);
|
||||
save_skp = true;
|
||||
let skillpoint_info = info[1].slice(27, 37);
|
||||
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(39);
|
||||
// TODO: Make this run in linear instead of quadratic time...
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let powders = "";
|
||||
let n_blocks = Base64.toInt(powder_info.charAt(0));
|
||||
powder_info = powder_info.slice(1);
|
||||
for (let j = 0; j < n_blocks; ++j) {
|
||||
let block = powder_info.slice(0,5);
|
||||
console.log(block);
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (version === "4") { //crafted support
|
||||
//@hpp
|
||||
let powder_info = info[1].slice(12);
|
||||
|
||||
powdering = parsePowdering(powder_info);
|
||||
}
|
||||
|
||||
for (let i in powderInputs) {
|
||||
setValue(powderInputs[i], powdering[i]);
|
||||
}
|
||||
|
@ -334,15 +321,27 @@ function decodeBuild(url_tag) {
|
|||
function encodeBuild() {
|
||||
if (player_build) {
|
||||
//@hpp update for 4_
|
||||
let build_string = "3_" + Base64.fromIntN(player_build.helmet.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.chestplate.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.leggings.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.boots.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.ring1.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.ring2.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.bracelet.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.necklace.get("id"), 3) +
|
||||
Base64.fromIntN(player_build.weapon.get("id"), 3);
|
||||
let build_string = "4_";
|
||||
let crafted_idx = 0;
|
||||
for (const item of player_build.items) {
|
||||
if (item.get("crafted")) {
|
||||
build_string += "-"+encodeCraft(player_build.craftedItems[crafted_idx])
|
||||
crafted_idx += 1
|
||||
}
|
||||
else {
|
||||
build_string += Base64.fromIntN(item.get("id"), 3);
|
||||
}
|
||||
}
|
||||
// this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
|
||||
// let build_string = "3_" + Base64.fromIntN(player_build.helmet.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.chestplate.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.leggings.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.boots.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.ring1.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.ring2.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.bracelet.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.necklace.get("id"), 3) +
|
||||
// Base64.fromIntN(player_build.weapon.get("id"), 3);
|
||||
|
||||
for (const skp of skp_order) {
|
||||
build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048
|
||||
|
@ -797,7 +796,7 @@ function calculateBuildStats() {
|
|||
let baditem = document.createElement("p");
|
||||
baditem.classList.add("nocolor");
|
||||
baditem.classList.add("itemp");
|
||||
baditem.textContent = item.get("name") + " requires level " + item.get("lvl") + " to use.";
|
||||
baditem.textContent = item.get("displayName") + " requires level " + item.get("lvl") + " to use.";
|
||||
lvlWarning.appendChild(baditem);
|
||||
}
|
||||
}
|
||||
|
|
2
craft.js
2
craft.js
|
@ -369,6 +369,8 @@ class Craft{
|
|||
statMap.get("maxRolls").set(id,0);
|
||||
}
|
||||
}
|
||||
|
||||
statMap.set("crafted", true);
|
||||
this.statMap = statMap;
|
||||
}
|
||||
}
|
27
crafter.js
27
crafter.js
|
@ -202,8 +202,9 @@ function calculateCraft() {
|
|||
//create the craft
|
||||
player_craft = new Craft(recipe,mat_tiers,ingreds,atkSpd,"");
|
||||
|
||||
location.hash = encodeCraft();
|
||||
player_craft.setHash(encodeCraft());
|
||||
let craft_str = encodeCraft(player_craft);
|
||||
location.hash = craft_str;
|
||||
player_craft.setHash(craft_str);
|
||||
console.log(player_craft);
|
||||
/*console.log(recipe)
|
||||
console.log(levelrange)
|
||||
|
@ -239,19 +240,19 @@ function calculateCraft() {
|
|||
|
||||
}
|
||||
|
||||
function encodeCraft() {
|
||||
if (player_craft) {
|
||||
function encodeCraft(craft) {
|
||||
if (craft) {
|
||||
let atkSpds = ["SLOW","NORMAL","FAST"];
|
||||
let craft_string = "1" +
|
||||
Base64.fromIntN(player_craft.ingreds[0].get("id"), 2) +
|
||||
Base64.fromIntN(player_craft.ingreds[1].get("id"), 2) +
|
||||
Base64.fromIntN(player_craft.ingreds[2].get("id"), 2) +
|
||||
Base64.fromIntN(player_craft.ingreds[3].get("id"), 2) +
|
||||
Base64.fromIntN(player_craft.ingreds[4].get("id"), 2) +
|
||||
Base64.fromIntN(player_craft.ingreds[5].get("id"), 2) +
|
||||
Base64.fromIntN(player_craft.recipe.get("id"),2) +
|
||||
Base64.fromIntN(player_craft.mat_tiers[0] + (player_craft.mat_tiers[1]-1)*3, 1) + //this maps tiers [a,b] to a+3b.
|
||||
Base64.fromIntN(atkSpds.indexOf(player_craft["atkSpd"]),1);
|
||||
Base64.fromIntN(craft.ingreds[0].get("id"), 2) +
|
||||
Base64.fromIntN(craft.ingreds[1].get("id"), 2) +
|
||||
Base64.fromIntN(craft.ingreds[2].get("id"), 2) +
|
||||
Base64.fromIntN(craft.ingreds[3].get("id"), 2) +
|
||||
Base64.fromIntN(craft.ingreds[4].get("id"), 2) +
|
||||
Base64.fromIntN(craft.ingreds[5].get("id"), 2) +
|
||||
Base64.fromIntN(craft.recipe.get("id"),2) +
|
||||
Base64.fromIntN(craft.mat_tiers[0] + (craft.mat_tiers[1]-1)*3, 1) + //this maps tiers [a,b] to a+3b.
|
||||
Base64.fromIntN(atkSpds.indexOf(craft["atkSpd"]),1);
|
||||
return craft_string;
|
||||
}
|
||||
return "";
|
||||
|
|
|
@ -5,6 +5,7 @@ The game, of course
|
|||
- wynncraft.com
|
||||
|
||||
Additional Contributors:
|
||||
- Phanta (WynnAtlas custom expression parser / item search)
|
||||
- QuantumNep (Layout code/layout ideas)
|
||||
- dr_carlos (Hiding UI elements properly, fade animations, proper error handling)
|
||||
- Atlas Inc discord (feedback, ideas, etc)
|
||||
|
|
|
@ -6,13 +6,15 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
|
|||
|
||||
let buildStats = new Map(stats);
|
||||
if(externalStats) { //if nothing is passed in, then this hopefully won't trigger
|
||||
for (const [key,value] of externalStats) {
|
||||
for (let i = 0; i < externalStats.length; i++) {
|
||||
const key = externalStats[i][0];
|
||||
const value = externalStats[i][1];
|
||||
if (typeof value === "number") {
|
||||
buildStats.set(key, buildStats.get(key) + value);
|
||||
} else if (Array.isArray(value)) {
|
||||
arr = [];
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
arr[i] = buildStats.get(key)[i] + value[i];
|
||||
for (let j = 0; j < value.length; j++) {
|
||||
arr[j] = buildStats.get(key)[j] + value[j];
|
||||
}
|
||||
buildStats.set(key, arr);
|
||||
}
|
||||
|
@ -21,8 +23,9 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
|
|||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
let damages = [];
|
||||
for (const damage_string of buildStats.get("damageRaw")) {
|
||||
const damage_vals = damage_string.split("-").map(Number);
|
||||
const rawDamages = buildStats.get("damageRaw");
|
||||
for (let i = 0; i < rawDamages.length; i++) {
|
||||
const damage_vals = rawDamages[i].split("-").map(Number);
|
||||
damages.push(damage_vals);
|
||||
}
|
||||
|
||||
|
@ -131,7 +134,10 @@ const spell_table = {
|
|||
{ title: "Heal", cost: 6, parts: [
|
||||
{ subtitle: "First Pulse", type: "heal", strength: 0.12 },
|
||||
{ subtitle: "Second and Third Pulses", type: "heal", strength: 0.06 },
|
||||
{ subtitle: "Total Heal", type: "heal", strength: 0.24, summary: true }
|
||||
{ subtitle: "Total Heal", type: "heal", strength: 0.24, summary: true },
|
||||
{ subtitle: "First Pulse (Ally)", type: "heal", strength: 0.20 },
|
||||
{ subtitle: "Second and Third Pulses (Ally)", type: "heal", strength: 0.1 },
|
||||
{ subtitle: "Total Heal (Ally)", type: "heal", strength: 0.4 }
|
||||
] },
|
||||
{ title: "Teleport", cost: 4, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [60, 0, 40, 0, 0, 0], summary: true },
|
||||
|
@ -150,9 +156,15 @@ const spell_table = {
|
|||
{ subtitle: "Explosion Damage", type: "damage", multiplier: 130, conversion: [100, 0, 0, 0, 0, 0]},
|
||||
{ subtitle: "Total Damage", type: "total", factors: [1, 1], summary: true },
|
||||
] },
|
||||
{ title: "Charge", cost: 4, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 0, 0, 40, 0], summary: true },
|
||||
] },
|
||||
{ title: "Charge", cost: 4, variants: {
|
||||
DEFAULT: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 0, 0, 40, 0], summary: true }
|
||||
],
|
||||
RALLY: [
|
||||
{ subtitle: "Self Heal", type: "heal", strength: 0.1, summary: true },
|
||||
{ subtitle: "Ally Heal", type: "heal", strength: 0.15 }
|
||||
]
|
||||
} },
|
||||
{ title: "Uppercut", cost: 9, parts: [
|
||||
{ subtitle: "First Damage", type: "damage", multiplier: 300, conversion: [70, 20, 10, 0, 0, 0] },
|
||||
{ subtitle: "Fireworks Damage", type: "damage", multiplier: 50, conversion: [60, 0, 40, 0, 0, 0] },
|
||||
|
@ -165,10 +177,16 @@ const spell_table = {
|
|||
] },
|
||||
],
|
||||
"bow": [
|
||||
{ title: "Arrow Storm", cost: 6, parts: [
|
||||
{ title: "Arrow Storm", cost: 6, variants: {
|
||||
DEFAULT: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [60, 0, 25, 0, 15, 0], summary: true },
|
||||
{ subtitle: "Per Arrow", type: "damage", multiplier: 10, conversion: [60, 0, 25, 0, 15, 0]},
|
||||
] },
|
||||
{ subtitle: "Per Arrow (60)", type: "damage", multiplier: 10, conversion: [60, 0, 25, 0, 15, 0]}
|
||||
],
|
||||
HAWKEYE: [
|
||||
{ subtitle: "Total Damage (Hawkeye)", type: "damage", multiplier: 400, conversion: [60, 0, 25, 0, 15, 0], summary: true },
|
||||
{ subtitle: "Per Arrow (5)", type: "damage", multiplier: 80, conversion: [60, 0, 25, 0, 15, 0]}
|
||||
],
|
||||
} },
|
||||
{ title: "Escape", cost: 3, parts: [
|
||||
{ subtitle: "Landing Damage", type: "damage", multiplier: 100, conversion: [50, 0, 0, 0, 0, 50], summary: true },
|
||||
] },
|
||||
|
@ -192,10 +210,16 @@ const spell_table = {
|
|||
{ subtitle: "Fatality", type: "damage", multiplier: 120, conversion: [20, 0, 30, 50, 0, 0] },
|
||||
{ subtitle: "Total Damage", type: "total", factors: [10, 1], summary: true },
|
||||
] },
|
||||
{ title: "Smoke Bomb", cost: 8, parts: [
|
||||
{ subtitle: "Tick Damage", type: "damage", multiplier: 60, conversion: [45, 25, 0, 0, 0, 30] },
|
||||
{ title: "Smoke Bomb", cost: 8, variants: {
|
||||
DEFAULT: [
|
||||
{ subtitle: "Tick Damage (10 max)", type: "damage", multiplier: 60, conversion: [45, 25, 0, 0, 0, 30] },
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [45, 25, 0, 0, 0, 30], summary: true },
|
||||
] },
|
||||
],
|
||||
CHERRY_BOMBS: [
|
||||
{ subtitle: "Total Damage (Cherry Bombs)", type: "damage", multiplier: 330, conversion: [45, 25, 0, 0, 0, 30], summary: true },
|
||||
{ subtitle: "Per Bomb", type: "damage", multiplier: 110, conversion: [45, 25, 0, 0, 0, 30] }
|
||||
]
|
||||
} },
|
||||
],
|
||||
"relik": [
|
||||
{ title: "Totem", cost: 4, parts: [
|
||||
|
|
40
display.js
40
display.js
|
@ -1,4 +1,4 @@
|
|||
let nonRolledIDs = ["name", "displayName", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq","str", "dex", "int", "agi", "def", "fixID", "category", "id", "skillpoints", "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_"];
|
||||
let nonRolledIDs = ["name", "displayName", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq","str", "dex", "int", "agi", "def", "fixID", "category", "id", "skillpoints", "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "majorIds"];
|
||||
let rolledIDs = ["hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd"];
|
||||
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||
let colorMap = new Map(
|
||||
|
@ -497,12 +497,12 @@ function displayExpandedItem(item, parent_id){
|
|||
"eSteal",
|
||||
"gXp", "gSpd",
|
||||
"#ldiv",
|
||||
"!elemental",
|
||||
"majorIds",
|
||||
"slots",
|
||||
"!elemental",
|
||||
"set",
|
||||
"quest",
|
||||
"restrict"];
|
||||
"restrict"
|
||||
];
|
||||
|
||||
// Clear the parent div.
|
||||
setHTML(parent_id, "");
|
||||
|
@ -511,7 +511,8 @@ function displayExpandedItem(item, parent_id){
|
|||
let active_elem;
|
||||
let fix_id = item.has("fixID") && item.get("fixID");
|
||||
let elemental_format = false;
|
||||
for (const command of display_commands) {
|
||||
for (let i = 0; i < display_commands.length; i++) {
|
||||
const command = display_commands[i];
|
||||
if (command.charAt(0) === "#") {
|
||||
if (command === "#cdiv") {
|
||||
active_elem = document.createElement('div');
|
||||
|
@ -561,6 +562,11 @@ function displayExpandedItem(item, parent_id){
|
|||
powderSuffix.textContent = "]";
|
||||
p_elem.appendChild(powderSuffix);
|
||||
active_elem.appendChild(p_elem);
|
||||
} else if (id === "majorIds") {
|
||||
let p_elem = document.createElement("p");
|
||||
p_elem.classList.add("itemp");
|
||||
p_elem.textContent = "Major IDs: " + item.get(id).toString();
|
||||
active_elem.appendChild(p_elem);
|
||||
} else {
|
||||
let p_elem;
|
||||
if ( !(item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") && (!skp_order.includes(id)) || (skp_order.includes(id) && item.get("tier") !== "Crafted" && active_elem.nodeName === "DIV") ) { //skp warp
|
||||
|
@ -720,12 +726,12 @@ function displayExpandedItem(item, parent_id){
|
|||
effects = powderSpecial["armorSpecialEffects"];
|
||||
specialTitle.textContent += powderSpecial["armorSpecialName"] + ": ";
|
||||
}
|
||||
for (const [key,value] of effects) {
|
||||
if (key !== "Description") {
|
||||
for (let i = 0; i < effects.length; i++) {
|
||||
if (effects[i][0] !== "Description") {
|
||||
let effect = document.createElement("p");
|
||||
effect.classList.add("itemp");
|
||||
effect.textContent += key + ": " + value[power] + specialSuffixes.get(key);
|
||||
if(key === "Damage"){
|
||||
effect.textContent += effects[i][0] + ": " + effects[i][1][power] + specialSuffixes.get(effects[i][0]);
|
||||
if(effects[i][0] === "Damage"){
|
||||
effect.textContent += elementIcons[skp_elements.indexOf(element)];
|
||||
}
|
||||
if (element === "w") {
|
||||
|
@ -1824,7 +1830,21 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell
|
|||
part_divavg.classList.add("nomargin");
|
||||
overallparent_elem.append(part_divavg);
|
||||
|
||||
for (const part of spell.parts) {
|
||||
let spell_parts;
|
||||
if (spell.parts) {
|
||||
spell_parts = spell.parts;
|
||||
}
|
||||
else {
|
||||
spell_parts = spell.variants.DEFAULT;
|
||||
for (const majorID of stats.get("activeMajorIDs")) {
|
||||
if (majorID in spell.variants) {
|
||||
spell_parts = spell.variants[majorID];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const part of spell_parts) {
|
||||
parent_elem.append(document.createElement("br"));
|
||||
let part_div = document.createElement("p");
|
||||
parent_elem.append(part_div);
|
||||
|
|
|
@ -44,6 +44,9 @@
|
|||
<div class = "headerright">
|
||||
|
||||
</div>
|
||||
<div class="center" id="advanced">
|
||||
<a href="./items_2.html">Advanced Search</a>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<br>
|
||||
|
|
77
items_2.html
Normal file
77
items_2.html
Normal file
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="icon" href="favicon.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>WynnAtlas</title>
|
||||
</head>
|
||||
<body class="all" style="overflow-y: scroll">
|
||||
<header class = "header nomarginp">
|
||||
<div class = "headerleft">
|
||||
<a href = "./" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/builder.png" class = "left linkoptions headericon">
|
||||
</img>
|
||||
<div class = "tooltiptext center">WynnBuilder</div>
|
||||
</a>
|
||||
<a href = "./crafter.html" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/crafter.png" class = "left linkoptions headericon">
|
||||
</img>
|
||||
<div class = "tooltiptext center">WynnCrafter</div>
|
||||
</a>
|
||||
<a href = "./items_2.html" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/searcher.png" class = "left linkoptions headericon">
|
||||
</img>
|
||||
<div class = "tooltiptext center">WynnAtlas</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class = "headercenter">
|
||||
<div >
|
||||
<p class = "itemp" id = "header">WynnAtlas</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "headerright">
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<div class="center" id="credits">
|
||||
<a href="credits.txt">Additional credits</a>
|
||||
</div>
|
||||
<div class="center" id="help">
|
||||
<a href="options.txt">Search Guide</a>
|
||||
</div>
|
||||
<div class="center" id="main" style="padding: 2%">
|
||||
<div id="search-container" style="margin-bottom: 1.5%">
|
||||
<div class="left" id="search-filter" style="display: inline-block; vertical-align: top">
|
||||
<label for="search-filter-field">Filter By:</label>
|
||||
<br>
|
||||
<input id="search-filter-field" type="text" placeholder="name ?= "blue" & str >= 15 & dex >= 10" style="width: 25vw; padding: 8px">
|
||||
<br>
|
||||
<div id="search-filter-error" style="color: #ff0000"></div>
|
||||
</div>
|
||||
<div class="left" id="search-sort" style="display: inline-block; vertical-align: top">
|
||||
<label for="search-sort-field">Sort By:</label>
|
||||
<br>
|
||||
<input id="search-sort-field" type="text" placeholder="str + dex; meleerawdmg + spellrawdmg" style="width: 25vw; padding: 8px">
|
||||
<br>
|
||||
<div id="search-sort-error" style="color: #ff0000"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="item-list-container">
|
||||
<div class="left" id="item-list" style="display: flex; flex-flow: row wrap"></div>
|
||||
<div class="center" id="item-list-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="utils.js"></script>
|
||||
<script type="text/javascript" src="build_utils.js"></script>
|
||||
<script type="text/javascript" src="damage_calc.js"></script>
|
||||
<script type="text/javascript" src="display.js"></script>
|
||||
<script type="text/javascript" src="query_2.js"></script>
|
||||
<script type="text/javascript" src="load.js"></script>
|
||||
<script type="text/javascript" src="items_2.js"></script>
|
||||
</body>
|
||||
</html>
|
193
items_2.js
Normal file
193
items_2.js
Normal file
|
@ -0,0 +1,193 @@
|
|||
// represents a field containing a query expression string
|
||||
class ExprField {
|
||||
constructor(fieldId, errorTextId, compiler) {
|
||||
this.field = document.getElementById(fieldId);
|
||||
this.errorText = document.getElementById(errorTextId);
|
||||
this.compiler = compiler;
|
||||
this.output = null;
|
||||
this.text = null;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.field.value;
|
||||
}
|
||||
|
||||
compile() {
|
||||
if (this.value === this.text) return false;
|
||||
this.text = this.value;
|
||||
this.errorText.innerText = '';
|
||||
try {
|
||||
this.output = this.compiler(this.text);
|
||||
} catch (e) {
|
||||
this.errorText.innerText = e.message;
|
||||
this.output = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function compareLexico(ia, keysA, ib, keysB) {
|
||||
for (let i = 0; i < keysA.length; i++) { // assuming keysA and keysB are the same length
|
||||
let aKey = keysA[i], bKey = keysB[i];
|
||||
if (typeof aKey !== typeof bKey) throw new Error(`Incomparable types ${typeof aKey} and ${typeof bKey}`); // can this even happen?
|
||||
switch (typeof aKey) {
|
||||
case 'string':
|
||||
aKey = aKey.toLowerCase();
|
||||
bKey = bKey.toLowerCase();
|
||||
if (aKey < bKey) return -1;
|
||||
if (aKey > bKey) return 1;
|
||||
break;
|
||||
case 'number': // sort numeric stuff in reverse order
|
||||
if (aKey < bKey) return 1;
|
||||
if (aKey > bKey) return -1;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Incomparable type ${typeof aKey}`);
|
||||
}
|
||||
}
|
||||
return ib.lvl - ia.lvl;
|
||||
}
|
||||
|
||||
function stringify(v) {
|
||||
return typeof v === 'number' ? (Math.round(v * 100) / 100).toString() : v;
|
||||
}
|
||||
|
||||
function init() {
|
||||
const itemList = document.getElementById('item-list');
|
||||
const itemListFooter = document.getElementById('item-list-footer');
|
||||
|
||||
// compile the search db from the item db
|
||||
const searchDb = items.filter(i => !i.remapID).map(i => [i, expandItem(i, [])]);
|
||||
|
||||
// init item list elements
|
||||
const ITEM_LIST_SIZE = 64;
|
||||
const itemEntries = [];
|
||||
for (let i = 0; i < ITEM_LIST_SIZE; i++) {
|
||||
const itemElem = document.createElement('div');
|
||||
itemElem.classList.add('box');
|
||||
itemElem.setAttribute('id', `item-entry-${i}`);
|
||||
itemElem.style.display = 'none';
|
||||
itemElem.style.width = '20vw';
|
||||
itemElem.style.margin = '1vw';
|
||||
itemElem.style.verticalAlign = 'top';
|
||||
itemList.append(itemElem);
|
||||
itemEntries.push(itemElem);
|
||||
}
|
||||
|
||||
// the two search query input boxes
|
||||
const searchFilterField = new ExprField('search-filter-field', 'search-filter-error', function(exprStr) {
|
||||
const expr = compileQueryExpr(exprStr);
|
||||
return expr !== null ? expr : (i, ie) => true;
|
||||
});
|
||||
const searchSortField = new ExprField('search-sort-field', 'search-sort-error', function(exprStr) {
|
||||
const subExprs = exprStr.split(';').map(compileQueryExpr).filter(f => f != null);
|
||||
return function(i, ie) {
|
||||
const sortKeys = [];
|
||||
for (let k = 0; k < subExprs.length; k++) sortKeys.push(subExprs[k](i, ie));
|
||||
return sortKeys;
|
||||
};
|
||||
});
|
||||
|
||||
// updates the current search state from the search query input boxes
|
||||
function updateSearch() {
|
||||
// compile query expressions, aborting if nothing has changed or either fails to compile
|
||||
const changed = searchFilterField.compile() | searchSortField.compile();
|
||||
if (!changed || searchFilterField.output === null || searchSortField.output === null) return;
|
||||
|
||||
// update url query string
|
||||
const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}`
|
||||
+ `?f=${encodeURIComponent(searchFilterField.value)}&s=${encodeURIComponent(searchSortField.value)}`;
|
||||
window.history.pushState({ path: newUrl }, '', newUrl);
|
||||
|
||||
// hide old search results
|
||||
itemListFooter.innerText = '';
|
||||
for (const itemEntry of itemEntries) itemEntry.style.display = 'none';
|
||||
|
||||
// index and sort search results
|
||||
const searchResults = [];
|
||||
try {
|
||||
for (let i = 0; i < searchDb.length; i++) {
|
||||
const item = searchDb[i][0], itemExp = searchDb[i][1];
|
||||
if (checkBool(searchFilterField.output(item, itemExp))) {
|
||||
searchResults.push({ item, itemExp, sortKeys: searchSortField.output(item, itemExp) });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
searchFilterField.errorText.innerText = e.message;
|
||||
return;
|
||||
}
|
||||
if (searchResults.length === 0) {
|
||||
itemListFooter.innerText = 'No results!';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
searchResults.sort((a, b) => compareLexico(a.item, a.sortKeys, b.item, b.sortKeys));
|
||||
} catch (e) {
|
||||
searchSortField.errorText.innerText = e.message;
|
||||
return;
|
||||
}
|
||||
|
||||
// display search results
|
||||
const searchMax = Math.min(searchResults.length, ITEM_LIST_SIZE);
|
||||
for (let i = 0; i < searchMax; i++) {
|
||||
const result = searchResults[i];
|
||||
itemEntries[i].style.display = 'inline-block';
|
||||
displayExpandedItem(result.itemExp, `item-entry-${i}`);
|
||||
if (result.sortKeys.length > 0) {
|
||||
const sortKeyListContainer = document.createElement('div');
|
||||
sortKeyListContainer.classList.add('itemleft');
|
||||
const sortKeyList = document.createElement('ul');
|
||||
sortKeyList.classList.add('itemp', 'T0');
|
||||
sortKeyList.style.marginLeft = '1.75em';
|
||||
sortKeyListContainer.append(sortKeyList);
|
||||
for (let j = 0; j < result.sortKeys.length; j++) {
|
||||
const sortKeyElem = document.createElement('li');
|
||||
sortKeyElem.innerText = stringify(result.sortKeys[j]);
|
||||
sortKeyList.append(sortKeyElem);
|
||||
}
|
||||
itemEntries[i].append(sortKeyListContainer);
|
||||
}
|
||||
}
|
||||
if (searchMax < searchResults.length) {
|
||||
itemListFooter.innerText = `${searchResults.length - searchMax} more...`;
|
||||
}
|
||||
}
|
||||
|
||||
// updates the search state from the input boxes after a brief delay, to prevent excessive DOM updates
|
||||
let updateSearchTask = null;
|
||||
function scheduleSearchUpdate() {
|
||||
if (updateSearchTask !== null) {
|
||||
clearTimeout(updateSearchTask);
|
||||
}
|
||||
updateSearchTask = setTimeout(() => {
|
||||
updateSearchTask = null;
|
||||
updateSearch();
|
||||
}, 500);
|
||||
}
|
||||
searchFilterField.field.addEventListener('input', e => scheduleSearchUpdate());
|
||||
searchSortField.field.addEventListener('input', e => scheduleSearchUpdate());
|
||||
|
||||
// parse query string, display initial search results
|
||||
if (window.location.search.startsWith('?')) {
|
||||
for (const entryStr of window.location.search.substring(1).split('&')) {
|
||||
const ndx = entryStr.indexOf('=');
|
||||
if (ndx !== -1) {
|
||||
switch (entryStr.substring(0, ndx)) {
|
||||
case 'f':
|
||||
searchFilterField.field.value = decodeURIComponent(entryStr.substring(ndx + 1));
|
||||
break;
|
||||
case 's':
|
||||
searchSortField.field.value = decodeURIComponent(entryStr.substring(ndx + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSearch();
|
||||
|
||||
// focus the query filter text box
|
||||
searchFilterField.field.focus();
|
||||
searchFilterField.field.select();
|
||||
}
|
||||
|
||||
load_init(init);
|
5
load.js
5
load.js
|
@ -1,4 +1,4 @@
|
|||
const DB_VERSION = 31;
|
||||
const DB_VERSION = 32;
|
||||
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.js
|
||||
|
||||
let db;
|
||||
|
@ -55,6 +55,9 @@ function clean_item(item) {
|
|||
item.skillpoints = [item.str, item.dex, item.int, item.def, item.agi];
|
||||
item.has_negstat = item.str < 0 || item.dex < 0 || item.int < 0 || item.def < 0 || item.agi < 0;
|
||||
item.reqs = [item.strReq, item.dexReq, item.intReq, item.defReq, item.agiReq];
|
||||
if (item.slots === undefined) {
|
||||
item.slots = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
152
options.txt
Normal file
152
options.txt
Normal file
|
@ -0,0 +1,152 @@
|
|||
Parser specification:
|
||||
|
||||
/*
|
||||
* disj := conj "|" disj
|
||||
* | conj
|
||||
*
|
||||
* conj := cmp "&" conj
|
||||
* | cmpEq
|
||||
*
|
||||
* cmpEq := cmpRel "=" cmpEq
|
||||
* | cmpRel "?=" prim
|
||||
* | cmpRel "!=" cmpEq
|
||||
*
|
||||
* cmpRel := sum "<=" cmpRel
|
||||
* | sum "<" cmpRel
|
||||
* | sum ">" cmpRel
|
||||
* | sum ">=" cmpRel
|
||||
* | sum
|
||||
*
|
||||
* sum := prod "+" sum
|
||||
* | prod "-" sum
|
||||
* | prod
|
||||
*
|
||||
* prod := exp "*" prod
|
||||
* | exp "/" prod
|
||||
* | exp
|
||||
*
|
||||
* exp := unary "^" exp
|
||||
* | unary
|
||||
*
|
||||
* unary := "-" unary
|
||||
* | "!" unary
|
||||
* | prim
|
||||
*
|
||||
* prim := nLit
|
||||
* | bLit
|
||||
* | sLit
|
||||
* | ident "(" [disj ["," disj...]] ")"
|
||||
* | ident
|
||||
* | "(" disj ")"
|
||||
*/
|
||||
|
||||
Basically just type math. You can use "-" to negate things (to sort by ascending order for example), use & (and) and | (or) to combine search filters, or use ! (not) to invert filters.
|
||||
|
||||
Use spaces between arguments I guess, sometimes its picky
|
||||
|
||||
|
||||
|
||||
Special operator: "?=" is used to find a "includes" relation -- for example:
|
||||
|
||||
name ?= "blue"
|
||||
|
||||
will find items whose name includes the strong "blue" (not case sensitive).
|
||||
|
||||
|
||||
|
||||
Below is a list of all the options.
|
||||
Left of colon is what you type into the search bar (sometimes multiple things can alias to the same values), right side is what it represents.
|
||||
|
||||
'name': item name
|
||||
'type': item type (helmet, chestplate, leggings, boots, ring, bracelet, necklace, wand, bow, dagger, spear, relik)
|
||||
['cat', 'category']: item category (armor, accessory, weapon)
|
||||
['rarityname', 'raritystr', 'tiername', 'tierstr']: item tier string (normal, unique, set, rare, legendary, fabled, mythic)
|
||||
['rarity', 'tier']: item tier number (0 = normal, 6 = mythic
|
||||
|
||||
['level', 'lvl', 'combatlevel', 'combatlvl']: item level req
|
||||
['strmin', 'strreq']: Item str req
|
||||
['dexmin', 'dexreq']: Item dex req
|
||||
['intmin', 'intreq']: Item int req
|
||||
['defmin', 'defreq']: Item def req
|
||||
['agimin', 'agireq']: Item agi req
|
||||
['summin', 'sumreq', 'totalmin', 'totalreq']: Item total req
|
||||
|
||||
'str': Item str bonus
|
||||
'dex': Item dex bonus
|
||||
'int': Item int bonus
|
||||
'def': Item def bonus
|
||||
'agi': Item agi bonus
|
||||
['skillpoints', 'skillpts', 'attributes', 'attrs']: Sum(item skill points bonus)
|
||||
|
||||
['neutraldmg', 'neutraldam', 'ndmg', 'ndam']: Item Neutral Damage, Average
|
||||
['earthdmg', 'earthdam', 'edmg', 'edam']: Item Earth Damage, Average
|
||||
['thunderdmg', 'thunderdam', 'tdmg', 'tdam']: Item Thunder Damage, Average
|
||||
['waterdmg', 'waterdam', 'wdmg', 'wdam']: Item Water Damage, Average
|
||||
['firedmg', 'firedam', 'fdmg', 'fdam']: Item Fire Damage, Average
|
||||
['airdmg', 'airdam', 'admg', 'adam']: Item Air Damage, Average
|
||||
['sumdmg', 'sumdam', 'totaldmg', 'totaldam']: Item Total Damage, Average
|
||||
|
||||
['earthdmg%', 'earthdam%', 'edmg%', 'edam%', 'edampct']: Earth Damage Bonus
|
||||
['thunderdmg%', 'thunderdam%', 'tdmg%', 'tdam%', 'tdampct']: Thunder Damage Bonus
|
||||
['waterdmg%', 'waterdam%', 'wdmg%', 'wdam%', 'wdampct']: Water Damage Bonus
|
||||
['firedmg%', 'firedam%', 'fdmg%', 'fdam%', 'fdampct']: Fire Damage Bonus
|
||||
['airdmg%', 'airdam%', 'admg%', 'adam%', 'adampct']: Air Damage Bonus
|
||||
['sumdmg%', 'sumdam%', 'totaldmg%', 'totaldam%', 'sumdampct', 'totaldampct']: Sum damages %
|
||||
|
||||
['mainatkdmg', 'mainatkdam', 'mainatkdmg%', 'mainatkdam%', 'meleedmg', 'meleedam', 'meleedmg%', 'meleedam%', 'mdpct']: Melee Damage Bonus (%)
|
||||
['mainatkrawdmg', 'mainatkrawdam', 'mainatkneutraldmg', 'mainatkneutraldam','meleerawdmg', 'meleerawdam', 'meleeneutraldmg', 'meleeneutraldam', 'mdraw']: Melee Damage (Raw)
|
||||
['spelldmg', 'spelldam', 'spelldmg%', 'spelldam%', 'sdpct']: Spell Damage (%)
|
||||
['spellrawdmg', 'spellrawdam', 'spellneutraldmg', 'spellneutraldam', 'sdraw']: Spell Damage (Raw)
|
||||
['attackspeed', 'atkspd']: Item Attack Speed
|
||||
['bonusattackspeed', 'bonusatkspd', 'attackspeedid', 'atkspdid', 'attackspeed+', 'atkspd+', 'atktier']: Attack Speed Bonus
|
||||
['sumattackspeed', 'totalattackspeed', 'sumatkspd', 'totalatkspd', 'sumatktier', 'totalatktier']: Total Attack Speed (Base speed + bonus)
|
||||
|
||||
['earthdef', 'edef']: Earth Defense Raw
|
||||
['thunderdef', 'tdef']: Thunder Defense Raw
|
||||
['waterdef', 'wdef']: Water Defense Raw
|
||||
['firedef', 'fdef']: Fire Defense Raw
|
||||
['airdef', 'adef']: Air Defense Raw
|
||||
['sumdef', 'totaldef']: Total Defense Raw
|
||||
|
||||
['earthdef%', 'edef%', 'edefpct']: Total Defense %
|
||||
['thunderdef%', 'tdef%', 'tdefpct']: Total Defense %
|
||||
['waterdef%', 'wdef%', 'wdefpct']: Total Defense %
|
||||
['firedef%', 'fdef%', 'fdefpct']: Total Defense %
|
||||
['airdef%', 'adef%', 'adefpct']: Total Defense %
|
||||
['sumdef%', 'totaldef%', 'sumdefpct', 'totaldefpct']: Total Defense %
|
||||
|
||||
['health', 'hp']: Health
|
||||
['bonushealth', 'healthid', 'bonushp', 'hpid', 'health+', 'hp+', 'hpbonus']: Health bonus
|
||||
['sumhealth', 'sumhp', 'totalhealth', 'totalhp']: Total Health (health + health bonus)
|
||||
|
||||
['hpregen', 'hpr', 'hr', 'hprraw']: Raw Health Regen
|
||||
['hpregen%', 'hpr%', 'hr%', 'hprpct']: Health Regen %
|
||||
['lifesteal', 'ls']: Lifesteal
|
||||
['manaregen', 'mr']: Mana Regen
|
||||
['manasteal', 'ms']: Mana Steal
|
||||
|
||||
['walkspeed', 'movespeed', 'ws', 'spd']: Walk Speed Bonus
|
||||
'sprint': Sprint Bonus
|
||||
['sprintregen', 'sprintreg']: Sprint Regen
|
||||
['jumpheight', 'jh']: Jump Height
|
||||
|
||||
['spellcost1', 'rawspellcost1', 'spcost1', 'spraw1']: 1st Spell Cost Raw (min roll)
|
||||
['spellcost1%', 'spcost1%', 'sppct1']: 1st Spell Cost % (min roll)
|
||||
['spellcost2', 'rawspellcost2', 'spcost2', 'spraw2']: 2nd Spell Cost Raw (min roll)
|
||||
['spellcost2%', 'spcost2%', 'sppct2']: 2nd Spell Cost % (min roll)
|
||||
['spellcost3', 'rawspellcost3', 'spcost3', 'spraw3']: 3rd Spell Cost Raw (min roll)
|
||||
['spellcost3%', 'spcost3%', 'sppct3']: 3rd Spell Cost % (min roll)
|
||||
['spellcost4', 'rawspellcost4', 'spcost4', 'spraw4']: 4th Spell Cost Raw (min roll)
|
||||
['spellcost4%', 'spcost4%', 'sppct4']: 4th Spell Cost % (min roll)
|
||||
['sumspellcost', 'totalspellcost', 'sumrawspellcost', 'totalrawspellcost', 'sumspcost', 'totalspcost', 'sumspraw', 'totalspraw']: Sum (Spell Cost Raw)
|
||||
['sumspellcost%', 'totalspellcost%', 'sumspcost%', 'totalspcost%', 'sumsppct', 'totalsppct']: Sum (Spell Cost %)
|
||||
|
||||
['exploding', 'expl', 'expd']: Exploding
|
||||
'poison': Poison
|
||||
'thorns': Thorns
|
||||
['reflection', 'refl', 'ref']: Reflection
|
||||
['soulpointregen', 'spr', 'spregen']: Soul Point Regen
|
||||
['lootbonus', 'lb']: Loot Bonus
|
||||
['xpbonus', 'xpb', 'xb']: XP Bonus
|
||||
['stealing', 'esteal']: Stealing
|
||||
['powderslots', 'powders', 'slots', 'sockets']: # Powder Slots
|
604
query_2.js
Normal file
604
query_2.js
Normal file
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* disj := conj "|" disj
|
||||
* | conj
|
||||
*
|
||||
* conj := cmp "&" conj
|
||||
* | cmpEq
|
||||
*
|
||||
* cmpEq := cmpRel "=" cmpEq
|
||||
* | cmpRel "?=" prim
|
||||
* | cmpRel "!=" cmpEq
|
||||
*
|
||||
* cmpRel := sum "<=" cmpRel
|
||||
* | sum "<" cmpRel
|
||||
* | sum ">" cmpRel
|
||||
* | sum ">=" cmpRel
|
||||
* | sum
|
||||
*
|
||||
* sum := prod "+" sum
|
||||
* | prod "-" sum
|
||||
* | prod
|
||||
*
|
||||
* prod := exp "*" prod
|
||||
* | exp "/" prod
|
||||
* | exp
|
||||
*
|
||||
* exp := unary "^" exp
|
||||
* | unary
|
||||
*
|
||||
* unary := "-" unary
|
||||
* | "!" unary
|
||||
* | prim
|
||||
*
|
||||
* prim := nLit
|
||||
* | bLit
|
||||
* | sLit
|
||||
* | ident "(" [disj ["," disj...]] ")"
|
||||
* | ident
|
||||
* | "(" disj ")"
|
||||
*/
|
||||
|
||||
// a list of tokens indexed by a single pointer
|
||||
class TokenList {
|
||||
constructor(tokens) {
|
||||
this.tokens = tokens;
|
||||
this.ptr = 0;
|
||||
}
|
||||
|
||||
get here() {
|
||||
if (this.ptr >= this.tokens.length) throw new Error('Reached end of expression');
|
||||
return this.tokens[this.ptr];
|
||||
}
|
||||
|
||||
advance(steps = 1) {
|
||||
this.ptr = Math.min(this.ptr + steps, this.tokens.length);
|
||||
}
|
||||
}
|
||||
|
||||
// type casts
|
||||
function checkBool(v) {
|
||||
if (typeof v !== 'boolean') throw new Error(`Expected boolean, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function checkNum(v) {
|
||||
if (typeof v !== 'number') throw new Error(`Expected number, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function checkStr(v) {
|
||||
if (typeof v !== 'string') throw new Error(`Expected string, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
// properties of items that can be looked up
|
||||
const itemQueryProps = (function() {
|
||||
const props = {};
|
||||
function prop(names, getProp) {
|
||||
if (Array.isArray(names)) {
|
||||
for (name of names) {
|
||||
props[name] = getProp;
|
||||
}
|
||||
} else {
|
||||
props[names] = getProp;
|
||||
}
|
||||
}
|
||||
function maxId(names, idKey) {
|
||||
prop(names, (i, ie) => ie.get('maxRolls').get(idKey) || 0);
|
||||
}
|
||||
function minId(names, idKey) {
|
||||
prop(names, (i, ie) => ie.get('minRolls').get(idKey) || 0);
|
||||
}
|
||||
function rangeAvg(names, getProp) {
|
||||
prop(names, (i, ie) => {
|
||||
const range = getProp(i, ie);
|
||||
if (!range) return 0;
|
||||
const ndx = range.indexOf('-');
|
||||
return (parseInt(range.substring(0, ndx), 10) + parseInt(range.substring(ndx + 1), 10)) / 2;
|
||||
});
|
||||
}
|
||||
function map(names, comps, f) {
|
||||
return prop(names, (i, ie) => {
|
||||
const args = [];
|
||||
for (let k = 0; k < comps.length; k++) args.push(comps[k](i, ie));
|
||||
return f.apply(null, args);
|
||||
});
|
||||
}
|
||||
function sum(names, ...comps) {
|
||||
return map(names, comps, (...summands) => {
|
||||
let total = 0;
|
||||
for (let i = 0; i < summands.length; i++) total += summands[i];
|
||||
return total;
|
||||
});
|
||||
}
|
||||
|
||||
prop('name', (i, ie) => i.displayName || i.name);
|
||||
prop('type', (i, ie) => i.type);
|
||||
prop(['cat', 'category'], (i, ie) => i.category);
|
||||
const tierIndices = { Normal: 0, Unique: 1, Set: 2, Rare: 3, Legendary: 4, Fabled: 5, Mythic: 6 };
|
||||
prop(['rarityname', 'raritystr', 'tiername', 'tierstr'], (i, ie) => i.tier);
|
||||
prop(['rarity', 'tier'], (i, ie) => tierIndices[i.tier]);
|
||||
|
||||
prop(['level', 'lvl', 'combatlevel', 'combatlvl'], (i, ie) => i.lvl);
|
||||
prop(['strmin', 'strreq'], (i, ie) => i.strReq);
|
||||
prop(['dexmin', 'dexreq'], (i, ie) => i.dexReq);
|
||||
prop(['intmin', 'intreq'], (i, ie) => i.intReq);
|
||||
prop(['defmin', 'defreq'], (i, ie) => i.defReq);
|
||||
prop(['agimin', 'agireq'], (i, ie) => i.agiReq);
|
||||
sum(['summin', 'sumreq', 'totalmin', 'totalreq'], props.strmin, props.dexmin, props.intmin, props.defmin, props.agimin);
|
||||
|
||||
prop('str', (i, ie) => i.str);
|
||||
prop('dex', (i, ie) => i.dex);
|
||||
prop('int', (i, ie) => i.int);
|
||||
prop('def', (i, ie) => i.def);
|
||||
prop('agi', (i, ie) => i.agi);
|
||||
sum(['skillpoints', 'skillpts', 'attributes', 'attrs'], props.str, props.dex, props.int, props.def, props.agi);
|
||||
|
||||
rangeAvg(['neutraldmg', 'neutraldam', 'ndmg', 'ndam'], (i, ie) => i.nDam);
|
||||
rangeAvg(['earthdmg', 'earthdam', 'edmg', 'edam'], (i, ie) => i.eDam);
|
||||
rangeAvg(['thunderdmg', 'thunderdam', 'tdmg', 'tdam'], (i, ie) => i.tDam);
|
||||
rangeAvg(['waterdmg', 'waterdam', 'wdmg', 'wdam'], (i, ie) => i.wDam);
|
||||
rangeAvg(['firedmg', 'firedam', 'fdmg', 'fdam'], (i, ie) => i.fDam);
|
||||
rangeAvg(['airdmg', 'airdam', 'admg', 'adam'], (i, ie) => i.aDam);
|
||||
sum(['sumdmg', 'sumdam', 'totaldmg', 'totaldam'], props.ndam, props.edam, props.tdam, props.wdam, props.fdam, props.adam);
|
||||
|
||||
maxId(['earthdmg%', 'earthdam%', 'edmg%', 'edam%', 'edampct'], 'eDamPct');
|
||||
maxId(['thunderdmg%', 'thunderdam%', 'tdmg%', 'tdam%', 'tdampct'], 'tDamPct');
|
||||
maxId(['waterdmg%', 'waterdam%', 'wdmg%', 'wdam%', 'wdampct'], 'wDamPct');
|
||||
maxId(['firedmg%', 'firedam%', 'fdmg%', 'fdam%', 'fdampct'], 'fDamPct');
|
||||
maxId(['airdmg%', 'airdam%', 'admg%', 'adam%', 'adampct'], 'aDamPct');
|
||||
sum(['sumdmg%', 'sumdam%', 'totaldmg%', 'totaldam%', 'sumdampct', 'totaldampct'], props.edampct, props.tdampct, props.wdampct, props.fdampct, props.adampct);
|
||||
|
||||
maxId(['mainatkdmg', 'mainatkdam', 'mainatkdmg%', 'mainatkdam%', 'meleedmg', 'meleedam', 'meleedmg%', 'meleedam%', 'mdpct'], 'mdPct');
|
||||
maxId(['mainatkrawdmg', 'mainatkrawdam', 'mainatkneutraldmg', 'mainatkneutraldam', 'meleerawdmg', 'meleerawdam', 'meleeneutraldmg', 'meleeneutraldam', 'mdraw'], 'mdRaw');
|
||||
maxId(['spelldmg', 'spelldam', 'spelldmg%', 'spelldam%', 'sdpct'], 'sdPct');
|
||||
maxId(['spellrawdmg', 'spellrawdam', 'spellneutraldmg', 'spellneutraldam', 'sdraw'], 'sdRaw');
|
||||
|
||||
const atkSpdIndices = { SUPER_SLOW: -3, VERY_SLOW: -2, SLOW: -1, NORMAL: 0, FAST: 1, VERY_FAST: 2, SUPER_FAST: 3 };
|
||||
prop(['attackspeed', 'atkspd'], (i, ie) => i.atkSpd ? atkSpdIndices[i.atkSpd] : 0);
|
||||
maxId(['bonusattackspeed', 'bonusatkspd', 'attackspeedid', 'atkspdid', 'attackspeed+', 'atkspd+', 'atktier'], 'atkTier');
|
||||
sum(['sumattackspeed', 'totalattackspeed', 'sumatkspd', 'totalatkspd', 'sumatktier', 'totalatktier'], props.atkspd, props.atktier);
|
||||
|
||||
prop(['earthdef', 'edef'], (i, ie) => i.eDef || 0);
|
||||
prop(['thunderdef', 'tdef'], (i, ie) => i.tDef || 0);
|
||||
prop(['waterdef', 'wdef'], (i, ie) => i.wDef || 0);
|
||||
prop(['firedef', 'fdef'], (i, ie) => i.fDef || 0);
|
||||
prop(['airdef', 'adef'], (i, ie) => i.aDef || 0);
|
||||
sum(['sumdef', 'totaldef'], props.edef, props.tdef, props.wdef, props.fdef, props.adef);
|
||||
|
||||
maxId(['earthdef%', 'edef%', 'edefpct'], 'eDefPct');
|
||||
maxId(['thunderdef%', 'tdef%', 'tdefpct'], 'tDefPct');
|
||||
maxId(['waterdef%', 'wdef%', 'wdefpct'], 'wDefPct');
|
||||
maxId(['firedef%', 'fdef%', 'fdefpct'], 'fDefPct');
|
||||
maxId(['airdef%', 'adef%', 'adefpct'], 'aDefPct');
|
||||
sum(['sumdef%', 'totaldef%', 'sumdefpct', 'totaldefpct'], props.edefpct, props.tdefpct, props.wdefpct, props.fdefpct, props.adefpct);
|
||||
|
||||
prop(['health', 'hp'], (i, ie) => i.hp || 0);
|
||||
maxId(['bonushealth', 'healthid', 'bonushp', 'hpid', 'health+', 'hp+', 'hpbonus'], 'hpBonus');
|
||||
sum(['sumhealth', 'sumhp', 'totalhealth', 'totalhp'], props.hp, props.hpid);
|
||||
|
||||
maxId(['hpregen', 'hpr', 'hr', 'hprraw'], 'hprRaw');
|
||||
maxId(['hpregen%', 'hpr%', 'hr%', 'hprpct'], 'hprPct');
|
||||
maxId(['lifesteal', 'ls'], 'ls');
|
||||
maxId(['manaregen', 'mr'], 'mr');
|
||||
maxId(['manasteal', 'ms'], 'ms');
|
||||
|
||||
maxId(['walkspeed', 'movespeed', 'ws', 'spd'], 'spd');
|
||||
maxId('sprint', 'sprint');
|
||||
maxId(['sprintregen', 'sprintreg'], 'sprintReg');
|
||||
maxId(['jumpheight', 'jh'], 'jh');
|
||||
|
||||
minId(['spellcost1', 'rawspellcost1', 'spcost1', 'spraw1'], 'spRaw1');
|
||||
minId(['spellcost1%', 'spcost1%', 'sppct1'], 'spPct1');
|
||||
minId(['spellcost2', 'rawspellcost2', 'spcost2', 'spraw2'], 'spRaw2');
|
||||
minId(['spellcost2%', 'spcost2%', 'sppct2'], 'spPct2');
|
||||
minId(['spellcost3', 'rawspellcost3', 'spcost3', 'spraw3'], 'spRaw3');
|
||||
minId(['spellcost3%', 'spcost3%', 'sppct3'], 'spPct3');
|
||||
minId(['spellcost4', 'rawspellcost4', 'spcost4', 'spraw4'], 'spRaw4');
|
||||
minId(['spellcost4%', 'spcost4%', 'sppct4'], 'spPct4');
|
||||
sum(['sumspellcost', 'totalspellcost', 'sumrawspellcost', 'totalrawspellcost', 'sumspcost', 'totalspcost', 'sumspraw', 'totalspraw'], props.spraw1, props.spraw2, props.spraw3, props.spraw4);
|
||||
sum(['sumspellcost%', 'totalspellcost%', 'sumspcost%', 'totalspcost%', 'sumsppct', 'totalsppct'], props.sppct1, props.sppct2, props.sppct3, props.sppct4);
|
||||
|
||||
maxId(['exploding', 'expl', 'expd'], 'expd');
|
||||
maxId('poison', 'poison');
|
||||
maxId('thorns', 'thorns');
|
||||
maxId(['reflection', 'refl', 'ref'], 'ref');
|
||||
maxId(['soulpointregen', 'spr', 'spregen'], 'spRegen');
|
||||
maxId(['lootbonus', 'lb'], 'lb');
|
||||
maxId(['xpbonus', 'xpb', 'xb'], 'xpb');
|
||||
maxId(['stealing', 'esteal'], 'eSteal');
|
||||
prop(['powderslots', 'powders', 'slots', 'sockets'], (i, ie) => i.slots || 0);
|
||||
|
||||
return props;
|
||||
})();
|
||||
|
||||
// functions that can be called in query expressions
|
||||
const itemQueryFuncs = {
|
||||
max(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to max()');
|
||||
let runningMax = -Infinity;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (checkNum(args[i]) > runningMax) runningMax = args[i];
|
||||
}
|
||||
return runningMax;
|
||||
},
|
||||
min(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to min()');
|
||||
let runningMin = Infinity;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (checkNum(args[i]) < runningMin) runningMin = args[i];
|
||||
}
|
||||
return runningMin;
|
||||
},
|
||||
floor(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to floor()');
|
||||
return Math.floor(checkNum(args[0]));
|
||||
},
|
||||
ceil(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to ceil()');
|
||||
return Math.ceil(checkNum(args[0]));
|
||||
},
|
||||
round(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to ceil()');
|
||||
return Math.round(checkNum(args[0]));
|
||||
},
|
||||
sqrt(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to ceil()');
|
||||
return Math.sqrt(checkNum(args[0]));
|
||||
},
|
||||
abs(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to ceil()');
|
||||
return Math.abs(checkNum(args[0]));
|
||||
},
|
||||
contains(args) {
|
||||
if (args.length < 2) throw new Error('Not enough args to contains()');
|
||||
return checkStr(args[0]).toLowerCase().includes(checkStr(args[1]).toLowerCase());
|
||||
},
|
||||
atkspdmod(args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to atkSpdMod()');
|
||||
switch (checkNum(args[0])) {
|
||||
case 2: return 3.1;
|
||||
case 1: return 2.5;
|
||||
case 0: return 2.05;
|
||||
case -1: return 1.5;
|
||||
case -2: return 0.83;
|
||||
}
|
||||
if (args[0] <= -3) return 0.51;
|
||||
if (args[0] >= 3) return 4.3;
|
||||
throw new Error('Invalid argument to atkSpdMod()');
|
||||
}
|
||||
};
|
||||
|
||||
// the compiler itself
|
||||
const compileQueryExpr = (function() {
|
||||
// tokenize an expression string
|
||||
function tokenize(exprStr) {
|
||||
exprStr = exprStr.trim();
|
||||
const tokens = [];
|
||||
let col = 0;
|
||||
function pushSymbol(sym) {
|
||||
tokens.push({ type: 'sym', sym });
|
||||
col += sym.length;
|
||||
}
|
||||
while (col < exprStr.length) {
|
||||
// parse fixed symbols, like operators and stuff
|
||||
switch (exprStr[col]) {
|
||||
case '(':
|
||||
case ')':
|
||||
case ',':
|
||||
case '&':
|
||||
case '|':
|
||||
case '+':
|
||||
case '-':
|
||||
case '*':
|
||||
case '/':
|
||||
case '^':
|
||||
case '=':
|
||||
pushSymbol(exprStr[col]);
|
||||
continue;
|
||||
case '>':
|
||||
pushSymbol(exprStr[col + 1] === '=' ? '>=' : '>');
|
||||
continue;
|
||||
case '<':
|
||||
pushSymbol(exprStr[col + 1] === '=' ? '<=' : '<');
|
||||
continue;
|
||||
case '!':
|
||||
pushSymbol(exprStr[col + 1] === '=' ? '!=' : '!');
|
||||
continue;
|
||||
case ' ': // ignore extra whitespace
|
||||
++col;
|
||||
continue;
|
||||
}
|
||||
if (exprStr.slice(col, col+2) === "?=") {
|
||||
pushSymbol("?=");
|
||||
continue;
|
||||
}
|
||||
// parse a numeric literal
|
||||
let m;
|
||||
if ((m = /^\d+(?:\.\d*)?/.exec(exprStr.substring(col))) !== null) {
|
||||
tokens.push({ type: 'num', value: parseFloat(m[0]) });
|
||||
col += m[0].length;
|
||||
continue;
|
||||
}
|
||||
// parse a string literal
|
||||
if ((m = /^"([^"]+)"/.exec(exprStr.substring(col))) !== null) { // with double-quotes
|
||||
tokens.push({ type: 'str', value: m[1] });
|
||||
col += m[0].length;
|
||||
continue;
|
||||
}
|
||||
if ((m = /^'([^']+)'/.exec(exprStr.substring(col))) !== null) { // with single-quotes
|
||||
tokens.push({ type: 'str', value: m[1] });
|
||||
col += m[0].length;
|
||||
continue;
|
||||
}
|
||||
// parse an identifier or boolean literal
|
||||
if ((m = /^\w[\w\d+%]*/.exec(exprStr.substring(col))) !== null) {
|
||||
switch (m[0]) {
|
||||
case 'true':
|
||||
tokens.push({ type: 'bool', value: true });
|
||||
col += 4;
|
||||
continue;
|
||||
case 'false':
|
||||
tokens.push({ type: 'bool', value: false });
|
||||
col += 5;
|
||||
continue;
|
||||
}
|
||||
tokens.push({ type: 'id', id: m[0] });
|
||||
col += m[0].length;
|
||||
continue;
|
||||
}
|
||||
// if we reach here without successfully parsing a token, it's an error
|
||||
throw new Error(`Could not parse character "${exprStr[col]}" at position ${col}`);
|
||||
}
|
||||
tokens.push({ type: 'eof' });
|
||||
return new TokenList(tokens);
|
||||
}
|
||||
|
||||
// parse tokens into an ast
|
||||
function takeDisj(tokens) {
|
||||
const left = takeConj(tokens);
|
||||
if (tokens.here.type === 'sym' && tokens.here.sym === '|') {
|
||||
tokens.advance();
|
||||
const right = takeDisj(tokens);
|
||||
return (i, ie) => checkBool(left(i, ie)) || checkBool(right(i, ie));
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function takeConj(tokens) {
|
||||
const left = takeCmpEq(tokens);
|
||||
if (tokens.here.type === 'sym' && tokens.here.sym === '&') {
|
||||
tokens.advance();
|
||||
const right = takeConj(tokens);
|
||||
return (i, ie) => checkBool(left(i, ie)) && checkBool(right(i, ie));
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function takeCmpEq(tokens) {
|
||||
const left = takeCmpRel(tokens);
|
||||
if (tokens.here.type === 'sym') {
|
||||
switch (tokens.here.sym) {
|
||||
case '=': {
|
||||
tokens.advance();
|
||||
const right = takeCmpEq(tokens);
|
||||
return (i, ie) => {
|
||||
const a = left(i, ie), b = right(i, ie);
|
||||
if (typeof a !== typeof b) return false;
|
||||
switch (typeof a) {
|
||||
case 'number':
|
||||
return Math.abs(left(i, ie) - right(i, ie)) < 1e-4;
|
||||
case 'boolean':
|
||||
return a === b;
|
||||
case 'string':
|
||||
return a.toLowerCase() === b.toLowerCase();
|
||||
}
|
||||
throw new Error('???'); // wut
|
||||
};
|
||||
}
|
||||
case '!=': {
|
||||
tokens.advance();
|
||||
const right = takeCmpEq(tokens);
|
||||
return (i, ie) => {
|
||||
const a = left(i, ie), b = right(i, ie);
|
||||
if (typeof a !== typeof b) return false;
|
||||
switch (typeof a) {
|
||||
case 'number':
|
||||
return Math.abs(left(i, ie) - right(i, ie)) >= 1e-4;
|
||||
case 'boolean':
|
||||
return a !== b;
|
||||
case 'string':
|
||||
return a.toLowerCase() !== b.toLowerCase();
|
||||
}
|
||||
throw new Error('???'); // wtf
|
||||
};
|
||||
}
|
||||
case '?=': {
|
||||
tokens.advance();
|
||||
const right = takePrim(tokens);
|
||||
return (i, ie) => {
|
||||
const a = left(i, ie), b = right(i, ie);
|
||||
if (typeof a !== typeof b) return false;
|
||||
switch (typeof a) {
|
||||
case 'number':
|
||||
return Math.abs(left(i, ie) - right(i, ie)) < 1e-4;
|
||||
case 'boolean':
|
||||
return a === b;
|
||||
case 'string':
|
||||
return a.toLowerCase().includes(b.toLowerCase());
|
||||
}
|
||||
throw new Error('???'); // wtf
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function takeCmpRel(tokens) {
|
||||
const left = takeSum(tokens);
|
||||
if (tokens.here.type === 'sym') {
|
||||
switch (tokens.here.sym) {
|
||||
case '<=': {
|
||||
tokens.advance();
|
||||
const right = takeCmpRel(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) <= checkNum(right(i, ie));
|
||||
}
|
||||
case '<': {
|
||||
tokens.advance();
|
||||
const right = takeCmpRel(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) < checkNum(right(i, ie));
|
||||
}
|
||||
case '>': {
|
||||
tokens.advance();
|
||||
const right = takeCmpRel(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) > checkNum(right(i, ie));
|
||||
}
|
||||
case '>=': {
|
||||
tokens.advance();
|
||||
const right = takeCmpRel(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) >= checkNum(right(i, ie));
|
||||
}
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function takeSum(tokens) {
|
||||
const left = takeProd(tokens);
|
||||
if (tokens.here.type === 'sym') {
|
||||
switch (tokens.here.sym) {
|
||||
case '+': {
|
||||
tokens.advance();
|
||||
const right = takeSum(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) + checkNum(right(i, ie));
|
||||
}
|
||||
case '-': {
|
||||
tokens.advance();
|
||||
const right = takeSum(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) - checkNum(right(i, ie));
|
||||
}
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function takeProd(tokens) {
|
||||
const left = takeExp(tokens);
|
||||
if (tokens.here.type === 'sym') {
|
||||
switch (tokens.here.sym) {
|
||||
case '*': {
|
||||
tokens.advance();
|
||||
const right = takeProd(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) * checkNum(right(i, ie));
|
||||
}
|
||||
case '/': {
|
||||
tokens.advance();
|
||||
const right = takeProd(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) / checkNum(right(i, ie));
|
||||
}
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function takeExp(tokens) {
|
||||
const left = takeUnary(tokens);
|
||||
if (tokens.here.type === 'sym' && tokens.here.sym === '^') {
|
||||
tokens.advance();
|
||||
const right = takeExp(tokens);
|
||||
return (i, ie) => checkNum(left(i, ie)) ** checkNum(right(i, ie));
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
function takeUnary(tokens) {
|
||||
if (tokens.here.type === 'sym') {
|
||||
switch (tokens.here.sym) {
|
||||
case '-': {
|
||||
tokens.advance();
|
||||
const operand = takeUnary(tokens);
|
||||
return (i, ie) => -checkNum(operand(i, ie));
|
||||
}
|
||||
case '!': {
|
||||
tokens.advance();
|
||||
const operand = takeUnary(tokens);
|
||||
return (i, ie) => !checkBool(operand(i, ie));
|
||||
}
|
||||
}
|
||||
}
|
||||
return takePrim(tokens);
|
||||
}
|
||||
|
||||
function takePrim(tokens) {
|
||||
switch (tokens.here.type) {
|
||||
case 'num': {
|
||||
const lit = tokens.here.value;
|
||||
tokens.advance();
|
||||
return (i, ie) => lit;
|
||||
}
|
||||
case 'bool': {
|
||||
const lit = tokens.here.value;
|
||||
tokens.advance();
|
||||
return (i, ie) => lit;
|
||||
}
|
||||
case 'str': {
|
||||
const lit = tokens.here.value;
|
||||
tokens.advance();
|
||||
console.log(lit);
|
||||
return (i, ie) => lit;
|
||||
}
|
||||
case 'id':
|
||||
const id = tokens.here.id;
|
||||
tokens.advance();
|
||||
if (tokens.here.type === 'sym' && tokens.here.sym === '(') { // it's a function call
|
||||
tokens.advance();
|
||||
const argExprs = [];
|
||||
if (tokens.here.type !== 'sym' || tokens.here.sym !== ')') {
|
||||
arg_iter: // collect arg expressions, if there are any
|
||||
while (true) {
|
||||
argExprs.push(takeDisj(tokens));
|
||||
if (tokens.here.type === 'sym') {
|
||||
switch (tokens.here.sym) {
|
||||
case ')':
|
||||
tokens.advance();
|
||||
break arg_iter;
|
||||
case ',':
|
||||
tokens.advance();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new Error(`Expected "," or ")", but got ${JSON.stringify(tokens.here)}`);
|
||||
}
|
||||
}
|
||||
const func = itemQueryFuncs[id.toLowerCase()];
|
||||
if (!func) throw new Error(`Unknown function: ${id}`);
|
||||
return (i, ie) => {
|
||||
const args = [];
|
||||
for (let k = 0; k < argExprs.length; k++) args.push(argExprs[k](i, ie));
|
||||
return func(args);
|
||||
};
|
||||
} else { // not a function call
|
||||
const prop = itemQueryProps[id.toLowerCase()];
|
||||
if (!prop) throw new Error(`Unknown property: ${id}`);
|
||||
return prop;
|
||||
}
|
||||
case 'sym':
|
||||
if (tokens.here.sym === '(') {
|
||||
tokens.advance();
|
||||
const expr = takeDisj(tokens);
|
||||
if (tokens.here.type !== 'sym' || tokens.here.sym !== ')') throw new Error('Bracket mismatch');
|
||||
tokens.advance();
|
||||
return expr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
throw new Error(tokens.here.type === 'eof' ? 'Reached end of expression' : `Unexpected token: ${JSON.stringify(tokens.here)}`);
|
||||
}
|
||||
|
||||
// full compilation function, with extra safety for empty input strings
|
||||
return function(exprStr) {
|
||||
const tokens = tokenize(exprStr);
|
||||
return tokens.tokens.length <= 1 ? null : takeDisj(tokens);
|
||||
};
|
||||
})();
|
|
@ -3,3 +3,4 @@ Version 1: http://localhost:8000/#1_0690px0CE0QR0050050K40BR0Qk00001004fI
|
|||
Version 2: http://localhost:8000/#2_2SG2SH2SI2SJ2SK0K22SM2SN05n000t210t0000000
|
||||
Version 3: https://localhost:8000/#3_0250px0uX0K50K20OK0OJ00A0Qe1z+m21001M1g0000100nZ6
|
||||
Version 3: https://localhost:8000/#3_0K60iv0CE0Qt0BK0BK0K40Jc0uG160V050o1L1g00001003C6
|
||||
Version 4: https://localhost:8000/#4_-1+W+W+W+W+W+W9g91-1+W+W+W+W+W+W9d91-1+W+W+W+W+W+W9i9--1+W+W+W+W+W+W9a91-1+W+W+W+W+W+W9m91-1+W+W+W+W+W+W9m91-1+W+W+W+W+W+W9c91-1+W+W+W+W+W+W9n91-1+W+W+W+W+W+W9q9100000000001g000010036C
|
||||
|
|
Loading…
Reference in a new issue