Compare commits

..

27 commits

Author SHA1 Message Date
5faee76143 ownfoanf 2024-07-10 03:35:00 +01:00
hppeng
a1ddce803f Item changes
scuffed and put in manually
2024-07-07 07:55:27 -07:00
hppeng
1d6b302f38 parent 3e725eded8
author hppeng <hppeng> 1699417872 -0800
committer hppeng <hppeng> 1720354753 -0700

parent 3e725eded8
author hppeng <hppeng> 1699417872 -0800
committer hppeng <hppeng> 1720354749 -0700

parent 3e725eded8
author hppeng <hppeng> 1699417872 -0800
committer hppeng <hppeng> 1720354744 -0700

parent 3e725eded8
author hppeng <hppeng> 1699417872 -0800
committer hppeng <hppeng> 1720354739 -0700

parent 3e725eded8
author hppeng <hppeng> 1699417872 -0800
committer hppeng <hppeng> 1720354735 -0700

parent 3e725eded8
author hppeng <hppeng> 1699417872 -0800
committer hppeng <hppeng> 1720354730 -0700

parent 3e725eded8
author hppeng <hppeng> 1699417872 -0800
committer hppeng <hppeng> 1720354688 -0700

Update recipes.json (#265)

Change ratio of gems to oil as it has been updated in 2.0.4

> Updated the Jeweling Recipe Changes (Bracelet- 2:1 gems:oil, Necklaces- 3:1 gems:oil)

https://forums.wynncraft.com/threads/2-0-4-full-changelog-new-bank-lootruns-more.310535/

Finish updating recipes.json

why are there 4 versions of this file active at any given time

Fix damage calculation for rainbow raw

wow this bug has been here for a LONG time

also bump version for ing db

Bunch of bugfixes

- new major ID
- divine honor: reduce earth damage
- radiance: don't boost tomes, xp/loot bonuses

atree:
- parry: minor typo
- death magnet: marked dep
- nightcloak knife: 15s desc

Api v3 (#267)

* Tweak ordering to be consistent internally

* v3 items  (#266)

* item_wrapper script

for updating item data with v3 endpoint

* metadata from v3

* v3 item format

For the purpose of wynnbuilder, additional mapping might be needed.

* v3 item format

additional mapping might be needed for wb

* v3 compressed item json

* clean item json v3 format

* Update translate map to api v3

partially... we will need to redo scripts to flatmap all the items

* Fix items for 2.0.4.3

finally

* New ingredients (and parse script update)

just realized I forgot to commit the parse script this whole time

* Forgot to commit data files, and bump ing db version

* Sketchily reverse translate major ids

internalname and separate lookup table lol

* Forgot to update data files

todo: script should update all files at once

* Bump wynn version number

already outdated...

* Forgot to update 2.0.4.3 major ids

---------

Co-authored-by: hppeng <hppeng>
Co-authored-by: RawFish69 <108964215+RawFish69@users.noreply.github.com>

Add missing fields to ingreds

missing ids and consumableIDs tags in some ingreds

Fix missing properties in item search setup

these should be unified maybe to avoid duplicated code

Fix sacshrine dependency on fluid healing

also: fix ": " in item searcher

I managed to mess up all major ids

note: major ids min file is generated along with atree. it uses numeric ids, not just json compress

2.0.4.4 update (#269)

* 2.0.4.4 update

Fix v3 item api debug script
Implement hellfire (discombob disallow not happening yet)

* Fix boiling blood implementation

slightly more intuitive
also, janky first pass implementation for hellfire

* Atree default update

Allow sliders to specify a default value, for puppet and boiling blood for now

* Fix rainbow def

display on items and build stats
Calculate into raw def correctly

* Atree backend improvements

Allow major ids to have dependencies
Implement cherry bomb new ver. (wooo replace_spell just works out of the box!)
Add comments to atree.js

* Fix name of normal items

don't you love it when wynn api makes breaking changes for no reason

* Misc bugfix

Reckless abandon req Tempest
new damage ID in search

* Fix major id search

and temblor desc

* Fix blockers on mage

* Fix flaming uppercut implementation

* Force base dps display to display less digits

* Tomes finally pulling from the API

but still with alias feature enabled!

* Lootrun tomes (finally?)

cool? maybe?

* Fix beachside set set bonus

---------

Co-authored-by: hppeng <hppeng>

Fix rainbow def

display on items and build stats
Calculate into raw def correctly

Fix major id search

and temblor desc

Force base dps display to display less digits

Fix beachside set set bonus

Fix build decode error

reading only 7 tome fields no matter what

Give NONE tomes correct ids in load_tome

i hate this system so much

Allow searching for max/min of ranges

Fix crafted item damage display

in the process, also update powder calculation logic! Should be fully correct now...

TL;DR: Weapon damage is floating point; item display is wrong; ingame displays (damage floaters and compass) are floored.

Fluid healing now multiplicative with heal efficiency ID

NOTE: this breaks backwards compatibility with older atree jsons.
Do we care about this?

Realizing how much of a nightmare it will be (and already is) to keep
atree fully backwards compatible. Maybe that will be something left to
`git clone` instead.

fix (#274)
2024-07-07 05:19:16 -07:00
hppeng-wynn
3e725eded8
update up to 2.0.4.1 (#264)
* Fix atree connection issue with cheaper multihit-psithurism

* Fix major IDs with empty effect tags

bugged: temblor and overwhelm

hpp remember that effects tag is mandatory
or maybe you should just fix this and let effects be empty so you will never forget

* Bump ingred db version?

someone's didn't load correctly

* Misc. fixes

atree bugs (stronger sunshower, sacred surge)

Major ID attack speed on forest's blessing

* Update to 2.0.4.1

* Fix tome defense numbers

long time coming

* Fix a bunch of atree bugs

lazy hpp

* I forgot to bump db version again

* Fix archer atree illegal connection

oops

* Fix cheaper shield connection (2)

* Update credits.txt

* spooky festival update ings (#262)

* Update ing compressed files

---------

Co-authored-by: hppeng <hppeng>
Co-authored-by: EdmondLie <126294740+EdmondLie@users.noreply.github.com>
2023-10-19 18:58:52 -07:00
hppeng-wynn
479af33a81
2.0.3 update (#260)
* Mage atree changes

* Bump version to 2.0.3.1

just tree for now

* Warrior tree

🙏

* Shaman tree

WIP some things are too jank to stay

* Fixes to bamboozle behavior

also echo was -60% instead of -65% in the file??? wtf

* Shurikens damage boost by echo

meme

* Fix description text in echo, update old version atree file

* Fix shaman tree

thanks spegg!

* Spegg atree changes

assassin tree
fixed many of my shaman mistakes
and other changes we missed over the months somehow?

* Updated archer ability tree

Thanks @mr_me! All credit to them.

* Fixes to spegg's fixes

implement beast lore

* Change how Chant of the Lunatic is calculated

coursing restraints ingame is 15% damage bonus

* Updated ingredients manually

thanks @watermelon (snownlite)!

* Forgor to bump ing db version

* Fix ingredient display jank

* 2.0.3 items (#259)

* API update

also add new IDs to a bunch of places... tech debt whyyy

* Forgot to update ingreds...

* Change heal power ID name to stack with tree abils, fix multi totem effect on totemic shatter

and req for mana traps

* Forgot to bump item db version

* Implement major IDs

not implemented: Gentle Glow, and Forest's Blessing damage increase (since I don't know the exact numbers)

and radiance boost is not implemented (radiance is currently handled way too jank)

might wait for buffs rework to handle radiance.

* patch item searcher

TODO: make this not disgusting... build_encode_decode file has gotten too big

---------

Co-authored-by: hppeng <hppeng>
Co-authored-by: RawFish69 <108964215+RawFish69@users.noreply.github.com>
2023-07-14 18:34:30 -07:00
hppeng
1a5ad19ef5 TEMP: updating live site with bug fixes pulled from 2.0.3 branch 2023-07-10 11:10:25 -07:00
hppeng-wynn
590aa2269d
update master (#258)
* Clean up logic for positive/negative rolls; fix customizer

logical xor moment

* ....why did I leave extra code in

embarassing bug how did you not catch this before pushing

anyway this should fix CI creator

* unicorn horn (#255)

* unicorn horn 

50% rn in game

* Update ingreds_clean.json

* Update ingreds_compress.json

* Bump ing db version

* Add parry buff, and lifesteal

lol offensive lifesteal
reinforcing the stereotype

* Bamboozle should be fire conversion

thanks felixtape also I thought I fixed this ages ago

* Update doc.html so it works

missing lifesteal elem

* Serve our own copies of bootstrap and autocomplete

random npm outage wtf

* Whoops... forgot to replace autocomplete.js, and macy

---------

Co-authored-by: hppeng <hppeng>
Co-authored-by: RawFish69 <108964215+RawFish69@users.noreply.github.com>
2023-07-09 23:07:05 -07:00
hppeng-wynn
d9e5d6da95
Updating to wynn 2.0.2.3 (except item database...)
* Fix enraged blow typo; allow "or" and "and" in adv search

...forgot to update json

* Lacerate is blocked by Echo, not Mirror Image

* Misc bugfix

Fix bug with skillpoints and negative set bonus
Add final multiplier for echo

* Clean up testing folder

and add script for quick plotting pairs of ids/item values

* Fix typo in better lightweaver

add to the correct dps

* Partial update to 2.0.2.3 (festival of heroes)

patch:
- ing changes (manual)
- two endgame items (the ones that I got customs for)

bugfix:
- Fix bug in reverse mapping that mapped item "type" to "accessoryType"

* Forgot to commit all the 2.0.2.3 data files...

* Fix epilogue displayName

* Fix minor incorrectness with fromIntV invocation

don't think this was a bug? but its not the correct number of arguments lol

* Move powder ingreds to ing load sequence

not used anywhere else
also, remove extra prints in crafter

* Refactor powder special display

fix quake/chain/courage not displaying some powder special information 💀

* Finally fix satsujin to work with powder specials

thanks to powder special display refactor

* Fix mask of the awakened giving outdated stats

e

* Add prologue and gleeman's tale

wynn api when
...fix epilgoue

---------

Co-authored-by: hppeng <hppeng>
2023-04-14 17:18:52 -07:00
hppeng
4b9460ebc2 Fix cost application 2023-03-05 01:45:50 -08:00
hppeng
645f3145f0 Fix echo and bamboozle crash
and fix modify to work a bit better... TODO need actual dependency resolution for spell modification
2023-03-04 23:15:46 -08:00
hppeng
39c8357c5d Misc bugfix in atree
better ophanim and gust connection
bamboozle damage buff with echo
2023-03-04 17:13:45 -08:00
hppeng
4cf270f24f Misc. fixes
- pirouette: not acrobat
- Nightcloak knife: implemented
- diversion: description fixed
2023-02-20 01:32:05 -08:00
hppeng
8058bb5374 Dissolution shadestepper req 2023-02-18 14:33:20 -08:00
hppeng
24de93168f Ambush isn't shadestepper 2023-02-18 13:22:45 -08:00
hppeng
53c4ea67cc Fix typos in ability tree 2023-02-18 11:00:23 -08:00
hppeng
fb8e62ce9c Fix ragnarokkr display button 2023-02-17 04:02:21 -08:00
hppeng
e3b2ea4cdf Misc. patches force push haha
-- fix better enraged blow existing
-- fix dissolution location
-- fix cheaper dash to death magnet connection
-- fix last laugh location
-- fix death magnet dependency for fatal spin
2023-02-17 01:57:31 -08:00
hppeng-wynn
dc878caf1c
2.0.2 (#252)
* 2.0.2 mage tree

Super super janky implementation of winded

* Clean up breathless definition

* Numerical values and archetype req update for non mage ability trees

Notably excludes:
Effigy attack speed
Double/Triple totem multipliers
more shields damage change
arrow hurricane nerf

don't think this includes dependencies but idk if any changed? Topology fixing still needed

* Archer, Warrior and Shaman updated (mostly)

* Assassin tree changes

* Item db update

guess this is the 2.0.2 branch now

* Mage edit to match ingame

* Fix misc. atree stuff that wasn't fixed yet

warrior connections and positions
double/triple totem implementation
ragna nerf

---------

Co-authored-by: hppeng <hppeng>
Co-authored-by: TopHat of Pride <33451677+TopHat-of-Pride@users.noreply.github.com>
Co-authored-by: aspiepuppy <emimeado@gmail.com>
2023-02-17 03:40:23 -08:00
Incompleteusern
2bdebda89f
forgot (#249) 2023-02-01 14:47:59 -08:00
Incompleteusern
bf29018a60
Searching Changes (#248)
* fix sort keys not clearing

* ingredient searching

* add dura and charges

* modify comment

* fix effectiveness formula)

* remove comment
2023-01-30 18:55:25 -08:00
hppeng-wynn
88fe5217d2
Manual pruning (2) (#247)
* manual pruning

woo

* Patch botched find/replace, update other json files and bump version

Co-authored-by: btdmaster1563 <85848265+btdmaster1563@users.noreply.github.com>
Co-authored-by: hppeng <hppeng>
2023-01-13 21:53:53 -08:00
hppeng
e37226fae7 Fix duplicate ID display for elemental damage IDs
oops
2023-01-05 01:05:19 -08:00
hppeng
cded54bcc0 Update atree file
...forgot to do this
2023-01-04 16:09:48 -08:00
hppeng
0a054b8d94 Fix bug with encoding fixed ID values that are exactly a powe of 64 2023-01-02 17:31:55 -08:00
hppeng-wynn
f90794070a
Misc bugfix (#245)
* Atree bugs fix

Fix phantom ray damage
Fix more focus II connection

* Fix item display bugs

missing some damages
effectiveness typo

* Fix builder bugs

damage calc didn't account for rDamPct
morph- shortcut broke

* Fix fatal typo

remove extra console log

Co-authored-by: hppeng <hppeng>
2023-01-03 01:21:45 +00:00
hppeng-wynn
cc7e5b6818
Bugfixes (#243)
* Iron lungs and air shout damage fix

* Fix updating bug (edit ID output did not update in the correct order... somehow this became a race condition?)

* Sadly, reverting double totem display bug for now

* Remove prints, add comment for graph jank

Co-authored-by: hppeng <hppeng>
2022-12-24 10:12:14 +00:00
hppeng
0e66c63cda HOTFIX: janky patch to make editable fields work without full page update 2022-12-21 10:15:21 -08:00
147 changed files with 340829 additions and 120522 deletions

View file

@ -10,9 +10,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sidebar.css"> <link rel="stylesheet" href="../css/sidebar.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
@ -27,6 +27,8 @@
<a href = "/wynnfo/index.html"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a> <a href = "/wynnfo/index.html"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<div class="container-fluid py-1 vh-100"> <div class="container-fluid py-1 vh-100">
@ -60,8 +62,8 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.min.js"></script> <script src="/thirdparty/autoComplete.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/macy@2"></script> <script src="/thirdparty/macy@2"></script>
<script type="text/javascript" src="../js/utils.js"></script> <script type="text/javascript" src="../js/utils.js"></script>

View file

@ -15,9 +15,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
<link rel="stylesheet" href="../css/sidebar.css"> <link rel="stylesheet" href="../css/sidebar.css">
@ -34,6 +34,8 @@
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a> <a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0"> <div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
@ -73,6 +75,14 @@
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;"> <img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
<span>Swap Icon Style</span> <span>Swap Icon Style</span>
</a> </a>
<a href="https://nori.fish/wynn/build/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_build.png" alt="" style="height: 100%;">
<span>Build Search</span>
</a>
<a href="https://nori.fish/wynn/recipe/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_recipe.png" alt="" style="height: 100%;">
<span>Recipe Search</span>
</a>
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;"> <a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/discord.png" alt="" style="height: 100%;"> <img src="../media/icons/discord.png" alt="" style="height: 100%;">
<span>Discord</span> <span>Discord</span>
@ -464,7 +474,7 @@
War Scream War Scream
</button> </button>
<button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="ragnarokkr-boost" onclick="update_boosts('ragnarokkr-boost')"> <button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="ragnarokkr-boost" onclick="update_boosts('ragnarokkr-boost')">
Ragnarokkr (+30%) Ragnarokkr (+20%)
</button> </button>
<button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="totem-boost" onclick="update_boosts('totem-boost')"> <button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="totem-boost" onclick="update_boosts('totem-boost')">
Vengeful Spirit (+20%) Vengeful Spirit (+20%)
@ -506,19 +516,19 @@
</div> </div>
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1" > <div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1" >
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-1" onclick = "updatePowderSpecials('Quake-1')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-1" onclick = "updatePowderSpecials('Quake-1')">
Lv.4 [e4e4] Lv.1
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-2" onclick = "updatePowderSpecials('Quake-2')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-2" onclick = "updatePowderSpecials('Quake-2')">
Lv.4.5 [e5e4] Lv.2
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-3" onclick = "updatePowderSpecials('Quake-3')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-3" onclick = "updatePowderSpecials('Quake-3')">
Lv.5 [e5e5] Lv.3
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-4" onclick = "updatePowderSpecials('Quake-4')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-4" onclick = "updatePowderSpecials('Quake-4')">
Lv.5.5 [e6e5] Lv.4
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-5" onclick = "updatePowderSpecials('Quake-5')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-5" onclick = "updatePowderSpecials('Quake-5')">
Lv.6 [e6e6] Lv.5
</button> </button>
</div> </div>
<div class="col eDam"> <div class="col eDam">
@ -531,19 +541,19 @@
</div> </div>
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1"> <div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-1" onclick = "updatePowderSpecials('Chain_Lightning-1')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-1" onclick = "updatePowderSpecials('Chain_Lightning-1')">
Lv.4 [t4t4] Lv.1
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-2" onclick = "updatePowderSpecials('Chain_Lightning-2')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-2" onclick = "updatePowderSpecials('Chain_Lightning-2')">
Lv.4.5 [t5t4] Lv.2
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-3" onclick = "updatePowderSpecials('Chain_Lightning-3')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-3" onclick = "updatePowderSpecials('Chain_Lightning-3')">
Lv.5 [t5t5] Lv.3
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-4" onclick = "updatePowderSpecials('Chain_Lightning-4')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-4" onclick = "updatePowderSpecials('Chain_Lightning-4')">
Lv.5.5 [t6t5] Lv.4
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-5" onclick = "updatePowderSpecials('Chain_Lightning-5')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-5" onclick = "updatePowderSpecials('Chain_Lightning-5')">
Lv.6 [t6t6] Lv.5
</button> </button>
</div> </div>
<div class="col tDam"> <div class="col tDam">
@ -556,19 +566,19 @@
</div> </div>
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1"> <div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-1" onclick = "updatePowderSpecials('Curse-1')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-1" onclick = "updatePowderSpecials('Curse-1')">
Lv.4 [w4w4] Lv.1
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-2" onclick = "updatePowderSpecials('Curse-2')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-2" onclick = "updatePowderSpecials('Curse-2')">
Lv.4.5 [w5w4] Lv.2
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-3" onclick = "updatePowderSpecials('Curse-3')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-3" onclick = "updatePowderSpecials('Curse-3')">
Lv.5 [w5w5] Lv.3
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-4" onclick = "updatePowderSpecials('Curse-4')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-4" onclick = "updatePowderSpecials('Curse-4')">
Lv.5.5 [w6w5] Lv.4
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-5" onclick = "updatePowderSpecials('Curse-5')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-5" onclick = "updatePowderSpecials('Curse-5')">
Lv.6 [w6w6] Lv.5
</button> </button>
</div> </div>
<div class="col wDam"> <div class="col wDam">
@ -581,19 +591,19 @@
</div> </div>
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1"> <div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-1" onclick = "updatePowderSpecials('Courage-1')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-1" onclick = "updatePowderSpecials('Courage-1')">
Lv.4 [f4f4] Lv.1
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-2" onclick = "updatePowderSpecials('Courage-2')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-2" onclick = "updatePowderSpecials('Courage-2')">
Lv.4.5 [f5f4] Lv.2
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-3" onclick = "updatePowderSpecials('Courage-3')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-3" onclick = "updatePowderSpecials('Courage-3')">
Lv.5 [f5f5] Lv.3
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-4" onclick = "updatePowderSpecials('Courage-4')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-4" onclick = "updatePowderSpecials('Courage-4')">
Lv.5.5 [f6f5] Lv.4
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-5" onclick = "updatePowderSpecials('Courage-5')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-5" onclick = "updatePowderSpecials('Courage-5')">
Lv.6 [f6f6] Lv.5
</button> </button>
</div> </div>
<div class="col fDam"> <div class="col fDam">
@ -606,19 +616,19 @@
</div> </div>
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1"> <div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-1" onclick = "updatePowderSpecials('Wind_Prison-1')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-1" onclick = "updatePowderSpecials('Wind_Prison-1')">
Lv.4 [a4a4] Lv.1
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-2" onclick = "updatePowderSpecials('Wind_Prison-2')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-2" onclick = "updatePowderSpecials('Wind_Prison-2')">
Lv.4.5 [a5a4] Lv.2
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-3" onclick = "updatePowderSpecials('Wind_Prison-3')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-3" onclick = "updatePowderSpecials('Wind_Prison-3')">
Lv.5 [a5a5] Lv.3
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-4" onclick = "updatePowderSpecials('Wind_Prison-4')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-4" onclick = "updatePowderSpecials('Wind_Prison-4')">
Lv.5.5 [a6a5] Lv.4
</button> </button>
<button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-5" onclick = "updatePowderSpecials('Wind_Prison-5')"> <button class = "button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-5" onclick = "updatePowderSpecials('Wind_Prison-5')">
Lv.6 [a6a6] Lv.5
</button> </button>
</div> </div>
<div class="col aDam"> <div class="col aDam">
@ -975,7 +985,15 @@
</div> </div>
</div> </div>
<div class = "col-lg-3 col-sm-6"> <div class = "col-lg-3 col-sm-6">
<div class = "row">
Life Steal:
</div>
<div class = "row">
<input type = "number" placeholder = "0" id="ls" name="ls" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
</div>
<div class = "row" id = "ls-base">
Original Value: 0
</div>
</div> </div>
</div> </div>
<div class = "row big-title justify-content-center"> <div class = "row big-title justify-content-center">
@ -1235,7 +1253,7 @@
<div class="col-12 dark-5 scaled-font"> <div class="col-12 dark-5 scaled-font">
<footer class="text-center"> <footer class="text-center">
<div id="header2"> <div id="header2">
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p> <p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, <b>reschan</b>, and <b>blankman</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p> <p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
</div> </div>
<div id="credits"> <div id="credits">
@ -1297,12 +1315,14 @@
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.min.js"></script> <script src="/thirdparty/autoComplete.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/macy@2"></script> <script src="/thirdparty/macy@2"></script>
<script type="text/javascript" src="../js/utils.js"></script> <script type="text/javascript" src="../js/utils.js"></script>
<script type="text/javascript" src="../js/build_utils.js"></script> <script type="text/javascript" src="../js/build_utils.js"></script>
<script type="text/javascript" src="../js/computation_graph.js"></script> <script type="text/javascript" src="../js/computation_graph.js"></script>
<script type="text/javascript">COMPUTE_GRAPH_DEBUG=true</script> <script type="text/javascript">COMPUTE_GRAPH_DEBUG=true</script>
<script type="text/javascript" src="../js/icons.js"></script> <script type="text/javascript" src="../js/icons.js"></script>
<script type="text/javascript" src="../js/powders.js"></script> <script type="text/javascript" src="../js/powders.js"></script>
<script type="text/javascript" src="../js/skillpoints.js"></script> <script type="text/javascript" src="../js/skillpoints.js"></script>
@ -1321,13 +1341,11 @@
<script type="text/javascript" src="../js/builder/builder_graph.js"></script> <script type="text/javascript" src="../js/builder/builder_graph.js"></script>
<script type="text/javascript" src="../js/builder/builder.js"></script> <script type="text/javascript" src="../js/builder/builder.js"></script>
<!--script type="text/javascript" src="../js/builder/optimize.js"></script--> <!--script type="text/javascript" src="../js/builder/optimize.js"></script-->
<div id="graph_body" style="max-width: 100%; height: 100vh"> <div id="graph_body" style="max-width: 100%; height: 100vh">
<button id="saveButton">JANKY Export SVG</button> <button id="saveButton">JANKY Export SVG</button>
<a id="saveLink">savelink</a> <a id="saveLink">savelink</a>
</div> </div>
<script src="https://d3js.org/d3.v7.js"></script> <script src="https://d3js.org/d3.v7.js"></script>
<script type="text/javascript" src="../js/debug/render_compute_graph.js"></script> <script type="text/javascript" src="../js/debug/render_compute_graph.js"></script>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

View file

@ -15,9 +15,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
<link rel="stylesheet" href="../css/sidebar.css"> <link rel="stylesheet" href="../css/sidebar.css">
@ -34,6 +34,8 @@
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a> <a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0"> <div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
@ -73,6 +75,14 @@
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;"> <img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
<span>Swap Icon Style</span> <span>Swap Icon Style</span>
</a> </a>
<a href="https://nori.fish/wynn/build/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_build.png" alt="" style="height: 100%;">
<span>Build Search</span>
</a>
<a href="https://nori.fish/wynn/recipe/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_recipe.png" alt="" style="height: 100%;">
<span>Recipe Search</span>
</a>
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;"> <a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/discord.png" alt="" style="height: 100%;"> <img src="../media/icons/discord.png" alt="" style="height: 100%;">
<span>Discord</span> <span>Discord</span>
@ -82,7 +92,7 @@
<div class="container-fluid overall-box mt-lg-2" style="margin-top: 6vh;"> <div class="container-fluid overall-box mt-lg-2" style="margin-top: 6vh;">
<!-- REMOVE THIS DIV AT SOME POINT. --> <!-- REMOVE THIS DIV AT SOME POINT. -->
<div class = "row scaled-font mx-auto" id = "discord-banner-dev"> <div class = "row scaled-font mx-auto" id = "discord-banner-dev">
<div class = "col text-center item-title">Join the <a class = "link" href = "https://discord.gg/CGavnAnerv" target = "_blank">discord</a> today to suggest new features, submit bug reports, and hangout/talk to devs!</div> <div id='discord-banner' class = "col text-center item-title">Join the <a class = "link" href = "https://discord.gg/CGavnAnerv" target = "_blank">discord</a> today to suggest new features, submit bug reports, and hangout/talk to devs!</div>
</div> </div>
<div class="row h-100 gx-lg-5 gy-3 mx-2 mx-lg-3 py-3 gx-0"> <div class="row h-100 gx-lg-5 gy-3 mx-2 mx-lg-3 py-3 gx-0">
<div class="col-xl-6"> <div class="col-xl-6">
@ -464,7 +474,7 @@
War Scream War Scream
</button> </button>
<button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="ragnarokkr-boost" onclick="update_boosts('ragnarokkr-boost')"> <button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="ragnarokkr-boost" onclick="update_boosts('ragnarokkr-boost')">
Ragnarokkr (+30%) Ragnarokkr (+20%)
</button> </button>
<button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="totem-boost" onclick="update_boosts('totem-boost')"> <button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="totem-boost" onclick="update_boosts('totem-boost')">
Vengeful Spirit (+20%) Vengeful Spirit (+20%)
@ -827,6 +837,30 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-auto rounded">
<div class="row h-100 dark-shadow rounded" id='lootrunTome1-dropdown'>
<div class="col-auto g-0 rounded-end my-auto text-center scaled-item-icon" id="lootrunTome1-img-loc">
<div id="lootrunTome1-img" class="img-fluid rounded tome-image"></div>
</div>
<div class="col-3">
<div class="row row-cols-1 h-100 align-items-center">
<div class="col scaled-font fw-bold gx-3">
Lootrun
</div>
<div class="col scaled-font fw-bold gx-3">
Tome
</div>
</div>
</div>
<div class="col g-0 rounded">
<div class="row row-cols-1 h-100 align-items-center">
<div class="col d-flex justify-content-end">
<input class="equipment-input border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" id="lootrunTome1-choice" name="lootrunTome1-choice" placeholder="No Tome" value=""/>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;"> <div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;">
@ -975,7 +1009,15 @@
</div> </div>
</div> </div>
<div class = "col-lg-3 col-sm-6"> <div class = "col-lg-3 col-sm-6">
<div class = "row">
Life Steal:
</div>
<div class = "row">
<input type = "number" placeholder = "0" id="ls" name="ls" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
</div>
<div class = "row" id = "ls-base">
Original Value: 0
</div>
</div> </div>
</div> </div>
<div class = "row big-title justify-content-center"> <div class = "row big-title justify-content-center">
@ -1297,8 +1339,8 @@
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.min.js"></script> <script src="/thirdparty/autoComplete.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/macy@2"></script> <script src="/thirdparty/macy@2"></script>
<script type="text/javascript" src="../js/utils.js"></script> <script type="text/javascript" src="../js/utils.js"></script>
<script type="text/javascript" src="../js/build_utils.js"></script> <script type="text/javascript" src="../js/build_utils.js"></script>
<script type="text/javascript" src="../js/computation_graph.js"></script> <script type="text/javascript" src="../js/computation_graph.js"></script>
@ -1320,5 +1362,11 @@
<script type="text/javascript" src="../js/builder/builder_graph.js"></script> <script type="text/javascript" src="../js/builder/builder_graph.js"></script>
<script type="text/javascript" src="../js/builder/builder.js"></script> <script type="text/javascript" src="../js/builder/builder.js"></script>
<!--script type="text/javascript" src="../js/builder/optimize.js"></script--> <!--script type="text/javascript" src="../js/builder/optimize.js"></script-->
<!--div id="graph_body" style="max-width: 100%; height: 100vh">
<button id="saveButton">JANKY Export SVG</button>
<a id="saveLink">savelink</a>
</div>
<script src="https://d3js.org/d3.v7.js"></script>
<script type="text/javascript" src="../js/debug/render_compute_graph.js"></script-->
</body> </body>
</html> </html>

175328
clean.json

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
<link rel="icon" href="../media/icons/new/crafter.png"> <link rel="icon" href="../media/icons/new/crafter.png">

View file

@ -10,9 +10,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
@ -29,6 +29,8 @@
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a> <a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0"> <div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
@ -68,6 +70,14 @@
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;"> <img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
<span>Swap Icon Style</span> <span>Swap Icon Style</span>
</a> </a>
<a href="https://nori.fish/wynn/build/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_build.png" alt="" style="height: 100%;">
<span>Build Search</span>
</a>
<a href="https://nori.fish/wynn/recipe/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_recipe.png" alt="" style="height: 100%;">
<span>Recipe Search</span>
</a>
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;"> <a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/discord.png" alt="" style="height: 100%;"> <img src="../media/icons/discord.png" alt="" style="height: 100%;">
<span>Discord</span> <span>Discord</span>

View file

@ -0,0 +1 @@
wrote some stuff in the file

View file

@ -17,6 +17,7 @@ Additional Contributors, in no particular order:
- lemonalade (ability tree pdf for us to copy from :) ) - lemonalade (ability tree pdf for us to copy from :) )
- Lennon (Skill point formula reversing) - Lennon (Skill point formula reversing)
- Phanta (WynnAtlas custom expression parser / item search) - Phanta (WynnAtlas custom expression parser / item search)
- RawFish (and WIM team) (wynn api shenanigans, major IDs, misc.)
- nbcss (and WIM team) (Crafted Item mechanics reverse engineering, testing) - nbcss (and WIM team) (Crafted Item mechanics reverse engineering, testing)
- dr_carlos (Hiding UI elements properly, fade animations, proper error handling) - dr_carlos (Hiding UI elements properly, fade animations, proper error handling)
- Atlas Inc discord (feedback, ideas, damage calc, etc) - Atlas Inc discord (feedback, ideas, damage calc, etc)

View file

@ -16,41 +16,49 @@
/* rarity selectors */ /* rarity selectors */
#rarity-box { #tier-box {
cursor: pointer; cursor: pointer;
width: 58.33%; width: 58.33%;
text-align: center;
} }
#rarity-box > div { #tier-box > div {
width: calc(100% / 7); width: calc(100% / 7);
aspect-ratio: 1/1; aspect-ratio: 1/1;
position: relative; position: relative;
display: inline-block; display: inline-block;
} }
#rarity-box > div > b { #tier-box > div > b {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
#rarity-box > div.rarity-selected > b { #tier-box > div.tier-selected > b {
color: black; color: black;
} }
#rarity-normal { color: #FFFFFF; } #tier-normal { color: #FFFFFF; }
#rarity-normal.rarity-selected { background-color: #FFFFFF; } #tier-normal.tier-selected { background-color: #FFFFFF; }
#rarity-unique { color: #FFFF55; } #tier-unique { color: #FFFF55; }
#rarity-unique.rarity-selected { background-color: #FFFF55; } #tier-unique.tier-selected { background-color: #FFFF55; }
#rarity-set { color: #55FF55; } #tier-set { color: #55FF55; }
#rarity-set.rarity-selected { background-color: #55FF55; } #tier-set.tier-selected { background-color: #55FF55; }
#rarity-rare { color: #FF55FF; } #tier-rare { color: #FF55FF; }
#rarity-rare.rarity-selected { background-color: #FF55FF; } #tier-rare.tier-selected { background-color: #FF55FF; }
#rarity-legendary { color: #55FFFF; } #tier-legendary { color: #55FFFF; }
#rarity-legendary.rarity-selected { background-color: #55FFFF; } #tier-legendary.tier-selected { background-color: #55FFFF; }
#rarity-fabled { color: #FF5555; } #tier-fabled { color: #FF5555; }
#rarity-fabled.rarity-selected { background-color: #FF5555; } #tier-fabled.tier-selected { background-color: #FF5555; }
#rarity-mythic { color: #AA00AA; } #tier-mythic { color: #AA00AA; }
#rarity-mythic.rarity-selected { background-color: #AA00AA; } #tier-mythic.tier-selected { background-color: #AA00AA; }
#tier-zero { color: #FFFFFF; }
#tier-zero.tier-selected { background-color: #FFFFFF; }
#tier-one { color: #FFFFBB; }
#tier-one.tier-selected { background-color: #FFFFBB; }
#tier-two { color: #FFFF88; }
#tier-two.tier-selected { background-color: #FFFF88; }
#tier-three { color: #FFFF55; }
#tier-three.tier-selected { background-color: #FFFF55; }
/* filters */ /* filters */
.filter-row { .filter-row {

View file

@ -10,9 +10,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
@ -31,6 +31,8 @@
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a> <a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0"> <div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
@ -70,6 +72,14 @@
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;"> <img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
<span>Swap Icon Style</span> <span>Swap Icon Style</span>
</a> </a>
<a href="https://nori.fish/wynn/build/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_build.png" alt="" style="height: 100%;">
<span>Build Search</span>
</a>
<a href="https://nori.fish/wynn/recipe/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_recipe.png" alt="" style="height: 100%;">
<span>Recipe Search</span>
</a>
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;"> <a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/discord.png" alt="" style="height: 100%;"> <img src="../media/icons/discord.png" alt="" style="height: 100%;">
<span>Discord</span> <span>Discord</span>

View file

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
<link rel="icon" href="../media/icons/new/crafter.png"> <link rel="icon" href="../media/icons/new/crafter.png">

1
data/2.0.1.1/majid.json Normal file
View file

@ -0,0 +1 @@
{"MAGNET":{"displayName":"Magnet","description":"Pulls items within an 8 block radius towards you","abilities":[]},"PLAGUE":{"displayName":"Plague","description":"Poisoned mobs spread their poison to nearby mobs","abilities":[]},"HAWKEYE":{"displayName":"Hawkeye","description":"Condense Arrow Storm into a tight beam. Arrows deal ✤10%, ✦1%, and ❋1%","abilities":[{"class":"Archer","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","behavior":"overwrite","hits":{"Single Arrow":5}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","behavior":"overwrite","multipliers":[10,0,1,0,0,1]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","behavior":"modify","hits":{"Single Stream":4}}]}]},"GREED":{"displayName":"Greed","description":"Picking up emeralds heals you and nearby players for 15% max health","abilities":[]},"CAVALRYMAN":{"displayName":"Cavalryman","description":"You may cast spells and attack with a 70% damage penalty while on a horse","abilities":[]},"GUARDIAN":{"displayName":"Guardian","description":"20% of the damage taken by nearby allies is redirected to you","abilities":[]},"HERO":{"displayName":"Saviours Sacrifice","description":"While under 50% maximum health, nearby allies gain 30% bonus damage and defense","abilities":[]},"ALTRUISM":{"displayName":"Heart of the Pack","description":"Nearby players gain 35% of the health you naturally regenerate","abilities":[]},"ARCANES":{"displayName":"Transcendence","description":"30% chance for spells to cost no mana when casted","abilities":[]},"ENTROPY":{"displayName":"Entropy","description":"Meteor falls three times faster","abilities":[]},"ROVINGASSASSIN":{"displayName":"Roving Assassin","description":"Vanish no longer drains mana while invisible","abilities":[]},"MADNESS":{"displayName":"Madness","description":"Cast a random ability every 3 seconds","abilities":[]},"LIGHTWEIGHT":{"displayName":"Lightweight","description":"You no longer take fall damage","abilities":[]},"SORCERY":{"displayName":"Sorcery","description":"30% chance for spells and attacks to cast a second time at no additional cost","abilities":[]},"TAUNT":{"displayName":"Taunt","description":"Mobs within 12 blocks target you upon casting War Scream","abilities":[]},"RALLY":{"displayName":"Rally","description":"Charge heals you by 10% and nearby allies by 15% on impact, but becomes harmless","abilities":[{"class":"Warrior","base_abil":4,"effects":[{"type":"add_spell_prop","base_spell":2,"display":"Rally Self Heal","target_part":"Rally Self Heal","power":0.1},{"type":"add_spell_prop","base_spell":2,"target_part":"Rally Ally Heal","power":0.15},{"type":"raw_stat","bonuses":[{"type":"stat","name":"damMult.Rally:2.Flying Kick","value":-100},{"type":"stat","name":"damMult.Rally:2.Collide","value":-100},{"type":"stat","name":"damMult.Rally:2.Heavy Impact","value":-100},{"type":"stat","name":"damMult.Rally:2.Flyby Jab","value":-100}]}]}]},"CHERRY_BOMBS":{"displayName":"Cherry Bombs","description":"Your Smoke Bombs explode instantly, and increase their Neutral Damage by +90%","abilities":[{"class":"Assassin","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Per Tick","multipliers":[90,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Per Bomb","hits":{"Per Tick":-9}}]}]},"FREERUNNER":{"displayName":"Freerunner","description":"Double your sprint speed when your sprint bar is under 30%","abilities":[]},"PEACEFUL_EFFIGY":{"displayName":"Peaceful Effigy","description":"Your totem will last twice as long","abilities":[]},"FURIOUS_EFFIGY":{"displayName":"Furious Effigy","description":"Totem effects are twice as fast, but duration is halved","abilities":[{"class":"Shaman","base_abil":0,"properties":{"rate":-0.2,"totem_mul":2.5},"effects":[]}]},"FLASHFREEZE":{"displayName":"Flashfreeze","description":"Ice Snake is instant but has a reduced range","abilities":[]},"GRAVITYWELL":{"displayName":"Gravity Well","description":"Meteor has increased blast radius and pulls enemies instead","abilities":[]},"DESC_SNOWYSTEPS":{"displayName":"Snowy Steps","description":"Leaves a trail of snow behind you","abilities":[]},"GEOCENTRISM":{"displayName":"Geocentrism","description":"Aura radiates from you instead of your totem and can be cast anytime","abilities":[]},"DESC_FESTIVESPIRIT":{"displayName":"Festive Spirits","description":"Plays wintery tunes","abilities":[]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.1.2/majid.json Normal file
View file

@ -0,0 +1 @@
{"MAGNET":{"displayName":"Magnet","description":"Pulls items within an 8 block radius towards you","abilities":[]},"PLAGUE":{"displayName":"Plague","description":"Poisoned mobs spread their poison to nearby mobs","abilities":[]},"HAWKEYE":{"displayName":"Hawkeye","description":"Condense Arrow Storm into a tight beam. Arrows deal ✤10%, ✦1%, and ❋1%","abilities":[{"class":"Archer","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","behavior":"overwrite","hits":{"Single Arrow":5}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","behavior":"overwrite","multipliers":[10,0,1,0,0,1]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","behavior":"modify","hits":{"Single Stream":4}}]}]},"GREED":{"displayName":"Greed","description":"Picking up emeralds heals you and nearby players for 15% max health","abilities":[]},"CAVALRYMAN":{"displayName":"Cavalryman","description":"You may cast spells and attack with a 70% damage penalty while on a horse","abilities":[]},"GUARDIAN":{"displayName":"Guardian","description":"20% of the damage taken by nearby allies is redirected to you","abilities":[]},"HERO":{"displayName":"Saviours Sacrifice","description":"While under 50% maximum health, nearby allies gain 30% bonus damage and defense","abilities":[]},"ALTRUISM":{"displayName":"Heart of the Pack","description":"Nearby players gain 35% of the health you naturally regenerate","abilities":[]},"ARCANES":{"displayName":"Transcendence","description":"30% chance for spells to cost no mana when casted","abilities":[]},"ENTROPY":{"displayName":"Entropy","description":"Meteor falls three times faster","abilities":[]},"ROVINGASSASSIN":{"displayName":"Roving Assassin","description":"Vanish no longer drains mana while invisible","abilities":[]},"MADNESS":{"displayName":"Madness","description":"Cast a random ability every 3 seconds","abilities":[]},"LIGHTWEIGHT":{"displayName":"Lightweight","description":"You no longer take fall damage","abilities":[]},"SORCERY":{"displayName":"Sorcery","description":"30% chance for spells and attacks to cast a second time at no additional cost","abilities":[]},"TAUNT":{"displayName":"Taunt","description":"Mobs within 12 blocks target you upon casting War Scream","abilities":[]},"RALLY":{"displayName":"Rally","description":"Charge heals you by 10% and nearby allies by 15% on impact, but becomes harmless","abilities":[{"class":"Warrior","base_abil":4,"effects":[{"type":"add_spell_prop","base_spell":2,"display":"Rally Self Heal","target_part":"Rally Self Heal","power":0.1},{"type":"add_spell_prop","base_spell":2,"target_part":"Rally Ally Heal","power":0.15},{"type":"raw_stat","bonuses":[{"type":"stat","name":"damMult.Rally:2.Flying Kick","value":-100},{"type":"stat","name":"damMult.Rally:2.Collide","value":-100},{"type":"stat","name":"damMult.Rally:2.Heavy Impact","value":-100},{"type":"stat","name":"damMult.Rally:2.Flyby Jab","value":-100}]}]}]},"CHERRY_BOMBS":{"displayName":"Cherry Bombs","description":"Your Smoke Bombs explode instantly, and increase their Neutral Damage by +90%","abilities":[{"class":"Assassin","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Per Tick","multipliers":[90,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Per Bomb","hits":{"Per Tick":-9}}]}]},"FREERUNNER":{"displayName":"Freerunner","description":"Double your sprint speed when your sprint bar is under 30%","abilities":[]},"PEACEFUL_EFFIGY":{"displayName":"Peaceful Effigy","description":"Your totem will last twice as long","abilities":[]},"FURIOUS_EFFIGY":{"displayName":"Furious Effigy","description":"Totem effects are twice as fast, but duration is halved","abilities":[{"class":"Shaman","base_abil":0,"properties":{"rate":-0.2,"totem_mul":2.5},"effects":[]}]},"FLASHFREEZE":{"displayName":"Flashfreeze","description":"Ice Snake is instant but has a reduced range","abilities":[]},"GRAVITYWELL":{"displayName":"Gravity Well","description":"Meteor has increased blast radius and pulls enemies instead","abilities":[]},"DESC_SNOWYSTEPS":{"displayName":"Snowy Steps","description":"Leaves a trail of snow behind you","abilities":[]},"GEOCENTRISM":{"displayName":"Geocentrism","description":"Aura radiates from you instead of your totem and can be cast anytime","abilities":[]},"DESC_FESTIVESPIRIT":{"displayName":"Festive Spirits","description":"Plays wintery tunes","abilities":[]}}

File diff suppressed because one or more lines are too long

1
data/2.0.2.1/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.2.1/items.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.0.2.1/majid.json Normal file
View file

@ -0,0 +1 @@
{"MAGNET":{"displayName":"Magnet","description":"Pulls items within an 8 block radius towards you","abilities":[]},"PLAGUE":{"displayName":"Plague","description":"Poisoned mobs spread their poison to nearby mobs","abilities":[]},"HAWKEYE":{"displayName":"Hawkeye","description":"Condense Arrow Storm into a tight beam. Arrows deal ✤10%, ✦1%, and ❋1%","abilities":[{"class":"Archer","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","behavior":"overwrite","hits":{"Single Arrow":5}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","behavior":"overwrite","multipliers":[10,0,1,0,0,1]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","behavior":"modify","hits":{"Single Stream":4}}]}]},"GREED":{"displayName":"Greed","description":"Picking up emeralds heals you and nearby players for 15% max health","abilities":[]},"CAVALRYMAN":{"displayName":"Cavalryman","description":"You may cast spells and attack with a 70% damage penalty while on a horse","abilities":[]},"GUARDIAN":{"displayName":"Guardian","description":"20% of the damage taken by nearby allies is redirected to you","abilities":[]},"HERO":{"displayName":"Saviours Sacrifice","description":"While under 50% maximum health, nearby allies gain 30% bonus damage and defense","abilities":[]},"ALTRUISM":{"displayName":"Heart of the Pack","description":"Nearby players gain 35% of the health you naturally regenerate","abilities":[]},"ARCANES":{"displayName":"Transcendence","description":"30% chance for spells to cost no mana when casted","abilities":[]},"ENTROPY":{"displayName":"Entropy","description":"Meteor falls three times faster","abilities":[]},"ROVINGASSASSIN":{"displayName":"Roving Assassin","description":"Vanish no longer drains mana while invisible","abilities":[]},"MADNESS":{"displayName":"Madness","description":"Cast a random ability every 3 seconds","abilities":[]},"LIGHTWEIGHT":{"displayName":"Lightweight","description":"You no longer take fall damage","abilities":[]},"SORCERY":{"displayName":"Sorcery","description":"30% chance for spells and attacks to cast a second time at no additional cost","abilities":[]},"TAUNT":{"displayName":"Taunt","description":"Mobs within 12 blocks target you upon casting War Scream","abilities":[]},"RALLY":{"displayName":"Rally","description":"Charge heals you by 10% and nearby allies by 15% on impact, but becomes harmless","abilities":[{"class":"Warrior","base_abil":4,"effects":[{"type":"add_spell_prop","base_spell":2,"display":"Rally Self Heal","target_part":"Rally Self Heal","power":0.1},{"type":"add_spell_prop","base_spell":2,"target_part":"Rally Ally Heal","power":0.15},{"type":"raw_stat","bonuses":[{"type":"stat","name":"damMult.Rally:2.Flying Kick","value":-100},{"type":"stat","name":"damMult.Rally:2.Collide","value":-100},{"type":"stat","name":"damMult.Rally:2.Heavy Impact","value":-100},{"type":"stat","name":"damMult.Rally:2.Flyby Jab","value":-100}]}]}]},"CHERRY_BOMBS":{"displayName":"Cherry Bombs","description":"Your Smoke Bombs explode instantly, and increase their Neutral Damage by +90%","abilities":[{"class":"Assassin","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Per Tick","multipliers":[90,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Per Bomb","hits":{"Per Tick":-9}}]}]},"FREERUNNER":{"displayName":"Freerunner","description":"Double your sprint speed when your sprint bar is under 30%","abilities":[]},"PEACEFUL_EFFIGY":{"displayName":"Peaceful Effigy","description":"Your totem will last twice as long","abilities":[]},"FURIOUS_EFFIGY":{"displayName":"Furious Effigy","description":"Totem effects are twice as fast, but duration is halved","abilities":[{"class":"Shaman","base_abil":0,"properties":{"rate":-0.2,"totem_mul":2.5},"effects":[]}]},"FLASHFREEZE":{"displayName":"Flashfreeze","description":"Ice Snake is instant but has a reduced range","abilities":[]},"GRAVITYWELL":{"displayName":"Gravity Well","description":"Meteor has increased blast radius and pulls enemies instead","abilities":[]},"DESC_SNOWYSTEPS":{"displayName":"Snowy Steps","description":"Leaves a trail of snow behind you","abilities":[]},"GEOCENTRISM":{"displayName":"Geocentrism","description":"Aura radiates from you instead of your totem and can be cast anytime","abilities":[]},"DESC_FESTIVESPIRIT":{"displayName":"Festive Spirits","description":"Plays wintery tunes","abilities":[]}}

File diff suppressed because one or more lines are too long

1069
data/2.0.2.1/tomes.json Normal file

File diff suppressed because it is too large Load diff

1
data/2.0.2.3/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.2.3/items.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.0.2.3/majid.json Normal file
View file

@ -0,0 +1 @@
{"MAGNET":{"displayName":"Magnet","description":"Pulls items within an 8 block radius towards you","abilities":[]},"PLAGUE":{"displayName":"Plague","description":"Poisoned mobs spread their poison to nearby mobs","abilities":[]},"HAWKEYE":{"displayName":"Hawkeye","description":"Condense Arrow Storm into a tight beam. Arrows deal ✤10%, ✦1%, and ❋1%","abilities":[{"class":"Archer","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","behavior":"overwrite","hits":{"Single Arrow":5}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","behavior":"overwrite","multipliers":[10,0,1,0,0,1]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","behavior":"modify","hits":{"Single Stream":4}}]}]},"GREED":{"displayName":"Greed","description":"Picking up emeralds heals you and nearby players for 15% max health","abilities":[]},"CAVALRYMAN":{"displayName":"Cavalryman","description":"You may cast spells and attack with a 70% damage penalty while on a horse","abilities":[]},"GUARDIAN":{"displayName":"Guardian","description":"20% of the damage taken by nearby allies is redirected to you","abilities":[]},"HERO":{"displayName":"Saviours Sacrifice","description":"While under 50% maximum health, nearby allies gain 30% bonus damage and defense","abilities":[]},"ALTRUISM":{"displayName":"Heart of the Pack","description":"Nearby players gain 35% of the health you naturally regenerate","abilities":[]},"ARCANES":{"displayName":"Transcendence","description":"30% chance for spells to cost no mana when casted","abilities":[]},"ENTROPY":{"displayName":"Entropy","description":"Meteor falls three times faster","abilities":[]},"ROVINGASSASSIN":{"displayName":"Roving Assassin","description":"Vanish no longer drains mana while invisible","abilities":[]},"MADNESS":{"displayName":"Madness","description":"Cast a random ability every 3 seconds","abilities":[]},"LIGHTWEIGHT":{"displayName":"Lightweight","description":"You no longer take fall damage","abilities":[]},"SORCERY":{"displayName":"Sorcery","description":"30% chance for spells and attacks to cast a second time at no additional cost","abilities":[]},"TAUNT":{"displayName":"Taunt","description":"Mobs within 12 blocks target you upon casting War Scream","abilities":[]},"RALLY":{"displayName":"Rally","description":"Charge heals you by 10% and nearby allies by 15% on impact, but becomes harmless","abilities":[{"class":"Warrior","base_abil":4,"effects":[{"type":"add_spell_prop","base_spell":2,"display":"Rally Self Heal","target_part":"Rally Self Heal","power":0.1},{"type":"add_spell_prop","base_spell":2,"target_part":"Rally Ally Heal","power":0.15},{"type":"raw_stat","bonuses":[{"type":"stat","name":"damMult.Rally:2.Flying Kick","value":-100},{"type":"stat","name":"damMult.Rally:2.Collide","value":-100},{"type":"stat","name":"damMult.Rally:2.Heavy Impact","value":-100},{"type":"stat","name":"damMult.Rally:2.Flyby Jab","value":-100}]}]}]},"CHERRY_BOMBS":{"displayName":"Cherry Bombs","description":"Your Smoke Bombs explode instantly, and increase their Neutral Damage by +90%","abilities":[{"class":"Assassin","base_abil":7,"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Per Tick","multipliers":[90,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Per Bomb","hits":{"Per Tick":-9}}]}]},"FREERUNNER":{"displayName":"Freerunner","description":"Double your sprint speed when your sprint bar is under 30%","abilities":[]},"PEACEFUL_EFFIGY":{"displayName":"Peaceful Effigy","description":"Your totem will last twice as long","abilities":[]},"FURIOUS_EFFIGY":{"displayName":"Furious Effigy","description":"Totem effects are twice as fast, but duration is halved","abilities":[{"class":"Shaman","base_abil":0,"properties":{"rate":-0.2,"totem_mul":2.5},"effects":[]}]},"FLASHFREEZE":{"displayName":"Flashfreeze","description":"Ice Snake is instant but has a reduced range","abilities":[]},"GRAVITYWELL":{"displayName":"Gravity Well","description":"Meteor has increased blast radius and pulls enemies instead","abilities":[]},"DESC_SNOWYSTEPS":{"displayName":"Snowy Steps","description":"Leaves a trail of snow behind you","abilities":[]},"GEOCENTRISM":{"displayName":"Geocentrism","description":"Aura radiates from you instead of your totem and can be cast anytime","abilities":[]},"DESC_FESTIVESPIRIT":{"displayName":"Festive Spirits","description":"Plays wintery tunes","abilities":[]}}

File diff suppressed because one or more lines are too long

1069
data/2.0.2.3/tomes.json Normal file

File diff suppressed because it is too large Load diff

1
data/2.0.3.1/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.3.1/items.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.0.3.1/majid.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1069
data/2.0.3.1/tomes.json Normal file

File diff suppressed because it is too large Load diff

1
data/2.0.4.1/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.4.1/items.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.0.4.1/majid.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1069
data/2.0.4.1/tomes.json Normal file

File diff suppressed because it is too large Load diff

1
data/2.0.4.3/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.4.3/items.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.0.4.3/majid.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1069
data/2.0.4.3/tomes.json Normal file

File diff suppressed because it is too large Load diff

1
data/2.0.4.4/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.4.4/items.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.0.4.4/majid.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.4.4/tomes.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.1.0.0/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.1.0.0/items.json Normal file

File diff suppressed because one or more lines are too long

1
data/2.1.0.0/majid.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.1.0.0/tomes.json Normal file

File diff suppressed because one or more lines are too long

0
dev/builder_colorcode.png Executable file → Normal file
View file

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

0
dev/compute_graph.svg Executable file → Normal file
View file

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -14,11 +14,11 @@
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" <link href="/thirdparty/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" <link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
@ -43,6 +43,8 @@
<a href="" onclick="toggleIcons()"><img src="../media/icons/new/reload.png" alt="" <a href="" onclick="toggleIcons()"><img src="../media/icons/new/reload.png" alt=""
title="Swap items on page"><b>Swap Icon Style</b></a> title="Swap items on page"><b>Swap Icon Style</b></a>
<hr /> <hr />
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<div class="container text-light px-5 scaled-font"> <div class="container text-light px-5 scaled-font">

View file

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
<link rel="icon" href="../media/icons/new/builder.png"> <link rel="icon" href="../media/icons/new/builder.png">

184
ingredients/index.html Normal file
View file

@ -0,0 +1,184 @@
<!DOCTYPE html>
<html scroll-behavior="smooth">
<head>
<title>WynnAtlas</title>
<link rel="icon" href="../media/icons/new/searcher.png" type="image/icon type">
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css">
<link rel="stylesheet" href="../css/sidebar.css">
<link rel="stylesheet" href="../css/wynnstyles.css">
<link rel="stylesheet" href="../css/search.css">
</head>
<body class = "text-light d-flex justify-content-center" id = "body">
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
<a href = "../custom/"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
<a href = "../map/"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div>
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
<div class="container-fluid scaled-font justify-content-center" style="height: 5vh;">
<button class="btn dropdown-toggle dark-2 px-4 text-white scaled-font border-dark border-3" onclick="toggle_tab('mobile-navbar-dropdown');"></button>
</div>
<div class="container-fluid scaled-font dark-3 px-3 py-3" id="mobile-navbar-dropdown" style="display: none;">
<a href="../builder/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
<span>WynnBuilder</span>
</a>
<a href="../crafter/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
<span>WynnCrafter</span>
</a>
<a href="../items/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
<span>WynnAtlas</span>
</a>
<a href="../custom/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
<span>WynnCustom</span>
</a>
<a href="../map/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
<span>WynnGPS</span>
</a>
<a href="../wynnfo/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
<span>WynnFo</span>
</a>
<a onclick = "toggleIcons()" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
<span>Swap Icon Style</span>
</a>
<a href="https://nori.fish/wynn/build/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_build.png" alt="" style="height: 100%;">
<span>Build Search</span>
</a>
<a href="https://nori.fish/wynn/recipe/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_recipe.png" alt="" style="height: 100%;">
<span>Recipe Search</span>
</a>
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/discord.png" alt="" style="height: 100%;">
<span>Discord</span>
</a>
</div>
</div>
<div class = "container py-5 vh-100 mx-0 mx-lg-auto scaled-font">
<div class = "row">
<div class = "row" style = "margin-top: 3ch;">
<div class = "col text-start" id = "credits">
<a href="../items/" class="link">Item Searching</a>
</div>
<div class = "col text-end">
<a href = "../ingredients_adv/">Advanced Ingredient Search</a>
</div>
</div>
<div class = "col-auto" style = "width: 12.5%;"></div>
<div class = "col-lg-4">
<div class = "row">
<div class = "col-lg col-sm-12">
<div class = "col fw-bold">Name:</div>
<input class = "col border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" style="width: 100%!important;" type="text" id="item-name-choice" name="item-name-choice" placeholder="Ingredient name (case insensitive)"/>
<p class="error col-auto"></p>
</div>
</div>
<div class = "row">
<div class = "col-lg col-sm-12">
<div class = "col"><span class = "fw-bold">Types:</span> <span style = "cursor: pointer; float: right;"><span id = "all-types">All</span> &nbsp; <span id = "none-types">None</span></span></div>
<div id = "type-box">
<p id = "type-armouring" style = "margin: 0; ">Armouring</p>
<p id = "type-tailoring" style = "margin: 0;">Tailoring</p>
<p id = "type-weaponsmithing" style = "margin: 0;">Weaponsmithing</p>
<p id = "type-woodworking" style = "margin: 0;">Woodworking</p>
<p id = "type-jeweling" style = "margin: 0;">Jeweling</p>
<p id = "type-cooking" style = "margin: 0;">Cooking</p>
<p id = "type-alchemism" style = "margin: 0;">Alchemism</p>
<p id = "type-scribing" style = "margin: 0;">Scribing</p>
</div>
<p class="error col-auto"></p>
</div>
</div>
<div class = "row">
<div class = "col-lg col-sm-12">
<div class = "col"><span class = "fw-bold">Stars:</span> <span style = "cursor: pointer; float: right;"><span id = "all-tiers">All</span> &nbsp; <span id = "none-tiers">None</span></span></div>
<div id = "tier-box">
<!-- unfortunately they must be stacked up like this because newlines are considered spaces and muck up the organization -->
<div id = "tier-zero" class = "tier-selected"><b>0</b></div><div id = "tier-one" class = "tier-selected"><b>1</b></div><div id = "tier-two" class = "tier-selected"><b>2</b></div><div id = "tier-three" class = "tier-selected"><b>3</b></div>
</div>
<p class="error col-auto"></p>
</div>
</div>
</div>
<div class = "col-lg-5">
<div id = "filter-container" class = "col">
<div class = "col fw-bold">Filters:</div>
<div class = "row">
<div id = "add-filter" class = "col fw-bold" style = "cursor: pointer; padding-top: 5px;">
+ Add Filter
</div>
</div>
</div>
<br/>
<div id = "exclude-container" class = "col">
<div class = "col fw-bold">Excluded Filters:</div>
<div class = "row">
<div id = "add-exclude" class = "col fw-bold" style = "cursor: pointer; padding-top: 5px;">
+ Add Excluded Filter
</div>
</div>
</div>
</div>
<div class = "row">
<div class = "col-auto" style = "width: 12.5%;"></div>
<div class = "col-auto">
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "search-button" onclick = "do_item_search()">
Search!
</button>
</div>
<div class = "col-auto">
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "reset-button" onclick = "reset_item_search()">
Reset
</button>
</div>
</div>
<div class = "row box-title justify-content-center" id = "summary">
</div>
<div class = "row" id = "search-results">
</div>
</div>
</div>
<script src="/thirdparty/autoComplete.min.js"></script>
<script type="text/javascript" src="../js/drag_drop_touch.js"></script>
<script type="text/javascript" src="../js/utils.js"></script>
<script type="text/javascript" src="../js/build_utils.js"></script>
<script type="text/javascript" src="../js/icons.js"></script>
<script type="text/javascript" src="../js/damage_calc.js"></script>
<script type="text/javascript" src="../js/display_constants.js"></script>
<script type="text/javascript" src="../js/display.js"></script>
<script type="text/javascript" src="../js/query.js"></script>
<script type="text/javascript" src="../js/expr_parser.js"></script>
<script type="text/javascript" src="../js/load_ing.js"></script>
<script type="text/javascript" src="../js/search.js"></script>
<script type="text/javascript" src="../js/ingredients.js"></script>
<script type="text/javascript" src="../js/powders.js"></script>
</body>
</html>

View file

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html scroll-behavior="smooth">
<head>
<title>WynnAtlas</title>
<link rel="icon" href="../media/icons/new/searcher.png" type="image/icon type">
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css">
<link rel="stylesheet" href="../css/items_adv.css">
<link rel="stylesheet" href="../css/sidebar.css">
<link rel="stylesheet" href="../css/wynnstyles.css">
</head>
<body class = "text-light d-flex justify-content-center" id = "body">
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
<a href = "/customizer.html"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
<a href = "/map.html"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
<a href = "/wynnfo/index.html"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div>
<div class = "container py-5 vh-100 mx-0 mx-lg-auto scaled-font">
<div class = "col">
<div class = "row">
<div class = "col text-start" id = "credits">
<a href="../credits.txt" class="link">Additional credits</a>
</div>
<div class = "col text-end">
<a href = "../ingredients/">Basic Ingredient Search</a>
</div>
</div>
<div class = "row">
<div class="col" id="main">
<div class = "row" id="search-container">
<div class="col search-field-container" id="search-filter">
<div class="col fw-bold">Filter By:</div>
<input class="col border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="search-filter-field" type="text" autofocus="true"
placeholder="name ?= &quot;blue&quot; & str >= 15 & dex >= 10">
<div class="search-field-error" id="search-filter-error"></div>
<div class="search-field-compl" id="search-filter-compl"></div>
</div>
<div class="col search-field-container" id="search-sort">
<div class="col fw-bold">Sort By:</div>
<input class="col border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="search-sort-field" type="text"
placeholder="str + dex; meleerawdmg + spellrawdmg">
<div class="search-field-error" id="search-sort-error"></div>
<div class="search-field-compl" id="search-sort-compl"></div>
</div>
</div>
<div class = "row" id="item-list-container">
<div class="row" id="item-list"></div>
<div class="row" id="item-list-footer"></div>
</div>
</div>
</div>
<div class = "row" id="scroll-up">&uparrow;</div>
</div>
</div>
<script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/build_utils.js"></script>
<script type="text/javascript" src="/js/icons.js"></script>
<script type="text/javascript" src="/js/powders.js"></script>
<script type="text/javascript" src="/js/damage_calc.js"></script>
<script type="text/javascript" src="/js/display_constants.js"></script>
<script type="text/javascript" src="/js/display.js"></script>
<script type="text/javascript" src="/js/query.js"></script>
<script type="text/javascript" src="/js/expr_parser.js"></script>
<script type="text/javascript" src="/js/load_ing.js"></script>
<script type="text/javascript" src="/js/search_adv.js"></script>
<script type="text/javascript" src="/js/ingredients_adv.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -11,9 +11,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
@ -30,6 +30,8 @@
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a> <a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
@ -56,6 +58,7 @@
</div> </div>
<script type="text/javascript" src="/js/utils.js"></script> <script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/build_utils.js"></script> <script type="text/javascript" src="/js/build_utils.js"></script>
<script type="text/javascript" src="/js/builder/build_encode_decode.js"></script>
<script type="text/javascript" src="/js/icons.js"></script> <script type="text/javascript" src="/js/icons.js"></script>
<script type="text/javascript" src="/js/damage_calc.js"></script> <script type="text/javascript" src="/js/damage_calc.js"></script>
<script type="text/javascript" src="/js/powders.js"></script> <script type="text/javascript" src="/js/powders.js"></script>

View file

@ -10,15 +10,15 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
<link rel="stylesheet" href="../css/sidebar.css"> <link rel="stylesheet" href="../css/sidebar.css">
<link rel="stylesheet" href="../css/wynnstyles.css"> <link rel="stylesheet" href="../css/wynnstyles.css">
<link rel="stylesheet" href="../css/items.css"> <link rel="stylesheet" href="../css/search.css">
</head> </head>
<body class = "text-light d-flex justify-content-center" id = "body"> <body class = "text-light d-flex justify-content-center" id = "body">
<div id="main-sidebar" class="sidebar dark-7 dark-shadow"> <div id="main-sidebar" class="sidebar dark-7 dark-shadow">
@ -30,6 +30,8 @@
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a> <a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0"> <div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
@ -69,6 +71,14 @@
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;"> <img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
<span>Swap Icon Style</span> <span>Swap Icon Style</span>
</a> </a>
<a href="https://nori.fish/wynn/build/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_build.png" alt="" style="height: 100%;">
<span>Build Search</span>
</a>
<a href="https://nori.fish/wynn/recipe/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/new/nori_recipe.png" alt="" style="height: 100%;">
<span>Recipe Search</span>
</a>
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;"> <a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
<img src="../media/icons/discord.png" alt="" style="height: 100%;"> <img src="../media/icons/discord.png" alt="" style="height: 100%;">
<span>Discord</span> <span>Discord</span>
@ -78,6 +88,9 @@
<div class = "container py-5 vh-100 mx-0 mx-lg-auto scaled-font"> <div class = "container py-5 vh-100 mx-0 mx-lg-auto scaled-font">
<div class = "row"> <div class = "row">
<div class = "row" style = "margin-top: 3ch;"> <div class = "row" style = "margin-top: 3ch;">
<div class = "col text-start" id = "credits">
<a href="../ingredients/" class="link">Ingredient Searching</a>
</div>
<div class = "col text-end"> <div class = "col text-end">
<a href = "../items_adv/">Advanced Item Search</a> <a href = "../items_adv/">Advanced Item Search</a>
</div> </div>
@ -113,10 +126,10 @@
</div> </div>
<div class = "row"> <div class = "row">
<div class = "col-lg col-sm-12"> <div class = "col-lg col-sm-12">
<div class = "col"><span class = "fw-bold">Rarity:</span> <span style = "cursor: pointer; float: right;"><span id = "all-rarities">All</span> &nbsp; <span id = "none-rarities">None</span></span></div> <div class = "col"><span class = "fw-bold">Rarity:</span> <span style = "cursor: pointer; float: right;"><span id = "all-tiers">All</span> &nbsp; <span id = "none-tiers">None</span></span></div>
<div id = "rarity-box"> <div id = "tier-box">
<!-- unfortunately they must be stacked up like this because newlines are considered spaces and muck up the organization --> <!-- unfortunately they must be stacked up like this because newlines are considered spaces and muck up the organization -->
<div id = "rarity-normal" class = "rarity-selected"><b>N</b></div><div id = "rarity-unique" class = "rarity-selected"><b>U</b></div><div id = "rarity-set" class = "rarity-selected"><b>S</b></div><div id = "rarity-rare" class = "rarity-selected"><b>R</b></div><div id = "rarity-legendary" class = "rarity-selected"><b>L</b></div><div id = "rarity-fabled" class = "rarity-selected"><b>F</b></div><div id = "rarity-mythic" class = "rarity-selected"><b>M</b></div> <div id = "tier-normal" class = "tier-selected"><b>N</b></div><div id = "tier-unique" class = "tier-selected"><b>U</b></div><div id = "tier-set" class = "tier-selected"><b>S</b></div><div id = "tier-rare" class = "tier-selected"><b>R</b></div><div id = "tier-legendary" class = "tier-selected"><b>L</b></div><div id = "tier-fabled" class = "tier-selected"><b>F</b></div><div id = "tier-mythic" class = "tier-selected"><b>M</b></div>
</div> </div>
<p class="error col-auto"></p> <p class="error col-auto"></p>
</div> </div>
@ -161,18 +174,20 @@
</div> </div>
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.min.js"></script> <script src="/thirdparty/autoComplete.min.js"></script>
<script type="text/javascript" src="../js/drag_drop_touch.js"></script> <script type="text/javascript" src="/js/drag_drop_touch.js"></script>
<script type="text/javascript" src="../js/utils.js"></script> <script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="../js/build_utils.js"></script> <script type="text/javascript" src="/js/build_utils.js"></script>
<script type="text/javascript" src="../js/icons.js"></script> <script type="text/javascript" src="/js/builder/build_encode_decode.js"></script>
<script type="text/javascript" src="../js/damage_calc.js"></script> <script type="text/javascript" src="/js/icons.js"></script>
<script type="text/javascript" src="../js/display_constants.js"></script> <script type="text/javascript" src="/js/damage_calc.js"></script>
<script type="text/javascript" src="../js/display.js"></script> <script type="text/javascript" src="/js/display_constants.js"></script>
<script type="text/javascript" src="../js/query.js"></script> <script type="text/javascript" src="/js/display.js"></script>
<script type="text/javascript" src="../js/expr_parser.js"></script> <script type="text/javascript" src="/js/query.js"></script>
<script type="text/javascript" src="../js/load.js"></script> <script type="text/javascript" src="/js/expr_parser.js"></script>
<script type="text/javascript" src="../js/items.js"></script> <script type="text/javascript" src="/js/load.js"></script>
<script type="text/javascript" src="../js/powders.js"></script> <script type="text/javascript" src="/js/search.js"></script>
<script type="text/javascript" src="/js/powders.js"></script>
<script type="text/javascript" src="/js/items.js"></script>
</body> </body>
</html> </html>

View file

@ -10,9 +10,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/sq2bs.css"> <link rel="stylesheet" href="../css/sq2bs.css">
@ -76,6 +76,7 @@
<script type="text/javascript" src="/js/utils.js"></script> <script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/build_utils.js"></script> <script type="text/javascript" src="/js/build_utils.js"></script>
<script type="text/javascript" src="/js/builder/build_encode_decode.js"></script>
<script type="text/javascript" src="/js/icons.js"></script> <script type="text/javascript" src="/js/icons.js"></script>
<script type="text/javascript" src="/js/powders.js"></script> <script type="text/javascript" src="/js/powders.js"></script>
<script type="text/javascript" src="/js/damage_calc.js"></script> <script type="text/javascript" src="/js/damage_calc.js"></script>
@ -84,6 +85,7 @@
<script type="text/javascript" src="/js/query.js"></script> <script type="text/javascript" src="/js/query.js"></script>
<script type="text/javascript" src="/js/expr_parser.js"></script> <script type="text/javascript" src="/js/expr_parser.js"></script>
<script type="text/javascript" src="/js/load.js"></script> <script type="text/javascript" src="/js/load.js"></script>
<script type="text/javascript" src="/js/search_adv.js"></script>
<script type="text/javascript" src="/js/items_adv.js"></script> <script type="text/javascript" src="/js/items_adv.js"></script>
</body> </body>
</html> </html>

View file

@ -13,9 +13,9 @@
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="/thirdparty/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css"> <link rel="stylesheet" href="/thirdparty/autoComplete.min.css">
<link rel="stylesheet" href="../css/article.css"> <link rel="stylesheet" href="../css/article.css">
@ -33,6 +33,8 @@
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a> <a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a>
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a> <a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
<hr/> <hr/>
<a href = "https://nori.fish/wynn/build/" target = "_blank"><img src = "../media/icons/new/nori_build.png" alt = "Build Search" title = "Build Search by Nori-Wynn"><b>Build Search</b></a>
<a href = "https://nori.fish/wynn/recipe/" target = "_blank"><img src = "../media/icons/new/nori_recipe.png" alt = "Recipe Search" title = "Recipe Search by Nori-Wynn"><b>Recipe Search</b></a>
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a> <a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
</div> </div>
<main> <main>
@ -152,7 +154,7 @@
['waterDmg%', 'number', ['waterDam%', 'wDmg%', 'wDam%', 'wDamPct'], 'The bonus water damage modifier on an item.'], ['waterDmg%', 'number', ['waterDam%', 'wDmg%', 'wDam%', 'wDamPct'], 'The bonus water damage modifier on an item.'],
['fireDmg%', 'number', ['fireDam%', 'fDmg%', 'fDam%', 'fDamPct'], 'The bonus fire damage modifier on an item.'], ['fireDmg%', 'number', ['fireDam%', 'fDmg%', 'fDam%', 'fDamPct'], 'The bonus fire damage modifier on an item.'],
['airDmg%', 'number', ['airDam%', 'aDmg%', 'aDam%', 'aDamPct'], 'The bonus air damage modifier on an item.'], ['airDmg%', 'number', ['airDam%', 'aDmg%', 'aDam%', 'aDamPct'], 'The bonus air damage modifier on an item.'],
['sumDmg%', 'number', ['sumDam%', 'totalDmg%', 'totalDam%', 'sumDamPct', 'totalDamPct'], 'The sum of the bonus elemental damage modifiers on an item.'], //['sumDmg%', 'number', ['sumDam%', 'totalDmg%', 'totalDam%', 'sumDamPct', 'totalDamPct'], 'The sum of the bonus elemental damage modifiers on an item.'],
['meleeDmg', 'number', ['meleeDam', 'meleeDmg%', 'meleeDam%', 'mdPct'], 'The bonus main-attack damage modifier on an item.'], ['meleeDmg', 'number', ['meleeDam', 'meleeDmg%', 'meleeDam%', 'mdPct'], 'The bonus main-attack damage modifier on an item.'],
['meleeNeutralDmg', 'number', ['meleeRawDam', 'meleeNeutralDmg', 'meleeNeutralDam', 'mdRaw'], 'The neutral main-attack damage on an item.'], ['meleeNeutralDmg', 'number', ['meleeRawDam', 'meleeNeutralDmg', 'meleeNeutralDam', 'mdRaw'], 'The neutral main-attack damage on an item.'],
['spellDmg', 'number', ['spellDam', 'spellDmg%', 'spellDam%', 'sdPct'], 'The bonus spell damage modifier on an item.'], ['spellDmg', 'number', ['spellDam', 'spellDmg%', 'spellDam%', 'sdPct'], 'The bonus spell damage modifier on an item.'],

196538
items_clean.json Normal file

File diff suppressed because it is too large Load diff

1
items_compress.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -60,7 +60,15 @@ const armorTypes = [ "helmet", "chestplate", "leggings", "boots" ];
const accessoryTypes = [ "ring", "bracelet", "necklace" ]; const accessoryTypes = [ "ring", "bracelet", "necklace" ];
const weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ]; const weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ];
const consumableTypes = [ "potion", "scroll", "food"]; const consumableTypes = [ "potion", "scroll", "food"];
const tome_types = ['weaponTome', 'armorTome', 'guildTome']; const tome_types = ['weaponTome', 'armorTome', 'guildTome', 'lootrunTome', 'gatherXpTome', 'dungeonXpTome', 'mobXpTome'];
const tome_type_map = new Map([["weaponTome", "Weapon Tome"],
["armorTome", "Armor Tome"],
["guildTome", "Guild Tome"],
["gatherXpTome", "Gather XP Tome"],
["dungeonXpTome", "Dungeon XP Tome"],
["mobXpTome", "Slaying XP Tome"],
]);
const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY_FAST", "SUPER_FAST"]; const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY_FAST", "SUPER_FAST"];
const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ]; const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ];
//0.51, 0.82, 1.50, 2.05, 2.50, 3.11, 4.27 //0.51, 0.82, 1.50, 2.05, 2.50, 3.11, 4.27
@ -75,7 +83,10 @@ let item_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(to
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ]; let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
let skpReqs = skp_order.map(x => x + "Req"); let skpReqs = skp_order.map(x => x + "Req");
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs", let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw",
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
"fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
// wynn2 damages. // wynn2 damages.
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw","eDamAddMin","eDamAddMax", "eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw","eDamAddMin","eDamAddMax",
@ -87,17 +98,20 @@ let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slot
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional. /*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw "rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
"critDamPct", "critDamPct",
"spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final" "spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final",
"healPct", "kb", "weakenEnemy", "slowEnemy", "rDefPct"
]; ];
// Extra fake IDs (reserved for use in spell damage calculation) : damMult, defMult, poisonPct, activeMajorIDs // Extra fake IDs (reserved for use in spell damage calculation) : damMult, defMult, poisonPct, activeMajorIDs
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ] let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ]
//File reading for ID translations for JSON purposes //File reading for ID translations for JSON purposes
let reversetranslations = new Map(); let reversetranslations = new Map();
let translations = new Map([["name", "name"],["displayName", "displayName"],["tier", "tier"],["set", "set"],["sockets", "slots"],["type", "type"],["armorColor", "color"],["addedLore", "lore"],["dropType", "drop"],["quest", "quest"],["restrictions", "restrict"],["damage", "nDam"],["fireDamage", "fDam"],["waterDamage", "wDam"],["airDamage", "aDam"],["thunderDamage", "tDam"],["earthDamage", "eDam"],["attackSpeed", "atkSpd"],["health", "hp"],["fireDefense", "fDef"],["waterDefense", "wDef"],["airDefense", "aDef"],["thunderDefense", "tDef"],["earthDefense", "eDef"],["level", "lvl"],["classRequirement", "classReq"],["strength", "strReq"],["dexterity", "dexReq"],["intelligence", "intReq"],["agility", "agiReq"],["defense", "defReq"],["healthRegen", "hprPct"],["manaRegen", "mr"],["spellDamageBonus", "sdPct"],["spellElementalDamageBonus", "rSdPct"],["spellNeutralDamageBonus", "nSdPct"],["spellFireDamageBonus", "fSdPct"],["spellWaterDamageBonus", "wSdPct"],["spellAirDamageBonus", "aSdPct"],["spellThunderDamageBonus", "tSdPct"],["spellEarthDamageBonus", "eSdPct"],["mainAttackDamageBonus", "mdPct"],["mainAttackElementalDamageBonus", "rMdPct"],["mainAttackNeutralDamageBonus", "nMdPct"],["mainAttackFireDamageBonus", "fMdPct"],["mainAttackWaterDamageBonus", "wMdPct"],["mainAttackAirDamageBonus", "aMdPct"],["mainAttackThunderDamageBonus", "tMdPct"],["mainAttackEarthDamageBonus", "eMdPct"],["lifeSteal", "ls"],["manaSteal", "ms"],["xpBonus", "xpb"],["lootBonus", "lb"],["reflection", "ref"],["strengthPoints", "str"],["dexterityPoints", "dex"],["intelligencePoints", "int"],["agilityPoints", "agi"],["defensePoints", "def"],["thorns", "thorns"],["exploding", "expd"],["speed", "spd"],["attackSpeedBonus", "atkTier"],["poison", "poison"],["healthBonus", "hpBonus"],["soulPoints", "spRegen"],["emeraldStealing", "eSteal"],["healthRegenRaw", "hprRaw"],["spellDamageBonusRaw", "sdRaw"],["spellElementalDamageBonusRaw", "rSdRaw"],["spellNeutralDamageBonusRaw", "nSdRaw"],["spellFireDamageBonusRaw", "fSdRaw"],["spellWaterDamageBonusRaw", "wSdRaw"],["spellAirDamageBonusRaw", "aSdRaw"],["spellThunderDamageBonusRaw", "tSdRaw"],["spellEarthDamageBonusRaw", "eSdRaw"],["mainAttackDamageBonusRaw", "mdRaw"],["mainAttackElementalDamageBonusRaw", "rMdRaw"],["mainAttackNeutralDamageBonusRaw", "nMdRaw"],["mainAttackFireDamageBonusRaw", "fMdRaw"],["mainAttackWaterDamageBonusRaw", "wMdRaw"],["mainAttackAirDamageBonusRaw", "aMdRaw"],["mainAttackThunderDamageBonusRaw", "tMdRaw"],["mainAttackEarthDamageBonusRaw", "eMdRaw"],["fireDamageBonus", "fDamPct"],["waterDamageBonus", "wDamPct"],["airDamageBonus", "aDamPct"],["thunderDamageBonus", "tDamPct"],["earthDamageBonus", "eDamPct"],["bonusFireDefense", "fDefPct"],["bonusWaterDefense", "wDefPct"],["bonusAirDefense", "aDefPct"],["bonusThunderDefense", "tDefPct"],["bonusEarthDefense", "eDefPct"],["accessoryType", "type"],["identified", "fixID"],["skin", "skin"],["category", "category"],["spellCostPct1", "spPct1"],["spellCostRaw1", "spRaw1"],["spellCostPct2", "spPct2"],["spellCostRaw2", "spRaw2"],["spellCostPct3", "spPct3"],["spellCostRaw3", "spRaw3"],["spellCostPct4", "spPct4"],["spellCostRaw4", "spRaw4"],["sprint", "sprint"],["sprintRegen", "sprintReg"],["jumpHeight", "jh"],["lootQuality", "lq"],["gatherXpBonus", "gXp"],["gatherSpeed", "gSpd"]]); let _translations_list = [["name", "name"],["displayName", "displayName"],["tier", "tier"],["set", "set"],["sockets", "slots"],["type", "type"],["armorColor", "color"],["addedLore", "lore"],["dropType", "drop"],["quest", "quest"],["restrictions", "restrict"],["damage", "nDam"],["fireDamage", "fDam"],["waterDamage", "wDam"],["airDamage", "aDam"],["thunderDamage", "tDam"],["earthDamage", "eDam"],["attackSpeed", "atkSpd"],["health", "hp"],["fireDefense", "fDef"],["waterDefense", "wDef"],["airDefense", "aDef"],["thunderDefense", "tDef"],["earthDefense", "eDef"],["level", "lvl"],["classRequirement", "classReq"],["strength", "strReq"],["dexterity", "dexReq"],["intelligence", "intReq"],["agility", "agiReq"],["defense", "defReq"],["healthRegen", "hprPct"],["manaRegen", "mr"],["spellDamageBonus", "sdPct"],["spellElementalDamageBonus", "rSdPct"],["spellNeutralDamageBonus", "nSdPct"],["spellFireDamageBonus", "fSdPct"],["spellWaterDamageBonus", "wSdPct"],["spellAirDamageBonus", "aSdPct"],["spellThunderDamageBonus", "tSdPct"],["spellEarthDamageBonus", "eSdPct"],["mainAttackDamageBonus", "mdPct"],["mainAttackElementalDamageBonus", "rMdPct"],["mainAttackNeutralDamageBonus", "nMdPct"],["mainAttackFireDamageBonus", "fMdPct"],["mainAttackWaterDamageBonus", "wMdPct"],["mainAttackAirDamageBonus", "aMdPct"],["mainAttackThunderDamageBonus", "tMdPct"],["mainAttackEarthDamageBonus", "eMdPct"],["lifeSteal", "ls"],["manaSteal", "ms"],["xpBonus", "xpb"],["lootBonus", "lb"],["reflection", "ref"],["strengthPoints", "str"],["dexterityPoints", "dex"],["intelligencePoints", "int"],["agilityPoints", "agi"],["defensePoints", "def"],["thorns", "thorns"],["exploding", "expd"],["speed", "spd"],["attackSpeedBonus", "atkTier"],["poison", "poison"],["healthBonus", "hpBonus"],["soulPoints", "spRegen"],["emeraldStealing", "eSteal"],["healthRegenRaw", "hprRaw"],["spellDamageBonusRaw", "sdRaw"],["spellElementalDamageBonusRaw", "rSdRaw"],["spellNeutralDamageBonusRaw", "nSdRaw"],["spellFireDamageBonusRaw", "fSdRaw"],["spellWaterDamageBonusRaw", "wSdRaw"],["spellAirDamageBonusRaw", "aSdRaw"],["spellThunderDamageBonusRaw", "tSdRaw"],["spellEarthDamageBonusRaw", "eSdRaw"],["mainAttackDamageBonusRaw", "mdRaw"],["mainAttackElementalDamageBonusRaw", "rMdRaw"],["mainAttackNeutralDamageBonusRaw", "nMdRaw"],["mainAttackFireDamageBonusRaw", "fMdRaw"],["mainAttackWaterDamageBonusRaw", "wMdRaw"],["mainAttackAirDamageBonusRaw", "aMdRaw"],["mainAttackThunderDamageBonusRaw", "tMdRaw"],["mainAttackEarthDamageBonusRaw", "eMdRaw"],["fireDamageBonus", "fDamPct"],["waterDamageBonus", "wDamPct"],["airDamageBonus", "aDamPct"],["thunderDamageBonus", "tDamPct"],["earthDamageBonus", "eDamPct"],["bonusFireDefense", "fDefPct"],["bonusWaterDefense", "wDefPct"],["bonusAirDefense", "aDefPct"],["bonusThunderDefense", "tDefPct"],["bonusEarthDefense", "eDefPct"],["accessoryType", "type"],["identified", "fixID"],["skin", "skin"],["category", "category"],["spellCostPct1", "spPct1"],["spellCostRaw1", "spRaw1"],["spellCostPct2", "spPct2"],["spellCostRaw2", "spRaw2"],["spellCostPct3", "spPct3"],["spellCostRaw3", "spRaw3"],["spellCostPct4", "spPct4"],["spellCostRaw4", "spRaw4"],["sprint", "sprint"],["sprintRegen", "sprintReg"],["jumpHeight", "jh"],["lootQuality", "lq"],["gatherXpBonus", "gXp"],["gatherSpeed", "gSpd"],["healingEfficiency", "healPct"], ["knockback", "kb"], ["weakenEnemy", "weakenEnemy"], ["slowEnemy", "slowEnemy"], ["elementalDefense", "rDefPct"]];
let translations = new Map(_translations_list);
//does not include damMobs (wep tomes) and defMobs (armor tomes) //does not include damMobs (wep tomes) and defMobs (armor tomes)
for (const [k, v] of translations) { for (const [k, v] of _translations_list) {
if (reversetranslations.has(v)) { continue; }
reversetranslations.set(v, k); reversetranslations.set(v, k);
} }
@ -174,10 +188,13 @@ let rolledIDs = [
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element "nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional. /*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw "rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
"spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final" "spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final",
"healPct", "kb", "weakenEnemy", "slowEnemy", "rDefPct"
]; ];
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];
let ingFields = rolledIDs.concat(["str", "dex", "int", "def", "agi"]);
/** /**
* Take an item with id list and turn it into a set of minrolls and maxrolls. * Take an item with id list and turn it into a set of minrolls and maxrolls.
*/ */
@ -195,28 +212,16 @@ function expandItem(item) {
} else { //The item does not have fixed IDs. } else { //The item does not have fixed IDs.
for (const id of rolledIDs) { for (const id of rolledIDs) {
let val = (item[id] || 0); let val = (item[id] || 0);
if (val > 0) { // positive rolled IDs if (val == 0) {
if (reversedIDs.includes(id)) {
maxRolls.set(id,idRound(val*0.7));
minRolls.set(id,idRound(val*1.3));
} else {
maxRolls.set(id,idRound(val*1.3));
minRolls.set(id,idRound(val*0.3));
}
} else if (val < 0) { //negative rolled IDs
if (reversedIDs.includes(id)) {
maxRolls.set(id,idRound(val*1.3));
minRolls.set(id,idRound(val*0.3));
}
else {
maxRolls.set(id,idRound(val*0.7));
minRolls.set(id,idRound(val*1.3));
}
}
else { // if val == 0
// NOTE: DO NOT remove this case! idRound behavior does not round to 0! // NOTE: DO NOT remove this case! idRound behavior does not round to 0!
maxRolls.set(id,0); maxRolls.set(id,0);
minRolls.set(id,0); minRolls.set(id,0);
} else if ((val > 0) != (reversedIDs.includes(id))) { // logical XOR. positive IDs
maxRolls.set(id,idRound(val*1.3));
minRolls.set(id,idRound(val*0.3));
} else { //negative rolled IDs
maxRolls.set(id,idRound(val*0.7));
minRolls.set(id,idRound(val*1.3));
} }
} }
} }
@ -300,7 +305,7 @@ function expandRecipe(recipe) {
function idRound(id){ function idRound(id){
rounded = Math.round(id); rounded = Math.round(id);
if(rounded == 0){ if(rounded == 0){
return 1; //this is a hack, will need changing along w/ rest of ID system if anything changes return Math.sign(id); //this is a hack, will need changing along w/ rest of ID system if anything changes
}else{ }else{
return rounded; return rounded;
} }
@ -310,8 +315,8 @@ function idRound(id){
* stupid stupid multiplicative stats * stupid stupid multiplicative stats
*/ */
function merge_stat(stats, name, value) { function merge_stat(stats, name, value) {
const start = name.slice(0, 7); const start = name.split('.', limit=1)[0];
if (start === 'damMult' || start === 'defMult') { if (start === 'damMult' || start === 'defMult' || start === 'healMult') {
if (!stats.has(start)) { if (!stats.has(start)) {
stats.set(start, new Map()); stats.set(start, new Map());
} }
@ -322,7 +327,7 @@ function merge_stat(stats, name, value) {
} }
return; return;
} }
merge_stat(map, name.slice(8), value); merge_stat(map, name.slice(name.indexOf('.')+1), value);
return; return;
} }
if (stats.has(name)) { if (stats.has(name)) {

View file

@ -45,9 +45,10 @@ add_spell_prop: {
base_spell: int // spell identifier base_spell: int // spell identifier
target_part: Optional[str] // Part of the spell to modify. Can be not present/empty for ex. cost modifier. target_part: Optional[str] // Part of the spell to modify. Can be not present/empty for ex. cost modifier.
// If target part does not exist, a new part is created. // If target part does not exist, a new part is created.
behavior: Optional[str] // One of: "merge", "modify". default: merge behavior: Optional[str] // One of: "merge", "modify", "overwrite". default: merge
// merge: add if exist, make new part if not exist // merge: add if exist, make new part if not exist
// modify: increment existing part. do nothing if not exist // modify: increment existing part. do nothing if not exist
// overwrite: set part. do nothing if not exist
cost: Optional[int] // change to spellcost. If the spell is not spell 1-4, this must be left empty. cost: Optional[int] // change to spellcost. If the spell is not spell 1-4, this must be left empty.
multipliers: Optional[array[float, 6]] // Additive changes to spellmult (for damage spell) multipliers: Optional[array[float, 6]] // Additive changes to spellmult (for damage spell)
power: Optional[float] // Additive change to healing power (for heal spell) power: Optional[float] // Additive change to healing power (for heal spell)
@ -75,22 +76,23 @@ raw_stat: {
bonuses: List[stat_bonus] bonuses: List[stat_bonus]
} }
stat_bonus: { stat_bonus: {
"type": "stat" | "prop", type: "stat" | "prop",
"abil": Optional[int], abil: Optional[int],
"name": str, name: str,
"value": float value: float
} }
stat_scaling: { stat_scaling: {
"type": "stat_scaling", type: "stat_scaling",
"slider": bool, slider: bool,
positive: bool // True to keep stat above 0. False to ignore floor. Default: True for normal, False for scaling positive: bool // True to keep stat above 0. False to ignore floor. Default: True for normal, False for scaling
"slider_name": Optional[str], slider_name: Optional[str],
"slider_step": Optional[float], slider_step: Optional[float],
round: Optional[bool] // Control floor behavior. True for stats and false for slider by default round: Optional[bool] // Control floor behavior. True for stats and false for slider by default
slider_behavior: Optional[str] // One of: "merge", "modify". default: merge behavior: Optional[str] // One of: "merge", "modify". default: merge
// merge: add if exist, make new part if not exist // merge: add if exist, make new part if not exist
// modify: change existing part, by incrementing properties. do nothing if not exist // modify: change existing part, by incrementing properties. do nothing if not exist
slider_max: Optional[float] // affected by slider_behavior slider_max: Optional[int] // affected by behavior
slider_default: Optional[int] // affected by behavior
inputs: Optional[list[scaling_target]] // List of things to scale. Omit this if using slider inputs: Optional[list[scaling_target]] // List of things to scale. Omit this if using slider
output: Optional[scaling_target | List[scaling_target]] // One of the following: output: Optional[scaling_target | List[scaling_target]] // One of the following:
@ -98,12 +100,12 @@ stat_scaling: {
// 2. List of scaling targets (all scaled the same) // 2. List of scaling targets (all scaled the same)
// 3. Omitted. no output (useful for modifying slider only without input or output) // 3. Omitted. no output (useful for modifying slider only without input or output)
scaling: Optional[list[float]] // One float for each input. Sums into output. scaling: Optional[list[float]] // One float for each input. Sums into output.
max: float max: float // Hardcap on this effect (slider value * slider_step). Can be negative if scaling is negative
} }
scaling_target: { scaling_target: {
"type": "stat" | "prop", type: "stat" | "prop",
"abil": Optional[int], abil: Optional[int],
"name": str name: str
} }
*/ */
@ -164,6 +166,64 @@ const default_abils = {
}, elem_mastery_abil ], }, elem_mastery_abil ],
}; };
/**
* Given a json of raw atree data, and a wynn class name (ex. Archer, Mage),
* sort out their ability tree and return it as a list in roughly topologically
* sorted order.
*
* TODO: Why do we care about the toposort?
* This is not a useful representation of the tree.
* It's useless.
* atree needs a cleanup and anything depending on the ordering of items
* coming out of this function is probably doing something wrong.
* NOTE2: Actually this might be more complicated because some nodes do need
* an application ordering and that matters (esp. replace_spell nodes).
*
* Parameters:
* --------------------------
* atrees: Raw atree data. This is a parameter to allow oldversion shenanigans
* player_class: Wynn class name (string)
*
* Return:
* List of atree nodes.
*/
function get_sorted_class_atree(atrees, player_class) {
const atree_raw = atrees[player_class];
if (!atree_raw) return [];
let atree_map = new Map();
let atree_head;
for (const i of atree_raw) {
atree_map.set(i.id, {children: [], ability: i});
if (i.parents.length == 0) {
// Assuming there is only one head.
atree_head = atree_map.get(i.id);
}
}
for (const i of atree_raw) {
let node = atree_map.get(i.id);
let parents = [];
for (const parent_id of node.ability.parents) {
let parent_node = atree_map.get(parent_id);
parent_node.children.push(node);
parents.push(parent_node);
}
node.parents = parents;
}
let sccs = make_SCC_graph(atree_head, atree_map.values());
let atree_topo_sort = [];
for (const scc of sccs) {
for (const node of scc.nodes) {
delete node.visited;
delete node.assigned;
delete node.scc;
atree_topo_sort.push(node);
}
}
return atree_topo_sort;
}
/** /**
* Update ability tree internal representation. (topologically sorted node list) * Update ability tree internal representation. (topologically sorted node list)
* *
@ -175,43 +235,7 @@ const atree_node = new (class extends ComputeNode {
compute_func(input_map) { compute_func(input_map) {
if (input_map.size !== 1) { throw "AbilityTreeUpdateNode accepts exactly one input (player-class)"; } if (input_map.size !== 1) { throw "AbilityTreeUpdateNode accepts exactly one input (player-class)"; }
const [player_class] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element const [player_class] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
return get_sorted_class_atree(atrees, player_class);
const atree_raw = atrees[player_class];
if (!atree_raw) return [];
let atree_map = new Map();
let atree_head;
for (const i of atree_raw) {
atree_map.set(i.id, {children: [], ability: i});
if (i.parents.length == 0) {
// Assuming there is only one head.
atree_head = atree_map.get(i.id);
}
}
for (const i of atree_raw) {
let node = atree_map.get(i.id);
let parents = [];
for (const parent_id of node.ability.parents) {
let parent_node = atree_map.get(parent_id);
parent_node.children.push(node);
parents.push(parent_node);
}
node.parents = parents;
}
let sccs = make_SCC_graph(atree_head, atree_map.values());
let atree_topo_sort = [];
for (const scc of sccs) {
for (const node of scc.nodes) {
delete node.visited;
delete node.assigned;
delete node.scc;
atree_topo_sort.push(node);
}
}
//console.log("Approximate topological order ability tree:");
//console.log(atree_topo_sort);
return atree_topo_sort;
} }
})(); })();
@ -421,6 +445,7 @@ const atree_merge = new (class extends ComputeNode {
const [hard_error, errors] = input_map.get('atree-errors'); const [hard_error, errors] = input_map.get('atree-errors');
if (hard_error) { return null; } if (hard_error) { return null; }
const player_class = input_map.get('player-class'); const player_class = input_map.get('player-class');
const build = input_map.get('build');
const atree_state = input_map.get('atree-state'); const atree_state = input_map.get('atree-state');
const atree_order = input_map.get('atree'); const atree_order = input_map.get('atree');
@ -433,26 +458,20 @@ const atree_merge = new (class extends ComputeNode {
else if (!Array.isArray(tmp_abil.desc)) { else if (!Array.isArray(tmp_abil.desc)) {
tmp_abil.desc = [tmp_abil.desc]; tmp_abil.desc = [tmp_abil.desc];
} }
tmp_abil.subparts = [abil.id];
abils_merged.set(abil.id, tmp_abil); abils_merged.set(abil.id, tmp_abil);
} }
for (const node of atree_order) { function merge_abil(abil) {
const abil_id = node.ability.id;
if (!atree_state.get(abil_id).active) {
continue;
}
const abil = node.ability;
if ('base_abil' in abil) { if ('base_abil' in abil) {
if (abils_merged.has(abil.base_abil)) { if (abils_merged.has(abil.base_abil)) {
// Merge abilities. // Merge abilities.
// TODO: What if there is more than one base abil? // TODO: What if there is more than one base abil?
let base_abil = abils_merged.get(abil.base_abil); let base_abil = abils_merged.get(abil.base_abil);
if (Array.isArray(abil.desc)) { base_abil.desc = base_abil.desc.concat(abil.desc); } if (abil.desc) {
else { base_abil.desc.push(abil.desc); } if (Array.isArray(abil.desc)) { base_abil.desc = base_abil.desc.concat(abil.desc); }
else { base_abil.desc.push(abil.desc); }
}
base_abil.subparts.push(abil.id);
base_abil.effects = base_abil.effects.concat(abil.effects); base_abil.effects = base_abil.effects.concat(abil.effects);
for (let propname in abil.properties) { for (let propname in abil.properties) {
if (propname in base_abil.properties) { if (propname in base_abil.properties) {
@ -468,10 +487,54 @@ const atree_merge = new (class extends ComputeNode {
if (!Array.isArray(tmp_abil.desc)) { if (!Array.isArray(tmp_abil.desc)) {
tmp_abil.desc = [tmp_abil.desc]; tmp_abil.desc = [tmp_abil.desc];
} }
tmp_abil.subparts = [abil.id]; abils_merged.set(abil.id, tmp_abil);
abils_merged.set(abil_id, tmp_abil);
} }
} }
for (const node of atree_order) {
const abil_id = node.ability.id;
if (!atree_state.get(abil_id).active) {
continue;
}
merge_abil(node.ability);
}
// Apply major IDs.
const build_class = wep_to_class.get(build.weapon.statMap.get("type"));
for (const major_id_name of build.statMap.get("activeMajorIDs")) {
// Sometimes, something silly happens and we haven't implemented a major ID that
// exists. This makes sure we don't try to apply unimplemented major IDs.
//
// `major_ids` is a global map loaded from data json.
if (major_id_name in major_ids) {
// A major ID can have multiple abilities, specified as atree nodes,
// as part of its effects. Apply each of them.
for (const abil of major_ids[major_id_name].abilities) {
// But only the ones that match the current class.
if (abil["class"] === build_class) {
// Major IDs can have ability dependencies.
// By default they are always on.
if (abil.dependencies !== undefined) {
let dep_satisfied = true;
for (const dep_id of abil.dependencies) {
if (!atree_state.get(dep_id).active) {
dep_satisfied = false;
break;
}
}
if (!dep_satisfied) { continue; }
}
merge_abil(abil);
}
}
}
}
console.log(abils_merged)
return abils_merged; return abils_merged;
} }
})().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state').link_to(atree_validate, 'atree-errors'); })().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state').link_to(atree_validate, 'atree-errors');
@ -511,27 +574,43 @@ const atree_make_interactives = new (class extends ComputeNode {
const slider_map = new Map(); const slider_map = new Map();
const button_map = new Map(); const button_map = new Map();
// first, pull out all the sliders and toggles. let to_process = [];
for (const [abil_id, ability] of merged_abils.entries()) { for (const [abil_id, ability] of merged_abils) {
for (const effect of ability.effects) { for (const effect of ability.effects) {
if (effect['type'] === "stat_scaling" && effect['slider'] === true) { if (effect['type'] === "stat_scaling" && effect['slider'] === true) {
const { slider_name, slider_behavior = 'merge', slider_max, slider_step } = effect; to_process.push([effect, abil_id, ability]);
}
if (effect['type'] === "raw_stat" && effect['toggle']) {
to_process.push([effect, abil_id, ability]);
}
}
}
let unprocessed = [];
// first, pull out all the sliders and toggles.
let k = to_process.length;
for (let i = 0; i < k; ++i) {
for (const [effect, abil_id, ability] of to_process) {
if (effect['type'] === "stat_scaling" && effect['slider'] === true) {
const { slider_name, behavior = 'merge', slider_max = 0, slider_step, slider_default = 0 } = effect;
if (slider_map.has(slider_name)) { if (slider_map.has(slider_name)) {
if (slider_max !== undefined) { const slider_info = slider_map.get(slider_name);
const slider_info = slider_map.get(slider_name); slider_info.max += slider_max;
slider_info.max += slider_max; slider_info.default_val += slider_default;
}
} }
else if (slider_behavior === 'merge') { else if (behavior === 'merge') {
slider_map.set(slider_name, { slider_map.set(slider_name, {
label_name: slider_name+' ('+ability.display_name+')', label_name: slider_name+' ('+ability.display_name+')',
max: slider_max, max: slider_max,
default_val: slider_default,
step: slider_step, step: slider_step,
id: "ability-slider"+ability.id, id: "ability-slider"+ability.id,
//color: effect['slider_color'] TODO: add colors to json //color: effect['slider_color'] TODO: add colors to json
abil: ability abil: ability
}); });
} }
else {
unprocessed.push([effect, abil_id, ability]);
}
} }
if (effect['type'] === "raw_stat" && effect['toggle']) { if (effect['type'] === "raw_stat" && effect['toggle']) {
const { toggle: toggle_name } = effect; const { toggle: toggle_name } = effect;
@ -540,6 +619,9 @@ const atree_make_interactives = new (class extends ComputeNode {
}); });
} }
} }
if (unprocessed.length == to_process.length) { break; }
to_process = unprocessed;
unprocessed = [];
} }
// next, render the sliders and toggles onto the abilities. // next, render the sliders and toggles onto the abilities.
for (const [slider_name, slider_info] of slider_map.entries()) { for (const [slider_name, slider_info] of slider_map.entries()) {
@ -623,9 +705,13 @@ const atree_scaling = new (class extends ComputeNode {
continue; continue;
case 'stat_scaling': case 'stat_scaling':
let total = 0; let total = 0;
const {slider = false, scaling = [0]} = effect; const {slider = false, scaling = [0], behavior="merge"} = effect;
let { positive = true, round = true } = effect; let { positive = true, round = true } = effect;
if (slider) { if (slider) {
if (behavior == "modify" && !slider_map.has(effect.slider_name)) {
// Dangerous control flow.. early continue
continue;
}
const slider_val = slider_map.get(effect.slider_name).slider.value; const slider_val = slider_map.get(effect.slider_name).slider.value;
total = parseInt(slider_val) * scaling[0]; total = parseInt(slider_val) * scaling[0];
round = false; round = false;
@ -641,7 +727,10 @@ const atree_scaling = new (class extends ComputeNode {
if ('output' in effect) { // sometimes nodes will modify slider without having effect. if ('output' in effect) { // sometimes nodes will modify slider without having effect.
if (round) { total = Math.floor(round_near(total)); } if (round) { total = Math.floor(round_near(total)); }
if (positive && total < 0) { total = 0; } // Normal stat scaling will not go negative. if (positive && total < 0) { total = 0; } // Normal stat scaling will not go negative.
if ('max' in effect && total > effect.max) { total = effect.max; } if ('max' in effect) {
if (effect.max > 0 && total > effect.max) { total = effect.max; }
if (effect.max < 0 && total < effect.max) { total = effect.max; }
}
if (Array.isArray(effect.output)) { if (Array.isArray(effect.output)) {
for (const output of effect.output) { for (const output of effect.output) {
apply_bonus(output, total); apply_bonus(output, total);
@ -814,33 +903,45 @@ const atree_collect_spells = new (class extends ComputeNode {
continue; continue;
case 'add_spell_prop': { case 'add_spell_prop': {
const { base_spell, target_part = null, cost = 0, behavior = 'merge'} = effect; const { base_spell, target_part = null, cost = 0, behavior = 'merge'} = effect;
const ret_spell = ret_spells.get(base_spell); if (!ret_spells.has(base_spell)) {
// TODO: unjankify this...
if ('cost' in ret_spell) { ret_spell.cost += cost; }
if (target_part === null) {
continue; continue;
} }
const ret_spell = ret_spells.get(base_spell);
// :enraged:
// NOTE to hpp: this is out here because:
// target_part doesn't exist for spell cost modification abilities
// except when it does... in which case it should apply exactly once.
if ('cost' in ret_spell) { ret_spell.cost += cost; }
// NOTE: see above comment for the weird placement of this code block.
if (target_part === null) { continue; }
let found_part = false; let found_part = false;
for (let part of ret_spell.parts) { // TODO: replace with Map? to avoid this linear search... idk prolly good since its not more verbose to type in json for (let part of ret_spell.parts) { // TODO: replace with Map? to avoid this linear search... idk prolly good since its not more verbose to type in json
if (part.name !== target_part) { if (part.name !== target_part) {
continue; continue;
} }
// we found the part. merge or modify it!
if ('multipliers' in effect) { if ('multipliers' in effect) {
for (const [idx, v] of effect.multipliers.entries()) { // python: enumerate() for (const [idx, v] of effect.multipliers.entries()) { // python: enumerate()
part.multipliers[idx] += v; if (behavior === 'overwrite') { part.multipliers[idx] = v; }
else { part.multipliers[idx] += v; }
} }
} }
else if ('power' in effect) { else if ('power' in effect) {
part.power += effect.power; if (behavior === 'overwrite') { part.power = effect.power; }
else { part.power += effect.power; }
} }
else if ('hits' in effect) { else if ('hits' in effect) {
for (const [idx, _v] of Object.entries(effect.hits)) { // looks kinda similar to multipliers case... hmm... can we unify all of these three? (make healpower a list) for (const [idx, _v] of Object.entries(effect.hits)) { // looks kinda similar to multipliers case... hmm... can we unify all of these three? (make healpower a list)
let v = translate(_v); let v = translate(_v);
if (idx in part.hits) { part.hits[idx] += v; } if (behavior === 'overwrite') { part.hits[idx] = v; }
else { part.hits[idx] = v; } else {
if (idx in part.hits) { part.hits[idx] += v; }
else { part.hits[idx] = v; }
}
} }
} }
else { else {
@ -864,6 +965,7 @@ const atree_collect_spells = new (class extends ComputeNode {
} }
continue; continue;
} }
// NOTE: Legacy support
case 'convert_spell_conv': case 'convert_spell_conv':
const { base_spell, target_part, conversion } = effect; const { base_spell, target_part, conversion } = effect;
const ret_spell = ret_spells.get(base_spell); const ret_spell = ret_spells.get(base_spell);
@ -964,13 +1066,9 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
// TODO shortcut update path for sliders // TODO shortcut update path for sliders
for (const [spell_id, spell] of new Map([...spell_map].sort((a, b) => a[0] - b[0])).entries()) { for (const [spell_id, spell] of new Map([...spell_map].sort((a, b) => a[0] - b[0])).entries()) {
let spell_node = new SpellSelectNode(spell) let calc_node = new SpellDamageCalcNode(spell)
.link_to(build_node, 'build');
let calc_node = new SpellDamageCalcNode(spell.base_spell)
.link_to(build_node, 'build') .link_to(build_node, 'build')
.link_to(stat_agg_node, 'stats') .link_to(stat_agg_node, 'stats');
.link_to(spell_node, 'spell-info');
this.spelldmg_nodes.push(calc_node); this.spelldmg_nodes.push(calc_node);
let display_elem = make_elem('div', ["col", "pe-0"]); let display_elem = make_elem('div', ["col", "pe-0"]);
@ -982,9 +1080,8 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
display_elem.append(spell_summary, spell_detail); display_elem.append(spell_summary, spell_detail);
let display_node = new SpellDisplayNode(spell.base_spell) let display_node = new SpellDisplayNode(spell)
.link_to(stat_agg_node, 'stats') .link_to(stat_agg_node, 'stats')
.link_to(spell_node, 'spell-info')
.link_to(calc_node, 'spell-damage'); .link_to(calc_node, 'spell-damage');
this.spell_display_elem.appendChild(display_elem); this.spell_display_elem.appendChild(display_elem);

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -10,7 +10,7 @@ const classDefenseMultipliers = new Map([ ["relik",0.60], ["bow",0.70], ["wand",
/* /*
* Class that represents a wynn player's build. * Class that represents a wynn player's build.
*/ */
class Build{ class Build {
/** /**
* @description Construct a build. * @description Construct a build.
@ -20,6 +20,7 @@ class Build{
* @param {Item} weapon: Weapon that this build is using. * @param {Item} weapon: Weapon that this build is using.
*/ */
constructor(level, items, weapon){ constructor(level, items, weapon){
if (level < 1) { //Should these be constants? if (level < 1) { //Should these be constants?
this.level = 1; this.level = 1;
} else if (level > 106) { } else if (level > 106) {
@ -78,7 +79,8 @@ class Build{
"aMdPct","aMdRaw","aSdPct","aSdRaw","aDamPct","aDamRaw","aDamAddMin","aDamAddMax", "aMdPct","aMdRaw","aSdPct","aSdRaw","aDamPct","aDamRaw","aDamAddMin","aDamAddMax",
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element "nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
"mdPct","mdRaw","sdPct","sdRaw","damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional. "mdPct","mdRaw","sdPct","sdRaw","damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct","rSdRaw","rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw "rMdPct","rMdRaw","rSdPct","rSdRaw","rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
"healPct",
] ]
//Create a map of this build's stats //Create a map of this build's stats
@ -114,8 +116,8 @@ class Build{
} }
statMap.set('damMult', new Map()); statMap.set('damMult', new Map());
statMap.set('defMult', new Map()); statMap.set('defMult', new Map());
statMap.get('damMult').set('tome', statMap.get('damMobs')) statMap.get('damMult').set('tome', statMap.get('damMobs'));
statMap.get('defMult').set('tome', statMap.get('defMobs')) statMap.get('defMult').set('tome', statMap.get('defMobs'));
statMap.set("activeMajorIDs", major_ids); statMap.set("activeMajorIDs", major_ids);
for (const [setName, count] of this.activeSetCounts) { for (const [setName, count] of this.activeSetCounts) {
const bonus = sets.get(setName).bonuses[count-1]; const bonus = sets.get(setName).bonuses[count-1];
@ -130,7 +132,8 @@ class Build{
} }
statMap.set("poisonPct", 0); statMap.set("poisonPct", 0);
statMap.set("critDamPct", 0); statMap.set("critDamPct", 0);
statMap.set("healPct", 0); statMap.set("healMult", new Map());
statMap.get('healMult').set('item', statMap.get('healPct'));
// The stuff relevant for damage calculation!!! @ferricles // The stuff relevant for damage calculation!!! @ferricles
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd")); statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));

View file

@ -29,27 +29,46 @@ function parsePowdering(powder_info) {
let atree_data = null; let atree_data = null;
const wynn_version_names = [ const wynn_version_names = [
'2.0.1.1', '2.0.1.1',
'2.0.1.2' '2.0.1.2',
'2.0.2.1',
'2.0.2.3',
'2.0.3.1',
'2.0.4.1',
'2.0.4.3',
'2.0.4.4',
'2.1.0.0'
]; ];
const WYNN_VERSION_LATEST = wynn_version_names.length - 1; const WYNN_VERSION_LATEST = wynn_version_names.length - 1;
// Default to the newest version. // Default to the newest version.
let wynn_version_id = WYNN_VERSION_LATEST; let wynn_version_id = WYNN_VERSION_LATEST;
let major_ids = null;
let major_id_load_complete = false;
async function load_major_id_data(version_str) {
let getUrl = window.location;
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// No random string -- we want to use caching
let url = `${baseUrl}/data/${version_str}/majid.json`;
major_ids = await (await fetch(url)).json();
major_id_load_complete = true;
}
/* /*
* Populate fields based on url, and calculate build. * Populate fields based on url, and calculate build.
* TODO: THIS CODE IS GOD AWFUL result of being lazy * TODO: THIS CODE IS GOD AWFUL result of being lazy
* fix all the slice() and break into functions or do something about it... its inefficient, ugly and error prone * fix all the slice() and break into functions or do something about it... its inefficient, ugly and error prone
*/ */
async function parse_hash(url_tag) { async function parse_hash(url_tag) {
const default_load_promises = [ load_atree_data(wynn_version_names[WYNN_VERSION_LATEST]), let latest_ver_name = wynn_version_names[WYNN_VERSION_LATEST];
load_init(), load_ing_init(), load_tome_init() ]; const default_load_promises = [ load_atree_data(latest_ver_name), load_major_id_data(latest_ver_name),
load_init(), load_ing_init(), load_tome_init()];
if (!url_tag) { if (!url_tag) {
await Promise.all(default_load_promises); await Promise.all(default_load_promises);
return; return;
} }
//default values //default values
let equipment = [null, null, null, null, null, null, null, null, null]; let equipment = [null, null, null, null, null, null, null, null, null];
let tomes = [null, null, null, null, null, null, null]; let tomes = [null, null, null, null, null, null, null, null];
let powdering = ["", "", "", "", ""]; let powdering = ["", "", "", "", ""];
let info = url_tag.split("_"); let info = url_tag.split("_");
let version = info[0]; let version = info[0];
@ -96,6 +115,7 @@ async function parse_hash(url_tag) {
else { else {
version_name = wynn_version_names[wynn_version_id]; version_name = wynn_version_names[wynn_version_id];
const load_promises = [ load_atree_data(version_name), const load_promises = [ load_atree_data(version_name),
load_major_id_data(version_name),
load_old_version(version_name), load_old_version(version_name),
load_ings_old_version(version_name), load_ings_old_version(version_name),
load_tome_old_version(version_name) ]; load_tome_old_version(version_name) ];
@ -103,6 +123,7 @@ async function parse_hash(url_tag) {
await Promise.all(load_promises); await Promise.all(load_promises);
} }
} }
if (wynn_version_id == WYNN_VERSION_LATEST) { if (wynn_version_id == WYNN_VERSION_LATEST) {
await Promise.all(default_load_promises); await Promise.all(default_load_promises);
} }
@ -133,7 +154,7 @@ async function parse_hash(url_tag) {
} }
data_str = info_str.slice(start_idx); data_str = info_str.slice(start_idx);
} }
else if (version_number <= 8) { else if (version_number <= 9) {
let info_str = data_str; let info_str = data_str;
let start_idx = 0; let start_idx = 0;
for (let i = 0; i < 9; ++i ) { for (let i = 0; i < 9; ++i ) {
@ -174,7 +195,7 @@ async function parse_hash(url_tag) {
let powder_info = data_str.slice(10); let powder_info = data_str.slice(10);
let res = parsePowdering(powder_info); let res = parsePowdering(powder_info);
powdering = res[0]; powdering = res[0];
} else if (version_number <= 8){ } else if (version_number <= 9){
level = Base64.toInt(data_str.slice(10,12)); level = Base64.toInt(data_str.slice(10,12));
setValue("level-choice",level); setValue("level-choice",level);
save_skp = true; save_skp = true;
@ -193,7 +214,7 @@ async function parse_hash(url_tag) {
if (version_number >= 6) { if (version_number >= 6) {
//tome values do not appear in anything before v6. //tome values do not appear in anything before v6.
if (version_number < 8) { if (version_number < 8) {
for (let i in tomes) { for (let i = 0; i < 7; ++i) {
let tome_str = data_str.charAt(i); let tome_str = data_str.charAt(i);
let tome_name = getTomeNameFromID(Base64.toInt(tome_str)); let tome_name = getTomeNameFromID(Base64.toInt(tome_str));
setValue(tomeInputs[i], tome_name); setValue(tomeInputs[i], tome_name);
@ -202,12 +223,21 @@ async function parse_hash(url_tag) {
} }
else { else {
// 2chr tome encoding to allow for more tomes. // 2chr tome encoding to allow for more tomes.
for (let i in tomes) {
// Lootrun tome was added in v9.
let num_tomes = 7;
if (version_number <= 8) {
num_tomes = 7;
}
else {
num_tomes = 8;
}
for (let i = 0; i < num_tomes; ++i) {
let tome_str = data_str.slice(2*i, 2*i+2); let tome_str = data_str.slice(2*i, 2*i+2);
let tome_name = getTomeNameFromID(Base64.toInt(tome_str)); let tome_name = getTomeNameFromID(Base64.toInt(tome_str));
setValue(tomeInputs[i], tome_name); setValue(tomeInputs[i], tome_name);
} }
data_str = data_str.slice(14); data_str = data_str.slice(num_tomes*2);
} }
} }
@ -232,119 +262,80 @@ async function parse_hash(url_tag) {
/* Stores the entire build in a string using B64 encoding and adds it to the URL. /* Stores the entire build in a string using B64 encoding and adds it to the URL.
*/ */
function encodeBuild(build, powders, skillpoints, atree, atree_state) { function encodeBuild(build, powders, skillpoints, atree, atree_state) {
//currently on version 8 - a unified version for all build types using bit-level encoding
if (build) { if (build) {
//final link will be [build_vers]_[len_string]_[build_string] let build_string;
build_version = 8;
let len_string = "";
let build_string = "";
let build_bits = new BitVector(0, 0);
//ITEMS
for (const item of build.items) {
if (item.statMap.get("NONE") && item.statMap.get("NONE") === true) {
build_bits.append(0, 2); //00
} else if (item.statMap.get("custom")) {
build_bits.append(3, 2); //11
//BitVector CI encoding TODO
// let custom = "CI-"+encodeCustom(item, true);
// build_string += Base64.fromIntN(custom.length, 3) + custom;
// build_version = Math.max(build_version, 5);
} else if (item.statMap.get("crafted")) {
build_bits.append(2, 2); //10
//BitVector CR encoding TODO
// build_string += "CR-"+encodeCraft(item);
} else {
if (item.statMap.get("category") === "tome") {
//we will encode tomes later
continue;
} else {
build_bits.append(1, 2); //01
build_bits.append(item.statMap.get("id"), 13);
//powderable
if (powderable_keys.includes(item.statMap.get("type"))) {
if (item.statMap.get("powders") && item.statMap.get("powders").length !== 0) {
//has powders
build_bits.append(1, 1);
//num of powders in 8 bits, then each powder (6 bits)
//Having more than 256 powders on a vanilla item is NOT HANDLED.
build_bits.append(item.statMap.get("powders").length, 8);
for (const powder of item.statMap.get("powders")) {
build_bits.append(powder, 6);
}
} else {
//no powders
build_bits.append(0, 1);
}
}
}
}
}
//SKILL POINTS
//the original schema included a flag to indicate whether or not skill points are included. //V6 encoding - Tomes
//any reason for having a flag isn't implemented yet, so for now every build will have the skill point flag set. //V7 encoding - ATree
build_bits.append(1, 1); //V8 encoding - wynn version
//V9 encoding - lootrun tome
build_version = 9;
build_string = "";
tome_string = "";
for (const skp of build.base_skillpoints) { for (const item of build.items) {
build_bits.append(skp, 8); // Maximum skillpoints: 255 (allows for manual assign up to 150) if (item.statMap.get("custom")) {
} let custom = "CI-"+encodeCustom(item, true);
build_string += Base64.fromIntN(custom.length, 3) + custom;
//BUILD LEVEL //build_version = Math.max(build_version, 5);
} else if (item.statMap.get("crafted")) {
// [flag to indicate if level is not 106 (0/1)] build_string += "CR-"+encodeCraft(item);
// [else: level (7 bits, allows for lv 1->127)] } else if (item.statMap.get("category") === "tome") {
if (player_build.level != 106) { let tome_id = item.statMap.get("id");
build_bits.append(1, 1); //if (tome_id <= 60) {
build_bits.append(player_build.level, 7); // valid normal tome. ID 61-63 is for NONE tomes.
} else { //build_version = Math.max(build_version, 6);
build_bits.append(0, 1); //}
} tome_string += Base64.fromIntN(tome_id, 2);
} else {
// TOMES build_string += Base64.fromIntN(item.statMap.get("id"), 3);
// [flag to indicate if tomes are included (0/1)]
// [if set: 7 sequential tome IDs, each 6 bits unsigned]
if (build.tomes.length > 0) {
build_bits.append(1, 1);
//decoding will assume that tomes has length of 7.
for (const tome of build.tomes) {
build_bits.append(tome.id, 6);
} }
} else {
build_bits.append(0, 1);
} }
// ATREE for (const skp of skillpoints) {
build_string += Base64.fromIntN(skp, 2); // Maximum skillpoints: 2048
}
build_string += Base64.fromIntN(build.level, 2);
for (const _powderset of powders) {
let n_bits = Math.ceil(_powderset.length / 6);
build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders.
// Slice copy.
let powderset = _powderset.slice();
while (powderset.length != 0) {
let firstSix = powderset.slice(0,6).reverse();
let powder_hash = 0;
for (const powder of firstSix) {
powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first.
}
build_string += Base64.fromIntN(powder_hash, 5);
powderset = powderset.slice(6);
}
}
build_string += tome_string;
// [flag to indicate if atree data is present]
// [atree data: see existing encoding impl] //idk the impl
if (atree.length > 0 && atree_state.get(atree[0].ability.id).active) { if (atree.length > 0 && atree_state.get(atree[0].ability.id).active) {
build_bits.append(1, 1); //build_version = Math.max(build_version, 7);
const atree_bitvec = encode_atree(atree, atree_state); const bitvec = encode_atree(atree, atree_state);
build_bits.append(atree_bitvec); build_string += bitvec.toB64();
} else {
build_bits.append(0, 1);
} }
//compute length and return final build hash return build_version.toString() + "_" + build_string;
return build_version.toString() + "_" + len_string + "_" + build_string;
} }
} }
function get_full_url() {
return `${url_base}?v=${wynn_version_id.toString()}${location.hash}`
}
function copyBuild() { function copyBuild() {
copyTextToClipboard(url_base+location.hash); copyTextToClipboard(get_full_url());
document.getElementById("copy-button").textContent = "Copied!"; document.getElementById("copy-button").textContent = "Copied!";
} }
function shareBuild(build) { function shareBuild(build) {
if (build) { if (build) {
let text = url_base+location.hash+"\n"+ let text = get_full_url()+"\n"+
"WynnBuilder build:\n"+ "WynnBuilder build:\n"+
"> "+build.items[0].statMap.get("displayName")+"\n"+ "> "+build.items[0].statMap.get("displayName")+"\n"+
"> "+build.items[1].statMap.get("displayName")+"\n"+ "> "+build.items[1].statMap.get("displayName")+"\n"+
@ -354,7 +345,13 @@ function shareBuild(build) {
"> "+build.items[5].statMap.get("displayName")+"\n"+ "> "+build.items[5].statMap.get("displayName")+"\n"+
"> "+build.items[6].statMap.get("displayName")+"\n"+ "> "+build.items[6].statMap.get("displayName")+"\n"+
"> "+build.items[7].statMap.get("displayName")+"\n"+ "> "+build.items[7].statMap.get("displayName")+"\n"+
"> "+build.items[15].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]"; "> "+build.items[16].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]\n";
for (let tomeslots = 8; tomeslots < 16; tomeslots++) {
if (!build.items[tomeslots].statMap.has('NONE')) {
text += ">"+' (Has Tomes)' ;
break;
}
}
copyTextToClipboard(text); copyTextToClipboard(text);
document.getElementById("share-button").textContent = "Copied!"; document.getElementById("share-button").textContent = "Copied!";
} }

View file

@ -89,13 +89,12 @@ function resetFields(){
} }
} }
for (const elem of skp_order) { for (const elem of skp_order) {
console.log(document.getElementById(elem + "_boost_armor").value);
document.getElementById(elem + "_boost_armor").value = 0; document.getElementById(elem + "_boost_armor").value = 0;
document.getElementById(elem + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`; document.getElementById(elem + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`;
document.getElementById(elem + "_boost_armor_label").textContent = `% ${damageClasses[skp_order.indexOf(elem)+1]} Damage Boost: 0`; document.getElementById(elem + "_boost_armor_label").textContent = `% ${damageClasses[skp_order.indexOf(elem)+1]} Damage Boost: 0`;
} }
const nodes_to_reset = item_nodes.concat(powder_nodes).concat(edit_input_nodes).concat([powder_special_input, boosts_node, armor_powder_node]); const nodes_to_reset = equip_inputs.concat(powder_nodes).concat(edit_input_nodes).concat([powder_special_input, boosts_node, armor_powder_node]);
for (const node of nodes_to_reset) { for (const node of nodes_to_reset) {
node.mark_dirty(); node.mark_dirty();
} }
@ -231,8 +230,10 @@ function init_autocomplete() {
} }
let tome_alias = tome_obj['alias']; let tome_alias = tome_obj['alias'];
tome_arr.push(tome_name); tome_arr.push(tome_name);
tome_arr.push(tome_alias); if (tome_alias) {
tome_aliases.set(tome_alias, tome_name); tome_arr.push(tome_alias);
tome_aliases.set(tome_alias, tome_name);
}
} }
// create dropdown // create dropdown
@ -368,7 +369,7 @@ async function init() {
console.log(e); console.log(e);
} }
builder_graph_init(save_skp); builder_graph_init(save_skp);
for (const item_node of item_nodes) { for (const item_node of item_final_nodes) {
if (item_node.get_value() === null) { if (item_node.get_value() === null) {
// likely DB load failure... // likely DB load failure...
if (confirm('One or more items failed to load correctly. This could be due to a corrupted build link, or (more likely) a database load failure. Would you like to reload?')) { if (confirm('One or more items failed to load correctly. This could be due to a corrupted build link, or (more likely) a database load failure. Would you like to reload?')) {

View file

@ -12,7 +12,7 @@ const BUILD_VERSION = "7.0.19";
let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison", let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison",
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
"hprRaw", "hprPct", "hpBonus", "atkTier", "hprRaw", "hprPct", "hpBonus", "atkTier", "ls",
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct1", "spRaw1", "spPct2", "spRaw2",
"spPct3", "spRaw3", "spPct4", "spRaw4" ]; "spPct3", "spRaw3", "spPct4", "spRaw4" ];
@ -60,6 +60,7 @@ let tome_fields = [
"armorTome3", "armorTome3",
"armorTome4", "armorTome4",
"guildTome1", "guildTome1",
"lootrunTome1"
] ]
let equipment_names = [ let equipment_names = [
"Helmet", "Helmet",
@ -99,7 +100,7 @@ let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots'];
let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace']; let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace'];
let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon']; let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon'];
let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon']; let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon'];
let tome_keys = ['weaponTome1', 'weaponTome2', 'armorTome1', 'armorTome2', 'armorTome3', 'armorTome4', 'guildTome1']; let tome_keys = ['weaponTome1', 'weaponTome2', 'armorTome1', 'armorTome2', 'armorTome3', 'armorTome4', 'guildTome1', 'lootrunTome1'];
let spell_disp = ['build-melee-stats', 'spell0-info', 'spell1-info', 'spell2-info', 'spell3-info']; let spell_disp = ['build-melee-stats', 'spell0-info', 'spell1-info', 'spell2-info', 'spell3-info'];
let other_disp = ['build-order', 'set-info', 'int-info']; let other_disp = ['build-order', 'set-info', 'int-info'];

View file

@ -17,7 +17,7 @@ let armor_powder_node = new (class extends ComputeNode {
} }
})(); })();
const damageMultipliers = new Map([ ["totem", 0.2], ["warscream", 0.0], ["ragnarokkr", 0.30], ["fortitude", 0.60], ["radiance", 0.0] ]); const damageMultipliers = new Map([ ["totem", 0.2], ["warscream", 0.0], ["ragnarokkr", 0.20], ["fortitude", 0.60], ["radiance", 0.0] ]);
let boosts_node = new (class extends ComputeNode { let boosts_node = new (class extends ComputeNode {
constructor() { super('builder-boost-input'); } constructor() { super('builder-boost-input'); }
@ -117,7 +117,7 @@ class PowderSpecialDisplayNode extends ComputeNode {
const powder_specials = input_map.get('powder-specials'); const powder_specials = input_map.get('powder-specials');
const stats = input_map.get('stats'); const stats = input_map.get('stats');
const weapon = input_map.get('build').weapon; const weapon = input_map.get('build').weapon;
displayPowderSpecials(document.getElementById("powder-special-stats"), powder_specials, stats, weapon.statMap, true); displayPowderSpecials(document.getElementById("powder-special-stats"), powder_specials, stats, weapon.statMap);
} }
} }
@ -183,7 +183,7 @@ class ItemInputNode extends InputNode {
for (const [i, x] of zip2(equipment_inputs, replace_items)) { setValue(i, x); } for (const [i, x] of zip2(equipment_inputs, replace_items)) { setValue(i, x); }
for (const node of item_nodes) { for (const node of equip_inputs) {
if (node !== this) { if (node !== this) {
// save a tiny bit of compute // save a tiny bit of compute
calcSchedule(node, 10); calcSchedule(node, 10);
@ -413,7 +413,8 @@ class BuildAssembleNode extends ComputeNode {
input_map.get('armorTome2'), input_map.get('armorTome2'),
input_map.get('armorTome3'), input_map.get('armorTome3'),
input_map.get('armorTome4'), input_map.get('armorTome4'),
input_map.get('guildTome1') input_map.get('guildTome1'),
input_map.get('lootrunTome1')
]; ];
let weapon = input_map.get('weapon'); let weapon = input_map.get('weapon');
let level = parseInt(input_map.get('level-input')); let level = parseInt(input_map.get('level-input'));
@ -428,7 +429,7 @@ class BuildAssembleNode extends ComputeNode {
if (all_none && !location.hash) { if (all_none && !location.hash) {
return null; return null;
} }
return new Build(level, equipments, tomes, weapon); return new Build(level, equipments, weapon);
} }
} }
@ -500,28 +501,6 @@ class PowderInputNode extends InputNode {
} }
} }
/**
* Select a spell+spell "variation" based on a build / spell idx.
* Right now this isn't much logic and is only used to abstract away major id interactions
* but will become significantly more complex in wynn2.
*
* Signature: SpellSelectNode<int>(build: Build) => [Spell, SpellParts]
*/
class SpellSelectNode extends ComputeNode {
constructor(spell) {
super("builder-spell"+spell.base_spell+"-select");
this.spell = spell;
}
compute_func(input_map) {
const build = input_map.get('build');
let stats = build.statMap;
// TODO: apply major ids... DOOM.....
return [this.spell, this.spell.parts];
}
}
/* /*
* Get all defensive stats for this build. * Get all defensive stats for this build.
*/ */
@ -550,7 +529,8 @@ function getDefenseStats(stats) {
defenseStats.push(totalHpr); defenseStats.push(totalHpr);
//EHPR //EHPR
let ehpr = [totalHpr, totalHpr]; let ehpr = [totalHpr, totalHpr];
ehpr[0] /= (1-def_pct)*(1-agi_pct)*defMult; ehpr[0] = ehpr[0] / (0.1*agi_pct + (1-agi_pct) * (1-def_pct));
ehpr[0] /= defMult;
ehpr[1] /= (1-def_pct)*defMult; ehpr[1] /= (1-def_pct)*defMult;
defenseStats.push(ehpr); defenseStats.push(ehpr);
//skp stats //skp stats
@ -558,7 +538,7 @@ function getDefenseStats(stats) {
//eledefs - TODO POWDERS //eledefs - TODO POWDERS
let eledefs = [0, 0, 0, 0, 0]; let eledefs = [0, 0, 0, 0, 0];
for(const i in skp_elements){ //kinda jank but ok for(const i in skp_elements){ //kinda jank but ok
eledefs[i] = rawToPct(stats.get(skp_elements[i] + "Def"), stats.get(skp_elements[i] + "DefPct")/100.); eledefs[i] = rawToPct(stats.get(skp_elements[i] + "Def"), (stats.get(skp_elements[i] + "DefPct") + stats.get("rDefPct"))/100.);
} }
defenseStats.push(eledefs); defenseStats.push(eledefs);
@ -575,15 +555,15 @@ function getDefenseStats(stats) {
* spell-info: [Spell, SpellParts]) => List[SpellDamage] * spell-info: [Spell, SpellParts]) => List[SpellDamage]
*/ */
class SpellDamageCalcNode extends ComputeNode { class SpellDamageCalcNode extends ComputeNode {
constructor(spell_num) { constructor(spell) {
super("builder-spell"+spell_num+"-calc"); super("builder-spell"+spell.base_spell+"-calc");
this.spell = spell;
} }
compute_func(input_map) { compute_func(input_map) {
const weapon = input_map.get('build').weapon.statMap; const weapon = input_map.get('build').weapon.statMap;
const spell_info = input_map.get('spell-info'); const spell = this.spell;
const spell = spell_info[0]; const spell_parts = spell.parts;
const spell_parts = spell_info[1];
const stats = input_map.get('stats'); const stats = input_map.get('stats');
const skillpoints = [ const skillpoints = [
stats.get('str'), stats.get('str'),
@ -592,13 +572,26 @@ class SpellDamageCalcNode extends ComputeNode {
stats.get('def'), stats.get('def'),
stats.get('agi') stats.get('agi')
]; ];
let spell_results = [] let display_spell_results = []
let spell_result_map = new Map(); let spell_result_map = new Map();
const use_speed = (('use_atkspd' in spell) ? spell.use_atkspd : true); const use_speed = (('use_atkspd' in spell) ? spell.use_atkspd : true);
const use_spell = (('scaling' in spell) ? spell.scaling === 'spell' : true); const use_spell = (('scaling' in spell) ? spell.scaling === 'spell' : true);
// TODO: move preprocessing to separate node/node chain
for (const part of spell_parts) { for (const part of spell_parts) {
const {name, display=true} = part;
spell_result_map.set(name, {type: "need_eval", store_part: part});
}
function eval_part(part_name) {
let dat = spell_result_map.get(part_name);
if (!dat) {
return dat; // return null, or undefined, or whatever it is that this gives
}
if (dat.type !== "need_eval") {
return dat; // Already evaluated. Return it
}
let part = dat.store_part;
let spell_result; let spell_result;
const part_id = spell.base_spell + '.' + part.name const part_id = spell.base_spell + '.' + part.name
if ('multipliers' in part) { // damage type spell if ('multipliers' in part) { // damage type spell
@ -614,65 +607,70 @@ class SpellDamageCalcNode extends ComputeNode {
} }
} else if ('power' in part) { } else if ('power' in part) {
// TODO: wynn2 formula // TODO: wynn2 formula
let _heal_amount = (part.power * getDefenseStats(stats)[0] * (1+stats.get('healPct')/100)); const mult_map = stats.get("healMult");
if (stats.has('healPct:'+part_id)) { let heal_mult = 1;
_heal_amount *= 1+(stats.get('healPct:'+part_id)/100); for (const [k, v] of mult_map.entries()) {
if (k.includes(':')) {
// TODO: fragile... checking for specific part multipliers.
const spell_match = k.split(':')[1];
if (spell_match !== part_id) {
continue;
}
}
heal_mult *= (1 + v/100);
} }
let _heal_amount = part.power * getDefenseStats(stats)[0] * heal_mult;
spell_result = { spell_result = {
type: "heal", type: "heal",
heal_amount: _heal_amount heal_amount: _heal_amount
} }
} } else { // if 'hits' in part
else { spell_result = {
continue; normal_min: [0, 0, 0, 0, 0, 0],
} normal_max: [0, 0, 0, 0, 0, 0],
const {name, display = true} = part; normal_total: [0, 0],
spell_result.name = name; crit_min: [0, 0, 0, 0, 0, 0],
spell_result.display = display; crit_max: [0, 0, 0, 0, 0, 0],
spell_results.push(spell_result); crit_total: [0, 0],
spell_result_map.set(name, spell_result); heal_amount: 0
}
for (const part of spell_parts) {
if (!('hits' in part)) { continue; }
let spell_result = {
normal_min: [0, 0, 0, 0, 0, 0],
normal_max: [0, 0, 0, 0, 0, 0],
normal_total: [0, 0],
crit_min: [0, 0, 0, 0, 0, 0],
crit_max: [0, 0, 0, 0, 0, 0],
crit_total: [0, 0],
heal_amount: 0
}
const dam_res_keys = ['normal_min', 'normal_max', 'normal_total', 'crit_min', 'crit_max', 'crit_total'];
for (const [subpart_name, hits] of Object.entries(part.hits)) {
const subpart = spell_result_map.get(subpart_name);
if (!subpart) { continue; }
if (spell_result.type) {
if (subpart.type !== spell_result.type) {
throw "SpellCalc total subpart type mismatch";
}
} }
else { const dam_res_keys = ['normal_min', 'normal_max', 'normal_total', 'crit_min', 'crit_max', 'crit_total'];
spell_result.type = subpart.type; for (const [subpart_name, hits] of Object.entries(part.hits)) {
} const subpart = eval_part(subpart_name);
if (spell_result.type === 'damage') { if (!subpart) { continue; }
for (const key of dam_res_keys) { if (spell_result.type) {
for (let i in spell_result.normal_min) { if (subpart.type !== spell_result.type) {
spell_result[key][i] += subpart[key][i] * hits; throw "SpellCalc total subpart type mismatch";
} }
} }
} else {
else { spell_result.type = subpart.type;
spell_result.heal_amount += subpart.heal_amount * hits; }
if (spell_result.type === 'damage') {
for (const key of dam_res_keys) {
for (let i in spell_result.normal_min) {
spell_result[key][i] += subpart[key][i] * hits;
}
}
}
else {
spell_result.heal_amount += subpart.heal_amount * hits;
}
} }
} }
const {name, display = true} = part; const {name, display = true} = part;
spell_result.name = name; spell_result.name = name;
spell_result.display = display; spell_result.display = display;
spell_results.push(spell_result);
spell_result_map.set(name, spell_result); spell_result_map.set(name, spell_result);
return spell_result;
} }
return spell_results;
// TODO: move preprocessing to separate node/node chain
for (const part of spell_parts) {
let spell_result = eval_part(part.name);
display_spell_results.push(spell_result);
}
return display_spell_results;
} }
} }
@ -686,18 +684,17 @@ class SpellDamageCalcNode extends ComputeNode {
* spell-damage: List[SpellDamage]) => null * spell-damage: List[SpellDamage]) => null
*/ */
class SpellDisplayNode extends ComputeNode { class SpellDisplayNode extends ComputeNode {
constructor(spell_num) { constructor(spell) {
super("builder-spell"+spell_num+"-display"); super("builder-spell"+spell.base_spell+"-display");
this.spell_idx = spell_num; this.spell = spell;
} }
compute_func(input_map) { compute_func(input_map) {
const stats = input_map.get('stats'); const stats = input_map.get('stats');
const spell_info = input_map.get('spell-info');
const damages = input_map.get('spell-damage'); const damages = input_map.get('spell-damage');
const spell = spell_info[0]; const spell = this.spell;
const i = this.spell_idx; const i = this.spell.base_spell;
let parent_elem = document.getElementById("spell"+i+"-info"); let parent_elem = document.getElementById("spell"+i+"-info");
let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); let overallparent_elem = document.getElementById("spell"+i+"-infoAvg");
displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i, damages); displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i, damages);
@ -847,9 +844,13 @@ class AggregateStatsNode extends ComputeNode {
} }
} }
let radiance_affected = [ /*"hp"*/, "fDef", "wDef", "aDef", "tDef", "eDef", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", let radiance_affected = [ /*"hp"*/, "fDef", "wDef", "aDef", "tDef", "eDef", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms",
/*"str", "dex", "int", "agi", "def",*/ // "xpb", "lb",
"thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "ref",
/*"str", "dex", "int", "agi", "def",*/ // TODO its affected but i have to make it not affect req
"thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh",
// "lq", "gXp", "gSpd",
// wynn2 damages. // wynn2 damages.
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw",//"eDamAddMin","eDamAddMax", "eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw",//"eDamAddMin","eDamAddMax",
@ -861,7 +862,8 @@ let radiance_affected = [ /*"hp"*/, "fDef", "wDef", "aDef", "tDef", "eDef", "hpr
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw",//"damAddMin","damAddMax", // These are the old ids. Become proportional. /*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw",//"damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw",//"rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw "rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw",//"rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
"critDamPct", "critDamPct",
//"spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final" //"spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final",
"healPct", "kb", "weakenEnemy", "slowEnemy", "rDefPct"
]; ];
/** /**
* Scale stats if radiance is enabled. * Scale stats if radiance is enabled.
@ -887,12 +889,12 @@ const radiance_node = new (class extends ComputeNode {
} }
} }
} }
const dam_mults = new Map(ret.get('damMult')); // const dam_mults = new Map(ret.get('damMult'));
dam_mults.set('tome', dam_mults.get('tome') * 1.2) // dam_mults.set('tome', dam_mults.get('tome') * 1.2)
ret.set('damMult', dam_mults) // ret.set('damMult', dam_mults)
const def_mults = new Map(ret.get('defMult')); // const def_mults = new Map(ret.get('defMult'));
def_mults.set('tome', def_mults.get('tome') * 1.2) // def_mults.set('tome', def_mults.get('tome') * 1.2)
ret.set('defMult', def_mults) // ret.set('defMult', def_mults)
return ret; return ret;
} }
else { else {
@ -951,6 +953,7 @@ class EditableIDSetterNode extends ComputeNode {
this.notify_nodes = notify_nodes.slice(); this.notify_nodes = notify_nodes.slice();
for (const child of this.notify_nodes) { for (const child of this.notify_nodes) {
child.link_to(this); child.link_to(this);
child.fail_cb = true;
} }
} }
@ -987,6 +990,9 @@ class SkillPointSetterNode extends ComputeNode {
this.notify_nodes = notify_nodes.slice(); this.notify_nodes = notify_nodes.slice();
for (const child of this.notify_nodes) { for (const child of this.notify_nodes) {
child.link_to(this); child.link_to(this);
child.fail_cb = true;
// This is needed because initially there is a value mismatch possibly... due to setting skillpoints manually
child.mark_input_clean(this.name, null);
} }
} }
@ -1029,8 +1035,7 @@ class SumNumberInputNode extends InputNode {
} }
} }
let item_nodes = []; let item_final_nodes = [];
let item_nodes_map = new Map();
let powder_nodes = []; let powder_nodes = [];
let edit_input_nodes = []; let edit_input_nodes = [];
let skp_inputs = []; let skp_inputs = [];
@ -1052,9 +1057,9 @@ function builder_graph_init(save_skp) {
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display. // "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
build_node = new BuildAssembleNode(); build_node = new BuildAssembleNode();
for (const input of item_nodes) {
}
build_node.link_to(level_input); build_node.link_to(level_input);
atree_merge.link_to(build_node, "build");
let build_encode_node = new BuildEncodeNode(); let build_encode_node = new BuildEncodeNode();
build_encode_node.link_to(build_node, 'build'); build_encode_node.link_to(build_node, 'build');
@ -1076,8 +1081,7 @@ function builder_graph_init(save_skp) {
.link_to(powder_node, 'powdering').link_to(item_input, 'item'); .link_to(powder_node, 'powdering').link_to(item_input, 'item');
item_input = item_powdering; item_input = item_powdering;
} }
item_nodes.push(item_input); item_final_nodes.push(item_input);
item_nodes_map.set(eq, item_input);
new ItemInputDisplayNode(eq+'-input-display', eq, item_image).link_to(item_input); new ItemInputDisplayNode(eq+'-input-display', eq, item_image).link_to(item_input);
new ItemDisplayNode(eq+'-item-display', display_elem).link_to(item_input); new ItemDisplayNode(eq+'-item-display', display_elem).link_to(item_input);
//new PrintNode(eq+'-debug').link_to(item_input); //new PrintNode(eq+'-debug').link_to(item_input);
@ -1085,13 +1089,13 @@ function builder_graph_init(save_skp) {
build_node.link_to(item_input, eq); build_node.link_to(item_input, eq);
} }
for (const [eq, none_item] of zip2(tome_fields, [none_tomes[0], none_tomes[0], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[2]])) { for (const [eq, none_item] of zip2(tome_fields, [none_tomes[0], none_tomes[0], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[2], none_tomes[3]])) {
let input_field = document.getElementById(eq+"-choice"); let input_field = document.getElementById(eq+"-choice");
let item_image = document.getElementById(eq+"-img"); let item_image = document.getElementById(eq+"-img");
let item_input = new ItemInputNode(eq+'-input', input_field, none_item); let item_input = new ItemInputNode(eq+'-input', input_field, none_item);
equip_inputs.push(item_input); equip_inputs.push(item_input);
item_nodes.push(item_input); item_final_nodes.push(item_input);
new ItemInputDisplayNode(eq+'-input-display', eq, item_image).link_to(item_input); new ItemInputDisplayNode(eq+'-input-display', eq, item_image).link_to(item_input);
build_node.link_to(item_input, eq); build_node.link_to(item_input, eq);
} }
@ -1099,7 +1103,7 @@ function builder_graph_init(save_skp) {
// weapon image changer node. // weapon image changer node.
let weapon_image = document.getElementById("weapon-img"); let weapon_image = document.getElementById("weapon-img");
let weapon_dps = document.getElementById("weapon-dps"); let weapon_dps = document.getElementById("weapon-dps");
new WeaponInputDisplayNode('weapon-type', weapon_image, weapon_dps).link_to(item_nodes[8]); new WeaponInputDisplayNode('weapon-type-display', weapon_image, weapon_dps).link_to(item_final_nodes[8]);
// linking to atree verification // linking to atree verification
atree_validate.link_to(level_input, 'level'); atree_validate.link_to(level_input, 'level');

View file

@ -0,0 +1,601 @@
{
"FISSION": {
"displayName": "Fission",
"description": "Explosions from your Exploding ID are twice as big and twice as strong",
"abilities": []
},
"EXPLOSIVE_IMPACT": {
"displayName": "Explosive Impact",
"description": "Your Exploding ID can trigger when hitting mobs with your Main Attack",
"abilities": []
},
"MAGNET": {
"displayName": "Magnet",
"description": "Pulls items within an 8 block radius towards you",
"abilities": []
},
"PLAGUE": {
"displayName": "Plague",
"description": "Poisoned mobs spread their poison to nearby mobs",
"abilities": []
},
"HAWKEYE": {
"displayName": "Hawkeye",
"description": "Condense Arrow Storm into a tight beam. Arrows deal ✤10%, ✦1%, and ❋1%",
"abilities": [{
"class": "Archer",
"base_abil": "Arrow Storm",
"effects": [
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Single Stream",
"behavior": "overwrite",
"hits": { "Single Arrow": 5 }
},
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Single Arrow",
"behavior": "overwrite",
"multipliers": [ 10, 0, 1, 0, 0, 1 ]
},
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Total Damage",
"behavior": "modify",
"hits": { "Single Stream": 4 }
}
]
}]
},
"GREED": {
"displayName": "Greed",
"description": "Picking up emeralds heals you and nearby players for 15% max health",
"abilities": []
},
"CAVALRYMAN": {
"displayName": "Cavalryman",
"description": "You may cast spells and attack with a 70% damage penalty while on a horse",
"abilities": []
},
"GUARDIAN": {
"displayName": "Guardian",
"description": "20% of the damage taken by nearby allies is redirected to you",
"abilities": []
},
"HERO": {
"displayName": "Saviours Sacrifice",
"description": "While under 50% maximum health, nearby allies gain 20% bonus damage and defense",
"abilities": []
},
"ALTRUISM": {
"displayName": "Heart of the Pack",
"description": "Nearby players gain 35% of the health you naturally regenerate",
"abilities": []
},
"ARCANES": {
"displayName": "Transcendence",
"description": "30% chance for spells to cost no mana when casted",
"abilities": []
},
"ENTROPY": {
"displayName": "Entropy",
"description": "Meteor falls three times faster",
"abilities": []
},
"ROVINGASSASSIN": {
"displayName": "Roving Assassin",
"description": "Vanish no longer drains mana while invisible",
"abilities": []
},
"MADNESS": {
"displayName": "Madness",
"description": "Cast a random ability every 3 seconds",
"abilities": []
},
"LIGHTWEIGHT": {
"displayName": "Lightweight",
"description": "You no longer take fall damage",
"abilities": []
},
"SORCERY": {
"displayName": "Sorcery",
"description": "30% chance for spells and attacks to cast a second time at no additional cost",
"abilities": []
},
"TAUNT": {
"displayName": "Taunt",
"description": "Mobs within 12 blocks target you upon casting War Scream",
"abilities": []
},
"RALLY": {
"displayName": "Rally",
"description": "Charge heals you by 10% and nearby allies by 15% on impact, but becomes harmless",
"abilities": [{
"class": "Warrior",
"base_abil": "Charge",
"effects": [
{
"type": "add_spell_prop",
"base_spell": 2,
"display": "Rally Self Heal",
"target_part": "Rally Self Heal",
"power": 0.1
},
{
"type": "add_spell_prop",
"base_spell": 2,
"target_part": "Rally Ally Heal",
"power": 0.15
},
{
"type": "raw_stat",
"bonuses": [
{
"type": "stat",
"name": "damMult.Rally:2.Flying Kick",
"value": -100
},
{
"type": "stat",
"name": "damMult.Rally:2.Collide",
"value": -100
},
{
"type": "stat",
"name": "damMult.Rally:2.Heavy Impact",
"value": -100
},
{
"type": "stat",
"name": "damMult.Rally:2.Flyby Jab",
"value": -100
}
]
}
]
}]
},
"CHERRY_BOMBS": {
"displayName": "Cherry Bombs",
"description": "Turn Smoke Bomb into three firecrackers that fly farther, tighter, and deal high damage instantly",
"abilities": [{
"class": "Assassin",
"base_abil": "Smoke Bomb",
"effects": [{
"type": "replace_spell",
"name": "Cherry Bomb",
"cost": 35,
"base_spell": 4,
"display": "Total Damage",
"parts": [
{
"name": "Single Bomb",
"type": "damage",
"multipliers": [95, 40, 0, 0, 0, 40]
},
{
"name": "Total Damage",
"type": "total",
"hits": {
"Single Bomb": 3
}
}
]
}]
}]
},
"FREERUNNER": {
"displayName": "Freerunner",
"description": "Double your sprint speed when your sprint bar is under 30%",
"abilities": []
},
"PEACEFUL_EFFIGY": {
"displayName": "Peaceful Effigy",
"description": "Your totem will last twice as long",
"abilities": []
},
"FURIOUS_EFFIGY": {
"displayName": "Furious Effigy",
"description": "Totem effects are twice as fast, but duration is halved",
"abilities": [{
"class": "Shaman",
"base_abil": "Totem",
"properties": {
"rate": -0.2,
"totem_mul": 2.5
},
"effects": []
}]
},
"FLASHFREEZE": {
"displayName": "Flashfreeze",
"description": "Ice Snake is instant but has a reduced range",
"abilities": []
},
"GRAVITYWELL": {
"displayName": "Gravity Well",
"description": "Meteor has increased blast radius and pulls enemies instead",
"abilities": []
},
"DESC_SNOWYSTEPS": {
"displayName": "Snowy Steps",
"description": "Leaves a trail of snow behind you",
"abilities": []
},
"GEOCENTRISM": {
"displayName": "Geocentrism",
"description": "Aura radiates from you instead of your totem and can be cast anytime",
"abilities": []
},
"DESC_FESTIVESPIRIT": {
"displayName": "Festive Spirit",
"description": "Plays wintery tunes",
"abilities": []
},
"TEMBLOR": {
"displayName": "Temblor",
"description": "Bash gains +1 Area of Effect and is 25% faster.",
"abilities": [{
"class": "Warrior",
"base_abil": "Bash",
"properties": { "aoe": 1 },
"effects": []
}]
},
"RECKLESS_ABANDON": {
"displayName": "Reckless Abandon",
"description": "Sacrifice War Scream's defense bonus to give Tempest two extra hits, extra damage, and extra speed",
"abilities": [{
"class": "Warrior",
"base_abil": "War Scream",
"dependencies": [ "Tempest" ],
"effects": [
{
"type": "add_spell_prop",
"base_spell": 4,
"target_part": "Tempest",
"behavior": "modify",
"multipliers": [0, 5, 0, 0, 15, 5]
},
{
"type": "add_spell_prop",
"base_spell": 4,
"target_part": "Tempest Total Damage",
"behavior": "modify",
"hits": { "Tempest": 2 }
}
]
}]
},
"ALTEREGO": {
"displayName": "Alter Ego",
"description": "Awakened can be activated after saving 40% less mana, but its duration is reduced by 25%.",
"abilities": []
},
"FOREST_BLESSING": {
"displayName": "Forest's Blessing",
"description": "Your archer summons have increased movement speed, attack speed and vision. Arrow Bomb's damage is reduced by -30% Neutral damage.",
"abilities": [{
"class": "Archer",
"base_abil": "Arrow Bomb",
"effects": [
{
"type": "add_spell_prop",
"base_spell": 3,
"target_part": "Arrow Bomb",
"behavior": "modify",
"multipliers": [-30, 0, 0, 0, 0, 0]
},
{
"type": "add_spell_prop",
"base_spell": 8,
"target_part": "DPS",
"behavior": "modify",
"hits": { "Single Hit": 1.666666666666667 }
},
{
"type": "add_spell_prop",
"base_spell": 10,
"target_part": "Crow DPS",
"behavior": "modify",
"hits": { "Single Hit": 0.555555555555556 }
}
]
}]
},
"SOUL_EATER": {
"displayName": "Soul Eater",
"description": "Devour and Harvester grant double mana, but your maximum Marks are decreased by 1.",
"abilities": [{
"class": "Assassin",
"base_abil": "Marked",
"effects": [{
"type": "stat_scaling",
"slider": true,
"slider_name": "Marked",
"slider_max": -1
}]
}]
},
"STRINGS_OF_FATE": {
"displayName": "Strings of Fate",
"description": "Your puppets have a lifetime of 3 seconds, but do double damage with attacks and explosions.",
"abilities": [{
"class": "Shaman",
"base_abil": "Puppet Master",
"effects": [
{
"type": "raw_stat",
"bonuses": [
{
"type": "stat",
"name": "damMult.FateString:6.Puppet Hit",
"value": 100
},
{
"type": "stat",
"name": "damMult.FateString:6.Puppet Explosion",
"value": 100
}
]
}
]
}]
},
"ESCAPE_ROUTE": {
"displayName": "Escape Route",
"description": "Frenzy and Time Dilation charge twice as fast, but to a halved maximum.",
"abilities": [
{
"class": "Archer",
"base_abil": "Frenzy",
"effects": [
{
"type": "stat_scaling",
"slider": true,
"slider_name": "Hits dealt",
"slider_max": -18,
"output": {
"type": "stat",
"name": "spd"
},
"scaling": [-3]
},
{
"type": "stat_scaling",
"slider": true,
"slider_name": "Hits dealt",
"output": {
"type": "stat",
"name": "spd"
},
"scaling": [6],
"max": 35
}
]
},
{
"class": "Mage",
"base_abil": "Time Dilation",
"effects": [
{
"type": "stat_scaling",
"slider": true,
"slider_name": "Time Dilated",
"slider_max": -22,
"output": {
"type": "stat",
"name": "spd"
},
"scaling": [-10]
},
{
"type": "stat_scaling",
"slider": true,
"slider_name": "Time Dilated",
"output": {
"type": "stat",
"name": "spd"
},
"scaling": [20],
"max": 150
}
]
}
]
},
"DIVINE_HONOR": {
"displayName": "Divine Honor",
"description": "Increase the bonus from Radiance by 5%. Decrease the Earth damage of Bash by -15%.",
"abilities": [{
"class": "Warrior",
"base_abil": "Bash",
"effects": [
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Single Hit",
"behavior": "modify",
"multipliers": [0, -15, 0, 0, 0, 0]
}
]
}]
},
"GENTLE_GLOW": {
"displayName": "Gentle Glow",
"description": "Orphion's Pulse and Fluid Healing restore more health, especially to allies, but at a slower speed.",
"abilities": []
},
"PERFECT_RECALL": {
"displayName": "Perfect Recall",
"description": "Memory Recollection casts an extra spell, but only activates at 150 banked mana.",
"abilities": []
},
"OVERWHELM": {
"displayName": "Overwhelm",
"description": "Bash will hit +2 times.",
"abilities": [{
"class": "Warrior",
"base_abil": "Bash",
"properties": { "hits": 2 },
"effects": []
}]
},
"JUGGLE": {
"displayName": "Juggle",
"description": "Stronger Multihit adds an additional 12 hits. All hits are reduced by -10% Neutral damage.",
"abilities": [{
"class": "Assassin",
"base_abil": "Multihit",
"effects": [
{
"type": "add_spell_prop",
"base_spell": 3,
"target_part": "Total Damage",
"behavior": "modify",
"hits": { "Per Hit": 12 }
},
{
"type": "add_spell_prop",
"base_spell": 3,
"target_part": "Per Hit",
"behavior": "modify",
"multipliers": [ -10, 0, 0, 0, 0, 0 ]
}
]
}]
},
"GRUESOME_KNOTS": {
"displayName": "Gruesome Knots",
"description":"Twisted Tether spends twice as much of your Blood Pool to deal triple damage",
"abilities": [{
"class": "Shaman",
"base_abil": "Twisted Tether",
"effects": [{
"type": "raw_stat",
"bonuses": [
{
"type": "stat",
"name": "damMult.GruesomeKnots:8.Tether Tick",
"value": 200
}
]
}]
}]
},
"COAGULATE": {
"displayName": "Coagulate",
"description":"Blood Connection launches you further and higher upon teleporting to a nearby Totem",
"abilities": []
},
"DEADWEIGHT": {
"displayName": "Dead Weight",
"description":"Totem's horizontal velocity is greatly increased at the cost of vertical movement",
"abilities": []
},
"EXPUNGE": {
"displayName": "Expunge",
"description":"When using Heal, instead cast one instant pulse that heals 20% of your max health",
"abilities": [{
"class": "Mage",
"base_abil": "Heal",
"effects": [
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Second and Third Pulses",
"behavior": "modify",
"power": -0.20
},
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Heal",
"behavior": "modify",
"power": 0.05
}
]
}]
},
"LUNGE": {
"displayName": "Lunge",
"description":"Hop's horizontal velocity is greatly increased",
"abilities": []
},
"WINDSURF": {
"displayName": "Windsurf",
"description":"Righting Reflex lasts twice as long and is affected stronger by movement speed",
"abilities": []
},
"HELLFIRE": {
"displayName": "Hellfire",
"description":"Boiling Blood has no cooldown, is stronger, and does not slow. Cuts Bash's power and disables Discombobulate",
"abilities": [{
"class": "Warrior",
"base_abil": "Bash",
"dependencies": [ "Boiling Blood" ],
"properties": {
"rate": 0.8333333333333333333333333333,
"num_bloods": 0
},
"effects": [
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Single Hit",
"behavior": "modify",
"multipliers": [ -85, -30, 0, 0, 0, 0 ]
},
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Boiling Blood Tick",
"behavior": "modify",
"multipliers": [ -25, 0, 0, 0, 30, 0 ]
},
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Boiling Blood DPS (Total)",
"behavior": "merge",
"display": "Boiling Blood DPS (Total)",
"hits": { "Boiling Blood DPS": "Bash.num_bloods" }
},
{
"type": "stat_scaling",
"slider": true,
"slider_name": "Boiling Blood Stacks",
"slider_step": 1,
"slider_max": 20,
"slider_default": 4,
"output": [
{
"type": "prop",
"abil": "Bash",
"name": "num_bloods"
}
],
"scaling": [1]
},
{
"type": "stat_scaling",
"slider": true,
"behavior": "modify",
"slider_name": "Hits dealt",
"output": [
{ "type": "stat", "name": "nDamAddMin" }, { "type": "stat", "name": "nDamAddMax" },
{ "type": "stat", "name": "eDamAddMin" }, { "type": "stat", "name": "eDamAddMax" },
{ "type": "stat", "name": "tDamAddMin" }, { "type": "stat", "name": "tDamAddMax" },
{ "type": "stat", "name": "wDamAddMin" }, { "type": "stat", "name": "wDamAddMax" },
{ "type": "stat", "name": "fDamAddMin" }, { "type": "stat", "name": "fDamAddMax" },
{ "type": "stat", "name": "aDamAddMin" }, { "type": "stat", "name": "aDamAddMax" }
],
"scaling": [-5]
}
]
}]
}
}

View file

@ -155,6 +155,7 @@ class ValueCheckComputeNode extends ComputeNode {
if (this.dirty === 0) { if (this.dirty === 0) {
return this; return this;
} }
if (COMPUTE_GRAPH_DEBUG) { node_debug_stack.push(this.name); }
let calc_inputs = new Map(); let calc_inputs = new Map();
for (const input of this.inputs) { for (const input of this.inputs) {
@ -170,6 +171,7 @@ class ValueCheckComputeNode extends ComputeNode {
for (const child of this.children) { for (const child of this.children) {
child.mark_input_clean(this.name, this.value); child.mark_input_clean(this.name, this.value);
} }
if (COMPUTE_GRAPH_DEBUG) { node_debug_stack.pop(this.name); }
return this; return this;
} }
@ -188,7 +190,6 @@ let graph_live_update = false;
* @param node : ComputeNode to schedule an update for. * @param node : ComputeNode to schedule an update for.
*/ */
function calcSchedule(node, timeout) { function calcSchedule(node, timeout) {
if (!graph_live_update) return;
if (node.update_task !== null) { if (node.update_task !== null) {
clearTimeout(node.update_task); clearTimeout(node.update_task);
} }
@ -225,8 +226,8 @@ class InputNode extends ValueCheckComputeNode {
constructor(name, input_field) { constructor(name, input_field) {
super(name); super(name);
this.input_field = input_field; this.input_field = input_field;
this.input_field.addEventListener("input", () => calcSchedule(this, 500)); this.input_field.addEventListener("input", () => { if (graph_live_update) calcSchedule(this, 500) } );
this.input_field.addEventListener("change", () => calcSchedule(this, 5)); this.input_field.addEventListener("change", () => { if (graph_live_update) calcSchedule(this, 5) } );
//calcSchedule(this); Manually fire first update for better control //calcSchedule(this); Manually fire first update for better control
} }

View file

@ -1,6 +1,5 @@
let recipeTypes = ["HELMET","CHESTPLATE","LEGGINGS","BOOTS","RELIK","WAND","SPEAR","DAGGER","BOW","RING","NECKLACE","BRACELET","SCROLL","FOOD","POTION"]; let recipeTypes = ["HELMET","CHESTPLATE","LEGGINGS","BOOTS","RELIK","WAND","SPEAR","DAGGER","BOW","RING","NECKLACE","BRACELET","POTION", "SCROLL","FOOD"];
let levelTypes = ["1-3","3-5","5-7","7-9","10-13","13-15","15-17","17-19","20-23","23-25","25-27","27-29","30-33","33-35","35-37","37-39","40-43","43-45","45-47","47-49","50-53","53-55","55-57","57-59","60-63","63-65","65-67","67-69","70-73","73-75","75-77","77-79","80-83","83-85","85-87","87-89","90-93","93-95","95-97","97-99","100-103","103-105",] let levelTypes = ["1-3","3-5","5-7","7-9","10-13","13-15","15-17","17-19","20-23","23-25","25-27","27-29","30-33","33-35","35-37","37-39","40-43","43-45","45-47","47-49","50-53","53-55","55-57","57-59","60-63","63-65","65-67","67-69","70-73","73-75","75-77","77-79","80-83","83-85","85-87","87-89","90-93","93-95","95-97","97-99","100-103","103-105",]
let ingFields = ["fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "lq", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "jh", "sprint", "sprintReg", "gXp", "gSpd"];
function encodeCraft(craft) { function encodeCraft(craft) {
if (craft) { if (craft) {
@ -181,7 +180,6 @@ class Craft{
/* 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 matmult = 1;
let tierToMult = [0,1,1.25,1.4]; let tierToMult = [0,1,1.25,1.4];

View file

@ -136,8 +136,6 @@ function calculateCraft() {
} }
let ingreds = []; let ingreds = [];
for (i = 1; i < 7; i++) { for (i = 1; i < 7; i++) {
console.log("ing-choice-"+i);
// console.log(getValue("ing-choice-"+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. let atkSpd = "NORMAL"; //default attack speed will be normal.

View file

@ -76,7 +76,7 @@ function encodeCustom(custom, verbose) {
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(val.replaceAll(" ", "%20").length, 2) + val.replaceAll(" ", "%20"); //values cannot go above 4096 chars!!!! Is this ok? hash += Base64.fromIntN(i, 2) + Base64.fromIntN(val.replaceAll(" ", "%20").length, 2) + val.replaceAll(" ", "%20"); //values cannot go above 4096 chars!!!! Is this ok?
} }
} else if (typeof (val) === "number" && val != 0) { } else if (typeof (val) === "number" && val != 0) {
let len = Math.max(1, Math.ceil(log(64, Math.abs(val)))); let len = Math.max(1, Math.ceil(log(64, Math.abs(val) + 1)));
let sign = Boolean(val / Math.abs(val) < 0) | 0; let sign = Boolean(val / Math.abs(val) < 0) | 0;
//console.log(sign); //console.log(sign);
//hash += Base64.fromIntN(i,2) + Base64.fromIntN(val,Math.max(1,Math.ceil(log(64,Math.abs(val))))) + "_"; //hash += Base64.fromIntN(i,2) + Base64.fromIntN(val,Math.max(1,Math.ceil(log(64,Math.abs(val))))) + "_";
@ -246,7 +246,6 @@ class Custom {
} }
} }
let type = this.statMap.get("type").toLowerCase(); let type = this.statMap.get("type").toLowerCase();
console.log(type);
if (weaponTypes.includes(type)) { if (weaponTypes.includes(type)) {
for (const n of ["nDam", "eDam", "tDam", "wDam", "fDam", "aDam"]) { for (const n of ["nDam", "eDam", "tDam", "wDam", "fDam", "aDam"]) {
if (!(this.statMap.has(n) && this.statMap.get(n))) { if (!(this.statMap.has(n) && this.statMap.get(n))) {

View file

@ -478,30 +478,18 @@ function base_to_range(id) {
let base = parseFloat(getValue(id+"-choice-base")); let base = parseFloat(getValue(id+"-choice-base"));
if(base) { if(base) {
//This version allows overriding of min and max. //This version allows overriding of min and max.
if (reversedIDs.includes(id)) { if (base == 0) {
if (base < 0) { // NOTE: DO NOT remove this case! idRound behavior does not round to 0!
setValue(id+"-choice-min", Math.min(Math.round(neg_range[1]*base),-1)); setValue(id+"-choice-max", 0);
} else { setValue(id+"-choice-min", 0);
setValue(id+"-choice-min", Math.max(Math.round(pos_range[1]*base),1)); }
} else if ((base > 0) != (reversedIDs.includes(id))) { // logical XOR. positive rolled IDs
if (base < 0) { setValue(id+"-choice-max", idRound(Math.round(pos_range[1]*base)));
setValue(id+"-choice-max", Math.min(Math.round(neg_range[0]*base),-1)); setValue(id+"-choice-min", idRound(Math.round(pos_range[0]*base)));
} else { } else { //negative rolled IDs
setValue(id+"-choice-max", Math.max(Math.round(pos_range[0]*base),1)); setValue(id+"-choice-max", idRound(Math.round(neg_range[1]*base)));
} setValue(id+"-choice-min", idRound(Math.round(neg_range[0]*base)));
} else {
if (base < 0) {
setValue(id+"-choice-min", Math.min(Math.round(neg_range[0]*base),-1));
} else {
setValue(id+"-choice-min", Math.max(Math.round(pos_range[0]*base),1));
}
if (base < 0) {
setValue(id+"-choice-max", Math.min(Math.round(neg_range[1]*base),-1));
} else {
setValue(id+"-choice-max", Math.max(Math.round(pos_range[1]*base),1));
}
} }
/* No overiding min/max version /* No overiding min/max version
if (!getValue(id+"-choice-min")) { if (!getValue(id+"-choice-min")) {

View file

@ -67,6 +67,9 @@ function calculateSpellDamage(stats, weapon, _conversions, use_spell_damage, ign
// 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here. // 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
let damages = []; let damages = [];
const neutral_convert = conversions[0] / 100; const neutral_convert = conversions[0] / 100;
if (neutral_convert == 0) {
present = [false, false, false, false, false, false]
}
let weapon_min = 0; let weapon_min = 0;
let weapon_max = 0; let weapon_max = 0;
for (const damage of weapon_damages) { for (const damage of weapon_damages) {
@ -134,15 +137,15 @@ function calculateSpellDamage(stats, weapon, _conversions, use_spell_damage, ign
let damageBoost = 1 + skill_boost[i] + static_boost let damageBoost = 1 + skill_boost[i] + static_boost
+ ((stats.get(damage_specific) + stats.get(damage_elements[i]+'DamPct')) /100); + ((stats.get(damage_specific) + stats.get(damage_elements[i]+'DamPct')) /100);
if (i > 0) { if (i > 0) {
damageBoost += stats.get('r'+specific_boost_str+'Pct') / 100; damageBoost += (stats.get('r'+specific_boost_str+'Pct') + stats.get('rDamPct')) / 100;
} }
damages[i][0] *= Math.max(damageBoost, 0); damages[i][0] *= Math.max(damageBoost, 0);
damages[i][1] *= Math.max(damageBoost, 0); damages[i][1] *= Math.max(damageBoost, 0);
// Collect total damage post %boost // Collect total damage post %boost
} }
let total_elem_min = total_min - damages[0][0]; let total_elem_min = total_min - save_prop[0][0];
let total_elem_max = total_max - damages[0][1]; let total_elem_max = total_max - save_prop[0][1];
// 5.2: Raw application. // 5.2: Raw application.
let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw'); let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw');
@ -337,17 +340,3 @@ const default_spells = {
] ]
}] }]
}; };
const spell_table = {
"powder": [ //This is how instant-damage powder specials are implemented.
{ title: "Quake", cost: 0, parts:[
{ subtitle: "Total Damage", type: "damage", multiplier: [155, 220, 285, 350, 415], conversion: [0,100,0,0,0,0], summary: true},
] },
{ title: "Chain Lightning", cost: 0, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: [200, 225, 250, 275, 300], conversion: [0,0,100,0,0,0], summary: true},
]},
{ title: "Courage", cost: 0, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: [75, 87.5, 100, 112.5, 125], conversion: [0,0,0,0,100,0], summary: true},
]}, //[75, 87.5, 100, 112.5, 125]
]
};

View file

@ -214,14 +214,18 @@ function displayExpandedItem(item, parent_id){
parent_div.appendChild(make_elem("div", ["col"], { textContent: "Set: " + item.get(id).toString() })); parent_div.appendChild(make_elem("div", ["col"], { textContent: "Set: " + item.get(id).toString() }));
} else if (id === "majorIds") { } else if (id === "majorIds") {
//console.log(item.get(id)); //console.log(item.get(id));
for (let majorID of item.get(id)) { for (let major_id_str of item.get(id)) {
if (major_id_str in major_ids) {
let major_id_info = major_ids[major_id_str];
major_id_str = `+${major_id_info.displayName}: ${major_id_info.description}`;
}
let p_elem = make_elem("div", ['col']); let p_elem = make_elem("div", ['col']);
let title_elem = make_elem("b"); let title_elem = make_elem("b");
let b_elem = make_elem("b"); let b_elem = make_elem("b");
if (majorID.includes(":")) { if (major_id_str.includes(":")) {
let name = majorID.substring(0, majorID.indexOf(":")+1); let name = major_id_str.substring(0, major_id_str.indexOf(":")+1);
let mid = majorID.substring(majorID.indexOf(":")+1); let mid = major_id_str.substring(major_id_str.indexOf(":")+1);
if (name.charAt(0) !== "+") {name = "+" + name} if (name.charAt(0) !== "+") {name = "+" + name}
title_elem.classList.add("Legendary"); title_elem.classList.add("Legendary");
title_elem.textContent = name; title_elem.textContent = name;
@ -230,10 +234,9 @@ function displayExpandedItem(item, parent_id){
p_elem.appendChild(title_elem); p_elem.appendChild(title_elem);
p_elem.appendChild(b_elem); p_elem.appendChild(b_elem);
} else { } else {
let name = item.get(id).toString() if (major_id_str.charAt(0) !== "+") {major_id_str = "+" + major_id_str}
if (name.charAt(0) !== "+") {name = "+" + name}
b_elem.classList.add("Legendary"); b_elem.classList.add("Legendary");
b_elem.textContent = name; b_elem.textContent = major_id_str;
p_elem.appendChild(b_elem); p_elem.appendChild(b_elem);
} }
parent_div.appendChild(p_elem); parent_div.appendChild(p_elem);
@ -306,11 +309,11 @@ function displayExpandedItem(item, parent_id){
// TODO: kinda jank but replacing lists with txt at this step // TODO: kinda jank but replacing lists with txt at this step
let damages = item.get(id); let damages = item.get(id);
if (item.get("tier") !== "Crafted") { if (item.get("tier") !== "Crafted") {
damages = damages.map(x => Math.round(x)); damages = damages.map(x => Math.floor(x));
item.set(id, damages[0]+"-"+damages[1]); item.set(id, damages[0]+"-"+damages[1]);
} }
else { else {
damages = damages.map(x => x.map(y => Math.round(y))); damages = damages.map(x => x.map(y => Math.floor(y)));
item.set(id, damages[0][0]+"-"+damages[0][1]+"\u279c"+damages[1][0]+"-"+damages[1][1]); item.set(id, damages[0][0]+"-"+damages[0][1]+"\u279c"+damages[1][0]+"-"+damages[1][1]);
} }
} }
@ -463,7 +466,6 @@ function displayExpandedItem(item, parent_id){
if (item.get("tier") && item.get("tier") !== " ") { if (item.get("tier") && item.get("tier") !== " ") {
let item_desc_elem = make_elem("div", ["col", item.get("tier")]); let item_desc_elem = make_elem("div", ["col", item.get("tier")]);
if (tome_types.includes(item.get("type"))) { if (tome_types.includes(item.get("type"))) {
tome_type_map = new Map([["weaponTome", "Weapon Tome"],["armorTome", "Armor Tome"],["guildTome", "Guild Tome"]]);
item_desc_elem.textContent = item.get("tier")+" "+tome_type_map.get(item.get("type")); item_desc_elem.textContent = item.get("tier")+" "+tome_type_map.get(item.get("type"));
} else { } else {
item_desc_elem.textContent = item.get("tier")+" "+item.get("type"); item_desc_elem.textContent = item.get("tier")+" "+item.get("type");
@ -493,7 +495,7 @@ function displayExpandedItem(item, parent_id){
base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3); base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3);
} }
else { else {
base_dps_elem.textContent = "Base DPS: "+(total_damages); base_dps_elem.textContent = "Base DPS: "+(total_damages.toFixed(3));
} }
parent_div.append(make_elem("p"), base_dps_elem); parent_div.append(make_elem("p"), base_dps_elem);
} }
@ -1178,14 +1180,6 @@ function displayDefenseStats(parent_elem, statMap, insertSummary){
boost.classList.add(eledefs[i] >= 0 ? "positive" : "negative"); boost.classList.add(eledefs[i] >= 0 ? "positive" : "negative");
boost.classList.add("col"); boost.classList.add("col");
boost.classList.add("text-end"); boost.classList.add("text-end");
let defRaw = statMap.get(skp_elements[i]+"Def");
let defPct = statMap.get(skp_elements[i]+"DefPct")/100;
if (defRaw < 0) {
defPct >= 0 ? defPct = "- " + defPct: defPct = "+ " + defPct;
} else {
defPct >= 0 ? defPct = "+ " + defPct: defPct = "- " + defPct;
}
eledefElemRow.appendChild(boost); eledefElemRow.appendChild(boost);
if (insertSummary) { if (insertSummary) {
@ -1231,7 +1225,7 @@ function displayDefenseStats(parent_elem, statMap, insertSummary){
} }
} }
function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overall=false) { function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon) {
parent_elem.textContent = ""; parent_elem.textContent = "";
if (powderSpecials.length === 0) { if (powderSpecials.length === 0) {
parent_elem.style = "display: none"; parent_elem.style = "display: none";
@ -1252,129 +1246,66 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
//each entry of powderSpecials is [ps, power] //each entry of powderSpecials is [ps, power]
for (special of specials) { for (special of specials) {
//iterate through the special and display its effects. //iterate through the special and display its effects.
let powder_special = make_elem("p", ["pt-3"]); let powder_special_elem = make_elem("p", ["pt-3"]);
let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]); let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]);
let specialTitle = make_elem("p"); let specialTitle = make_elem("p");
let specialEffects = make_elem("p"); let specialEffects = make_elem("p");
specialTitle.classList.add(damageClasses[powderSpecialStats.indexOf(special[0]) + 1]); // TODO janky and depends on the order of powder specials being ETWFA. This should be encoded in the powder special object.
let effects = special[0]["weaponSpecialEffects"]; let element_num = powderSpecialStats.indexOf(special[0]) + 1;
specialTitle.classList.add(damageClasses[element_num]);
let powder_special = special[0];
let power = special[1]; let power = special[1];
specialTitle.textContent = special[0]["weaponSpecialName"] + " " + Math.floor((power-1)*0.5 + 4) + (power % 2 == 0 ? ".5" : ""); specialTitle.textContent = powder_special.weaponSpecialName + " " + Math.floor((power-1)*0.5 + 4) + (power % 2 == 0 ? ".5" : "");
if (!overall || powderSpecialStats.indexOf(special[0]) == 2 || powderSpecialStats.indexOf(special[0]) == 3 || powderSpecialStats.indexOf(special[0]) == 4) { for (const [key,value] of powder_special.weaponSpecialEffects) {
for (const [key,value] of effects) { if(key === "Damage"){
//if this special is an instant-damage special (Quake, Chain Lightning, Courage Burst), display the damage.
let specialDamage = document.createElement("p");
// specialDamage.classList.add("item-margin");
let conversions = [0, 0, 0, 0, 0, 0];
conversions[element_num] = powder_special.weaponSpecialEffects.get("Damage")[power-1];
let _results = calculateSpellDamage(stats, weapon, conversions, false, true, "0.Powder Special");
let critChance = skillPointsToPercentage(skillpoints[1]);
let save_damages = [];
let totalDamNormal = _results[0];
let totalDamCrit = _results[1];
let results = _results[2];
for (let i = 0; i < 6; ++i) {
for (let j in results[i]) {
results[i][j] = results[i][j].toFixed(2);
}
}
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
let averageWrap = document.createElement("p");
let averageLabel = document.createElement("span");
averageLabel.textContent = "Average: ";
let averageLabelDmg = document.createElement("span");
averageLabelDmg.classList.add("Damage");
averageLabelDmg.textContent = averageDamage.toFixed(2);
averageWrap.appendChild(averageLabel);
averageWrap.appendChild(averageLabelDmg);
specialDamage.appendChild(averageWrap);
specialEffects.append(specialDamage);
}
else {
let effect = document.createElement("p"); let effect = document.createElement("p");
effect.textContent += key + ": " + value[power-1] + specialSuffixes.get(key); effect.textContent += key + ": " + value[power-1] + specialSuffixes.get(key);
if(key === "Damage"){
effect.textContent += elementIcons[powderSpecialStats.indexOf(special[0])];
}
if(special[0]["weaponSpecialName"] === "Wind Prison" && key === "Damage Boost") {
effect.textContent += " (only 1st hit)";
}
specialEffects.appendChild(effect); specialEffects.appendChild(effect);
} }
} }
powder_special.appendChild(specialTitle);
powder_special.appendChild(specialEffects);
//if this special is an instant-damage special (Quake, Chain Lightning, Courage Burst), display the damage. powder_special_elem.appendChild(specialTitle);
let specialDamage = document.createElement("p"); powder_special_elem.appendChild(specialEffects);
// specialDamage.classList.add("item-margin");
let spells = spell_table["powder"];
if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage
let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]);
let part = spell["parts"][0];
let tmp_conv = []; parent_elem.appendChild(powder_special_elem);
for (let i in part.conversion) {
tmp_conv.push(part.conversion[i] * part.multiplier[power-1] / 100);
}
console.log(tmp_conv);
let _results = calculateSpellDamage(stats, weapon, tmp_conv, false, true);
let critChance = skillPointsToPercentage(skillpoints[1]);
let save_damages = [];
let totalDamNormal = _results[0];
let totalDamCrit = _results[1];
let results = _results[2];
for (let i = 0; i < 6; ++i) {
for (let j in results[i]) {
results[i][j] = results[i][j].toFixed(2);
}
}
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
let averageWrap = document.createElement("p");
let averageLabel = document.createElement("span");
averageLabel.textContent = "Average: ";
let averageLabelDmg = document.createElement("span");
averageLabelDmg.classList.add("Damage");
averageLabelDmg.textContent = averageDamage.toFixed(2);
averageWrap.appendChild(averageLabel);
averageWrap.appendChild(averageLabelDmg);
specialDamage.appendChild(averageWrap);
if (!overall) {
let nonCritLabel = document.createElement("p");
nonCritLabel.textContent = "Non-Crit Average: "+nonCritAverage.toFixed(2);
nonCritLabel.classList.add("damageSubtitle");
nonCritLabel.classList.add("item-margin");
specialDamage.append(nonCritLabel);
for (let i = 0; i < 6; i++){
if (results[i][1] > 0){
let p = document.createElement("p");
p.classList.add("damagep");
p.classList.add(damageClasses[i]);
p.textContent = results[i][0]+"-"+results[i][1];
specialDamage.append(p);
}
}
let normalDamage = document.createElement("p");
normalDamage.textContent = "Total: " + totalDamNormal[0].toFixed(2) + "-" + totalDamNormal[1].toFixed(2);
normalDamage.classList.add("itemp");
specialDamage.append(normalDamage);
let nonCritChanceLabel = document.createElement("p");
nonCritChanceLabel.textContent = "Non-Crit Chance: " + ((1-critChance)*100).toFixed(2) + "%";
specialDamage.append(nonCritChanceLabel);
let critLabel = document.createElement("p");
critLabel.textContent = "Crit Average: "+critAverage.toFixed(2);
critLabel.classList.add("damageSubtitle");
critLabel.classList.add("item-margin");
specialDamage.append(critLabel);
for (let i = 0; i < 6; i++){
if (results[i][1] > 0){
let p = document.createElement("p");
p.classList.add("damagep");
p.classList.add(damageClasses[i]);
p.textContent = results[i][2]+"-"+results[i][3];
specialDamage.append(p);
}
}
let critDamage = document.createElement("p");
critDamage.textContent = "Total: " + totalDamCrit[0].toFixed(2) + "-" + totalDamCrit[1].toFixed(2);
critDamage.classList.add("itemp");
specialDamage.append(critDamage);
let critChanceLabel = document.createElement("p");
critChanceLabel.textContent = "Crit Chance: " + (critChance*100).toFixed(2) + "%";
specialDamage.append(critChanceLabel);
save_damages.push(averageDamage);
}
powder_special.append(specialDamage);
}
parent_elem.appendChild(powder_special);
} }
} }

View file

@ -35,42 +35,60 @@ let idPrefixes = {"displayName": "",
"str":"Strength: ", "str":"Strength: ",
"dex":"Dexterity: ", "dex":"Dexterity: ",
"int":"Intelligence: ", "int":"Intelligence: ",
"def":"Defense: ","agi":"Agility: ", "def":"Defense: ",
"agi":"Agility: ",
"hpBonus":"Health Bonus: ", "hpBonus":"Health Bonus: ",
"hprRaw":"Health Regen Raw: ", "hprRaw":"Raw Health Regen: ",
"hprPct":"Health Regen %: ", "hprPct":"Health Regen %: ",
"sdRaw":"Raw Spell Damage: ", "healPct":"Healing Effectiveness %",
"sdRaw":"Spell Damage Raw: ",
"rSdRaw":"Elem. Spell Damage Raw: ", "rSdRaw":"Elem. Spell Damage Raw: ",
"nSdRaw":"Neut. Spell Damage Raw: ", "nSdRaw":"Neut. Spell Damage Raw: ",
"eSdRaw":"Raw Earth Spell Damage: ", "eSdRaw":"Earth Spell Damage Raw: ",
"tSdRaw":"Raw Thunder Spell Damage: ", "tSdRaw":"Thunder Spell Damage Raw: ",
"wSdRaw":"Raw Water Spell Damage: ", "wSdRaw":"Water Spell Damage Raw: ",
"fSdRaw":"Raw Fire Spell Damage: ", "fSdRaw":"Fire Spell Damage Raw: ",
"aSdRaw":"Raw Air Spell Damage: ", "aSdRaw":"Air Spell Damage Raw: ",
"sdPct":"Spell Damage %: ", "sdPct":"Spell Damage %: ",
"rSdPct":"Elem. Spell Damage %: ", "rSdPct":"Elem. Spell Damage %: ",
"nSdPct":"Neut. Spell Damage %: ", "nSdPct":"Neut. Spell Damage %: ",
"eSdPct":"% Earth Spell Damage: ", "eSdPct":"Earth Spell Damage %: ",
"tSdPct":"% Thunder Spell Damage: ", "tSdPct":"Thunder Spell Damage %: ",
"wSdPct":"% Water Spell Damage: ", "wSdPct":"Water Spell Damage %: ",
"fSdPct":"% Fire Spell Damage: ", "fSdPct":"Fire Spell Damage %: ",
"aSdPct":"% Air Spell Damage: ", "aSdPct":"Air Spell Damage %: ",
"mdRaw":"Raw Melee Damage: ", "mdRaw":"Melee Damage Raw: ",
"rMdRaw":"Elem. Melee Damage Raw: ", "rMdRaw":"Elem. Melee Damage Raw: ",
"nMdRaw":"Neut. Melee Damage Raw: ", "nMdRaw":"Neut. Melee Damage Raw: ",
"eMdRaw":"Raw Earth Melee Damage: ", "eMdRaw":"Earth Melee Damage Raw: ",
"tMdRaw":"Raw Thunder Melee Damage: ", "tMdRaw":"Thunder Melee Damage Raw: ",
"wMdRaw":"Raw Water Melee Damage: ", "wMdRaw":"Water Melee Damage Raw: ",
"fMdRaw":"Raw Fire Melee Damage: ", "fMdRaw":"Fire Melee Damage Raw: ",
"aMdRaw":"Raw Air Melee Damage: ", "aMdRaw":"Air Melee Damage Raw: ",
"mdPct":"Melee Damage %: ", "mdPct":"Melee Damage %: ",
"rMdPct":"Elem. Melee Damage %: ", "rMdPct":"Elem. Melee Damage %: ",
"nMdPct":"Neut. Melee Damage %: ", "nMdPct":"Neut. Melee Damage %: ",
"eMdPct":"% Earth Melee Damage: ", "eMdPct":"Earth Melee Damage %: ",
"tMdPct":"% Thunder Melee Damage: ", "tMdPct":"Thunder Melee Damage %: ",
"wMdPct":"% Water Melee Damage: ", "wMdPct":"Water Melee Damage %: ",
"fMdPct":"% Fire Melee Damage: ", "fMdPct":"Fire Melee Damage %: ",
"aMdPct":"% Air Melee Damage: ", "aMdPct":"Air Melee Damage %: ",
"damRaw":"Damage Raw: ",
"rDamRaw":"Elemental Damage Raw: ",
"nDamRaw":"Neutral Damage Raw: ",
"eDamRaw":"Earth Damage Raw: ",
"tDamRaw":"Thunder Damage Raw: ",
"wDamRaw":"Water Damage Raw: ",
"fDamRaw":"Fire Damage Raw: ",
"aDamRaw":"Air Damage Raw: ",
"damPct":"Damage %: ",
"rDamPct":"Elemental Damage %: ",
"nDamPct":"Neutral Damage %: ",
"eDamPct":"Earth Damage %: ",
"tDamPct":"Thunder Damage %: ",
"wDamPct":"Water Damage %: ",
"fDamPct":"Fire Damage %: ",
"aDamPct":"Air Damage %: ",
"mr":"Mana Regen: ", "mr":"Mana Regen: ",
"ms":"Mana Steal: ", "ms":"Mana Steal: ",
"ref":"Reflection: ", "ref":"Reflection: ",
@ -80,16 +98,12 @@ let idPrefixes = {"displayName": "",
"expd":"Exploding: ", "expd":"Exploding: ",
"spd":"Walk Speed Bonus: ", "spd":"Walk Speed Bonus: ",
"atkTier":"Attack Speed Bonus: ", "atkTier":"Attack Speed Bonus: ",
"eDamPct":"Earth Damage %: ",
"tDamPct":"Thunder Damage %: ",
"wDamPct":"Water Damage %: ",
"fDamPct":"Fire Damage %: ",
"aDamPct":"Air Damage %: ",
"eDefPct":"Earth Defense %: ", "eDefPct":"Earth Defense %: ",
"tDefPct":"Thunder Defense %: ", "tDefPct":"Thunder Defense %: ",
"wDefPct":"Water Defense %: ", "wDefPct":"Water Defense %: ",
"fDefPct":"Fire Defense %: ", "fDefPct":"Fire Defense %: ",
"aDefPct":"Air Defense %: ", "aDefPct":"Air Defense %: ",
"rDefPct":"Elemental Defense %: ",
"spPct1":"1st Spell Cost %: ", "spPct1":"1st Spell Cost %: ",
"spRaw1":"1st Spell Cost Raw: ", "spRaw1":"1st Spell Cost Raw: ",
"spPct2":"2nd Spell Cost %: ", "spPct2":"2nd Spell Cost %: ",
@ -108,6 +122,9 @@ let idPrefixes = {"displayName": "",
"eSteal":"Stealing: ", "eSteal":"Stealing: ",
"gXp":"Gathering XP Bonus: ", "gXp":"Gathering XP Bonus: ",
"gSpd":"Gathering Speed Bonus: ", "gSpd":"Gathering Speed Bonus: ",
"kb":"Knockback: ",
"weakenEnemy":"Weaken Enemy: ",
"slowEnemy":"Slow Enemy: ",
"slots":"Powder Slots: ", "slots":"Powder Slots: ",
"set":"Set: ", "set":"Set: ",
"quest":"Quest Req: ", "quest":"Quest Req: ",
@ -143,6 +160,7 @@ let idSuffixes = {"displayName": "",
"hpBonus":"", "hpBonus":"",
"hprRaw":"", "hprRaw":"",
"hprPct":"%", "hprPct":"%",
"healPct":"%",
"sdRaw":"", "sdRaw":"",
"rSdRaw":"", "rSdRaw":"",
"nSdRaw":"", "nSdRaw":"",
@ -175,6 +193,22 @@ let idSuffixes = {"displayName": "",
"wMdPct":"%", "wMdPct":"%",
"fMdPct":"%", "fMdPct":"%",
"aMdPct":"%", "aMdPct":"%",
"damRaw":"",
"rDamRaw":"",
"nDamRaw":"",
"eDamRaw":"",
"tDamRaw":"",
"wDamRaw":"",
"fDamRaw":"",
"aDamRaw":"",
"damPct":"%",
"rDamPct":"%",
"nDamPct":"%",
"eDamPct":"%",
"tDamPct":"%",
"wDamPct":"%",
"fDamPct":"%",
"aDamPct":"%",
"mr":"/5s", "mr":"/5s",
"ms":"/3s", "ms":"/3s",
"ref":"%", "ref":"%",
@ -184,16 +218,12 @@ let idSuffixes = {"displayName": "",
"expd":"%", "expd":"%",
"spd":"%", "spd":"%",
"atkTier":" tier", "atkTier":" tier",
"eDamPct":"%",
"tDamPct":"%",
"wDamPct":"%",
"fDamPct":"%",
"aDamPct":"%",
"eDefPct":"%", "eDefPct":"%",
"tDefPct":"%", "tDefPct":"%",
"wDefPct":"%", "wDefPct":"%",
"fDefPct":"%", "fDefPct":"%",
"aDefPct":"%", "aDefPct":"%",
"rDefPct":"%",
"spPct1":"%", "spPct1":"%",
"spRaw1":"", "spRaw1":"",
"spPct2":"%", "spPct2":"%",
@ -212,11 +242,14 @@ let idSuffixes = {"displayName": "",
"eSteal":"%", "eSteal":"%",
"gXp":"%", "gXp":"%",
"gSpd":"%", "gSpd":"%",
"kb": "%",
"weakenEnemy": "%",
"slowEnemy": "%",
"slots":"", "slots":"",
"set":" set.", "set":" set.",
"quest":"", "quest":"",
"restrict":"", "restrict":"",
"lore": "" "lore": "",
}; };
//Used for item IDs and ingredient id field IDs //Used for item IDs and ingredient id field IDs
@ -245,10 +278,10 @@ let itemIDPrefixes = {
//Used for ingredient posMods IDs //Used for ingredient posMods IDs
let posModPrefixes = { let posModPrefixes = {
"left":"Effectiveness Left: ", "left":"Effectiveness Left: ",
"right":"EFfectiveness Right: ", "right":"Effectiveness Right: ",
"above":"Effectiveness Above: ", "above":"Effectiveness Above: ",
"under":"Effectiveness Under: ", "under":"Effectiveness Under: ",
"touching":"EFfectiveness Touching: ", "touching":"Effectiveness Touching: ",
"notTouching":"Effectiveness Not Touching: " "notTouching":"Effectiveness Not Touching: "
} }
let posModSuffixes = { let posModSuffixes = {
@ -279,6 +312,7 @@ let build_overall_display_commands = [
"spRegen", "spRegen",
"eSteal", "eSteal",
"gXp", "gSpd", "gXp", "gSpd",
"kb", "weakenEnemy", "slowEnemy",
]; ];
let build_detailed_display_commands = [ let build_detailed_display_commands = [
@ -286,19 +320,24 @@ let build_detailed_display_commands = [
"str", "dex", "int", "def", "agi", "str", "dex", "int", "def", "agi",
"!spacer", "!spacer",
"mr", "ms", "mr", "ms",
"hprRaw", "hprPct", "hprRaw", "hprPct", "healPct",
"ls", "ls",
"sdRaw", "nSdRaw", "rSdRaw", "sdRaw", "nSdRaw", "rSdRaw",
"sdPct", "nSdPct", "rSdPct", "sdPct", "nSdPct", "rSdPct",
"mdRaw", "nMdRaw", "rMdRaw", "mdRaw", "nMdRaw", "rMdRaw",
"mdPct", "nMdPct", "rMdPct", "mdPct", "nMdPct", "rMdPct",
"damRaw", "nDamRaw", "rDamRaw",
"damPct", "nDamPct", "rDamPct",
"!elemental", "!elemental",
"fSdRaw", "wSdRaw", "aSdRaw", "tSdRaw", "eSdRaw", "fSdRaw", "wSdRaw", "aSdRaw", "tSdRaw", "eSdRaw",
"fSdPct", "wSdPct", "aSdPct", "tSdPct", "eSdPct", "fSdPct", "wSdPct", "aSdPct", "tSdPct", "eSdPct",
"fMdRaw", "wMdRaw", "aMdRaw", "tMdRaw", "eMdRaw", "fMdRaw", "wMdRaw", "aMdRaw", "tMdRaw", "eMdRaw",
"fMdPct", "wMdPct", "aMdPct", "tMdPct", "eMdPct", "fMdPct", "wMdPct", "aMdPct", "tMdPct", "eMdPct",
"fDamRaw", "wDamRaw", "aDamRaw", "tDamRaw", "eDamRaw",
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
"!elemental", "!elemental",
"rDefPct",
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
"atkTier", "atkTier",
"poison", "poison",
@ -312,6 +351,7 @@ let build_detailed_display_commands = [
"spRegen", "spRegen",
"eSteal", "eSteal",
"gXp", "gSpd", "gXp", "gSpd",
"kb", "weakenEnemy", "slowEnemy",
]; ];
// full // full
@ -368,11 +408,7 @@ let sq2_item_display_commands = [
"!spacer", "!spacer",
"str", "dex", "int", "def", "agi", "str", "dex", "int", "def", "agi",
"hpBonus", "hpBonus",
"hprRaw", "hprPct", "hprRaw", "hprPct", "healPct",
"sdRaw", "nSdRaw", "eSdRaw", "tSdRaw", "wSdRaw", "fSdRaw", "aSdRaw", "rSdRaw",
"sdPct", "nSdPct", "eSdPct", "tSdPct", "wSdPct", "fSdPct", "aSdPct", "rSdPct",
"mdRaw", "nMdRaw", "eMdRaw", "tMdRaw", "wMdRaw", "fMdRaw", "aMdRaw", "rMdRaw",
"mdPct", "nMdPct", "eMdPct", "tMdPct", "wMdPct", "fMdPct", "aMdPct", "rMdPct",
"mr", "ms", "mr", "ms",
"ref", "thorns", "ref", "thorns",
"ls", "ls",
@ -380,10 +416,23 @@ let sq2_item_display_commands = [
"expd", "expd",
"spd", "spd",
"atkTier", "atkTier",
"sdRaw", "nSdRaw", "rSdRaw",
"sdPct", "nSdPct", "rSdPct",
"mdRaw", "nMdRaw", "rMdRaw",
"mdPct", "nMdPct", "rMdPct",
"damRaw", "nDamRaw", "rDamRaw",
"damPct", "nDamPct", "rDamPct",
"!elemental", "!elemental",
"fSdRaw", "wSdRaw", "aSdRaw", "tSdRaw", "eSdRaw",
"fSdPct", "wSdPct", "aSdPct", "tSdPct", "eSdPct",
"fMdRaw", "wMdRaw", "aMdRaw", "tMdRaw", "eMdRaw",
"fMdPct", "wMdPct", "aMdPct", "tMdPct", "eMdPct",
"fDamRaw", "wDamRaw", "aDamRaw", "tDamRaw", "eDamRaw",
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
"!elemental", "!elemental",
"rDefPct",
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
"sprint", "sprintReg", "sprint", "sprintReg",
"jh", "jh",
@ -391,6 +440,7 @@ let sq2_item_display_commands = [
"spRegen", "spRegen",
"eSteal", "eSteal",
"gXp", "gSpd", "gXp", "gSpd",
"kb", "weakenEnemy", "slowEnemy",
"majorIds", "majorIds",
"!spacer", "!spacer",
"slots", "slots",
@ -398,7 +448,7 @@ let sq2_item_display_commands = [
"set", "set",
"lore", "lore",
"quest", "quest",
"restrict" "restrict",
]; ];
let sq2_ing_display_order = [ let sq2_ing_display_order = [

View file

@ -50,6 +50,14 @@ const ExprParser = (function() {
case '=': case '=':
pushSymbol(exprStr[col]); pushSymbol(exprStr[col]);
continue; continue;
case 'or':
tokens.push({ type: '|' });
col += 2;
continue;
case 'and':
tokens.push({ type: '&' });
col += 2;
continue;
case '>': case '>':
pushSymbol(exprStr[col + 1] === '=' ? '>=' : '>'); pushSymbol(exprStr[col + 1] === '=' ? '>=' : '>');
continue; continue;

209
js/ingredients.js Normal file
View file

@ -0,0 +1,209 @@
// commented out filters
// "Name": "name",
// "Display Name": "displayName",
// "Tier": "stars",
// "Powder Slots": "slots",
// "Health": "hp",
// "Raw Fire Defense": "fDef",
// "Raw Water Defense": "wDef",
//"Raw Air Defense": "aDef",
// "Raw Thunder Defense": "tDef",
// "Raw Earth Defense": "eDef",
const translate_mappings = {
"Durability": "durability",
"Duration": "duration",
"Charges": "charges",
"Effectiveness Left": "left",
"Effectiveness Right": "right",
"Effectiveness Above": "above",
"Effectiveness Under": "under",
"Effectiveness Touching": "touching",
"Effectiveness Not Touching": "nottouching",
"Combat Level": "lvl",
"Req Strength": "strReq",
"Req Dexterity": "dexReq",
"Req Intelligence": "intReq",
"Req Agility": "agiReq",
"Req Defense": "defReq",
"% Health Regen": "hprPct",
"Mana Regen": "mr",
"Life Steal": "ls",
"Mana Steal": "ms",
"XP Bonus": "xpb",
"Loot Bonus": "lb",
"Reflection": "ref",
"Strength": "str",
"Dexterity": "dex",
"Intelligence": "int",
"Agility": "agi",
"Defense": "def",
"Thorns": "thorns",
"Exploding": "expd",
"Walk Speed": "spd",
"Attack Speed Bonus": "atkTier",
"Poison": "poison",
"Health Bonus": "hpBonus",
"Soul Point Regen": "spRegen",
"Stealing": "eSteal",
"Raw Health Regen": "hprRaw",
"Spell Damage Raw": "sdRaw",
"Elem. Spell Damage Raw": "rSdRaw",
"Neut. Spell Damage Raw": "nSdRaw",
"Earth Spell Damage Raw": "eSdRaw",
"Thunder Spell Damage Raw": "tSdRaw",
"Water Spell Damage Raw": "wSdRaw",
"Fire Spell Damage Raw": "fSdRaw",
"Air Spell Damage Raw": "aSdRaw",
"Spell Damage %": "sdPct",
"Elem. Spell Damage %": "rSdPct",
"Neut. Spell Damage %": "nSdPct",
"Earth Spell Damage %": "eSdPct",
"Thunder Spell Damage %": "tSdPct",
"Water Spell Damage %": "wSdPct",
"Fire Spell Damage %": "fSdPct",
"Air Spell Damage %": "aSdPct",
"Melee Damage Raw": "mdRaw",
"Elem. Melee Damage Raw": "rMdRaw",
"Neut. Melee Damage Raw": "nMdRaw",
"Earth Melee Damage Raw": "eMdRaw",
"Thunder Melee Damage Raw": "tMdRaw",
"Water Melee Damage Raw": "wMdRaw",
"Fire Melee Damage Raw": "fMdRaw",
"Air Melee Damage Raw": "aMdRaw",
"Melee Damage %": "mdPct",
"Elem. Melee Damage %": "rMdPct",
"Neut. Melee Damage %": "nMdPct",
"Earth Melee Damage %": "eMdPct",
"Thunder Melee Damage %": "tMdPct",
"Water Melee Damage %": "wMdPct",
"Fire Melee Damage %": "fMdPct",
"Air Melee Damage %": "aMdPct",
"Damage Raw": "damRaw",
"Elemental Damage Raw": "rDamRaw",
"Neutral Damage Raw": "nDamRaw",
"Earth Damage Raw": "eDamRaw",
"Thunder Damage Raw": "tDamRaw",
"Water Damage Raw": "wDamRaw",
"Fire Damage Raw": "fDamRaw",
"Air Damage Raw": "aDamRaw",
"Damage %": "damPct",
"Elemental Damage %": "rDamPct",
"Neutral Damage %": "nDamPct",
"Earth Damage %": "eDamPct",
"Thunder Damage %": "tDamPct",
"Water Damage %": "wDamPct",
"Fire Damage %": "fDamPct",
"Air Damage %": "aDamPct",
"% Fire Defense": "fDefPct",
"% Water Defense": "wDefPct",
"% Air Defense": "aDefPct",
"% Thunder Defense": "tDefPct",
"% Earth Defense": "eDefPct",
"% Elemental Defense": "rDefPct",
"1st Spell Cost %": "-spPct1",
"1st Spell Cost Raw": "-spRaw1",
"2nd Spell Cost %": "-spPct2",
"2nd Spell Cost Raw": "-spRaw2",
"3rd Spell Cost %": "-spPct3",
"3rd Spell Cost Raw": "-spRaw3",
"4th Spell Cost %": "-spPct4",
"4th Spell Cost Raw": "-spRaw4",
"Rainbow Spell Damage Raw": "rainbowRaw",
"Sprint": "sprint",
"Sprint Regen": "sprintReg",
"Jump Height": "jh",
"Loot Quality": "lq",
"Gather XP Bonus": "gXp",
"Gather Speed Bonus": "gSpd",
"Healing Efficiency": "healPct",
"Knockback": "kb",
"Weaken Enemy": "weakenEnemy",
"Slow Enemy": "slowEnemy"
};
const special_mappings = {
"Sum (skill points)": "str+dex+int+def+agi",
"Sum (Mana Sustain)": "mr+ms",
"Sum (Life Sustain)": "hpr+ls",
"Sum (Effectiveness)": "7/3 * touching + 8/3 * nottouching + 2/3 * (top + bottom) + 1/2 * (left + right)"
};
for (let x in translate_mappings) {
item_filters.push(x);
}
for (let x in special_mappings) {
item_filters.push(x);
}
types = {armouring: false, tailoring: false, weaponsmithing: false, woodworking: false, jeweling: false, cooking: false, alchemism: false, scribing: false};
search_tiers = {zero: true, one: true, two: true, three: true};
function display(ing_copy) {
let ing_parent = document.getElementById("search-results");
for (let i in ing_copy) {
if (i > 200) {break;}
let ing = ing_copy[i].itemExp;
let box = make_elem('div', ['ing-stats', 'col-lg-3', 'p-2', 'col-sm-6'], {id: 'ing'+i});
let bckgrdbox = make_elem('div', ["rounded", "g-0", "dark-7", "border", "border-dark", "dark-shadow", "p-3", "col-auto"], {id: 'ing'+i+'b'});
box.append(bckgrdbox);
ing_parent.appendChild(box);
displayExpandedIngredient(ing, bckgrdbox.id, true);
}
}
function filter_types_tiers(queries) {
// type
let allTypes = true, noTypes = true;
let typeQuery = "f:("
for (const type of Object.keys(types)) {
if (types[type]) {
typeQuery += type + "|";
noTypes = false;
} else {
allTypes = false;
}
}
if (noTypes) {
document.getElementById("summary").innerHTML = "Error: Cannot search without at least 1 type selected";
return false;
} else if (!allTypes) {
queries.push(typeQuery.substring(0, typeQuery.length - 1) + ")");
}
// stars
let allStars = true, noStars = true;
let starQuery = "f:("
for (const star of Object.keys(search_tiers)) {
if (search_tiers[star]) {
starQuery += "starsname=\"" + star + "\"|";
noStars = false;
} else {
allStars = false;
}
}
if (noStars) {
document.getElementById("summary").innerHTML = "Error: Cannot search without at least 1 star selected";
return false;
} else if (!allStars) {
queries.push(starQuery.substring(0, starQuery.length - 1) + ")");
}
return true;
}
function init_values() {
search_db = ings.filter( i => ! i.remapID ).map( i => [i, expandIngredient(i, [])] );
expr_parser = new ExprParser(ingredientQueryProps, queryFuncs);
}
(async function() {
await Promise.resolve(load_ing_init());
init_search();
})();

54
js/ingredients_adv.js Normal file
View file

@ -0,0 +1,54 @@
const getQueryIdentifiers = (function() {
let identCache = null;
return function() {
if (identCache === null) {
const idents = new Set();
for (const ident of Object.keys(ingredientQueryProps)) {
idents.add(ident);
}
for (const ident of Object.keys(queryFuncs)) {
idents.add(ident);
}
identCache = [...idents].sort(); // might use a trie optimally, but the set is probably small enough...
}
return identCache;
};
})();
function generateEntries(size, itemList, itemEntries) {
for (let i = 0; i < size; i++) {
const itemElem = document.createElement('div');
itemElem.classList.add('col-lg-3', 'col-sm-6', "p-2", "ing-stats");
// itemElem.setAttribute('id', `item-entry-${i}`);
itemList.append(itemElem);
itemEntries.push(itemElem);
const itemElemContained = document.createElement("div");
itemElemContained.classList.add("dark-7", "rounded", "p-3", "col-auto", "g-0", "border", "border-dark", "dark-shadow");
itemElemContained.setAttribute('id', `item-entry-${i}`);
itemElem.appendChild(itemElemContained);
const sortKeyListContainer = document.createElement('div');
sortKeyListContainer.classList.add('row');
sortKeyListContainer.setAttribute('id', `item-sort-entry-${i}`);
itemEntries[i].append(sortKeyListContainer);
}
}
function init_values() {
// compile the search db from the item db
searchDb = ings.filter(i => !i.remapID).map(i => [i, expandIngredient(i)]);
// create the expression parser
exprParser = new ExprParser(ingredientQueryProps, queryFuncs);
}
function display(itemExp, id) {
displayExpandedIngredient(itemExp, id);
}
(async function() {
await Promise.resolve(load_ing_init());
init_items_adv();
})();

View file

@ -60,7 +60,8 @@ function toggleAmps(button_id) {
(async function() { (async function() {
let load_promises = [ load_init() ]; let latest_ver_name = wynn_version_names[WYNN_VERSION_LATEST];
let load_promises = [ load_init(), load_major_id_data(latest_ver_name) ];
await Promise.all(load_promises); await Promise.all(load_promises);
init_itempage(); init_itempage();
})(); })();

414
js/item_display.js Normal file
View file

@ -0,0 +1,414 @@
/*
* File for display commands specific to the single item page.
*/
/** Displays the ID costs of an item
*
* @param {String} elemID - the id of the parent element.
* @param {Map} item - the statMap of an item.
*/
function displayIDCosts(elemID, item) {
let parent_elem = document.getElementById(elemID);
let tier = item.get("tier");
if ( (item.has("fixID") && item.get("fixID")) || ["Normal","Crafted","Custom","none", " ",].includes(item.get("tier"))) {
return;
} else {
/** Returns the number of inventory slots minimum an amount of emeralds would take up + the configuration of doing so.
* Returns an array of [invSpace, E, EB, LE, Stx LE]
*
* @param {number} ems - the total numerical value of emeralds to compact.
*/
function emsToInvSpace(ems) {
let stx = Math.floor(ems/262144);
ems -= stx*4096*64;
let LE = Math.floor(ems/4096);
ems -= LE*4096;
let EB = Math.floor(ems/64);
ems -= EB*64;
let e = ems;
return [ stx + Math.ceil(LE/64) + Math.ceil(EB/64) + Math.ceil(e/64) , e, EB, LE, stx];
}
/**
*
* @param {String} tier - item tier
* @param {Number} lvl - item level
*/
function getIDCost(tier, lvl) {
switch (tier) {
case "Unique":
return Math.round(0.5*lvl + 3);
case "Rare":
return Math.round(1.2*lvl + 8);
case "Legendary":
return Math.round(4.5*lvl + 12);
case "Fabled":
return Math.round(12*lvl + 26);
case "Mythic":
return Math.round(18*lvl + 90);
case "Set":
return Math.round(1.5*lvl + 8)
default:
return -1;
}
}
parent_elem.style = "display: visible";
let lvl = item.get("lvl");
if (typeof(lvl) === "string") { lvl = parseFloat(lvl); }
let title_elem = document.createElement("p");
title_elem.classList.add("smalltitle");
title_elem.style.color = "white";
title_elem.textContent = "Identification Costs";
parent_elem.appendChild(title_elem);
parent_elem.appendChild(document.createElement("br"));
let grid_item = document.createElement("div");
grid_item.style.display = "flex";
grid_item.style.flexDirection = "rows";
grid_item.style.flexWrap = "wrap";
grid_item.style.gap = "5px";
parent_elem.appendChild(grid_item);
let IDcost = getIDCost(tier, lvl);
let initIDcost = IDcost;
let invSpace = emsToInvSpace(IDcost);
let rerolls = 0;
while(invSpace[0] <= 28 && IDcost > 0) {
let container = document.createElement("div");
container.classList.add("container");
container.style = "grid-item-" + (rerolls+1);
container.style.maxWidth = "max(120px, 15%)";
let container_title = document.createElement("p");
container_title.style.color = "white";
if (rerolls == 0) {
container_title.textContent = "Initial ID Cost: ";
} else {
container_title.textContent = "Reroll to [" + (rerolls+1) + "] Cost:";
}
container.appendChild(container_title);
let total_cost_container = document.createElement("p");
let total_cost_number = document.createElement("b");
total_cost_number.classList.add("Set");
total_cost_number.textContent = IDcost + " ";
let total_cost_suffix = document.createElement("b");
total_cost_suffix.textContent = "emeralds."
total_cost_container.appendChild(total_cost_number);
total_cost_container.appendChild(total_cost_suffix);
container.appendChild(total_cost_container);
let OR = document.createElement("p");
OR.classList.add("center");
OR.textContent = "OR";
container.appendChild(OR);
let esuffixes = ["", "emeralds.", "EB.", "LE.", "stacks of LE."];
for (let i = 4; i > 0; i--) {
let n_container = document.createElement("p");
let n_number = document.createElement("b");
n_number.classList.add("Set");
n_number.textContent = invSpace[i] + " ";
let n_suffix = document.createElement("b");
n_suffix.textContent = esuffixes[i];
n_container.appendChild(n_number);
n_container.appendChild(n_suffix);
container.appendChild(n_container);
}
grid_item.appendChild(container);
rerolls += 1;
IDcost = Math.round(initIDcost * (5 ** rerolls));
invSpace = emsToInvSpace(IDcost);
}
}
}
/** Displays Additional Info for
*
* @param {String} elemID - the parent element's id
* @param {Map} item - the statMap of the item
* @returns
*/
function displayAdditionalInfo(elemID, item) {
let parent_elem = document.getElementById(elemID);
parent_elem.classList.add("left");
let droptype_elem = document.createElement("div");
droptype_elem.classList.add("container");
droptype_elem.style.marginBottom = "5px";
droptype_elem.textContent = "Drop type: " + (item.has("drop") ? item.get("drop"): "NEVER");
parent_elem.appendChild(droptype_elem);
let warning_elem = document.createElement("div");
warning_elem.classList.add("container");
warning_elem.style.marginBottom ="5px";
warning_elem.textContent = "This page is incomplete. Will work on it later.";
parent_elem.appendChild(warning_elem);
return;
}
/** Displays the individual probabilities of each possible value of each rollable ID for this item.
*
* @param {String} parent_id the document id of the parent element
* @param {String} item expandedItem object
* @param {String} amp the level of corkian amplifier used. 0 means no amp, 1 means Corkian Amplifier I, etc. [0,3]
*/
function displayIDProbabilities(parent_id, item, amp) {
if (item.has("fixID") && item.get("fixID")) {return}
let parent_elem = document.getElementById(parent_id);
parent_elem.style.display = "";
parent_elem.innerHTML = "";
let title_elem = document.createElement("p");
title_elem.textContent = "Identification Probabilities";
title_elem.id = "ID_PROB_TITLE";
title_elem.classList.add("Legendary");
title_elem.classList.add("title");
parent_elem.appendChild(title_elem);
let disclaimer_elem = document.createElement("p");
disclaimer_elem.textContent = "IDs are rolled on a uniform distribution. A chance of 0% means that either the minimum or maximum possible multiplier must be rolled to get this value."
parent_elem.appendChild(disclaimer_elem);
let amp_row = document.createElement("p");
amp_row.id = "amp_row";
let amp_text = document.createElement("b");
amp_text.textContent = "Corkian Amplifier Used: "
amp_row.appendChild(amp_text);
let amp_1 = document.createElement("button");
amp_1.id = "cork_amp_1";
amp_1.textContent = "I";
amp_row.appendChild(amp_1);
let amp_2 = document.createElement("button");
amp_2.id = "cork_amp_2";
amp_2.textContent = "II";
amp_row.appendChild(amp_2);
let amp_3 = document.createElement("button");
amp_3.id = "cork_amp_3";
amp_3.textContent = "III";
amp_row.appendChild(amp_3);
amp_1.addEventListener("click", (event) => {toggleAmps(1)});
amp_2.addEventListener("click", (event) => {toggleAmps(2)});
amp_3.addEventListener("click", (event) => {toggleAmps(3)});
parent_elem.appendChild(amp_row);
if (amp != 0) {toggleButton("cork_amp_" + amp)}
let item_name = item.get("displayName");
console.log(itemMap.get(item_name))
let table_elem = document.createElement("table");
parent_elem.appendChild(table_elem);
for (const [id,val] of Object.entries(itemMap.get(item_name))) {
if (rolledIDs.includes(id)) {
if (!item.get("maxRolls").get(id)) { continue; }
let min = item.get("minRolls").get(id);
let max = item.get("maxRolls").get(id);
//Apply corkian amps
if (val > 0) {
let base = itemMap.get(item_name)[id];
if (reversedIDs.includes(id)) {max = Math.max( Math.round((0.3 + 0.05*amp) * base), 1)}
else {min = Math.max( Math.round((0.3 + 0.05*amp) * base), 1)}
}
let row_title = document.createElement("tr");
//row_title.style.textAlign = "left";
let title_left = document.createElement("td");
let left_elem = document.createElement("p");
let left_val_title = document.createElement("b");
let left_val_elem = document.createElement("b");
title_left.style.textAlign = "left";
left_val_title.textContent = idPrefixes[id] + "Base ";
left_val_elem.textContent = val + idSuffixes[id];
if (val > 0 == !reversedIDs.includes(id)) {
left_val_elem.classList.add("positive");
} else if (val > 0 == reversedIDs.includes(id)) {
left_val_elem.classList.add("negative");
}
left_elem.appendChild(left_val_title);
left_elem.appendChild(left_val_elem);
title_left.appendChild(left_elem);
row_title.appendChild(title_left);
let title_right = document.createElement("td");
let title_right_text = document.createElement("b");
title_right.style.textAlign = "left";
title_right_text.textContent = "[ " + min + idSuffixes[id] + ", " + max + idSuffixes[id] + " ]";
if ( (min > 0 && max > 0 && !reversedIDs.includes(id)) || (min < 0 && max < 0 && reversedIDs.includes(id)) ) {
title_right_text.classList.add("positive");
} else if ( (min < 0 && max < 0 && !reversedIDs.includes(id)) || (min > 0 && max > 0 && reversedIDs.includes(id)) ) {
title_right_text.classList.add("negative");
}
title_right.appendChild(title_right_text);
let title_input = document.createElement("td");
let title_input_slider = document.createElement("input");
title_input_slider.type = "range";
title_input_slider.id = id+"-slider";
if (!reversedIDs.includes(id)) {
title_input_slider.step = 1;
title_input_slider.min = `${min}`;
title_input_slider.max = `${max}`;
title_input_slider.value = `${max}`;
} else {
title_input_slider.step = 1;
title_input_slider.min = `${-1*min}`;
title_input_slider.max = `${-1*max}`;
title_input_slider.value = `${-1*max}`;
}
let title_input_textbox = document.createElement("input");
title_input_textbox.type = "text";
title_input_textbox.value = `${max}`;
title_input_textbox.id = id+"-textbox";
title_input_textbox.classList.add("small-input");
title_input.appendChild(title_input_slider);
title_input.appendChild(title_input_textbox);
row_title.appendChild(title_left);
row_title.appendChild(title_right);
row_title.appendChild(title_input);
let row_chances = document.createElement("tr");
let chance_cdf = document.createElement("td");
let chance_pdf = document.createElement("td");
let cdf_p = document.createElement("p");
cdf_p.id = id+"-cdf";
let pdf_p = document.createElement("p");
pdf_p.id = id+"-pdf";
chance_cdf.appendChild(cdf_p);
chance_pdf.appendChild(pdf_p);
row_chances.appendChild(chance_cdf);
row_chances.appendChild(chance_pdf);
table_elem.appendChild(row_title);
table_elem.appendChild(row_chances);
stringPDF(id, max, val, amp); //val is base roll
stringCDF(id, max, val, amp); //val is base roll
title_input_slider.addEventListener("change", (event) => {
let id_name = event.target.id.split("-")[0];
let textbox_elem = document.getElementById(id_name+"-textbox");
if (reversedIDs.includes(id_name)) {
if (event.target.value < -1*min) { event.target.value = -1*min}
if (event.target.value > -1*max) { event.target.value = -1*max}
stringPDF(id_name, -1*event.target.value, val, amp); //val is base roll
stringCDF(id_name, -1*event.target.value, val, amp); //val is base roll
} else {
if (event.target.value < min) { event.target.value = min}
if (event.target.value > max) { event.target.value = max}
stringPDF(id_name, 1*event.target.value, val, amp); //val is base roll
stringCDF(id_name, 1*event.target.value, val, amp); //val is base roll
}
if (textbox_elem && textbox_elem.value !== event.target.value) {
if (reversedIDs.includes(id_name)) {
textbox_elem.value = -event.target.value;
} else {
textbox_elem.value = event.target.value;
}
}
});
title_input_textbox.addEventListener("change", (event) => {
let id_name = event.target.id.split("-")[0];
if (reversedIDs.includes(id_name)) {
if (event.target.value > min) { event.target.value = min}
if (event.target.value < max) { event.target.value = max}
} else {
if (event.target.value < min) { event.target.value = min}
if (event.target.value > max) { event.target.value = max}
}
let slider_elem = document.getElementById(id_name+"-slider");
if (slider_elem.value !== event.target.value) {
slider_elem.value = -event.target.value;
}
stringPDF(id_name, 1*event.target.value, val, amp);
stringCDF(id_name, 1*event.target.value, val, amp);
});
}
}
}
//helper functions. id - the string of the id's name, val - the value of the id, base - the base value of the item for this id
function stringPDF(id,val,base,amp) {
/** [0.3b,1.3b] positive normal
* [1.3b,0.3b] positive reversed
* [1.3b,0.7b] negative normal
* [0.7b,1.3b] negative reversed
*
* [0.3, 1.3] minr, maxr [0.3b, 1.3b] min, max
* the minr/maxr decimal roll that corresponds to val -> minround, maxround
*/
let p; let min; let max; let minr; let maxr; let minround; let maxround;
if (base > 0) {
minr = 0.3 + 0.05*amp; maxr = 1.3;
min = Math.max(1, Math.round(minr*base)); max = Math.max(1, Math.round(maxr*base));
minround = (min == max) ? (minr) : ( Math.max(minr, (val-0.5) / base) );
maxround = (min == max) ? (maxr) : ( Math.min(maxr, (val+0.5) / base) );
} else {
minr = 1.3; maxr = 0.7;
min = Math.min(-1, Math.round(minr*base)); max = Math.min(-1, Math.round(maxr*base));
minround = (min == max) ? (minr) : ( Math.min(minr, (val-0.5) / base) );
maxround = (min == max) ? (maxr) : ( Math.max(maxr, (val+0.5) / base) );
}
p = Math.abs(maxround-minround)/Math.abs(maxr-minr)*100;
p = p.toFixed(3);
let b1 = document.createElement("b");
b1.textContent = "Roll exactly ";
let b2 = document.createElement("b");
b2.textContent = val + idSuffixes[id];
if (val > 0 == !reversedIDs.includes(id)) {b2.classList.add("positive")}
if (val > 0 == reversedIDs.includes(id)) {b2.classList.add("negative")}
let b3 = document.createElement("b");
b3.textContent = ": " + p + "%";
document.getElementById(id + "-pdf").innerHTML = "";
document.getElementById(id + "-pdf").appendChild(b1);
document.getElementById(id + "-pdf").appendChild(b2);
document.getElementById(id + "-pdf").appendChild(b3);
}
function stringCDF(id,val,base,amp) {
let p; let min; let max; let minr; let maxr; let minround; let maxround;
if (base > 0) {
minr = 0.3 + 0.05*amp; maxr = 1.3;
min = Math.max(1, Math.round(minr*base)); max = Math.max(1, Math.round(maxr*base));
minround = (min == max) ? (minr) : ( Math.max(minr, (val-0.5) / base) );
maxround = (min == max) ? (maxr) : ( Math.min(maxr, (val+0.5) / base) );
} else {
minr = 1.3; maxr = 0.7;
min = Math.min(-1, Math.round(minr*base)); max = Math.min(-1, Math.round(maxr*base));
minround = (min == max) ? (minr) : ( Math.min(minr, (val-0.5) / base) );
maxround = (min == max) ? (maxr) : ( Math.max(maxr, (val+0.5) / base) );
}
if (reversedIDs.includes(id)) {
p = Math.abs(minr-maxround)/Math.abs(maxr-minr)*100;
} else {
p = Math.abs(maxr-minround)/Math.abs(maxr-minr)*100;
}
p = p.toFixed(3);
let b1 = document.createElement("b");
b1.textContent = "Roll ";
let b2 = document.createElement("b");
b2.textContent = val + idSuffixes[id];
if (val > 0 == !reversedIDs.includes(id)) {b2.classList.add("positive")}
if (val > 0 == reversedIDs.includes(id)) {b2.classList.add("negative")}
let b3 = document.createElement("b");
b3.textContent= " or better: " + p + "%";
document.getElementById(id + "-cdf").innerHTML = "";
document.getElementById(id + "-cdf").appendChild(b1);
document.getElementById(id + "-cdf").appendChild(b2);
document.getElementById(id + "-cdf").appendChild(b3);
}

View file

@ -56,18 +56,62 @@ const translate_mappings = {
"Soul Point Regen": "spRegen", "Soul Point Regen": "spRegen",
"Stealing": "eSteal", "Stealing": "eSteal",
"Raw Health Regen": "hprRaw", "Raw Health Regen": "hprRaw",
"Raw Spell": "sdRaw", "Spell Damage Raw": "sdRaw",
"Raw Melee": "mdRaw", "Elem. Spell Damage Raw": "rSdRaw",
"% Fire Damage": "fDamPct", "Neut. Spell Damage Raw": "nSdRaw",
"% Water Damage": "wDamPct", "Earth Spell Damage Raw": "eSdRaw",
"% Air Damage": "aDamPct", "Thunder Spell Damage Raw": "tSdRaw",
"% Thunder Damage": "tDamPct", "Water Spell Damage Raw": "wSdRaw",
"% Earth Damage": "eDamPct", "Fire Spell Damage Raw": "fSdRaw",
"Air Spell Damage Raw": "aSdRaw",
"Spell Damage %": "sdPct",
"Elem. Spell Damage %": "rSdPct",
"Neut. Spell Damage %": "nSdPct",
"Earth Spell Damage %": "eSdPct",
"Thunder Spell Damage %": "tSdPct",
"Water Spell Damage %": "wSdPct",
"Fire Spell Damage %": "fSdPct",
"Air Spell Damage %": "aSdPct",
"Melee Damage Raw": "mdRaw",
"Elem. Melee Damage Raw": "rMdRaw",
"Neut. Melee Damage Raw": "nMdRaw",
"Earth Melee Damage Raw": "eMdRaw",
"Thunder Melee Damage Raw": "tMdRaw",
"Water Melee Damage Raw": "wMdRaw",
"Fire Melee Damage Raw": "fMdRaw",
"Air Melee Damage Raw": "aMdRaw",
"Melee Damage %": "mdPct",
"Elem. Melee Damage %": "rMdPct",
"Neut. Melee Damage %": "nMdPct",
"Earth Melee Damage %": "eMdPct",
"Thunder Melee Damage %": "tMdPct",
"Water Melee Damage %": "wMdPct",
"Fire Melee Damage %": "fMdPct",
"Air Melee Damage %": "aMdPct",
"Damage Raw": "damRaw",
"Elemental Damage Raw": "rDamRaw",
"Neutral Damage Raw": "nDamRaw",
"Earth Damage Raw": "eDamRaw",
"Thunder Damage Raw": "tDamRaw",
"Water Damage Raw": "wDamRaw",
"Fire Damage Raw": "fDamRaw",
"Air Damage Raw": "aDamRaw",
"Damage %": "damPct",
"Elemental Damage %": "rDamPct",
"Neutral Damage %": "nDamPct",
"Earth Damage %": "eDamPct",
"Thunder Damage %": "tDamPct",
"Water Damage %": "wDamPct",
"Fire Damage %": "fDamPct",
"Air Damage %": "aDamPct",
"% Fire Defense": "fDefPct", "% Fire Defense": "fDefPct",
"% Water Defense": "wDefPct", "% Water Defense": "wDefPct",
"% Air Defense": "aDefPct", "% Air Defense": "aDefPct",
"% Thunder Defense": "tDefPct", "% Thunder Defense": "tDefPct",
"% Earth Defense": "eDefPct", "% Earth Defense": "eDefPct",
"% Elemental Defense": "rDefPct",
"1st Spell Cost %": "-spPct1", "1st Spell Cost %": "-spPct1",
"1st Spell Cost Raw": "-spRaw1", "1st Spell Cost Raw": "-spRaw1",
"2nd Spell Cost %": "-spPct2", "2nd Spell Cost %": "-spPct2",
@ -76,13 +120,16 @@ const translate_mappings = {
"3rd Spell Cost Raw": "-spRaw3", "3rd Spell Cost Raw": "-spRaw3",
"4th Spell Cost %": "-spPct4", "4th Spell Cost %": "-spPct4",
"4th Spell Cost Raw": "-spRaw4", "4th Spell Cost Raw": "-spRaw4",
"Rainbow Spell Damage Raw": "rainbowRaw",
"Sprint": "sprint", "Sprint": "sprint",
"Sprint Regen": "sprintReg", "Sprint Regen": "sprintReg",
"Jump Height": "jh", "Jump Height": "jh",
"Loot Quality": "lq", "Loot Quality": "lq",
"Gather XP Bonus": "gXp", "Gather XP Bonus": "gXp",
"Gather Speed Bonus": "gSpd" "Gather Speed Bonus": "gSpd",
"Healing Efficiency": "healPct",
"Knockback": "kb",
"Weaken Enemy": "weakenEnemy",
"Slow Enemy": "slowEnemy"
}; };
const special_mappings = { const special_mappings = {
@ -93,7 +140,6 @@ const special_mappings = {
"Base DPS": "(nDam+fDam+wDam+aDam+tDam+eDam) * atkspdmod(atkspd)" "Base DPS": "(nDam+fDam+wDam+aDam+tDam+eDam) * atkspdmod(atkspd)"
}; };
let item_filters = [];
for (let x in translate_mappings) { for (let x in translate_mappings) {
item_filters.push(x); item_filters.push(x);
} }
@ -101,18 +147,17 @@ for (let x in special_mappings) {
item_filters.push(x); item_filters.push(x);
} }
let item_categories = ["armor", "accessory", "weapon"]; types = {bow: false, spear: false, wand: false, dagger: false, relik: false, helmet: false, chestplate: false, leggings: false, boots: false, ring: false, bracelet: false, necklace: false};
search_tiers = {normal: true, unique: true, set: true, rare: true, legendary: true, fabled: true, mythic: true};
const types = {bow: false, spear: false, wand: false, dagger: false, relik: false, helmet: false, chestplate: false, leggings: false, boots: false, ring: false, bracelet: false, necklace: false}; function display(items_copy) {
const rarities = {normal: true, unique: true, set: true, rare: true, legendary: true, fabled: true, mythic: true};
const filters = [], excludes = [];
let filter_id_counter = 0;
function displayItems(items_copy) {
let items_parent = document.getElementById("search-results"); let items_parent = document.getElementById("search-results");
for (let i in items_copy) { for (let i in items_copy) {
if (i > 200) {break;} if (i > 200) {break;}
let item = items_copy[i].itemExp; let item = items_copy[i].itemExp;
let box = make_elem('div', ['col-lg-3', 'col-sm-6', 'p-2'], {id: 'item'+i}); let box = make_elem('div', ['col-lg-3', 'col-sm-6', 'p-2'], {id: 'item'+i});
let bckgrdbox = make_elem("div", ["dark-7", "rounded", "px-2", "col-auto"], {id: 'item'+i+'b'}); let bckgrdbox = make_elem("div", ["dark-7", "rounded", "px-2", "col-auto"], {id: 'item'+i+'b'});
@ -126,19 +171,7 @@ function displayItems(items_copy) {
} }
} }
let search_db; function filter_types_tiers(queries) {
let expr_parser;
function do_item_search() {
document.getElementById("summary").style.color = "red"; // to display errors, changed to white if search successful
window.scrollTo(0, 0);
let queries = [];
// name
if (document.getElementById("item-name-choice").value != "") {
queries.push("f:name?=\"" + document.getElementById("item-name-choice").value.trim() + "\"");
}
// types // types
let allTypes = true, noTypes = true; let allTypes = true, noTypes = true;
let typeQuery = "f:(" let typeQuery = "f:("
@ -152,7 +185,7 @@ function do_item_search() {
} }
if (noTypes) { if (noTypes) {
document.getElementById("summary").innerHTML = "Error: Cannot search without at least 1 type selected"; document.getElementById("summary").innerHTML = "Error: Cannot search without at least 1 type selected";
return; return false;
} else if (!allTypes) { } else if (!allTypes) {
queries.push(typeQuery.substring(0, typeQuery.length - 1) + ")"); queries.push(typeQuery.substring(0, typeQuery.length - 1) + ")");
} }
@ -160,8 +193,8 @@ function do_item_search() {
// rarities // rarities
let allRarities = true, noRarities = true; let allRarities = true, noRarities = true;
let rarityQuery = "f:(" let rarityQuery = "f:("
for (const rarity of Object.keys(rarities)) { for (const rarity of Object.keys(search_tiers)) {
if (rarities[rarity]) { if (search_tiers[rarity]) {
rarityQuery += "tiername=\"" + rarity + "\"|"; rarityQuery += "tiername=\"" + rarity + "\"|";
noRarities = false; noRarities = false;
} else { } else {
@ -170,353 +203,20 @@ function do_item_search() {
} }
if (noRarities) { if (noRarities) {
document.getElementById("summary").innerHTML = "Error: Cannot search without at least 1 rarity selected"; document.getElementById("summary").innerHTML = "Error: Cannot search without at least 1 rarity selected";
return; return false;
} else if (!allRarities) { } else if (!allRarities) {
queries.push(rarityQuery.substring(0, rarityQuery.length - 1) + ")"); queries.push(rarityQuery.substring(0, rarityQuery.length - 1) + ")");
} }
// filters
for (const filter of filters) {
let min = parseInt(filter.min_elem.value);
let max = parseInt(filter.max_elem.value);
if (min > max) {
document.getElementById("summary").innerHTML = "Error: The minimum of filter " + filter.input_elem.value + " (" + min + ") is greater than its maximum (" + max + ")";
return;
}
let zero_in_min_max = (isNaN(min) || min < 0) && (isNaN(max) || max > 0);
let raw_name = filter.input_elem.value; return true;
if (raw_name == "") {
continue; // empty
}
let filter_name = translate_mappings[raw_name];
if (filter_name === undefined) {
filter_name = special_mappings[raw_name];
if (filter_name === undefined) {
document.getElementById("summary").innerHTML = "Error: The filter \"" + filter.input_elem.value + "\" is not recognized";
return;
}
filter_name = "(" + filter_name + ")";
}
if (!isNaN(min)) {
queries.push("f:" + filter_name + ">=" + min);
}
if (!isNaN(max)) {
queries.push("f:" + filter_name + "<=" + max);
}
if (zero_in_min_max) {
queries.push("f:" + filter_name + "!=0");
}
queries.push("s:" + (filter.ascending ? "0-" : "") + filter_name);
}
// excludes
for (const exclude of excludes) {
let raw_name = exclude.input_elem.value;
if (raw_name == "") {
continue; // empty
}
let filter_name = translate_mappings[raw_name];
if (filter_name === undefined) {
filter_name = special_mappings[raw_name];
if (filter_name === undefined) {
document.getElementById("summary").innerHTML = "Error: The excluded filter \"" + exclude.input_elem.value + "\" is not recognized";
return;
}
filter_name = "(" + filter_name + ")";
}
queries.push("f:" + filter_name + "=0");
}
let filter_query = "true";
let sort_queries = [];
console.log(queries);
for (const query of queries) {
if (query.startsWith("s:")) {
sort_queries.push(query.slice(2));
}
else if (query.startsWith("f:")) {
filter_query = filter_query + "&" + query.slice(2);
}
}
document.getElementById("search-results").textContent = "";
let results = [];
try {
const filter_expr = expr_parser.parse(filter_query);
const sort_exprs = sort_queries.map(q => expr_parser.parse(q));
for (let i = 0; i < search_db.length; ++i) {
const item = search_db[i][0];
const itemExp = search_db[i][1];
if (checkBool(filter_expr.resolve(item, itemExp))) {
results.push({ item, itemExp, sortKeys: sort_exprs.map(e => e.resolve(item, itemExp)) });
}
}
results.sort((a, b) => {
return compareLexico(a.item, a.sortKeys, b.item, b.sortKeys);
});
} catch (e) {
document.getElementById("summary").textContent = e.message;
return;
}
document.getElementById("summary").textContent = results.length + " results:";
document.getElementById("summary").style.color = "white";
displayItems(results);
} }
function init_items() { function init_values() {
search_db = items.filter( i => ! i.remapID ).map( i => [i, expandItem(i, [])] ); search_db = items.filter( i => ! i.remapID ).map( i => [i, expandItem(i, [])] );
expr_parser = new ExprParser(itemQueryProps, itemQueryFuncs); expr_parser = new ExprParser(itemQueryProps, queryFuncs);
// init type buttons
for (const type of Object.keys(types)) {
document.getElementById("type-" + type).addEventListener("click", function() {
types[type] = !types[type];
this.classList.toggle("type-selected");
});
}
document.getElementById("all-types").addEventListener("click", function() {
for (const type of Object.keys(types)) {
types[type] = true;
document.getElementById("type-" + type).classList.add("type-selected");
}
});
document.getElementById("none-types").addEventListener("click", function() {
for (const type of Object.keys(types)) {
types[type] = false;
document.getElementById("type-" + type).classList.remove("type-selected");
}
});
// init rarity buttons
for (const rarity of Object.keys(rarities)) {
document.getElementById("rarity-" + rarity).addEventListener("click", function() {
rarities[rarity] = !rarities[rarity];
this.classList.toggle("rarity-selected");
});
}
document.getElementById("all-rarities").addEventListener("click", function() {
for (const rarity of Object.keys(rarities)) {
rarities[rarity] = true;
document.getElementById("rarity-" + rarity).classList.add("rarity-selected");
}
});
document.getElementById("none-rarities").addEventListener("click", function() {
for (const rarity of Object.keys(rarities)) {
rarities[rarity] = false;
document.getElementById("rarity-" + rarity).classList.remove("rarity-selected");
}
});
// filters
document.getElementById("add-filter").addEventListener("click", create_filter);
document.getElementById("add-exclude").addEventListener("click", create_exclude);
create_filter();
filters[0].input_elem.value = "Combat Level";
init_filter_drag();
}
function reset_item_search() {
document.getElementById("item-name-choice").value = "";
document.getElementById("all-types").click();
document.getElementById("all-rarities").click();
}
function create_filter() {
let data = {ascending: false};
let row = make_elem("div", ["row", "filter-row"], {});
let col = make_elem("div", ["col"], {});
row.appendChild(col);
data.div = row;
let reorder_img = make_elem("img", ["reorder-filter"], {src: "../media/icons/3-lines.svg", draggable: "true"});
col.appendChild(reorder_img);
let filter_input = make_elem("input",
["col", "border-dark", "text-light", "dark-5", "rounded", "scaled-font", "form-control", "form-control-sm", "filter-input"],
{id: "filter-input-" + filter_id_counter, type: "text", placeholder: "Filter"}
);
filter_id_counter++;
col.appendChild(filter_input);
data.input_elem = filter_input;
let asc_desc = make_elem("div", [], {style: "cursor: pointer; display: inline-block;"});
asc_desc.appendChild(make_elem("img", ["desc-icon", "asc-sel"], {src: "../media/icons/triangle.svg"}));
asc_desc.appendChild(make_elem("img", ["asc-icon"], {src: "../media/icons/triangle.svg"}));
asc_desc.addEventListener("click", function() {
data.ascending = !data.ascending;
asc_desc.children[0].classList.toggle("asc-sel");
asc_desc.children[1].classList.toggle("asc-sel");
});
col.appendChild(asc_desc);
let min = make_elem("input",
["col", "border-dark", "text-light", "dark-5", "rounded", "scaled-font", "form-control", "form-control-sm", "min-max-input"],
{type: "number", placeholder: "-\u221E"}
);
col.appendChild(min);
data.min_elem = min;
let to = make_elem("span", [], {innerHTML: "&nbsp;to&nbsp;"});
col.appendChild(to);
let max = make_elem("input",
["col", "border-dark", "text-light", "dark-5", "rounded", "scaled-font", "form-control", "form-control-sm", "min-max-input"],
{type: "number", placeholder: "\u221E"}
);
col.appendChild(max);
data.max_elem = max;
let trash = make_elem("img", ["delete-filter"], {src: "../media/icons/trash.svg"});
trash.addEventListener("click", function() {
filters.splice(Array.from(row.parentElement.children).indexOf(row) - 1, 1);
row.remove();
});
col.appendChild(trash);
document.getElementById("filter-container").insertBefore(row, document.getElementById("add-filter").parentElement);
filters.push(data);
init_filter_dropdown(data);
}
let currently_dragging = null;
function init_filter_drag() {
let container = document.getElementById("filter-container");
container.addEventListener("dragstart", function(e) {
if (e.path[0].classList.contains("reorder-filter")) {
currently_dragging = filters[Array.from(e.path[3].children).indexOf(e.path[2]) - 1];
} else {
e.preventDefault();
}
});
container.addEventListener("dragenter", function(e) {
e.preventDefault();
});
container.addEventListener("dragleave", function(e) {
e.preventDefault();
});
container.addEventListener("dragend", function(e) {
e.preventDefault();
for (const el of document.getElementsByClassName("filter-dragged-over")) {
el.classList.remove("filter-dragged-over");
}
currently_dragging = null;
});
container.addEventListener("dragover", function(e) {
e.preventDefault();
for (const el of document.getElementsByClassName("filter-dragged-over")) {
el.classList.remove("filter-dragged-over");
}
if (!e.path.includes(currently_dragging.div)) {
for (let i = 0; i < e.path.length; i++) {
if (e.path[i].classList.contains("filter-row")) {
e.path[i].classList.add("filter-dragged-over");
break;
}
}
}
});
container.addEventListener("drop", function(e) {
e.preventDefault();
for (const el of document.getElementsByClassName("filter-dragged-over")) {
el.classList.remove("filter-dragged-over");
}
if (!e.path.includes(currently_dragging.div)) {
for (let i = 0; i < e.path.length; i++) {
if (e.path[i].classList.contains("filter-row")) {
let old_index = filters.indexOf(currently_dragging);
let new_index = Array.from(e.path[i + 1].children).indexOf(e.path[i]) - 1;
filters.splice(old_index, 1);
filters.splice(new_index, 0, currently_dragging);
currently_dragging.div.remove();
container.insertBefore(currently_dragging.div, container.children[new_index + 1]);
break;
}
}
}
currently_dragging = null;
});
}
function create_exclude() {
let data = {};
let row = make_elem("div", ["row", "filter-row"], {});
let col = make_elem("div", ["col"], {});
row.appendChild(col);
data.div = row;
let filter_input = make_elem("input",
["col", "border-dark", "text-light", "dark-5", "rounded", "scaled-font", "form-control", "form-control-sm", "filter-input"],
{id: "filter-input-" + filter_id_counter, type: "text", placeholder: "Excluded Filter"}
);
filter_id_counter++;
col.appendChild(filter_input);
data.input_elem = filter_input;
let trash = make_elem("img", ["delete-filter"], {src: "../media/icons/trash.svg"});
trash.addEventListener("click", function() {
excludes.splice(Array.from(row.parentElement.children).indexOf(row) - 1, 1);
row.remove();
});
col.appendChild(trash);
document.getElementById("exclude-container").insertBefore(row, document.getElementById("add-exclude").parentElement);
excludes.push(data);
init_filter_dropdown(data);
}
function init_filter_dropdown(filter) {
let field_choice = filter.input_elem;
field_choice.onclick = function() {field_choice.dispatchEvent(new Event('input', {bubbles:true}));};
filter.autoComplete = new autoComplete({
data: {
src: item_filters,
},
threshold: 0,
selector: "#" + field_choice.id,
wrapper: false,
resultsList: {
maxResults: 100,
tabSelect: true,
noResults: true,
class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm",
element: (list, data) => {
let position = field_choice.getBoundingClientRect();
list.style.top = position.bottom + window.scrollY +"px";
list.style.left = position.x+"px";
list.style.width = position.width+"px";
list.style.maxHeight = position.height * 4 +"px";
if (!data.results.length) {
const message = make_elem('li', ['scaled-font'], {textContent: "No results found!"});
list.prepend(message);
};
},
},
resultItem: {
class: "scaled-font search-item",
selected: "dark-5",
},
events: {
input: {
selection: (event) => {
if (event.detail.selection.value) {
event.target.value = event.detail.selection.value;
};
},
},
}
});
} }
(async function() { (async function() {
await Promise.resolve(load_init()); await Promise.resolve(load_init(), load_major_id_data(wynn_version_names[WYNN_VERSION_LATEST]));
init_items(); init_search();
})(); })();

Some files were not shown because too many files have changed in this diff Show more