Merge pull request #8 from phantamanta44/atlas-dual-filter

Segregated-filter expression-based item search
This commit is contained in:
hppeng-wynn 2021-01-30 06:41:11 -06:00 committed by GitHub
commit 7047212e31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 262399 additions and 623 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
*.swp *.swp
*.bat *.bat
sets/ sets/
.idea/
*.iml

View file

@ -133,9 +133,9 @@ class Build{
this.powders[2] = this.powders[2].slice(0,leggings.slots); this.powders[2] = this.powders[2].slice(0,leggings.slots);
this.leggings = expandItem(leggings, this.powders[2]); this.leggings = expandItem(leggings, this.powders[2]);
}else{ }else{
const chestplate = itemMap.get("No Leggings"); const leggings = itemMap.get("No Leggings");
this.powders[1] = this.powders[1].slice(0,chestplate.slots); this.powders[2] = this.powders[2].slice(0,leggings.slots);
this.chestplate = expandItem(chestplate, this.powders[1]); this.leggings = expandItem(leggings, this.powders[2]);
errors.push(new ItemNotFound(equipment[2], "leggings", true)); 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") {

View file

@ -3,11 +3,18 @@ const url_tag = location.hash.slice(1);
console.log(url_base); console.log(url_base);
console.log(url_tag); console.log(url_tag);
const BUILD_VERSION = "6.9.7"; const BUILD_VERSION = "6.9.9";
function setTitle() { function setTitle() {
document.getElementById("header").textContent = "WynnBuilder version "+BUILD_VERSION+" (db version "+DB_VERSION+")"; let text;
document.getElementById("header").classList.add("funnynumber"); if (url_base.includes("hppeng-wynn")) {
text = "WynnBuilder UNSTABLE version "+BUILD_VERSION+" (db version "+DB_VERSION+")";
}
else {
text = "WynnBuilder version "+BUILD_VERSION+" (db version "+DB_VERSION+")";
document.getElementById("header").classList.add("funnynumber");
}
document.getElementById("header").textContent = text;
} }
setTitle(); setTitle();
@ -71,18 +78,6 @@ let equipment_names = [
let equipmentInputs = equipment_fields.map(x => x + "-choice"); let equipmentInputs = equipment_fields.map(x => x + "-choice");
let buildFields = equipment_fields.map(x => "build-"+x); let buildFields = equipment_fields.map(x => "build-"+x);
let powderIDs = new Map();
let powderNames = new Map();
let _powderID = 0;
for (const x of skp_elements) {
for (let i = 1; i <= 6; ++i) {
// Support both upper and lowercase, I guess.
powderIDs.set(x.toUpperCase()+i, _powderID);
powderIDs.set(x+i, _powderID);
powderNames.set(_powderID, x+i);
_powderID++;
}
}
let powderInputs = [ let powderInputs = [
"helmet-powder", "helmet-powder",
"chestplate-powder", "chestplate-powder",
@ -90,45 +85,6 @@ let powderInputs = [
"boots-powder", "boots-powder",
"weapon-powder", "weapon-powder",
]; ];
// Ordering: [dmgMin, dmgMax, convert, defPlus, defMinus (+6 mod 5)]
class Powder {
constructor(min, max, convert, defPlus, defMinus) {
this.min = min;
this.max = max;
this.convert = convert;
this.defPlus = defPlus;
this.defMinus = defMinus;
}
}
function _p(a,b,c,d,e) { return new Powder(a,b,c,d,e); } //bruh moment
let powderStats = [
_p(3,6,17,2,1), _p(6,9,21,4,2), _p(8,14,25,8,3), _p(11,16,31,14,5), _p(15,18,38,22,9), _p(18,22,46,30,13),
_p(1,8,9,3,1), _p(1,13,11,5,1), _p(2,18,14,9,2), _p(3,24,17,14,4), _p(3,32,22,20,7), _p(5,40,28,28,10),
_p(3,4,13,3,1), _p(4,7,15,6,1), _p(6,10,17,11,2), _p(8,12,21,18,4), _p(11,14,26,28,7), _p(13,17,32,40,10),
_p(2,5,14,3,1), _p(4,8,16,5,2), _p(6,10,19,9,3), _p(9,13,24,16,5), _p(12,16,30,25,9), _p(15,19,37,36,13),
_p(2,6,11,3,1), _p(4,9,14,6,2), _p(7,10,17,10,3), _p(9,13,22,16,5), _p(13,18,28,24,9), _p(16,18,35,34,13)
];
//Ordering: [weapon special name, weapon special effects, armor special name, armor special effects]
class PowderSpecial{
constructor(wSpName, wSpEff, aSpName, aSpEff, cap){
this.weaponSpecialName = wSpName;
this.weaponSpecialEffects = wSpEff;
this.armorSpecialName = aSpName;
this.armorSpecialEffects = aSpEff;
this.cap = cap;
}
}
function _ps(a,b,c,d,e) { return new PowderSpecial(a,b,c,d,e); } //bruh moment
let powderSpecialStats = [
_ps("Quake",new Map([["Radius",[5,5.5,6,6.5,7]], ["Damage",[155,220,285,350,415]] ]),"Rage",new Map([ ["Damage", [0.3,0.4,0.5,0.7,1.0]],["Description", "% " + "\u2764" + " Missing"] ]),400), //e
_ps("Chain Lightning",new Map([ ["Chains", [5,6,7,8,9]], ["Damage", [200,225,250,275,300]] ]),"Kill Streak",new Map([ ["Damage", [3,4.5,6,7.5,9]],["Duration", [5,5,5,5,5]],["Description", "Mob Killed"] ]),200), //t
_ps("Curse",new Map([ ["Duration", [7,7.5,8,8.5,9]],["Damage Boost", [90,120,150,180,210]] ]),"Concentration",new Map([ ["Damage", [1,2,3,4,5]],["Duration",[1,1,1,1,1]],["Description", "Mana Used"] ]),150), //w
_ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f
_ps("Air Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [400,450,500,550,600]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a
];
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes); let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes);
let itemLists = new Map(); let itemLists = new Map();
@ -138,6 +94,7 @@ for (const it of itemTypes) {
let itemMap = new Map(); let itemMap = new Map();
/* Mapping from item names to set names. */ /* Mapping from item names to set names. */
let idMap = new Map(); let idMap = new Map();
let redirectMap = new Map();
/* /*
* Function that takes an item list and populates its corresponding dropdown. * Function that takes an item list and populates its corresponding dropdown.
@ -194,13 +151,18 @@ function init() {
items = items.concat(noneItems); items = items.concat(noneItems);
console.log(items); console.log(items);
for (const item of items) { for (const item of items) {
itemLists.get(item.type).push(item.displayName); if (item.remapID === undefined) {
itemMap.set(item.displayName, item); itemLists.get(item.type).push(item.displayName);
if (noneItems.includes(item)) { itemMap.set(item.displayName, item);
idMap.set(item.id, ""); if (noneItems.includes(item)) {
idMap.set(item.id, "");
}
else {
idMap.set(item.id, item.displayName);
}
} }
else { else {
idMap.set(item.id, item.displayName); redirectMap.set(item.id, item.remapID);
} }
} }
@ -255,6 +217,13 @@ function init() {
decodeBuild(url_tag); decodeBuild(url_tag);
} }
function getItemNameFromID(id) {
if (redirectMap.has(id)) {
return getItemNameFromID(redirectMap.get(id));
}
return idMap.get(id);
}
/* /*
* Populate fields based on url, and calculate build. * Populate fields based on url, and calculate build.
*/ */
@ -270,7 +239,7 @@ function decodeBuild(url_tag) {
if (version === "0" || version === "1" || version === "2" || version === "3") { if (version === "0" || version === "1" || version === "2" || version === "3") {
let equipments = info[1]; let equipments = info[1];
for (let i = 0; i < 9; ++i ) { for (let i = 0; i < 9; ++i ) {
equipment[i] = idMap.get(Base64.toInt(equipments.slice(i*3,i*3+3))); equipment[i] = getItemNameFromID(Base64.toInt(equipments.slice(i*3,i*3+3)));
} }
} }
if (version === "1") { if (version === "1") {
@ -767,7 +736,8 @@ function calculateBuildStats() {
//skpRow.classList.add("left"); //skpRow.classList.add("left");
let td = document.createElement("p"); let td = document.createElement("p");
//td.classList.add("left"); //td.classList.add("left");
let skpSummary = document.createElement("b");
/* let skpSummary = document.createElement("b");
skpSummary.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Total: ("; skpSummary.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Total: (";
//skpSummary.classList.add("itemp"); //skpSummary.classList.add("itemp");
td.appendChild(skpSummary); td.appendChild(skpSummary);
@ -786,12 +756,12 @@ function calculateBuildStats() {
let skpEnd = document.createElement("b"); let skpEnd = document.createElement("b");
skpEnd.textContent = ")"; skpEnd.textContent = ")";
td.appendChild(skpEnd); td.appendChild(skpEnd);
skpRow.append(td); skpRow.append(td); */
let remainingSkp = document.createElement("p"); let remainingSkp = document.createElement("p");
remainingSkp.classList.add("center"); remainingSkp.classList.add("center");
let remainingSkpTitle = document.createElement("b"); let remainingSkpTitle = document.createElement("b");
remainingSkpTitle.textContent = "Remaining skillpoints: "; remainingSkpTitle.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Remaining skillpoints: ";
let remainingSkpContent = document.createElement("b"); let remainingSkpContent = document.createElement("b");
//remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints < 0 ? "< 0" : levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints); //remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints < 0 ? "< 0" : levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints);
remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints); remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints);

View file

@ -1,7 +1,7 @@
{ {
"items": [ "items": [
{ {
"name": "Demon Tide (1.20)", "name": "Demon Tide",
"tier": "Legendary", "tier": "Legendary",
"type": "leggings", "type": "leggings",
"set": null, "set": null,
@ -35506,6 +35506,7 @@
}, },
{ {
"name": "Cancer\u058e", "name": "Cancer\u058e",
"displayName": "Cancer",
"tier": "Legendary", "tier": "Legendary",
"type": "helmet", "type": "helmet",
"set": null, "set": null,
@ -265939,8 +265940,8 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "boots", "type": "boots",
"name": "Ensa\u2019s Ideals (1.20)", "name": "Ensa's Ideals (1.20)",
"displayName": "Ensa\u2019s Ideals (1.20)", "displayName": "Ensa's Ideals (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -266378,8 +266379,8 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "ring", "type": "ring",
"name": "Tisaun\u2019s Honor (1.20)", "name": "Tisaun's Honor (1.20)",
"displayName": "Tisaun\u2019s Honor (1.20)", "displayName": "Tisaun's Honor (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -266718,8 +266719,8 @@
{ {
"tier": "Unique", "tier": "Unique",
"type": "chestplate", "type": "chestplate",
"name": "Nether\u2019s Reach (1.20)", "name": "Nether's Reach (1.20)",
"displayName": "Nether\u2019s Reach (1.20)", "displayName": "Nether's Reach (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -266792,8 +266793,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "dagger", "type": "dagger",
"name": "Bob\u2019s Mythic Daggers (1.20)", "name": "Bob's Mythic Daggers (1.20)",
"displayName": "Bob\u2019s Mythic Daggers (1.20)", "displayName": "Bob's Mythic Daggers (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -267206,8 +267207,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "spear", "type": "spear",
"name": "Bob\u2019s Mythic Spear (1.20)", "name": "Bob's Mythic Spear (1.20)",
"displayName": "Bob\u2019s Mythic Spear (1.20)", "displayName": "Bob's Mythic Spear (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -267445,8 +267446,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "chestplate", "type": "chestplate",
"name": "Terra\u2019s Mold (1.20)", "name": "Terra's Mold (1.20)",
"displayName": "Terra\u2019s Mold (1.20)", "displayName": "Terra's Mold (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -267900,8 +267901,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "ring", "type": "ring",
"name": "Old Keeper\u2019s Ring (1.20)", "name": "Old Keeper's Ring (1.20)",
"displayName": "Old Keeper\u2019s Ring (1.20)", "displayName": "Old Keeper's Ring (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -268068,7 +268069,7 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "leggings", "type": "leggings",
"name": "Paradox (1.20)", "name": "One For All",
"displayName": "Paradox (1.20)", "displayName": "Paradox (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
@ -268272,7 +268273,7 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "boots", "type": "boots",
"name": "Dragon Dance (1.20)", "name": "Paradox",
"displayName": "Dragon Dance (1.20)", "displayName": "Dragon Dance (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
@ -268560,8 +268561,8 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "leggings", "type": "leggings",
"name": "Lion\u2019s Pelt (1.20)", "name": "Lion's Pelt (1.20)",
"displayName": "Lion\u2019s Pelt (1.20)", "displayName": "Lion's Pelt (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -269385,8 +269386,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "chestplate", "type": "chestplate",
"name": "Tisaun\u2019s Valor (1.20)", "name": "Tisaun's Valor (1.20)",
"displayName": "Tisaun\u2019s Valor (1.20)", "displayName": "Tisaun's Valor (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -269965,8 +269966,8 @@
{ {
"tier": "Unique", "tier": "Unique",
"type": "helmet", "type": "helmet",
"name": "Sano\u2019s Care (1.20)", "name": "Sano's Care (1.20)",
"displayName": "Sano\u2019s Care (1.20)", "displayName": "Sano's Care (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -270772,12 +270773,11 @@
{ {
"tier": "Set", "tier": "Set",
"type": "ring", "type": "ring",
"name": "Black Catalyst Set (1.20)", "name": "Black Catalyst Set",
"displayName": "Black Catalyst Set (1.20)", "displayName": "Black Catalyst Set (DEPRECATED!!!)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
"restrict": "1.20 item",
"fixID": true, "fixID": true,
"strReq": 0, "strReq": 0,
"dexReq": 0, "dexReq": 0,
@ -272002,8 +272002,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "boots", "type": "boots",
"name": "Nether\u2019s Scar (1.20)", "name": "Nether's Scar (1.20)",
"displayName": "Nether\u2019s Scar (1.20)", "displayName": "Nether's Scar (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -272146,8 +272146,8 @@
{ {
"tier": "Unique", "tier": "Unique",
"type": "leggings", "type": "leggings",
"name": "Bantisu\u2019s Approach (1.20)", "name": "Bantisu's Approach (1.20)",
"displayName": "Bantisu\u2019s Approach (1.20)", "displayName": "Bantisu's Approach (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -272406,8 +272406,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "helmet", "type": "helmet",
"name": "Gale\u2019s Sight (1.20)", "name": "Gale's Sight (1.20)",
"displayName": "Gale\u2019s Sight (1.20)", "displayName": "Gale's Sight (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -272584,8 +272584,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "bow", "type": "bow",
"name": "Bob\u2019s Mythic Bow (1.20)", "name": "Bob's Mythic Bow (1.20)",
"displayName": "Bob\u2019s Mythic Bow (1.20)", "displayName": "Bob's Mythic Bow (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -272824,8 +272824,8 @@
{ {
"tier": "Unique", "tier": "Unique",
"type": "chestplate", "type": "chestplate",
"name": "Ahm\u2019s Remains (1.20)", "name": "Ahm's Remains (1.20)",
"displayName": "Ahm\u2019s Remains (1.20)", "displayName": "Ahm's Remains (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -273249,8 +273249,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "wand", "type": "wand",
"name": "Bob\u2019s Mythic Wand (1.20)", "name": "Bob's Mythic Wand (1.20)",
"displayName": "Bob\u2019s Mythic Wand (1.20)", "displayName": "Bob's Mythic Wand (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -273535,8 +273535,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "boots", "type": "boots",
"name": "Cerid\u2019s Ingenuity (1.20)", "name": "Cerid's Ingenuity (1.20)",
"displayName": "Cerid\u2019s Ingenuity (1.20)", "displayName": "Cerid's Ingenuity (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -273845,8 +273845,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "leggings", "type": "leggings",
"name": "Ohms\u2019 Rage (1.20)", "name": "Ohms' Rage (1.20)",
"displayName": "Ohms\u2019 Rage (1.20)", "displayName": "Ohms' Rage (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -274024,8 +274024,8 @@
{ {
"tier": "Legendary", "tier": "Legendary",
"type": "necklace", "type": "necklace",
"name": "Ensa\u2019s Faith (1.20)", "name": "Ensa's Faith (1.20)",
"displayName": "Ensa\u2019s Faith (1.20)", "displayName": "Ensa's Faith (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -274798,8 +274798,8 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "leggings", "type": "leggings",
"name": "Rodoroc\u2019s Guard (1.20)", "name": "Rodoroc's Guard (1.20)",
"displayName": "Rodoroc\u2019s Guard (1.20)", "displayName": "Rodoroc's Guard (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -275277,7 +275277,7 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "boots", "type": "boots",
"name": "Weatherwalkers (1.20)", "name": "All For One",
"displayName": "Weatherwalkers (1.20)", "displayName": "Weatherwalkers (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
@ -275654,8 +275654,8 @@
{ {
"tier": "Rare", "tier": "Rare",
"type": "dagger", "type": "dagger",
"name": "Skien\u2019s Paranoia (1.20)", "name": "Skien's Paranoia (1.20)",
"displayName": "Skien\u2019s Paranoia (1.20)", "displayName": "Skien's Paranoia (1.20)",
"set": null, "set": null,
"quest": null, "quest": null,
"classReq": null, "classReq": null,
@ -276257,11 +276257,11 @@
"atkSpd": "SLOW", "atkSpd": "SLOW",
"lvl": 97, "lvl": 97,
"classReq": null, "classReq": null,
"strReq": 40, "strReq": 35,
"dexReq": 40, "dexReq": 35,
"intReq": 40, "intReq": 35,
"agiReq": 40, "agiReq": 35,
"defReq": 40, "defReq": 35,
"hprPct": 0, "hprPct": 0,
"mr": 0, "mr": 0,
"sdPct": 0, "sdPct": 0,
@ -276384,6 +276384,80 @@
"gXp": 0, "gXp": 0,
"gSpd": 0, "gSpd": 0,
"id": 10406 "id": 10406
},
{
"name": "Cursed Jackboots",
"tier": "Legendary",
"type": "boots",
"set": null,
"quest": "Lost Soles",
"poison": 0,
"thorns": 0,
"sprint": 0,
"category": "armor",
"slots": 2,
"drop": "never",
"hp": 1400,
"fDef": 0,
"wDef": 50,
"aDef": 0,
"tDef": 80,
"eDef": 0,
"lvl": 60,
"classReq": null,
"strReq": 0,
"dexReq": 0,
"intReq": 0,
"agiReq": 0,
"defReq": 0,
"hprPct": -20,
"mr": 0,
"sdPct": 0,
"mdPct": 0,
"ls": 0,
"ms": 2,
"xpb": 0,
"lb": 0,
"ref": 0,
"str": 0,
"dex": 7,
"int": 7,
"agi": 0,
"def": 0,
"expd": 0,
"spd": 0,
"atkTier": 0,
"hpBonus": 0,
"spRegen": 0,
"eSteal": 0,
"hprRaw": 0,
"sdRaw": 100,
"mdRaw": 0,
"fDamPct": 0,
"wDamPct": 10,
"aDamPct": 0,
"tDamPct": 10,
"eDamPct": 0,
"fDefPct": 0,
"wDefPct": 0,
"aDefPct": 0,
"tDefPct": 0,
"eDefPct": -35,
"spPct1": 0,
"spRaw1": 0,
"spPct2": 0,
"spRaw2": 0,
"spPct3": 0,
"spRaw3": 0,
"spPct4": 0,
"spRaw4": 0,
"rainbowRaw": 0,
"sprintReg": 0,
"jh": 0,
"lq": 0,
"gXp": 0,
"gSpd": 0,
"id": 10407
} }
], ],
"sets": { "sets": {
@ -276998,13 +277072,17 @@
"xpb": -5 "xpb": -5
}, },
{ {
"mr": 1, "hp": -325,
"sdPct": 10, "str": 0,
"xpb": 30, "dex": 0,
"expd": 10, "int": 0,
"hpBonus": 325, "def": 0,
"agi": 0,
"xpb": 25,
"spRegen": 10, "spRegen": 10,
"sdRaw": 90 "sdPct": 8,
"spPct1": -12,
"spPct3": -12
} }
] ]
}, },

File diff suppressed because one or more lines are too long

View file

@ -10,12 +10,12 @@ class Craft{
@param mat_tiers: [1->3, 1->3]. An array with 2 numbers. @param mat_tiers: [1->3, 1->3]. An array with 2 numbers.
@param ingreds: []. An array with 6 entries, each with an ingredient Map. @param ingreds: []. An array with 6 entries, each with an ingredient Map.
*/ */
constructor(recipe, mat_tiers, ingreds) { constructor(recipe, mat_tiers, ingreds, attackSpeed) {
this.recipe = recipe; this.recipe = recipe;
this.mat_tiers = mat_tiers; this.mat_tiers = mat_tiers;
this.ingreds = ingreds; this.ingreds = ingreds;
this.statMap = new Map(); //can use the statMap as an expanded Item this.statMap = new Map(); //can use the statMap as an expanded Item
this.atkSpd = attackSpeed;
this.initCraftStats(); this.initCraftStats();
} }
@ -83,13 +83,60 @@ class Craft{
} }
//statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]); //statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
statMap.set("category","weapon"); statMap.set("category","weapon");
statMap.set("atkSpd",this.atkSpd);
} }
statMap.set("powders",""); statMap.set("powders","");
/* Change certain IDs based on material tier. /* Change certain IDs based on material tier.
healthOrDamage changes. healthOrDamage changes.
duration and durability change. (but not basicDuration) duration and durability change. (but not basicDuration)
*/ */
let matmult = 1;
let sorted = this.mat_tiers.sort();
//TODO - idfk how this works
if( sorted[0] == 1 && sorted[1] == 1) {
matmult = 1;
} else if( sorted[0] == 1 && sorted[1] == 2) {
matmult = 1.079;
}else if( sorted[0] == 1 && sorted[1] == 3) {
matmult = 1.15;
}else if( sorted[0] == 2 && sorted[1] == 2) {
matmult = 1.24;
}else if( sorted[0] == 2 && sorted[1] == 3) {
matmult = 1.3;
}else if( sorted[0] == 3 && sorted[1] == 3) {
matmult = 1.4;
}
let low = this.recipe.get("healthOrDamage")[0];
let high = this.recipe.get("healthOrDamage")[1];
if (statMap.get("category") === "consumable") {
//duration modifier
if(statMap.has("hp")) { //hack
statMap.set("hp", Math.floor( low * matmult )+ "-" + Math.floor( high * matmult ));
} else {
statMap.set("duration", [Math.floor( statMap.get("duration")[0] * matmult ), Math.floor( statMap.get("duration")[1] * matmult )]);
}
} else {
//durability modifier
statMap.set("durability", [Math.floor( statMap.get("durability")[0] * matmult ), Math.floor( statMap.get("durability")[1] * matmult )]);
}
if (statMap.get("category") === "weapon") {
//attack damages oh boy
let ratio = 2.05; //UNSURE IF THIS IS HOW IT'S DONE. MIGHT BE DONE WITH SKETCHY FLOORING.
if (this.atkSpd === "SLOW") {
ratio /= 1.5;
} else if (this.atkSpd === "NORMAL") {
ratio = 1;
} else if (this.atkSpd = "FAST") {
ratio /= 2.5;
}
low *= ratio*matmult;
high *= ratio*matmult;
this.recipe.get("healthOrDamage")[0] = low;
this.recipe.get("healthOrDamage")[1] = high;
}
/* END SECTION */
//calc ingredient effectivenesses -> see https://wynndata.tk/cr/585765168 //calc ingredient effectivenesses -> see https://wynndata.tk/cr/585765168
let eff = [[100,100],[100,100],[100,100]]; let eff = [[100,100],[100,100],[100,100]];
@ -141,10 +188,6 @@ class Craft{
} }
} }
} }
//apply material tiers - the good thing is that this should be symmetric.
for (const mat of this.mat_tiers) {
}
//apply ingredient effectivness - on ids, and reqs (itemIDs). NOT on durability, duration, or charges. //apply ingredient effectivness - on ids, and reqs (itemIDs). NOT on durability, duration, or charges.
let eff_flat = eff.flat(); let eff_flat = eff.flat();

View file

@ -100,6 +100,26 @@
</td> </td>
</tr> </tr>
</table> </table>
<label for = "atkSpdChoices"> Attack Speed (weapons only):</label>
<table class = "center" id = "atkSpdChoices">
<tr>
<td class = "center">
<button class = "button" id = "slow-atk-button" onclick = "toggleAtkSpd('slow-atk-button')">
Slow
</button>
</td>
<td class = "center">
<button class = "button" id = "normal-atk-button" onclick = "toggleAtkSpd('normal-atk-button')">
Normal
</button>
</td>
<td class = "center">
<button class = "button" id = "fast-atk-button" onclick = "toggleAtkSpd('fast-atk-button')">
Fast
</button>
</td>
</tr>
</table>
<table class = "center"> <table class = "center">
<tr> <tr>
<td class = "center"> <td class = "center">
@ -125,12 +145,12 @@
</tr> </tr>
</table> </table>
</div> </div>
<div class = "recipe hide-container-block" style = "display:none"> <div class = "recipe center hide-container-block" style = "display:none">
<div class = "recipe-stats"> <div class = "recipe-stats">
<div class = "center" id = "recipe-stats"></div> <div class = "center" id = "recipe-stats"></div>
</div> </div>
</div> </div>
<div class = "crafted hide-container-block" style = "display:none"> <div class = "crafted center hide-container-block" style = "display:none">
<div class = "craft-stats"> <div class = "craft-stats">
<div class = "center" id = "craft-stats"></div> <div class = "center" id = "craft-stats"></div>
</div> </div>
@ -140,10 +160,8 @@
<div class = "craft-warnings"> <div class = "craft-warnings">
<div class = "center" id = "craft-warnings"></div> <div class = "center" id = "craft-warnings"></div>
</div> </div>
<p> <p></p>
</p> <p></p>
<p>
</p>
<div class="ingredients-container hide-container-grid" id = "ingreds" style = "display:none"> <div class="ingredients-container hide-container-grid" id = "ingreds" style = "display:none">
<p class="center title hide-container-block" style = "display:block"> <p class="center title hide-container-block" style = "display:block">
Ingredients Ingredients
@ -180,6 +198,7 @@
</div> </div>
<script type="text/javascript" src="utils.js"></script> <script type="text/javascript" src="utils.js"></script>
<script type="text/javascript" src="powders.js"></script>
<script type="text/javascript" src="build_utils.js"></script> <script type="text/javascript" src="build_utils.js"></script>
<script type="text/javascript" src="skillpoints.js"></script> <script type="text/javascript" src="skillpoints.js"></script>
<script type="text/javascript" src="damage_calc.js"></script> <script type="text/javascript" src="damage_calc.js"></script>

View file

@ -86,6 +86,18 @@ function updateMaterials() {
document.getElementById("mat-2").textContent = "Material 2 Tier:"; document.getElementById("mat-2").textContent = "Material 2 Tier:";
} }
} }
function toggleAtkSpd(buttonId) {
let buttons = ["slow-atk-button", "normal-atk-button", "fast-atk-button"];
let elem = document.getElementById(buttonId);
if (elem.classList.contains("toggleOn")) {
elem.classList.remove("toggleOn");
} else {
for (const button of buttons) {
document.getElementById(button).classList.remove("toggleOn");
}
elem.classList.add("toggleOn");
}
}
function calculateCraft() { function calculateCraft() {
//Make things display. //Make things display.
@ -117,9 +129,15 @@ function calculateCraft() {
for (i = 1; i < 7; i++) { for (i = 1; i < 7; i++) {
getValue("ing-choice-" + i) === "" ? ingreds.push(expandIngredient(ingMap.get("No Ingredient"))) : ingreds.push(expandIngredient(ingMap.get(getValue("ing-choice-" + i)))); getValue("ing-choice-" + i) === "" ? ingreds.push(expandIngredient(ingMap.get("No Ingredient"))) : ingreds.push(expandIngredient(ingMap.get(getValue("ing-choice-" + i))));
} }
let atkSpd = "NORMAL"; //default attack speed will be normal.
for (const b of ["slow-atk-button", "normal-atk-button", "fast-atk-button"]) {
button = document.getElementById(b);
if (button.classList.contains("toggleOn")) {
atkSpd = b.split("-")[0].toUpperCase();
}
}
//create the craft //create the craft
player_craft = new Craft(recipe,mat_tiers,ingreds); player_craft = new Craft(recipe,mat_tiers,ingreds,atkSpd);
console.log(player_craft); console.log(player_craft);
/*console.log(recipe) /*console.log(recipe)
console.log(levelrange) console.log(levelrange)
@ -130,6 +148,9 @@ function calculateCraft() {
//Display Recipe Stats //Display Recipe Stats
displayRecipeStats(player_craft, "recipe-stats"); displayRecipeStats(player_craft, "recipe-stats");
for(let i = 0; i < 6; i++) {
displayExpandedIngredient(player_craft["ingreds"][i],"tooltip-" + i);
}
//Display Craft Stats //Display Craft Stats
displayCraftStats(player_craft, "craft-stats"); displayCraftStats(player_craft, "craft-stats");
//Display Ingredients' Stats //Display Ingredients' Stats

View file

@ -6,13 +6,15 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
let buildStats = new Map(stats); let buildStats = new Map(stats);
if(externalStats) { //if nothing is passed in, then this hopefully won't trigger 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") { if (typeof value === "number") {
buildStats.set(key, buildStats.get(key) + value); buildStats.set(key, buildStats.get(key) + value);
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
arr = []; arr = [];
for (let i = 0; i < value.length; i++) { for (let j = 0; j < value.length; j++) {
arr[i] = buildStats.get(key)[i] + value[i]; arr[j] = buildStats.get(key)[j] + value[j];
} }
buildStats.set(key, arr); 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). // Array of neutral + ewtfa damages. Each entry is a pair (min, max).
let damages = []; let damages = [];
for (const damage_string of buildStats.get("damageRaw")) { const rawDamages = buildStats.get("damageRaw");
const damage_vals = damage_string.split("-").map(Number); for (let i = 0; i < rawDamages.length; i++) {
const damage_vals = rawDamages[i].split("-").map(Number);
damages.push(damage_vals); damages.push(damage_vals);
} }
@ -40,10 +43,11 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
} }
//console.log(damages); //console.log(damages);
let rawBoosts = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]; let rawBoosts = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]];
for (const powderID of weapon.get("powders")) { const powders = weapon.get("powders");
const powder = powderStats[powderID]; for (let i = 0; i < powders.length; i++) {
const powder = powderStats[powders[i]];
// Bitwise to force conversion to integer (integer division). // Bitwise to force conversion to integer (integer division).
const element = (powderID/6) | 0; const element = (powders[i]/6) | 0;
let conversionRatio = powder.convert/100; let conversionRatio = powder.convert/100;
if (neutralRemainingRaw[1] > 0) { if (neutralRemainingRaw[1] > 0) {
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]); let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
@ -146,7 +150,7 @@ const spell_table = {
{ title: "Charge", cost: 4, parts: [ { title: "Charge", cost: 4, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 0, 0, 40, 0], summary: true }, { subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 0, 0, 40, 0], summary: true },
] }, ] },
{ title: "Uppercut", cost: 10, parts: [ { title: "Uppercut", cost: 9, parts: [
{ subtitle: "First Damage", type: "damage", multiplier: 300, conversion: [70, 20, 10, 0, 0, 0] }, { 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] }, { subtitle: "Fireworks Damage", type: "damage", multiplier: 50, conversion: [60, 0, 40, 0, 0, 0] },
{ subtitle: "Crash Damage", type: "damage", multiplier: 50, conversion: [80, 0, 20, 0, 0, 0] }, { subtitle: "Crash Damage", type: "damage", multiplier: 50, conversion: [80, 0, 20, 0, 0, 0] },
@ -177,7 +181,7 @@ const spell_table = {
{ title: "Spin Attack", cost: 6, parts: [ { title: "Spin Attack", cost: 6, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [70, 0, 30, 0, 0, 0], summary: true}, { subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [70, 0, 30, 0, 0, 0], summary: true},
] }, ] },
{ title: "Vanish", cost: 1, parts: [ { title: "Vanish", cost: 2, parts: [
{ subtitle: "No Damage", type: "none", summary: true } { subtitle: "No Damage", type: "none", summary: true }
] }, ] },
{ title: "Multihit", cost: 8, parts: [ { title: "Multihit", cost: 8, parts: [
@ -194,7 +198,7 @@ const spell_table = {
{ title: "Totem", cost: 4, parts: [ { title: "Totem", cost: 4, parts: [
{ subtitle: "Smash Damage", type: "damage", multiplier: 100, conversion: [80, 0, 0, 0, 20, 0]}, { subtitle: "Smash Damage", type: "damage", multiplier: 100, conversion: [80, 0, 0, 0, 20, 0]},
{ subtitle: "Damage Tick", type: "damage", multiplier: 20, conversion: [80, 0, 0, 0, 0, 20]}, { subtitle: "Damage Tick", type: "damage", multiplier: 20, conversion: [80, 0, 0, 0, 0, 20]},
{ subtitle: "Heal Tick", type: "heal", strength: 0.04, summary: true }, { subtitle: "Heal Tick", type: "heal", strength: 0.03, summary: true },
] }, ] },
{ title: "Haul", cost: 1, parts: [ { title: "Haul", cost: 1, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [80, 0, 20, 0, 0, 0], summary: true }, { subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [80, 0, 20, 0, 0, 0], summary: true },
@ -203,7 +207,7 @@ const spell_table = {
{ subtitle: "One Wave", type: "damage", multiplier: 200, conversion: [70, 0, 0, 30, 0, 0], summary: true }, { subtitle: "One Wave", type: "damage", multiplier: 200, conversion: [70, 0, 0, 30, 0, 0], summary: true },
] }, ] },
{ title: "Uproot", cost: 6, parts: [ { title: "Uproot", cost: 6, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 50, conversion: [70, 30, 0, 0, 0, 0], summary: true }, { subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [70, 30, 0, 0, 0, 0], summary: true },
] }, ] },
], ],
"powder": [ //This is how instant-damage powder specials are implemented. To view time-boosted damage powder specials (curse, 2nd courage, air prison, view @TODO) "powder": [ //This is how instant-damage powder specials are implemented. To view time-boosted damage powder specials (curse, 2nd courage, air prison, view @TODO)

View file

@ -249,6 +249,7 @@ function displayBuildStats(parent_id,build){
"#table", "#table",
"str", "dex", "int", "def", "agi", "str", "dex", "int", "def", "agi",
"mr", "ms", "mr", "ms",
"hprRaw", "hprPct",
"sdRaw", "sdPct", "sdRaw", "sdPct",
"mdRaw", "mdPct", "mdRaw", "mdPct",
"ref", "thorns", "ref", "thorns",
@ -419,7 +420,7 @@ function displayExpandedItem(item, parent_id){
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined); let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
let damages = results[2]; let damages = results[2];
let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
for (const i in damage_keys) { for (let i = 0; i < damage_keys.length; i++) {
item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]); item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]);
} }
} }
@ -482,7 +483,8 @@ function displayExpandedItem(item, parent_id){
let active_elem; let active_elem;
let fix_id = item.has("fixID") && item.get("fixID"); let fix_id = item.has("fixID") && item.get("fixID");
let elemental_format = false; 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.charAt(0) === "#") {
if (command === "#cdiv") { if (command === "#cdiv") {
active_elem = document.createElement('div'); active_elem = document.createElement('div');
@ -695,12 +697,12 @@ function displayExpandedItem(item, parent_id){
effects = powderSpecial["armorSpecialEffects"]; effects = powderSpecial["armorSpecialEffects"];
specialTitle.textContent += powderSpecial["armorSpecialName"] + ": "; specialTitle.textContent += powderSpecial["armorSpecialName"] + ": ";
} }
for (const [key,value] of effects) { for (let i = 0; i < effects.length; i++) {
if (key !== "Description") { if (effects[i][0] !== "Description") {
let effect = document.createElement("p"); let effect = document.createElement("p");
effect.classList.add("itemp"); effect.classList.add("itemp");
effect.textContent += key + ": " + value[power] + specialSuffixes.get(key); effect.textContent += effects[i][0] + ": " + effects[i][1][power] + specialSuffixes.get(effects[i][0]);
if(key === "Damage"){ if(effects[i][0] === "Damage"){
effect.textContent += elementIcons[skp_elements.indexOf(element)]; effect.textContent += elementIcons[skp_elements.indexOf(element)];
} }
if (element === "w") { if (element === "w") {
@ -802,7 +804,7 @@ function displayRecipeStats(craft, parent_id) {
let ingredTable = document.createElement("table"); let ingredTable = document.createElement("table");
ingredTable.classList.add("itemtable"); ingredTable.classList.add("itemtable");
ingredTable.style.tableLayout = "fixed"; ingredTable.classList.add("ingredTable");
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
let row = document.createElement("tr"); let row = document.createElement("tr");
for (let j = 0; j < 2; j++) { for (let j = 0; j < 2; j++) {
@ -811,6 +813,7 @@ function displayRecipeStats(craft, parent_id) {
cell.style.width = "50%"; cell.style.width = "50%";
cell.classList.add("center"); cell.classList.add("center");
cell.classList.add("box"); cell.classList.add("box");
cell.classList.add("tooltip");
let b = document.createElement("b"); let b = document.createElement("b");
b.textContent = ingredName; b.textContent = ingredName;
b.classList.add("space"); b.classList.add("space");
@ -825,10 +828,15 @@ function displayRecipeStats(craft, parent_id) {
cell.appendChild(b); cell.appendChild(b);
cell.appendChild(eff); cell.appendChild(eff);
row.appendChild(cell); row.appendChild(cell);
let tooltip = document.createElement("div");
tooltip.classList.add("tooltiptext");
tooltip.classList.add("center");
tooltip.id = "tooltip-" + (2*i + j);
cell.appendChild(tooltip);
} }
ingredTable.appendChild(row); ingredTable.appendChild(row);
} }
elem.appendChild(ldiv); elem.appendChild(ldiv);
elem.appendChild(ingredTable); elem.appendChild(ingredTable);
} }
@ -841,7 +849,6 @@ function displayCraftStats(craft, parent_id) {
//Displays an ingredient in item format. However, an ingredient is too far from a normal item to display as one. //Displays an ingredient in item format. However, an ingredient is too far from a normal item to display as one.
function displayExpandedIngredient(ingred, parent_id) { function displayExpandedIngredient(ingred, parent_id) {
console.log(ingred);
let parent_elem = document.getElementById(parent_id); let parent_elem = document.getElementById(parent_id);
parent_elem.textContent = ""; parent_elem.textContent = "";
let display_order = [ let display_order = [
@ -1875,7 +1882,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell
let overallhealLabel = document.createElement("p"); let overallhealLabel = document.createElement("p");
let first = document.createElement("b"); let first = document.createElement("b");
let second = document.createElement("b"); let second = document.createElement("b");
first.textContent = part.subtitle + ":"; first.textContent = part.subtitle + ": ";
second.textContent = heal_amount; second.textContent = heal_amount;
overallhealLabel.appendChild(first); overallhealLabel.appendChild(first);
second.classList.add("Set"); second.classList.add("Set");

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

53
items.html Normal file
View file

@ -0,0 +1,53 @@
<!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">
<div class="header" id="header">
WynnAtlas
</div>
<div class="center" id="header2">
Made by: hppeng and ferricles
</div>
<div class="center" id="credits">
<a href="credits.txt">Additional credits</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="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.js"></script>
<script type="text/javascript" src="load.js"></script>
<script type="text/javascript" src="items.js"></script>
</body>
</html>

193
items.js Normal file
View 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);

19
load.js
View file

@ -1,4 +1,4 @@
const DB_VERSION = 24; const DB_VERSION = 27;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.js // @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.js
let db; let db;
@ -35,7 +35,7 @@ async function load_local(init_func) {
} }
else { else {
console.log("Successfully read local set db."); console.log("Successfully read local set db.");
console.log(sets); //console.log(sets);
init_func(); init_func();
} }
} }
@ -48,12 +48,14 @@ async function load_local(init_func) {
* Clean bad item data. For now just assigns display name if it isn't already assigned. * Clean bad item data. For now just assigns display name if it isn't already assigned.
*/ */
function clean_item(item) { function clean_item(item) {
if (item.displayName === undefined) { if (item.remapID === undefined) {
item.displayName = item.name; if (item.displayName === undefined) {
item.displayName = item.name;
}
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];
} }
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];
} }
/* /*
@ -77,7 +79,8 @@ async function load(init_func) {
await clear_tx.complete; await clear_tx.complete;
let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite'); let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite');
add_tx.onabort = function() { add_tx.onabort = function(e) {
console.log(e);
console.log("Not enough space..."); console.log("Not enough space...");
}; };
let items_store = add_tx.objectStore('item_db'); let items_store = add_tx.objectStore('item_db');

View file

@ -6,6 +6,14 @@
grid-column: 2; grid-column: 2;
grid-row: 2; grid-row: 2;
} }
.equipment{
padding: 0%;
display: grid;
grid-template-columns: min-content min-content auto;
gap: 5px;
grid-template-rows: min-content min-content auto;
}
.summary { .summary {
padding: 2% 4% 4%; padding: 2% 4% 4%;
display: grid; display: grid;
@ -17,6 +25,9 @@
} }
.ingredTable {
table-layout: "fixed";
}
.build, .spells .misc { .build, .spells .misc {
padding: 2%; padding: 2%;
display: grid; display: grid;
@ -33,6 +44,12 @@
max-height: 50px; max-height: 50px;
} }
.powderinput {
width: 25vw;
height: 10vw;
max-height: 50px;
}
.skpInput, .idInput { .skpInput, .idInput {
width: 90%; width: 90%;
height: 7vw; height: 7vw;
@ -44,7 +61,7 @@
} }
.idLabel, .skpLabel, .idDesc, .skpDesc { .idLabel, .skpLabel, .idDesc, .skpDesc {
font-size: 100%; font-size: 90%;
} }
.title{ .title{

52
powders.js Normal file
View file

@ -0,0 +1,52 @@
let powderIDs = new Map();
let powderNames = new Map();
let _powderID = 0;
for (const x of skp_elements) {
for (let i = 1; i <= 6; ++i) {
// Support both upper and lowercase, I guess.
powderIDs.set(x.toUpperCase()+i, _powderID);
powderIDs.set(x+i, _powderID);
powderNames.set(_powderID, x+i);
_powderID++;
}
}
// Ordering: [dmgMin, dmgMax, convert, defPlus, defMinus (+6 mod 5)]
class Powder {
constructor(min, max, convert, defPlus, defMinus) {
this.min = min;
this.max = max;
this.convert = convert;
this.defPlus = defPlus;
this.defMinus = defMinus;
}
}
function _p(a,b,c,d,e) { return new Powder(a,b,c,d,e); } //bruh moment
let powderStats = [
_p(3,6,17,2,1), _p(6,9,21,4,2), _p(8,14,25,8,3), _p(11,16,31,14,5), _p(15,18,38,22,9), _p(18,22,46,30,13),
_p(1,8,9,3,1), _p(1,13,11,5,1), _p(2,18,14,9,2), _p(3,24,17,14,4), _p(3,32,22,20,7), _p(5,40,28,28,10),
_p(3,4,13,3,1), _p(4,7,15,6,1), _p(6,10,17,11,2), _p(8,12,21,18,4), _p(11,14,26,28,7), _p(13,17,32,40,10),
_p(2,5,14,3,1), _p(4,8,16,5,2), _p(6,10,19,9,3), _p(9,13,24,16,5), _p(12,16,30,25,9), _p(15,19,37,36,13),
_p(2,6,11,3,1), _p(4,9,14,6,2), _p(7,10,17,10,3), _p(9,13,22,16,5), _p(13,18,28,24,9), _p(16,18,35,34,13)
];
//Ordering: [weapon special name, weapon special effects, armor special name, armor special effects]
class PowderSpecial{
constructor(wSpName, wSpEff, aSpName, aSpEff, cap){
this.weaponSpecialName = wSpName;
this.weaponSpecialEffects = wSpEff;
this.armorSpecialName = aSpName;
this.armorSpecialEffects = aSpEff;
this.cap = cap;
}
}
function _ps(a,b,c,d,e) { return new PowderSpecial(a,b,c,d,e); } //bruh moment
let powderSpecialStats = [
_ps("Quake",new Map([["Radius",[5,5.5,6,6.5,7]], ["Damage",[155,220,285,350,415]] ]),"Rage",new Map([ ["Damage", [0.3,0.4,0.5,0.7,1.0]],["Description", "% " + "\u2764" + " Missing"] ]),400), //e
_ps("Chain Lightning",new Map([ ["Chains", [5,6,7,8,9]], ["Damage", [200,225,250,275,300]] ]),"Kill Streak",new Map([ ["Damage", [3,4.5,6,7.5,9]],["Duration", [5,5,5,5,5]],["Description", "Mob Killed"] ]),200), //t
_ps("Curse",new Map([ ["Duration", [7,7.5,8,8.5,9]],["Damage Boost", [90,120,150,180,210]] ]),"Concentration",new Map([ ["Damage", [1,2,3,4,5]],["Duration",[1,1,1,1,1]],["Description", "Mana Used"] ]),150), //w
_ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f
_ps("Air Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [400,450,500,550,600]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a
];

582
query.js Normal file
View file

@ -0,0 +1,582 @@
/*
* disj := conj "|" disj
* | conj
*
* conj := cmp "&" conj
* | cmpEq
*
* cmpEq := cmpRel "=" cmpEq
* | 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;
}
// 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
};
}
}
}
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);
};
})();

View file

@ -24,14 +24,6 @@ div {
padding: 0%; padding: 0%;
} }
.equipment{
padding: 0%;
display: grid;
grid-template-columns: min-content min-content min-content;
gap: 5px;
grid-template-rows: min-content min-content auto;
}
.skillpoints{ .skillpoints{
padding: 0% 4% 2%; padding: 0% 4% 2%;
display: grid; display: grid;
@ -92,13 +84,23 @@ table.center{
text-align: left; text-align: left;
} }
.build-helmet, .build-chestplate, .build-leggings, .build-boots, .build-ring1, .build-ring2, .build-bracelet, .build-necklace, .build-weapon, .build-order, .build-overall, .build-melee-stats, .build-defense-stats, .spell-info, .set-info, .powder-special, .powder-special-stats, .int-info, .id-box, .box { .build-helmet, .build-chestplate, .build-leggings, .build-boots, .build-ring1, .build-ring2, .build-bracelet, .build-necklace, .build-weapon, .build-order, .build-overall, .build-melee-stats, .build-defense-stats, .spell-info, .set-info, .powder-special, .powder-special-stats, .int-info, .box {
color: #aaa; color: #aaa;
background: #121516; background: #121516;
border: 3px solid #BCBCBC; border: 3px solid #BCBCBC;
border-radius: 3px; border-radius: 3px;
width: 96%; width: 96%;
} }
.id-box {
color: #aaa;
background: #121516;
border: 3px solid #BCBCBC;
border-radius: 3px;
width: 96%;
margin: 1em 0px 0px 0px;
}
.crafter, .recipe-stats, .craft-stats, .ing-stats { .crafter, .recipe-stats, .craft-stats, .ing-stats {
color: #aaa; color: #aaa;
background: #121516; background: #121516;
@ -131,6 +133,12 @@ table.center{
margin: 2px 2%; margin: 2px 2%;
padding: 0; padding: 0;
} }
.powderLeft {
margin-right: 0px;
}
.powderRight {
margin-left: 0px;
}
.space { .space {
margin-right: 5px; margin-right: 5px;
} }
@ -387,3 +395,24 @@ button.toggleOn:hover {
color: #ff0; color: #ff0;
background: #775; background: #775;
} }
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
color: #aaa;
background: #110110;
width: min(200%, 75vw);
text-align: center;
border: 5px solid #BCBCBC;
border-radius: 10px;
padding: 5px 0;
position: absolute;
z-index: 1;
}
.recipe {
z-index: 1;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}

80
update_merge.py Normal file
View file

@ -0,0 +1,80 @@
import json
with open("clean.json") as infile:
olds = json.load(infile)
items = olds["items"]
item_oldnames_map = dict()
item_newnames_map = dict()
VERSION_STR = " (1.20)"
max_old_id = 0
for item in items:
item_id = item["id"]
if "displayName" in item:
displayName = item["displayName"]
else:
displayName = item["name"]
item_name = displayName.replace(VERSION_STR, "")
if item_id > 10000:
map_name = item["name"].replace(VERSION_STR, "")
item_newnames_map[map_name] = item
item["displayName"] = item_name
else:
item_oldnames_map[item_name] = item
if item_id > max_old_id:
max_old_id = item_id
dummy_items = []
for (name, item) in item_newnames_map.items():
if name in item_oldnames_map:
old_item = item_oldnames_map[name]
if "displayName" in item:
displayName = item["displayName"].replace(VERSION_STR, "")
else:
displayName = name
save_old = ["id","set","quest","drop","restrict", "name"]
old_mappings = { k: old_item[k] for k in save_old if k in old_item }
old_item.clear()
if "restrict" in item:
del item["restrict"]
for k in item:
old_item[k] = item[k]
for k in old_mappings:
old_item[k] = old_mappings[k]
save_id = item["id"]
item.clear()
item["id"] = save_id
item["name"] = str(save_id)
item["remapID"] = old_item["id"]
else:
if "restrict" in item:
in_str = input(name + " restriction: ").strip()
if in_str:
item["restrict"] = in_str
else:
del item["restrict"]
item["name"] = name
dummy_item = dict()
dummy_item["id"] = item["id"]
max_old_id += 1
item["id"] = max_old_id
dummy_item["remapID"] = item["id"]
dummy_items.append(dummy_item)
items.extend(dummy_items)
sets = olds["sets"]
data = dict()
data["items"] = items
data["sets"] = sets
with open("updated.json", "w") as outfile:
json.dump(data, outfile, indent=2)

260617
updated.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,15 @@
.spell-info-container { .spell-info-container {
grid-column:4; grid-column:4;
} }
.equipment{
padding: 0%;
display: grid;
grid-template-columns: repeat(3, 1fr);
width: 50vw;
gap: 5px;
grid-template-rows: min-content min-content auto;
}
.sticky-box { .sticky-box {
position: sticky; position: sticky;
top: 0; top: 0;
@ -51,9 +60,9 @@
} }
.skpInput, .idInput { .skpInput, .idInput {
width: 90%; width: 95%;
height: 7vw; height: 7vw;
max-height: 30px; max-height: 20px;
} }
.wide-space { .wide-space {