Merge pull request #8 from phantamanta44/atlas-dual-filter
Segregated-filter expression-based item search
This commit is contained in:
commit
7047212e31
22 changed files with 262399 additions and 623 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
*.swp
|
||||
*.bat
|
||||
sets/
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
|
|
6
build.js
6
build.js
|
@ -133,9 +133,9 @@ class Build{
|
|||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
}else{
|
||||
const chestplate = itemMap.get("No Leggings");
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
const leggings = itemMap.get("No Leggings");
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
errors.push(new ItemNotFound(equipment[2], "leggings", true));
|
||||
}
|
||||
if(itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") {
|
||||
|
|
96
builder.js
96
builder.js
|
@ -3,11 +3,18 @@ const url_tag = location.hash.slice(1);
|
|||
console.log(url_base);
|
||||
console.log(url_tag);
|
||||
|
||||
const BUILD_VERSION = "6.9.7";
|
||||
const BUILD_VERSION = "6.9.9";
|
||||
|
||||
function setTitle() {
|
||||
document.getElementById("header").textContent = "WynnBuilder version "+BUILD_VERSION+" (db version "+DB_VERSION+")";
|
||||
document.getElementById("header").classList.add("funnynumber");
|
||||
let text;
|
||||
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();
|
||||
|
@ -71,18 +78,6 @@ let equipment_names = [
|
|||
let equipmentInputs = equipment_fields.map(x => x + "-choice");
|
||||
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 = [
|
||||
"helmet-powder",
|
||||
"chestplate-powder",
|
||||
|
@ -90,45 +85,6 @@ let powderInputs = [
|
|||
"boots-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 itemLists = new Map();
|
||||
|
@ -138,6 +94,7 @@ for (const it of itemTypes) {
|
|||
let itemMap = new Map();
|
||||
/* Mapping from item names to set names. */
|
||||
let idMap = new Map();
|
||||
let redirectMap = new Map();
|
||||
|
||||
/*
|
||||
* Function that takes an item list and populates its corresponding dropdown.
|
||||
|
@ -194,13 +151,18 @@ function init() {
|
|||
items = items.concat(noneItems);
|
||||
console.log(items);
|
||||
for (const item of items) {
|
||||
itemLists.get(item.type).push(item.displayName);
|
||||
itemMap.set(item.displayName, item);
|
||||
if (noneItems.includes(item)) {
|
||||
idMap.set(item.id, "");
|
||||
if (item.remapID === undefined) {
|
||||
itemLists.get(item.type).push(item.displayName);
|
||||
itemMap.set(item.displayName, item);
|
||||
if (noneItems.includes(item)) {
|
||||
idMap.set(item.id, "");
|
||||
}
|
||||
else {
|
||||
idMap.set(item.id, item.displayName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
idMap.set(item.id, item.displayName);
|
||||
redirectMap.set(item.id, item.remapID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,6 +217,13 @@ function init() {
|
|||
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.
|
||||
*/
|
||||
|
@ -270,7 +239,7 @@ 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] = 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") {
|
||||
|
@ -767,7 +736,8 @@ function calculateBuildStats() {
|
|||
//skpRow.classList.add("left");
|
||||
let td = document.createElement("p");
|
||||
//td.classList.add("left");
|
||||
let skpSummary = document.createElement("b");
|
||||
|
||||
/* let skpSummary = document.createElement("b");
|
||||
skpSummary.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Total: (";
|
||||
//skpSummary.classList.add("itemp");
|
||||
td.appendChild(skpSummary);
|
||||
|
@ -786,12 +756,12 @@ function calculateBuildStats() {
|
|||
let skpEnd = document.createElement("b");
|
||||
skpEnd.textContent = ")";
|
||||
td.appendChild(skpEnd);
|
||||
skpRow.append(td);
|
||||
skpRow.append(td); */
|
||||
|
||||
let remainingSkp = document.createElement("p");
|
||||
remainingSkp.classList.add("center");
|
||||
let remainingSkpTitle = document.createElement("b");
|
||||
remainingSkpTitle.textContent = "Remaining skillpoints: ";
|
||||
remainingSkpTitle.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Remaining skillpoints: ";
|
||||
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);
|
||||
|
|
198
clean.json
198
clean.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "Demon Tide (1.20)",
|
||||
"name": "Demon Tide",
|
||||
"tier": "Legendary",
|
||||
"type": "leggings",
|
||||
"set": null,
|
||||
|
@ -35506,6 +35506,7 @@
|
|||
},
|
||||
{
|
||||
"name": "Cancer\u058e",
|
||||
"displayName": "Cancer",
|
||||
"tier": "Legendary",
|
||||
"type": "helmet",
|
||||
"set": null,
|
||||
|
@ -265939,8 +265940,8 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "boots",
|
||||
"name": "Ensa\u2019s Ideals (1.20)",
|
||||
"displayName": "Ensa\u2019s Ideals (1.20)",
|
||||
"name": "Ensa's Ideals (1.20)",
|
||||
"displayName": "Ensa's Ideals (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -266378,8 +266379,8 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "ring",
|
||||
"name": "Tisaun\u2019s Honor (1.20)",
|
||||
"displayName": "Tisaun\u2019s Honor (1.20)",
|
||||
"name": "Tisaun's Honor (1.20)",
|
||||
"displayName": "Tisaun's Honor (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -266718,8 +266719,8 @@
|
|||
{
|
||||
"tier": "Unique",
|
||||
"type": "chestplate",
|
||||
"name": "Nether\u2019s Reach (1.20)",
|
||||
"displayName": "Nether\u2019s Reach (1.20)",
|
||||
"name": "Nether's Reach (1.20)",
|
||||
"displayName": "Nether's Reach (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -266792,8 +266793,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "dagger",
|
||||
"name": "Bob\u2019s Mythic Daggers (1.20)",
|
||||
"displayName": "Bob\u2019s Mythic Daggers (1.20)",
|
||||
"name": "Bob's Mythic Daggers (1.20)",
|
||||
"displayName": "Bob's Mythic Daggers (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -267206,8 +267207,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "spear",
|
||||
"name": "Bob\u2019s Mythic Spear (1.20)",
|
||||
"displayName": "Bob\u2019s Mythic Spear (1.20)",
|
||||
"name": "Bob's Mythic Spear (1.20)",
|
||||
"displayName": "Bob's Mythic Spear (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -267445,8 +267446,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "chestplate",
|
||||
"name": "Terra\u2019s Mold (1.20)",
|
||||
"displayName": "Terra\u2019s Mold (1.20)",
|
||||
"name": "Terra's Mold (1.20)",
|
||||
"displayName": "Terra's Mold (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -267900,8 +267901,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "ring",
|
||||
"name": "Old Keeper\u2019s Ring (1.20)",
|
||||
"displayName": "Old Keeper\u2019s Ring (1.20)",
|
||||
"name": "Old Keeper's Ring (1.20)",
|
||||
"displayName": "Old Keeper's Ring (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -268068,7 +268069,7 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "leggings",
|
||||
"name": "Paradox (1.20)",
|
||||
"name": "One For All",
|
||||
"displayName": "Paradox (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
|
@ -268272,7 +268273,7 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "boots",
|
||||
"name": "Dragon Dance (1.20)",
|
||||
"name": "Paradox",
|
||||
"displayName": "Dragon Dance (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
|
@ -268560,8 +268561,8 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "leggings",
|
||||
"name": "Lion\u2019s Pelt (1.20)",
|
||||
"displayName": "Lion\u2019s Pelt (1.20)",
|
||||
"name": "Lion's Pelt (1.20)",
|
||||
"displayName": "Lion's Pelt (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -269385,8 +269386,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "chestplate",
|
||||
"name": "Tisaun\u2019s Valor (1.20)",
|
||||
"displayName": "Tisaun\u2019s Valor (1.20)",
|
||||
"name": "Tisaun's Valor (1.20)",
|
||||
"displayName": "Tisaun's Valor (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -269965,8 +269966,8 @@
|
|||
{
|
||||
"tier": "Unique",
|
||||
"type": "helmet",
|
||||
"name": "Sano\u2019s Care (1.20)",
|
||||
"displayName": "Sano\u2019s Care (1.20)",
|
||||
"name": "Sano's Care (1.20)",
|
||||
"displayName": "Sano's Care (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -270772,12 +270773,11 @@
|
|||
{
|
||||
"tier": "Set",
|
||||
"type": "ring",
|
||||
"name": "Black Catalyst Set (1.20)",
|
||||
"displayName": "Black Catalyst Set (1.20)",
|
||||
"name": "Black Catalyst Set",
|
||||
"displayName": "Black Catalyst Set (DEPRECATED!!!)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
"restrict": "1.20 item",
|
||||
"fixID": true,
|
||||
"strReq": 0,
|
||||
"dexReq": 0,
|
||||
|
@ -272002,8 +272002,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "boots",
|
||||
"name": "Nether\u2019s Scar (1.20)",
|
||||
"displayName": "Nether\u2019s Scar (1.20)",
|
||||
"name": "Nether's Scar (1.20)",
|
||||
"displayName": "Nether's Scar (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -272146,8 +272146,8 @@
|
|||
{
|
||||
"tier": "Unique",
|
||||
"type": "leggings",
|
||||
"name": "Bantisu\u2019s Approach (1.20)",
|
||||
"displayName": "Bantisu\u2019s Approach (1.20)",
|
||||
"name": "Bantisu's Approach (1.20)",
|
||||
"displayName": "Bantisu's Approach (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -272406,8 +272406,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "helmet",
|
||||
"name": "Gale\u2019s Sight (1.20)",
|
||||
"displayName": "Gale\u2019s Sight (1.20)",
|
||||
"name": "Gale's Sight (1.20)",
|
||||
"displayName": "Gale's Sight (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -272584,8 +272584,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "bow",
|
||||
"name": "Bob\u2019s Mythic Bow (1.20)",
|
||||
"displayName": "Bob\u2019s Mythic Bow (1.20)",
|
||||
"name": "Bob's Mythic Bow (1.20)",
|
||||
"displayName": "Bob's Mythic Bow (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -272824,8 +272824,8 @@
|
|||
{
|
||||
"tier": "Unique",
|
||||
"type": "chestplate",
|
||||
"name": "Ahm\u2019s Remains (1.20)",
|
||||
"displayName": "Ahm\u2019s Remains (1.20)",
|
||||
"name": "Ahm's Remains (1.20)",
|
||||
"displayName": "Ahm's Remains (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -273249,8 +273249,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "wand",
|
||||
"name": "Bob\u2019s Mythic Wand (1.20)",
|
||||
"displayName": "Bob\u2019s Mythic Wand (1.20)",
|
||||
"name": "Bob's Mythic Wand (1.20)",
|
||||
"displayName": "Bob's Mythic Wand (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -273535,8 +273535,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "boots",
|
||||
"name": "Cerid\u2019s Ingenuity (1.20)",
|
||||
"displayName": "Cerid\u2019s Ingenuity (1.20)",
|
||||
"name": "Cerid's Ingenuity (1.20)",
|
||||
"displayName": "Cerid's Ingenuity (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -273845,8 +273845,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "leggings",
|
||||
"name": "Ohms\u2019 Rage (1.20)",
|
||||
"displayName": "Ohms\u2019 Rage (1.20)",
|
||||
"name": "Ohms' Rage (1.20)",
|
||||
"displayName": "Ohms' Rage (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -274024,8 +274024,8 @@
|
|||
{
|
||||
"tier": "Legendary",
|
||||
"type": "necklace",
|
||||
"name": "Ensa\u2019s Faith (1.20)",
|
||||
"displayName": "Ensa\u2019s Faith (1.20)",
|
||||
"name": "Ensa's Faith (1.20)",
|
||||
"displayName": "Ensa's Faith (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -274798,8 +274798,8 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "leggings",
|
||||
"name": "Rodoroc\u2019s Guard (1.20)",
|
||||
"displayName": "Rodoroc\u2019s Guard (1.20)",
|
||||
"name": "Rodoroc's Guard (1.20)",
|
||||
"displayName": "Rodoroc's Guard (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -275277,7 +275277,7 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "boots",
|
||||
"name": "Weatherwalkers (1.20)",
|
||||
"name": "All For One",
|
||||
"displayName": "Weatherwalkers (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
|
@ -275654,8 +275654,8 @@
|
|||
{
|
||||
"tier": "Rare",
|
||||
"type": "dagger",
|
||||
"name": "Skien\u2019s Paranoia (1.20)",
|
||||
"displayName": "Skien\u2019s Paranoia (1.20)",
|
||||
"name": "Skien's Paranoia (1.20)",
|
||||
"displayName": "Skien's Paranoia (1.20)",
|
||||
"set": null,
|
||||
"quest": null,
|
||||
"classReq": null,
|
||||
|
@ -276257,11 +276257,11 @@
|
|||
"atkSpd": "SLOW",
|
||||
"lvl": 97,
|
||||
"classReq": null,
|
||||
"strReq": 40,
|
||||
"dexReq": 40,
|
||||
"intReq": 40,
|
||||
"agiReq": 40,
|
||||
"defReq": 40,
|
||||
"strReq": 35,
|
||||
"dexReq": 35,
|
||||
"intReq": 35,
|
||||
"agiReq": 35,
|
||||
"defReq": 35,
|
||||
"hprPct": 0,
|
||||
"mr": 0,
|
||||
"sdPct": 0,
|
||||
|
@ -276384,6 +276384,80 @@
|
|||
"gXp": 0,
|
||||
"gSpd": 0,
|
||||
"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": {
|
||||
|
@ -276998,13 +277072,17 @@
|
|||
"xpb": -5
|
||||
},
|
||||
{
|
||||
"mr": 1,
|
||||
"sdPct": 10,
|
||||
"xpb": 30,
|
||||
"expd": 10,
|
||||
"hpBonus": 325,
|
||||
"hp": -325,
|
||||
"str": 0,
|
||||
"dex": 0,
|
||||
"int": 0,
|
||||
"def": 0,
|
||||
"agi": 0,
|
||||
"xpb": 25,
|
||||
"spRegen": 10,
|
||||
"sdRaw": 90
|
||||
"sdPct": 8,
|
||||
"spPct1": -12,
|
||||
"spPct3": -12
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
File diff suppressed because one or more lines are too long
55
craft.js
55
craft.js
|
@ -10,12 +10,12 @@ class Craft{
|
|||
@param mat_tiers: [1->3, 1->3]. An array with 2 numbers.
|
||||
@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.mat_tiers = mat_tiers;
|
||||
this.ingreds = ingreds;
|
||||
this.statMap = new Map(); //can use the statMap as an expanded Item
|
||||
|
||||
this.atkSpd = attackSpeed;
|
||||
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("category","weapon");
|
||||
statMap.set("atkSpd",this.atkSpd);
|
||||
}
|
||||
statMap.set("powders","");
|
||||
|
||||
/* Change certain IDs based on material tier.
|
||||
healthOrDamage changes.
|
||||
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
|
||||
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.
|
||||
let eff_flat = eff.flat();
|
||||
|
|
31
crafter.html
31
crafter.html
|
@ -100,6 +100,26 @@
|
|||
</td>
|
||||
</tr>
|
||||
</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">
|
||||
<tr>
|
||||
<td class = "center">
|
||||
|
@ -125,12 +145,12 @@
|
|||
</tr>
|
||||
</table>
|
||||
</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 = "center" id = "recipe-stats"></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 = "center" id = "craft-stats"></div>
|
||||
</div>
|
||||
|
@ -140,10 +160,8 @@
|
|||
<div class = "craft-warnings">
|
||||
<div class = "center" id = "craft-warnings"></div>
|
||||
</div>
|
||||
<p>
|
||||
</p>
|
||||
<p>
|
||||
</p>
|
||||
<p></p>
|
||||
<p></p>
|
||||
<div class="ingredients-container hide-container-grid" id = "ingreds" style = "display:none">
|
||||
<p class="center title hide-container-block" style = "display:block">
|
||||
Ingredients
|
||||
|
@ -180,6 +198,7 @@
|
|||
</div>
|
||||
|
||||
<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="skillpoints.js"></script>
|
||||
<script type="text/javascript" src="damage_calc.js"></script>
|
||||
|
|
25
crafter.js
25
crafter.js
|
@ -86,6 +86,18 @@ function updateMaterials() {
|
|||
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() {
|
||||
//Make things display.
|
||||
|
@ -117,9 +129,15 @@ function calculateCraft() {
|
|||
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))));
|
||||
}
|
||||
|
||||
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
|
||||
player_craft = new Craft(recipe,mat_tiers,ingreds);
|
||||
player_craft = new Craft(recipe,mat_tiers,ingreds,atkSpd);
|
||||
console.log(player_craft);
|
||||
/*console.log(recipe)
|
||||
console.log(levelrange)
|
||||
|
@ -130,6 +148,9 @@ function calculateCraft() {
|
|||
|
||||
//Display Recipe Stats
|
||||
displayRecipeStats(player_craft, "recipe-stats");
|
||||
for(let i = 0; i < 6; i++) {
|
||||
displayExpandedIngredient(player_craft["ingreds"][i],"tooltip-" + i);
|
||||
}
|
||||
//Display Craft Stats
|
||||
displayCraftStats(player_craft, "craft-stats");
|
||||
//Display Ingredients' Stats
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -40,10 +43,11 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
|
|||
}
|
||||
//console.log(damages);
|
||||
let rawBoosts = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]];
|
||||
for (const powderID of weapon.get("powders")) {
|
||||
const powder = powderStats[powderID];
|
||||
const powders = weapon.get("powders");
|
||||
for (let i = 0; i < powders.length; i++) {
|
||||
const powder = powderStats[powders[i]];
|
||||
// Bitwise to force conversion to integer (integer division).
|
||||
const element = (powderID/6) | 0;
|
||||
const element = (powders[i]/6) | 0;
|
||||
let conversionRatio = powder.convert/100;
|
||||
if (neutralRemainingRaw[1] > 0) {
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
|
@ -146,7 +150,7 @@ const spell_table = {
|
|||
{ title: "Charge", cost: 4, parts: [
|
||||
{ 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: "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] },
|
||||
|
@ -177,7 +181,7 @@ const spell_table = {
|
|||
{ title: "Spin Attack", cost: 6, parts: [
|
||||
{ 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 }
|
||||
] },
|
||||
{ title: "Multihit", cost: 8, parts: [
|
||||
|
@ -194,7 +198,7 @@ const spell_table = {
|
|||
{ title: "Totem", cost: 4, parts: [
|
||||
{ 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: "Heal Tick", type: "heal", strength: 0.04, summary: true },
|
||||
{ subtitle: "Heal Tick", type: "heal", strength: 0.03, summary: true },
|
||||
] },
|
||||
{ title: "Haul", cost: 1, parts: [
|
||||
{ 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 },
|
||||
] },
|
||||
{ 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)
|
||||
|
|
27
display.js
27
display.js
|
@ -249,6 +249,7 @@ function displayBuildStats(parent_id,build){
|
|||
"#table",
|
||||
"str", "dex", "int", "def", "agi",
|
||||
"mr", "ms",
|
||||
"hprRaw", "hprPct",
|
||||
"sdRaw", "sdPct",
|
||||
"mdRaw", "mdPct",
|
||||
"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 damages = results[2];
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
@ -482,7 +483,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');
|
||||
|
@ -695,12 +697,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") {
|
||||
|
@ -802,7 +804,7 @@ function displayRecipeStats(craft, parent_id) {
|
|||
|
||||
let ingredTable = document.createElement("table");
|
||||
ingredTable.classList.add("itemtable");
|
||||
ingredTable.style.tableLayout = "fixed";
|
||||
ingredTable.classList.add("ingredTable");
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let row = document.createElement("tr");
|
||||
for (let j = 0; j < 2; j++) {
|
||||
|
@ -811,6 +813,7 @@ function displayRecipeStats(craft, parent_id) {
|
|||
cell.style.width = "50%";
|
||||
cell.classList.add("center");
|
||||
cell.classList.add("box");
|
||||
cell.classList.add("tooltip");
|
||||
let b = document.createElement("b");
|
||||
b.textContent = ingredName;
|
||||
b.classList.add("space");
|
||||
|
@ -825,10 +828,15 @@ function displayRecipeStats(craft, parent_id) {
|
|||
cell.appendChild(b);
|
||||
cell.appendChild(eff);
|
||||
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);
|
||||
}
|
||||
|
||||
elem.appendChild(ldiv);
|
||||
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.
|
||||
function displayExpandedIngredient(ingred, parent_id) {
|
||||
console.log(ingred);
|
||||
let parent_elem = document.getElementById(parent_id);
|
||||
parent_elem.textContent = "";
|
||||
let display_order = [
|
||||
|
@ -1875,7 +1882,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell
|
|||
let overallhealLabel = document.createElement("p");
|
||||
let first = document.createElement("b");
|
||||
let second = document.createElement("b");
|
||||
first.textContent = part.subtitle + ":";
|
||||
first.textContent = part.subtitle + ": ";
|
||||
second.textContent = heal_amount;
|
||||
overallhealLabel.appendChild(first);
|
||||
second.classList.add("Set");
|
||||
|
|
File diff suppressed because one or more lines are too long
874
index.html
874
index.html
File diff suppressed because it is too large
Load diff
53
items.html
Normal file
53
items.html
Normal 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
193
items.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);
|
19
load.js
19
load.js
|
@ -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
|
||||
|
||||
let db;
|
||||
|
@ -35,7 +35,7 @@ async function load_local(init_func) {
|
|||
}
|
||||
else {
|
||||
console.log("Successfully read local set db.");
|
||||
console.log(sets);
|
||||
//console.log(sets);
|
||||
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.
|
||||
*/
|
||||
function clean_item(item) {
|
||||
if (item.displayName === undefined) {
|
||||
item.displayName = item.name;
|
||||
if (item.remapID === undefined) {
|
||||
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;
|
||||
|
||||
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...");
|
||||
};
|
||||
let items_store = add_tx.objectStore('item_db');
|
||||
|
|
19
narrow.css
19
narrow.css
|
@ -6,6 +6,14 @@
|
|||
grid-column: 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 {
|
||||
padding: 2% 4% 4%;
|
||||
display: grid;
|
||||
|
@ -17,6 +25,9 @@
|
|||
|
||||
}
|
||||
|
||||
.ingredTable {
|
||||
table-layout: "fixed";
|
||||
}
|
||||
.build, .spells .misc {
|
||||
padding: 2%;
|
||||
display: grid;
|
||||
|
@ -33,6 +44,12 @@
|
|||
max-height: 50px;
|
||||
}
|
||||
|
||||
.powderinput {
|
||||
width: 25vw;
|
||||
height: 10vw;
|
||||
max-height: 50px;
|
||||
}
|
||||
|
||||
.skpInput, .idInput {
|
||||
width: 90%;
|
||||
height: 7vw;
|
||||
|
@ -44,7 +61,7 @@
|
|||
}
|
||||
|
||||
.idLabel, .skpLabel, .idDesc, .skpDesc {
|
||||
font-size: 100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.title{
|
||||
|
|
52
powders.js
Normal file
52
powders.js
Normal 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
582
query.js
Normal 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);
|
||||
};
|
||||
})();
|
47
styles.css
47
styles.css
|
@ -24,14 +24,6 @@ div {
|
|||
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{
|
||||
padding: 0% 4% 2%;
|
||||
display: grid;
|
||||
|
@ -92,13 +84,23 @@ table.center{
|
|||
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;
|
||||
background: #121516;
|
||||
border: 3px solid #BCBCBC;
|
||||
border-radius: 3px;
|
||||
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 {
|
||||
color: #aaa;
|
||||
background: #121516;
|
||||
|
@ -131,6 +133,12 @@ table.center{
|
|||
margin: 2px 2%;
|
||||
padding: 0;
|
||||
}
|
||||
.powderLeft {
|
||||
margin-right: 0px;
|
||||
}
|
||||
.powderRight {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.space {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
@ -387,3 +395,24 @@ button.toggleOn:hover {
|
|||
color: #ff0;
|
||||
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
80
update_merge.py
Normal 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
260617
updated.json
Normal file
File diff suppressed because it is too large
Load diff
13
wide.css
13
wide.css
|
@ -4,6 +4,15 @@
|
|||
.spell-info-container {
|
||||
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 {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
@ -51,9 +60,9 @@
|
|||
}
|
||||
|
||||
.skpInput, .idInput {
|
||||
width: 90%;
|
||||
width: 95%;
|
||||
height: 7vw;
|
||||
max-height: 30px;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.wide-space {
|
||||
|
|
Loading…
Reference in a new issue