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)
This commit is contained in:
hppeng 2023-11-07 20:31:12 -08:00
parent 3e725eded8
commit 1d6b302f38
83 changed files with 325505 additions and 118669 deletions

View file

@ -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 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-fluid py-1 vh-100">

View file

@ -34,6 +34,8 @@
<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">
@ -73,6 +75,14 @@
<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>

File diff suppressed because one or more lines are too long

View file

@ -34,6 +34,8 @@
<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">
@ -73,6 +75,14 @@
<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>
@ -82,7 +92,7 @@
<div class="container-fluid overall-box mt-lg-2" style="margin-top: 6vh;">
<!-- REMOVE THIS DIV AT SOME POINT. -->
<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 class="row h-100 gx-lg-5 gy-3 mx-2 mx-lg-3 py-3 gx-0">
<div class="col-xl-6">
@ -827,6 +837,30 @@
</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 class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;">
@ -1328,5 +1362,11 @@
<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/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>
</html>

181278
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

@ -29,6 +29,8 @@
<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">
@ -68,6 +70,14 @@
<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>

View file

@ -31,6 +31,8 @@
<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">
@ -70,6 +72,14 @@
<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>

0
data/2.0.3.1/majid.json Executable file → Normal file
View file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
data/2.0.4.1/majid.json Executable file → 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/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

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

@ -43,6 +43,8 @@
<a href="" 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 text-light px-5 scaled-font">

View file

@ -30,6 +30,8 @@
<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">
@ -60,11 +62,19 @@
<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>g
</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>

View file

@ -30,6 +30,8 @@
<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">

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -30,6 +30,8 @@
<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>
@ -56,6 +58,7 @@
</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/builder/build_encode_decode.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/powders.js"></script>

View file

@ -30,6 +30,8 @@
<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">
@ -69,6 +71,14 @@
<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>

View file

@ -33,6 +33,8 @@
<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>
<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>
<main>
@ -152,7 +154,7 @@
['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.'],
['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.'],
['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.'],

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 weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ];
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 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
@ -307,8 +315,8 @@ function idRound(id){
* stupid stupid multiplicative stats
*/
function merge_stat(stats, name, value) {
const start = name.slice(0, 7);
if (start === 'damMult' || start === 'defMult') {
const start = name.split('.', limit=1)[0];
if (start === 'damMult' || start === 'defMult' || start === 'healMult') {
if (!stats.has(start)) {
stats.set(start, new Map());
}
@ -319,7 +327,7 @@ function merge_stat(stats, name, value) {
}
return;
}
merge_stat(map, name.slice(8), value);
merge_stat(map, name.slice(name.indexOf('.')+1), value);
return;
}
if (stats.has(name)) {

View file

@ -76,22 +76,23 @@ raw_stat: {
bonuses: List[stat_bonus]
}
stat_bonus: {
"type": "stat" | "prop",
"abil": Optional[int],
"name": str,
"value": float
type: "stat" | "prop",
abil: Optional[int],
name: str,
value: float
}
stat_scaling: {
"type": "stat_scaling",
"slider": bool,
type: "stat_scaling",
slider: bool,
positive: bool // True to keep stat above 0. False to ignore floor. Default: True for normal, False for scaling
"slider_name": Optional[str],
"slider_step": Optional[float],
round: Optional[bool] // Control floor behavior. True for stats and false for slider by default
behavior: Optional[str] // One of: "merge", "modify". default: merge
slider_name: Optional[str],
slider_step: Optional[float],
round: Optional[bool] // Control floor behavior. True for stats and false for slider by default
behavior: Optional[str] // One of: "merge", "modify". default: merge
// merge: add if exist, make new part if not exist
// modify: change existing part, by incrementing properties. do nothing if not exist
slider_max: Optional[float] // affected by 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
output: Optional[scaling_target | List[scaling_target]] // One of the following:
@ -99,12 +100,12 @@ stat_scaling: {
// 2. List of scaling targets (all scaled the same)
// 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.
max: float
max: float // Hardcap on this effect (slider value * slider_step). Can be negative if scaling is negative
}
scaling_target: {
"type": "stat" | "prop",
"abil": Optional[int],
"name": str
type: "stat" | "prop",
abil: Optional[int],
name: str
}
*/
@ -165,6 +166,64 @@ const default_abils = {
}, 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)
*
@ -176,43 +235,7 @@ const atree_node = new (class extends ComputeNode {
compute_func(input_map) {
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 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;
return get_sorted_class_atree(atrees, player_class);
}
})();
@ -476,14 +499,42 @@ const atree_merge = new (class extends ComputeNode {
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) {
if (abil["class"] === build_class) { merge_abil(abil); }
// 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;
}
})().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state').link_to(atree_validate, 'atree-errors');
@ -540,20 +591,17 @@ const atree_make_interactives = new (class extends ComputeNode {
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, slider_step } = effect;
const { slider_name, behavior = 'merge', slider_max = 0, slider_step, slider_default = 0 } = effect;
if (slider_map.has(slider_name)) {
if (slider_max !== undefined) {
const slider_info = slider_map.get(slider_name);
slider_info.max += slider_max;
}
else {
unprocessed.push([effect, abil_id, ability]);
}
const slider_info = slider_map.get(slider_name);
slider_info.max += slider_max;
slider_info.default_val += slider_default;
}
else if (behavior === 'merge') {
slider_map.set(slider_name, {
label_name: slider_name+' ('+ability.display_name+')',
max: slider_max,
default_val: slider_default,
step: slider_step,
id: "ability-slider"+ability.id,
//color: effect['slider_color'] TODO: add colors to json

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -116,8 +116,8 @@ class Build {
}
statMap.set('damMult', new Map());
statMap.set('defMult', new Map());
statMap.get('damMult').set('tome', statMap.get('damMobs'))
statMap.get('defMult').set('tome', statMap.get('defMobs'))
statMap.get('damMult').set('tome', statMap.get('damMobs'));
statMap.get('defMult').set('tome', statMap.get('defMobs'));
statMap.set("activeMajorIDs", major_ids);
for (const [setName, count] of this.activeSetCounts) {
const bonus = sets.get(setName).bonuses[count-1];
@ -132,7 +132,8 @@ class Build {
}
statMap.set("poisonPct", 0);
statMap.set("critDamPct", 0);
statMap.set("healMult", 0);
statMap.set("healMult", new Map());
statMap.get('healMult').set('item', statMap.get('healPct'));
// The stuff relevant for damage calculation!!! @ferricles
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));

View file

@ -33,7 +33,9 @@ const wynn_version_names = [
'2.0.2.1',
'2.0.2.3',
'2.0.3.1',
'2.0.4.1'
'2.0.4.1',
'2.0.4.3',
'2.0.4.4'
];
const WYNN_VERSION_LATEST = wynn_version_names.length - 1;
// Default to the newest version.
@ -65,7 +67,7 @@ async function parse_hash(url_tag) {
}
//default values
let equipment = [null, null, null, null, null, null, null, null, null];
let tomes = [null, null, null, null, null, null, null];
let tomes = [null, null, null, null, null, null, null, null];
let powdering = ["", "", "", "", ""];
let info = url_tag.split("_");
let version = info[0];
@ -120,6 +122,7 @@ async function parse_hash(url_tag) {
await Promise.all(load_promises);
}
}
if (wynn_version_id == WYNN_VERSION_LATEST) {
await Promise.all(default_load_promises);
}
@ -150,7 +153,7 @@ async function parse_hash(url_tag) {
}
data_str = info_str.slice(start_idx);
}
else if (version_number <= 8) {
else if (version_number <= 9) {
let info_str = data_str;
let start_idx = 0;
for (let i = 0; i < 9; ++i ) {
@ -191,7 +194,7 @@ async function parse_hash(url_tag) {
let powder_info = data_str.slice(10);
let res = parsePowdering(powder_info);
powdering = res[0];
} else if (version_number <= 8){
} else if (version_number <= 9){
level = Base64.toInt(data_str.slice(10,12));
setValue("level-choice",level);
save_skp = true;
@ -210,7 +213,7 @@ async function parse_hash(url_tag) {
if (version_number >= 6) {
//tome values do not appear in anything before v6.
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_name = getTomeNameFromID(Base64.toInt(tome_str));
setValue(tomeInputs[i], tome_name);
@ -219,12 +222,21 @@ async function parse_hash(url_tag) {
}
else {
// 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_name = getTomeNameFromID(Base64.toInt(tome_str));
setValue(tomeInputs[i], tome_name);
}
data_str = data_str.slice(14);
data_str = data_str.slice(num_tomes*2);
}
}
@ -256,7 +268,8 @@ function encodeBuild(build, powders, skillpoints, atree, atree_state) {
//V6 encoding - Tomes
//V7 encoding - ATree
//V8 encoding - wynn version
build_version = 8;
//V9 encoding - lootrun tome
build_version = 9;
build_string = "";
tome_string = "";
@ -269,10 +282,10 @@ function encodeBuild(build, powders, skillpoints, atree, atree_state) {
build_string += "CR-"+encodeCraft(item);
} else if (item.statMap.get("category") === "tome") {
let tome_id = item.statMap.get("id");
if (tome_id <= 60) {
//if (tome_id <= 60) {
// valid normal tome. ID 61-63 is for NONE tomes.
//build_version = Math.max(build_version, 6);
}
//}
tome_string += Base64.fromIntN(tome_id, 2);
} else {
build_string += Base64.fromIntN(item.statMap.get("id"), 3);
@ -331,8 +344,8 @@ function shareBuild(build) {
"> "+build.items[5].statMap.get("displayName")+"\n"+
"> "+build.items[6].statMap.get("displayName")+"\n"+
"> "+build.items[7].statMap.get("displayName")+"\n"+
"> "+build.items[15].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]\n";
for (let tomeslots = 8; tomeslots < 15; tomeslots++) {
"> "+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;

View file

@ -230,10 +230,13 @@ function init_autocomplete() {
}
let tome_alias = tome_obj['alias'];
tome_arr.push(tome_name);
tome_arr.push(tome_alias);
tome_aliases.set(tome_alias, tome_name);
if (tome_alias) {
tome_arr.push(tome_alias);
tome_aliases.set(tome_alias, tome_name);
}
}
console.log(tome_arr);
// create dropdown
dropdowns.set(eq, new autoComplete({
data: {

View file

@ -60,6 +60,7 @@ let tome_fields = [
"armorTome3",
"armorTome4",
"guildTome1",
"lootrunTome1"
]
let equipment_names = [
"Helmet",
@ -99,7 +100,7 @@ let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots'];
let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace'];
let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', '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 other_disp = ['build-order', 'set-info', 'int-info'];

View file

@ -413,8 +413,10 @@ class BuildAssembleNode extends ComputeNode {
input_map.get('armorTome2'),
input_map.get('armorTome3'),
input_map.get('armorTome4'),
input_map.get('guildTome1')
input_map.get('guildTome1'),
input_map.get('lootrunTome1')
];
console.log(equipments);
let weapon = input_map.get('weapon');
let level = parseInt(input_map.get('level-input'));
if (isNaN(level)) {
@ -537,7 +539,7 @@ function getDefenseStats(stats) {
//eledefs - TODO POWDERS
let eledefs = [0, 0, 0, 0, 0];
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);
@ -576,8 +578,21 @@ class SpellDamageCalcNode extends ComputeNode {
const use_speed = (('use_atkspd' in spell) ? spell.use_atkspd : 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) {
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;
const part_id = spell.base_spell + '.' + part.name
if ('multipliers' in part) { // damage type spell
@ -593,65 +608,68 @@ class SpellDamageCalcNode extends ComputeNode {
}
} else if ('power' in part) {
// TODO: wynn2 formula
let heal_additive = stats.get('healPct');
if (stats.has('healPct:'+part_id)) {
heal_additive += stats.get('healPct:'+part_id);
const mult_map = stats.get("healMult");
let heal_mult = 1;
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_mult = 1+(stats.get('healMult') / 100)
let _heal_amount = part.power * getDefenseStats(stats)[0] * (1 + (heal_additive/100)) * heal_mult;
let _heal_amount = part.power * getDefenseStats(stats)[0] * heal_mult;
spell_result = {
type: "heal",
heal_amount: _heal_amount
}
}
else {
continue;
}
const {name, display = true} = part;
spell_result.name = name;
spell_result.display = display;
display_spell_results.push(spell_result);
spell_result_map.set(name, spell_result);
}
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 { // if 'hits' in part
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
}
else {
spell_result.type = subpart.type;
}
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;
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 = eval_part(subpart_name);
if (!subpart) { continue; }
if (spell_result.type) {
if (subpart.type !== spell_result.type) {
throw "SpellCalc total subpart type mismatch";
}
}
}
else {
spell_result.heal_amount += subpart.heal_amount * hits;
else {
spell_result.type = subpart.type;
}
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;
spell_result.name = name;
spell_result.display = display;
display_spell_results.push(spell_result);
spell_result_map.set(name, spell_result);
return spell_result;
}
// 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;
}
@ -827,9 +845,13 @@ class AggregateStatsNode extends ComputeNode {
}
}
let radiance_affected = [ /*"hp"*/, "fDef", "wDef", "aDef", "tDef", "eDef", "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",
let radiance_affected = [ /*"hp"*/, "fDef", "wDef", "aDef", "tDef", "eDef", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms",
// "xpb", "lb",
"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.
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw",//"eDamAddMin","eDamAddMax",
@ -868,12 +890,12 @@ const radiance_node = new (class extends ComputeNode {
}
}
}
const dam_mults = new Map(ret.get('damMult'));
dam_mults.set('tome', dam_mults.get('tome') * 1.2)
ret.set('damMult', dam_mults)
const def_mults = new Map(ret.get('defMult'));
def_mults.set('tome', def_mults.get('tome') * 1.2)
ret.set('defMult', def_mults)
// const dam_mults = new Map(ret.get('damMult'));
// dam_mults.set('tome', dam_mults.get('tome') * 1.2)
// ret.set('damMult', dam_mults)
// const def_mults = new Map(ret.get('defMult'));
// def_mults.set('tome', def_mults.get('tome') * 1.2)
// ret.set('defMult', def_mults)
return ret;
}
else {
@ -1068,7 +1090,7 @@ function builder_graph_init(save_skp) {
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 item_image = document.getElementById(eq+"-img");

View file

@ -1,4 +1,14 @@
{
"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",
@ -150,24 +160,31 @@
},
"CHERRY_BOMBS": {
"displayName": "Cherry Bombs",
"description": "Your Smoke Bombs explode instantly, and increase their Neutral Damage by +90%",
"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": "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 }
}
]
"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": {
@ -214,13 +231,13 @@
"abilities": []
},
"DESC_FESTIVESPIRIT": {
"displayName": "Festive Spirits",
"displayName": "Festive Spirit",
"description": "Plays wintery tunes",
"abilities": []
},
"TEMBLOR": {
"displayName": "Temblor",
"description": "Bash gains +1 Area of Effect and is 20% faster.",
"description": "Bash gains +1 Area of Effect and is 25% faster.",
"abilities": [{
"class": "Warrior",
"base_abil": "Bash",
@ -230,37 +247,31 @@
},
"RECKLESS_ABANDON": {
"displayName": "Reckless Abandon",
"description": "Tempest deals +15% Fire damage and gains one additional charge. War Scream no longer grants a defence bonus.",
"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, 0, 0, 0, 15, 0]
"multipliers": [0, 5, 0, 0, 15, 5]
},
{
"type": "add_spell_prop",
"base_spell": 4,
"target_part": "Tempest Total Damage",
"behavior": "modify",
"hits": { "Tempest": 1 }
},
{
"type": "add_spell_prop",
"base_spell": 4,
"target_part": "Total Damage",
"behavior": "modify",
"hits": { "Tempest": 1 }
"hits": { "Tempest": 2 }
}
]
}]
},
"ALTEREGO": {
"displayName": "Alterego",
"displayName": "Alter Ego",
"description": "Awakened can be activated after saving 40% less mana, but its duration is reduced by 25%.",
"abilities": []
},
@ -296,7 +307,7 @@
}]
},
"SOUL_EATER": {
"displayName": "Soul eater",
"displayName": "Soul Eater",
"description": "Devour and Harvester grant double mana, but your maximum Marks are decreased by 1.",
"abilities": [{
"class": "Assassin",
@ -398,7 +409,7 @@
},
"DIVINE_HONOR": {
"displayName": "Divine Honor",
"description": "Increase the bonus from Radiance by 5%. Decrease the Neutral damage of Bash by -15%.",
"description": "Increase the bonus from Radiance by 5%. Decrease the Earth damage of Bash by -15%.",
"abilities": [{
"class": "Warrior",
"base_abil": "Bash",
@ -408,7 +419,7 @@
"base_spell": 1,
"target_part": "Single Hit",
"behavior": "modify",
"multipliers": [-15, 0, 0, 0, 0, 0]
"multipliers": [0, -15, 0, 0, 0, 0]
}
]
}]
@ -456,5 +467,135 @@
}
]
}]
},
"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

@ -1,4 +1,4 @@
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",]
function encodeCraft(craft) {
@ -180,7 +180,6 @@ class Craft{
/* Change certain IDs based on material tier.
healthOrDamage changes.
duration and durability change. (but not basicDuration)
*/
let matmult = 1;
let tierToMult = [0,1,1.25,1.4];

View file

@ -144,8 +144,8 @@ function calculateSpellDamage(stats, weapon, _conversions, use_spell_damage, ign
// Collect total damage post %boost
}
let total_elem_min = total_min - damages[0][0];
let total_elem_max = total_max - damages[0][1];
let total_elem_min = total_min - save_prop[0][0];
let total_elem_max = total_max - save_prop[0][1];
// 5.2: Raw application.
let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw');

View file

@ -309,11 +309,11 @@ function displayExpandedItem(item, parent_id){
// TODO: kinda jank but replacing lists with txt at this step
let damages = item.get(id);
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]);
}
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]);
}
}
@ -466,7 +466,6 @@ function displayExpandedItem(item, parent_id){
if (item.get("tier") && item.get("tier") !== " ") {
let item_desc_elem = make_elem("div", ["col", item.get("tier")]);
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"));
} else {
item_desc_elem.textContent = item.get("tier")+" "+item.get("type");
@ -496,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);
}
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);
}
@ -1181,14 +1180,6 @@ function displayDefenseStats(parent_elem, statMap, insertSummary){
boost.classList.add(eledefs[i] >= 0 ? "positive" : "negative");
boost.classList.add("col");
boost.classList.add("text-end");
let defRaw = statMap.get(skp_elements[i]+"Def");
let defPct = (statMap.get(skp_elements[i]+"DefPct") + statMap.get('rDefPct'))/100;
if (defRaw < 0) {
defPct >= 0 ? defPct = "- " + defPct: defPct = "+ " + defPct;
} else {
defPct >= 0 ? defPct = "+ " + defPct: defPct = "- " + defPct;
}
eledefElemRow.appendChild(boost);
if (insertSummary) {

View file

@ -335,7 +335,9 @@ let build_detailed_display_commands = [
"fMdPct", "wMdPct", "aMdPct", "tMdPct", "eMdPct",
"fDamRaw", "wDamRaw", "aDamRaw", "tDamRaw", "eDamRaw",
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
"!elemental",
"rDefPct",
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
"atkTier",
"poison",
@ -429,6 +431,8 @@ let sq2_item_display_commands = [
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
"!elemental",
"rDefPct",
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
"sprint", "sprintReg",
"jh",

View file

@ -47,54 +47,54 @@ const translate_mappings = {
"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",
"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",

View file

@ -60,7 +60,8 @@ function toggleAmps(button_id) {
(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);
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,54 +56,54 @@ const translate_mappings = {
"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",
"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",

View file

@ -1,4 +1,4 @@
const DB_VERSION = 129;
const DB_VERSION = 133;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
let db;

View file

@ -1,4 +1,4 @@
const ING_DB_VERSION = 28;
const ING_DB_VERSION = 32;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.js

View file

@ -1,4 +1,4 @@
const TOME_DB_VERSION = 7;
const TOME_DB_VERSION = 9;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
let tdb;
@ -134,9 +134,10 @@ async function load_tome_init() {
}
const none_tomes_info = [
["tome", "weaponTome", "No Weapon Tome"],
["tome", "armorTome", "No Armor Tome"],
["tome", "guildTome", "No Guild Tome"]
["tome", "weaponTome", "No Weapon Tome", 61],
["tome", "armorTome", "No Armor Tome", 62],
["tome", "guildTome", "No Guild Tome", 63],
["tome", "lootrunTome", "No Lootrun Tome", 93]
];
let none_tomes;
@ -152,7 +153,7 @@ function init_tome_maps() {
}
none_tomes = [];
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 4; i++) {
let tome = Object();
tome.slots = 0;
tome.category = none_tomes_info[i][0];
@ -166,7 +167,7 @@ function init_tome_maps() {
tome.reqs = [0, 0, 0, 0, 0];
tome.fixID = true;
tome.tier = "Normal";
tome.id = 61 + i; //special case!
tome.id = none_tomes_info[i][3];
tome.nDam = "0-0";
tome.eDam = "0-0";
tome.tDam = "0-0";

View file

@ -134,8 +134,9 @@ function calc_weapon_powder(weapon, damageBases) {
//1st round - apply each as ingred, 2nd round - apply as normal
if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) {
for (const p of powders.concat(weapon.get("ingredPowders"))) {
let powder = powderStats[p]; //use min, max, and convert
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
const powder = powderStats[p]; //use min, max, and convert
// Bitwise to force conversion to integer (integer division).
const element = (p/6) | 0;
let diff = Math.floor(damageBases[0] * powder.convert/100);
damageBases[0] -= diff;
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );

View file

@ -46,13 +46,34 @@ const itemQueryProps = (function() {
prop(names, 'number', (i, ie) => ie.get('minRolls').get(idKey) || 0);
}
function rangeAvg(names, getProp) {
function rangeAll(names, getProp) {
// Max
prop(names.map((s) => s+'max'), 'number', (i, ie) => {
const range = getProp(i, ie);
if (!range) return 0;
const ndx = range.indexOf('-');
return parseInt(range.substring(ndx + 1), 10);
});
// Min
prop(names.map((s) => s+'min'), 'number', (i, ie) => {
const range = getProp(i, ie);
if (!range) return 0;
const ndx = range.indexOf('-');
return parseInt(range.substring(0, ndx), 10);
});
// Average
prop(names, 'number', (i, ie) => {
const range = getProp(i, ie);
if (!range) return 0;
const ndx = range.indexOf('-');
return (parseInt(range.substring(0, ndx), 10) + parseInt(range.substring(ndx + 1), 10)) / 2;
});
prop(names.map((s) => s+'avg'), 'number', (i, ie) => {
const range = getProp(i, ie);
if (!range) return 0;
const ndx = range.indexOf('-');
return (parseInt(range.substring(0, ndx), 10) + parseInt(range.substring(ndx + 1), 10)) / 2;
});
}
function map(names, comps, outType, f) {
@ -77,6 +98,8 @@ const itemQueryProps = (function() {
const tierIndices = { Normal: 0, Unique: 1, Set: 2, Rare: 3, Legendary: 4, Fabled: 5, Mythic: 6 };
prop(['rarityname', 'raritystr', 'tiername', 'tierstr'], 'string', (i, ie) => i.tier);
prop(['rarity', 'tier'], 'number', (i, ie) => tierIndices[i.tier]);
prop(['majid', 'majorid'], 'string', (i, ie) => ((i.majorIds || [""])[0] || ""));
prop(['majids', 'majorids'], 'number', (i, ie) => (i.majorIds || []).length);
prop(['level', 'lvl', 'combatlevel', 'combatlvl'], 'number', (i, ie) => i.lvl);
prop(['strmin', 'strreq'], 'number', (i, ie) => i.strReq);
@ -93,12 +116,12 @@ const itemQueryProps = (function() {
prop('agi', 'number', (i, ie) => i.agi);
sum(['skillpoints', 'skillpts', 'attributes', 'attrs'], props.str, props.dex, props.int, props.def, props.agi);
rangeAvg(['neutraldmg', 'neutraldam', 'ndmg', 'ndam'], (i, ie) => i.nDam);
rangeAvg(['earthdmg', 'earthdam', 'edmg', 'edam'], (i, ie) => i.eDam);
rangeAvg(['thunderdmg', 'thunderdam', 'tdmg', 'tdam'], (i, ie) => i.tDam);
rangeAvg(['waterdmg', 'waterdam', 'wdmg', 'wdam'], (i, ie) => i.wDam);
rangeAvg(['firedmg', 'firedam', 'fdmg', 'fdam'], (i, ie) => i.fDam);
rangeAvg(['airdmg', 'airdam', 'admg', 'adam'], (i, ie) => i.aDam);
rangeAll(['neutraldmg', 'neutraldam', 'ndmg', 'ndam'], (i, ie) => i.nDam);
rangeAll(['earthdmg', 'earthdam', 'edmg', 'edam'], (i, ie) => i.eDam);
rangeAll(['thunderdmg', 'thunderdam', 'tdmg', 'tdam'], (i, ie) => i.tDam);
rangeAll(['waterdmg', 'waterdam', 'wdmg', 'wdam'], (i, ie) => i.wDam);
rangeAll(['firedmg', 'firedam', 'fdmg', 'fdam'], (i, ie) => i.fDam);
rangeAll(['airdmg', 'airdam', 'admg', 'adam'], (i, ie) => i.aDam);
sum(['sumdmg', 'sumdam', 'totaldmg', 'totaldam'], props.ndam, props.edam, props.tdam, props.wdam, props.fdam, props.adam);
maxId(['earthdmg%', 'earthdam%', 'edmg%', 'edam%', 'edampct'], 'eDamPct');
@ -106,13 +129,43 @@ const itemQueryProps = (function() {
maxId(['waterdmg%', 'waterdam%', 'wdmg%', 'wdam%', 'wdampct'], 'wDamPct');
maxId(['firedmg%', 'firedam%', 'fdmg%', 'fdam%', 'fdampct'], 'fDamPct');
maxId(['airdmg%', 'airdam%', 'admg%', 'adam%', 'adampct'], 'aDamPct');
sum(['sumdmg%', 'sumdam%', 'totaldmg%', 'totaldam%', 'sumdampct', 'totaldampct'], props.edampct, props.tdampct, props.wdampct, props.fdampct, props.adampct);
//sum(['sumdmg%', 'sumdam%', 'totaldmg%', 'totaldam%', 'sumdampct', 'totaldampct'], props.edampct, props.tdampct, props.wdampct, props.fdampct, props.adampct);
maxId(['mainatkdmg', 'mainatkdam', 'mainatkdmg%', 'mainatkdam%', 'meleedmg', 'meleedam', 'meleedmg%', 'meleedam%', 'mdpct'], 'mdPct');
maxId(['emdpct'], 'eMdPct');
maxId(['tmdpct'], 'tMdPct');
maxId(['wmdpct'], 'wMdPct');
maxId(['fmdpct'], 'fMdPct');
maxId(['amdpct'], 'aMdPct');
maxId(['nmdpct'], 'nMdPct');
maxId(['rmdpct'], 'rMdPct');
maxId(['mainatkrawdmg', 'mainatkrawdam', 'mainatkneutraldmg', 'mainatkneutraldam', 'meleerawdmg', 'meleerawdam', 'meleeneutraldmg', 'meleeneutraldam', 'mdraw'], 'mdRaw');
maxId(['emdraw'], 'eMdRaw');
maxId(['tmdraw'], 'tMdRaw');
maxId(['wmdraw'], 'wMdRaw');
maxId(['fmdraw'], 'fMdRaw');
maxId(['amdraw'], 'aMdRaw');
maxId(['nmdraw'], 'nMdRaw');
maxId(['rmdraw'], 'rMdRaw');
maxId(['spelldmg', 'spelldam', 'spelldmg%', 'spelldam%', 'sdpct'], 'sdPct');
maxId(['esdpct'], 'eSdPct');
maxId(['tsdpct'], 'tSdPct');
maxId(['wsdpct'], 'wSdPct');
maxId(['fsdpct'], 'fSdPct');
maxId(['asdpct'], 'aSdPct');
maxId(['nsdpct'], 'nSdPct');
maxId(['rsdpct'], 'rSdPct');
maxId(['spellrawdmg', 'spellrawdam', 'spellneutraldmg', 'spellneutraldam', 'sdraw'], 'sdRaw');
maxId(['rainbowraw'], 'rSdRaw');
maxId(['esdraw'], 'eSdRaw');
maxId(['tsdraw'], 'tSdRaw');
maxId(['wsdraw'], 'wSdRaw');
maxId(['fsdraw'], 'fSdRaw');
maxId(['asdraw'], 'aSdRaw');
maxId(['nsdraw'], 'nSdRaw');
maxId(['rainbowraw', 'rsdraw'], 'rSdRaw');
const atkSpdIndices = { SUPER_SLOW: -3, VERY_SLOW: -2, SLOW: -1, NORMAL: 0, FAST: 1, VERY_FAST: 2, SUPER_FAST: 3 };
prop(['attackspeed', 'atkspd'], 'string', (i, ie) => i.atkSpd ? atkSpdIndices[i.atkSpd] : 0);
@ -131,6 +184,7 @@ const itemQueryProps = (function() {
maxId(['waterdef%', 'wdef%', 'wdefpct'], 'wDefPct');
maxId(['firedef%', 'fdef%', 'fdefpct'], 'fDefPct');
maxId(['airdef%', 'adef%', 'adefpct'], 'aDefPct');
maxId(['eledef%', 'rdef%', 'rdefpct'], 'rDefPct');
sum(['sumdef%', 'totaldef%', 'sumdefpct', 'totaldefpct'], props.edefpct, props.tdefpct, props.wdefpct, props.fdefpct, props.adefpct);
prop(['health', 'hp'], 'number', (i, ie) => i.hp || 0);
@ -167,6 +221,14 @@ const itemQueryProps = (function() {
maxId(['lootbonus', 'lb'], 'lb');
maxId(['xpbonus', 'xpb', 'xb'], 'xpb');
maxId(['stealing', 'esteal'], 'eSteal');
maxId(['lq', 'quality'], 'lq');
maxId('gxp', 'gXp');
maxId('gspd', 'gSpd');
maxId(['healeff', 'healpct'], 'healPct');
maxId('kb', 'kb');
maxId('weakenenemy', 'weakenEnemy');
maxId('slowenemy', 'slowEnemy');
prop(['powderslots', 'powders', 'slots', 'sockets'], 'number', (i, ie) => i.slots || 0);
return props;

View file

@ -206,6 +206,7 @@ class ExprField {
try {
this.output = this.compiler(this.text);
} catch (e) {
console.log(e);
this.errorText.innerText = e.message;
this.output = null;
}
@ -303,6 +304,7 @@ function init_items_adv() {
}
}
} catch (e) {
console.log(e);
searchFilterField.errorText.innerText = e.message;
return;
}
@ -320,6 +322,7 @@ function init_items_adv() {
}
});
} catch (e) {
console.log(e);
searchSortField.errorText.innerText = e.message;
return;
}

View file

@ -43,6 +43,8 @@
<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">
@ -82,6 +84,14 @@
<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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -6,71 +6,73 @@ given [atree_constants.js] .js form of the Ability Tree with reference as string
"""
import json
def translate_id(id_data, atree_data):
def translate_spell_part(id_data, part):
if 'hits' in part: # Translate parametrized hits...
hits_mapping = part['hits']
keys = list(hits_mapping.keys())
for k in keys:
v = hits_mapping[k]
if isinstance(v, str):
abil_id, propname = v.split('.')
hits_mapping[k] = str(id_data[abil_id])+'.'+propname
def translate_effect(id_data, effect):
if effect["type"] == "raw_stat":
for bonus in effect["bonuses"]:
if "abil" in bonus and bonus["abil"] in id_data:
bonus["abil"] = id_data[bonus["abil"]]
elif effect["type"] == "replace_spell":
for part in effect['parts']:
translate_spell_part(id_data, part)
elif effect["type"] == "add_spell_prop":
translate_spell_part(id_data, effect)
elif effect["type"] == "stat_scaling":
if "inputs" in effect: # Might not exist for sliders
for _input in effect["inputs"]:
if "abil" in _input and _input["abil"] in id_data:
_input["abil"] = id_data[_input["abil"]]
if "output" in effect:
if isinstance(effect["output"], list):
for output in effect["output"]:
if "abil" in output and output["abil"] in id_data:
output["abil"] = id_data[output["abil"]]
else:
if "abil" in effect["output"] and effect["output"]["abil"] in id_data:
effect["output"]["abil"] = id_data[effect["output"]["abil"]]
def translate_abil(id_data, abil, tree=True):
def translate(path, ref):
ref_dict = abil
for x in path:
ref_dict = ref_dict[x]
ref_dict[ref] = id_data[ref_dict[ref]]
for optional_key in ["parents", "dependencies", "blockers"]:
if optional_key not in abil:
if tree:
print(f"WARNING: atree node missing required key [{optional_key}]")
continue
for ref in range(len(abil[optional_key])):
translate([optional_key], ref)
if "base_abil" in abil:
base_abil_name = abil["base_abil"]
if base_abil_name in id_data:
translate([], "base_abil")
if "effects" not in abil:
print("WARNING: abil missing 'effects' tag")
print(abil)
abil["effects"] = []
for effect in abil["effects"]:
translate_effect(id_data, effect)
def translate_all(id_data, atree_data):
for _class, info in atree_data.items():
def translate(path, ref):
ref_dict = info
for x in path:
ref_dict = ref_dict[x]
ref_dict[ref] = id_data[_class][ref_dict[ref]]
for abil in range(len(info)):
info[abil]["id"] = id_data[_class][info[abil]["display_name"]]
for ref in range(len(info[abil]["parents"])):
translate([abil, "parents"], ref)
for abil in info:
abil["id"] = id_data[_class][abil["display_name"]]
translate_abil(id_data[_class], abil)
for ref in range(len(info[abil]["dependencies"])):
translate([abil, "dependencies"], ref)
for ref in range(len(info[abil]["blockers"])):
translate([abil, "blockers"], ref)
if "base_abil" in info[abil]:
base_abil_name = info[abil]["base_abil"]
if base_abil_name in id_data[_class]:
translate([abil], "base_abil")
if "effects" not in info[abil]:
print("WARNING: abil missing 'effects' tag")
print(info[abil])
info[abil]["effects"] = []
for effect in info[abil]["effects"]:
if effect["type"] == "raw_stat":
for bonus in effect["bonuses"]:
if "abil" in bonus and bonus["abil"] in id_data[_class]:
bonus["abil"] = id_data[_class][bonus["abil"]]
elif effect["type"] == "replace_spell":
for part in effect['parts']:
if 'hits' in part: # Translate parametrized hits...
hits_mapping = part['hits']
keys = list(hits_mapping.keys())
for k in keys:
v = hits_mapping[k]
if isinstance(v, str):
abil_id, propname = v.split('.')
hits_mapping[k] = str(id_data[_class][abil_id])+'.'+propname
elif effect["type"] == "add_spell_prop":
if 'hits' in effect: # Translate parametrized hits...
hits_mapping = effect['hits']
keys = list(hits_mapping.keys())
for k in keys:
v = hits_mapping[k]
if isinstance(v, str):
abil_id, propname = v.split('.')
hits_mapping[k] = str(id_data[_class][abil_id])+'.'+propname
elif effect["type"] == "stat_scaling":
if "inputs" in effect: # Might not exist for sliders
for _input in effect["inputs"]:
if "abil" in _input and _input["abil"] in id_data[_class]:
_input["abil"] = id_data[_class][_input["abil"]]
if "output" in effect:
if isinstance(effect["output"], list):
for output in effect["output"]:
if "abil" in output and output["abil"] in id_data[_class]:
output["abil"] = id_data[_class][output["abil"]]
else:
if "abil" in effect["output"] and effect["output"]["abil"] in id_data[_class]:
effect["output"]["abil"] = id_data[_class][effect["output"]["abil"]]
abilDict = {}
with open("atree_constants.js") as f:
@ -87,15 +89,14 @@ with open("atree_constants.js") as f:
with open("atree_ids.json", "w", encoding='utf-8') as id_dest:
json.dump(abilDict, id_dest, ensure_ascii=False, indent=4)
translate_id(abilDict, data)
translate_all(abilDict, data)
with open("major_ids_clean.json") as maj_id_file:
maj_id_dat = json.load(maj_id_file)
for k, v in maj_id_dat.items():
for abil in v['abilities']:
clazz = abil['class']
base_abil = abil['base_abil']
abil['base_abil'] = abilDict[clazz][base_abil]
translate_abil(abilDict[clazz], abil, tree=False)
with open("major_ids_min.json", "w", encoding='utf-8') as maj_id_out:
json.dump(maj_id_dat, maj_id_out, ensure_ascii=False, separators=(',', ':'))

View file

@ -3847,5 +3847,28 @@
"Pain Cycle": 3845,
"Psionic Pretense": 3846,
"Propeller Hat": 3847,
"Tremorcaller": 3848
"Tremorcaller": 3848,
"Black Skull": 3849,
"Replica Hallowynn Mask": 3850,
"Esteemed Bronze Mask of Legendary Victory": 3851,
"Swashbuckler's Brogues": 3852,
"Outlandish Replica Face Mask of Legendary Victory": 3853,
"Mystical Tags": 3854,
"Future Shock Plating": 3855,
"Spider Leg Suit": 3856,
"Ceremonial Skirt": 3857,
"Runebound Chains": 3858,
"Ice-cold Robe": 3859,
"Blob Monster Mask": 3860,
"Psychopomp's Pileus": 3861,
"Revered Silver Mask of Legendary Victory": 3862,
"Treasured Diamond Mask of Legendary Victory": 3863,
"Venerated Gold Mask of Legendary Victory": 3864,
"Beige Wynnter Sweater": 3865,
"Scarlet Wynnter Sweater": 3866,
"Orange Wynnter Sweater": 3867,
"Pine Wynnter Sweater": 3868,
"Indigo Wynnter Sweater": 3869,
"Air In A Can": 3870,
"Iosis": 3871
}

View file

@ -0,0 +1,234 @@
{
"identifications": [
"rawMainAttackDamage",
"rawSpellDamage",
"healthRegenRaw",
"manaSteal",
"walkSpeed",
"thunderDamage",
"rawStrength",
"rawDexterity",
"rawIntelligence",
"rawDefence",
"rawAgility",
"lootBonus",
"fireDefence",
"airDefence",
"mainAttackDamage",
"spellDamage",
"exploding",
"airDamage",
"rawHealth",
"reflection",
"earthDefence",
"earthDamage",
"waterDamage",
"waterDefence",
"healthRegen",
"manaRegen",
"fireDamage",
"lifeSteal",
"rawAttackSpeed",
"xpBonus",
"thunderDefence",
"thorns",
"soulPointRegen",
"stealing",
"1stSpellCost",
"2ndSpellCost",
"raw1stSpellCost",
"raw3rdSpellCost",
"jumpHeight",
"airSpellDamage",
"poison",
"elementalDamage",
"healingEfficiency",
"raw4thSpellCost",
"raw2ndSpellCost",
"sprintRegen",
"slowEnemy",
"3rdSpellCost",
"sprint",
"elementalSpellDamage",
"rawNeutralSpellDamage",
"4thSpellCost",
"knockback",
"waterSpellDamage",
"fireSpellDamage",
"rawAirMainAttackDamage",
"rawAirSpellDamage",
"earthSpellDamage",
"rawThunderDamage",
"rawWaterDamage",
"rawElementalDamage",
"rawEarthSpellDamage",
"elementalDefence",
"rawThunderMainAttackDamage",
"thunderSpellDamage",
"rawThunderSpellDamage",
"rawFireMainAttackDamage",
"weakenEnemy",
"rawWaterSpellDamage",
"earthMainAttackDamage",
"rawFireSpellDamage",
"rawElementalSpellDamage",
"healing",
"rawElementalMainAttackDamage",
"airMainAttackDamage",
"thunderMainAttackDamage",
"leveledLootBonus",
"damageFromMobs",
"leveledXpBonus",
"elementalDefense",
"rawAirDamage",
"rawEarthDamage",
"rawFireDamage",
"rawNeutralDamage",
"lootQuality",
"gatherXpBonus",
"gatherSpeed",
"rawWaterMainAttackDamage",
"rawEarthMainAttackDamage"
],
"majorIds": [
"Divine Honor",
"Greed",
"Magnet",
"Plague",
"Saviour\u2019s Sacrifice",
"Sorcery",
"Soul Eater",
"Cherry Bombs",
"Expunge",
"Flashfreeze",
"Gentle Glow",
"Gruesome Knots",
"Peaceful Effigy",
"Rally",
"Reckless Abandon",
"Gravity Well",
"Perfect Recall",
"Cavalryman",
"Entropy",
"Escape Route",
"Lightweight",
"Snowy Steps",
"Taunt",
"Dead Weight",
"Furious Effigy",
"Geocentrism",
"Strings of Fate",
"Alter Ego",
"Explosive Impact",
"Festive Spirit",
"Freerunner",
"Hawkeye",
"Temblor",
"Windsurf",
"Juggle",
"Roving Assassin",
"Transcendence",
"Fission",
"Forest's Blessing",
"Heart of the Pack",
"Coagulate",
"Guardian",
"Hellfire",
"Overwhelm",
"Lunge",
"Madness"
],
"filters": {
"type": [
"weapons",
"armour",
"accessories",
"tomes",
"charms",
"tools",
"ingredients",
"materials"
],
"advanced": {
"attackSpeed": [
"super_slow",
"very_slow",
"slow",
"normal",
"fast",
"very_fast",
"super_fast"
],
"weapons": [
"bow",
"relik",
"wand",
"dagger",
"spear"
],
"armour": [
"helmet",
"chestplate",
"leggings",
"boots"
],
"accessories": [
"necklace",
"ring",
"bracelet"
],
"tomes": [
"weaponTome",
"armourTome",
"slayingTome",
"dungeonTome",
"gatheringTome",
"guildTome",
"lootrunTome"
],
"tools": [
"axe",
"pickaxe",
"rod",
"scythe"
],
"crafting": [
"alchemism",
"armouring",
"cooking",
"jeweling",
"scribing",
"tailoring",
"weaponsmithing",
"woodworking"
],
"gathering": [
"mining",
"fishing",
"farming",
"woodcutting"
]
},
"tier": {
"items": [
"common",
"fabled",
"legendary",
"mythic",
"rare",
"set",
"unique"
],
"ingredients": [
0,
1,
2,
3
]
},
"levelRange": {
"items": 110,
"ingredients": 105
}
}
}

114
py_script/item_wrapper.py Normal file
View file

@ -0,0 +1,114 @@
"""
Description: Quick item save/search with v3 item database
API Documentation: https://documentation.wynncraft.com/docs/
Update item db: python item_wrapper.py update-item [file_directory]
Item search: python item_wrapper.py search -keyword [War] -itemType [mythic] ...
"""
import requests
import json
import argparse
class Items:
"""v3 item wrapping - Synchronous"""
def fetch(self, url):
response = requests.get(url)
return response.json()
def post(self, url, data=None):
response = requests.post(url, json=data)
return response.json()
def get_all_items(self):
api_url = "https://api.wynncraft.com/v3/item/database?fullResult=True"
return self.fetch(api_url)
def get_metadata(self):
url = "https://api.wynncraft.com/v3/item/metadata"
return self.fetch(url)
def item_query(self, data=None):
api_url = "https://api.wynncraft.com/v3/item/search?fullResult=True"
return self.post(api_url, data)
def update_items(file_path):
data = Items().get_all_items()
update_file(data, file_path)
print(f"{len(data)} items updated")
def update_metadata(file_path):
data = Items().get_metadata()
update_file(data, file_path)
print("Metadata updated")
def update_file(input, output):
try:
with open(output, "w") as file:
json.dump(input, file, indent=3)
except Exception as error:
print(f"File update error: {error}")
def item_search_param(keyword=None, itemType=None, itemTier=None, atkSpeed=None, lvlRange=None, prof=None, ids=None, majorId=None):
payload = {
"query": [] if keyword is None else keyword,
"type": [] if itemType is None else itemType,
"tier": [] if itemTier is None else itemTier,
"attackSpeed": [] if atkSpeed is None else atkSpeed,
"levelRange": [] if lvlRange is None else lvlRange,
"professions": [] if prof is None else prof,
"identifications": [] if ids is None else ids,
"majorIds": [] if majorId is None else majorId
}
try:
response = Items().item_query(payload)
print(json.dumps(response, indent=3))
# Save the response as needed
except requests.RequestException as error:
print(f"Request error: {error}")
def main():
parser = argparse.ArgumentParser(description='Wynncraft Item API Script')
subparsers = parser.add_subparsers(dest='command', help='Pick your poison')
update_items_parser = subparsers.add_parser('update-items', help='Update all items')
update_items_parser.add_argument('file', help='File path for saving item json')
update_metadata_parser = subparsers.add_parser('update-metadata', help='Update metadata')
update_metadata_parser.add_argument('file', help='File path for saving metadata json')
search_parser = subparsers.add_parser('search', help='Search for items with parameters')
search_parser.add_argument('-keyword', type=str, default=None, help='Keyword for item search')
search_parser.add_argument('-itemType', type=str, default=None, help='Item type: wand, bow, etc')
search_parser.add_argument('-itemTier', type=str, default=None, help='Item tier: mythic, legendary, etc')
search_parser.add_argument('-atkSpeed', type=str, default=None, help='Attack speed param')
search_parser.add_argument('-lvlRange', nargs=2, type=int, default=None, help='Level range for: min, max')
search_parser.add_argument('-prof', type=str, default=None, help='Professions (Ing)')
search_parser.add_argument('-ids', type=str, default=None, help='Identifications field')
search_parser.add_argument('-majorId', type=str, default=None, help='Major IDs')
args = parser.parse_args()
if args.command == 'update-items':
update_items(args.file)
elif args.command == 'update-metadata':
update_metadata(args.file)
elif args.command == 'search':
item_search_param(
keyword=args.keyword,
itemType=args.itemType,
itemTier=args.itemTier,
atkSpeed=args.atkSpeed,
lvlRange=args.lvlRange,
prof=args.prof,
ids=args.ids,
majorId=args.majorId
)
if __name__ == "__main__":
main()

View file

@ -1,140 +1,6 @@
translate_mappings = {
#"name": "name",
#"displayName": "displayName",
#"tier": "tier",
#"set": "set",
"sockets": "slots",
#"type": "type",
#"armorType": "armorType", (deleted)
"armorColor": "color", #(deleted)
"addedLore": "lore", #(deleted)
#"material": "material", (deleted)
"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",
#"bonusFireDamage": "fDamPct",
#"bonusWaterDamage": "wDamPct",
#"bonusAirDamage": "aDamPct",
#"bonusThunderDamage": "tDamPct",
#"bonusEarthDamage": "eDamPct",
"fireDamageBonus": "fDamPct",
"waterDamageBonus": "wDamPct",
"airDamageBonus": "aDamPct",
"thunderDamageBonus": "tDamPct",
"earthDamageBonus": "eDamPct",
"elementalDamageBonus": "rDamPct",
"fireDamageBonusRaw": "fDamRaw",
"waterDamageBonusRaw": "wDamRaw",
"airDamageBonusRaw": "aDamRaw",
"thunderDamageBonusRaw": "tDamRaw",
"earthDamageBonusRaw": "eDamRaw",
"elementalDamageBonusRaw": "rDamRaw",
"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",
}
import json
with open("translate_mappings.json", 'r') as infile:
translate_mappings = json.load(infile)
delete_keys = [
#"addedLore",
@ -143,3 +9,36 @@ delete_keys = [
#"armorColor",
#"material"
]
if __name__ == "__main__":
# SELF TEST: compare dict keys with `item_metadata.json`.
from item_wrapper import Items
local_metadata_file = "item_metadata.json"
def debug(*args, **kwargs):
print(*args, **kwargs)
try:
debug("updating item metadata...")
metadata_check = Items().get_metadata()
with open(local_metadata_file, 'w') as outfile:
json.dump(metadata_check, outfile, indent=2)
except:
debug("Could not update item metadata. using local wynn metadata")
with open(local_metadata_file, 'r') as infile:
metadata_check = json.load(infile)
checklist = set(x for x in translate_mappings['identifications'].keys())
debug(f"Checking {len(checklist)} identifications")
n = 0
for identification in metadata_check['identifications']:
if identification in checklist:
checklist.remove(identification)
else:
print(f"WARNING: id not accounted for: {identification}")
n += 1
debug(f"{n} unmapped API identifications.")
for identification in checklist:
print(f"WARNING: unused translate map entry {identification}")
debug(f"{len(checklist)} unused translation entries.")

View file

@ -187,7 +187,7 @@ for ing in ings:
"notTouching": 0
}
if 'itemIDs' not in ing:
ing['consumableIDs'] = {
ing['itemIDs'] = {
"dura": 0,
"strReq": 0,
"dexReq": 0,

View file

@ -15,15 +15,16 @@ import sys
import os
import base64
import argparse
from items_common import translate_mappings, delete_keys
#from items_common import translate_mappings, delete_keys
parser = argparse.ArgumentParser(description="Process raw pulled item data.")
parser.add_argument('infile', help='input file to read data from')
parser.add_argument('infile', help='input file to read data from', default=None, nargs='?')
parser.add_argument('outfile', help='output file to dump clean data into')
args = parser.parse_args()
infile, outfile = args.infile, args.outfile
if args.infile is None:
print("Grabbing json data from wynn api")
with open(infile, "r") as in_file:
with open(args.infile, "r") as in_file:
data = json.loads(in_file.read())
@ -65,8 +66,9 @@ for item in items:
for k, v in translate_mappings.items():
if k in item:
item[v] = item[k]
tmp = item[k]
del item[k]
item[v] = tmp
if not (item["name"] in id_map):
while max_id in used_ids:

View file

@ -0,0 +1,212 @@
{
"tome": {
"name": "displayName",
"internalName": "name",
"tier": "CAPS;tier",
"material": "DELETE;",
"base": "UNWRAP;tome.base",
"dropMeta": "DELETE;",
"identifications": "UNWRAP;identifications",
"requirements": "UNWRAP;requirements",
"raidReward": "DELETE;",
"dropRestriction": "drop",
"restrictions": "restrict",
"tomeType": "type",
"tomeVariant": "DELETE;"
},
"tome.base": {
"gatheringXP": "tomeGatherXP",
"slayingXP": "tomeXP",
"dungeonXP": "tomeDungeonXP",
"defenceToMobs": "defMobs",
"damageToMobs": "damMobs"
},
"ingredient": {
"name": "displayName",
"internalName": "name",
"tier": "tier",
"material": "DELETE;",
"skin": "DELETE;",
"itemOnlyIDs": "RECURSE_ingredient.itemIDs;itemIDs",
"consumableOnlyIDs": "RECURSE_ingredient.consumableIDs;consumableIDs",
"ingredientPositionModifiers": "RECURSE_ingredient.posMods;posMods",
"identifications": "RECURSE_identifications;ids",
"droppedBy": "DELETE;",
"___droppedBy_comment:": "Deleting for now because it is way too large and we cannot use it in a meaningful way. But this is nice data for when wynnatlas ever gets updated",
"requirements": "UNWRAP;requirements"
},
"ingredient.itemIDs": {
"durabilityModifier": "dura",
"strengthRequirement": "strReq",
"dexterityRequirement": "dexReq",
"intelligenceRequirement": "intReq",
"defenceRequirement": "defReq",
"agilityRequirement": "agiReq",
"attackSpeedModifier": "atkSpdMod",
"powderSlotModifier": "slotMod"
},
"ingredient.consumableIDs": {
"duration": "dura",
"charges": "charges"
},
"ingredient.posMods": {
"left": "left",
"right": "right",
"above": "above",
"under": "under",
"touching": "touching",
"notTouching": "notTouching"
},
"item": {
"name": "displayName",
"internalName": "name",
"tier": "CAPS;tier",
"set": "set",
"powderSlots": "slots",
"type": "type",
"armorType": "armorType",
"material": "material",
"dropRestriction": "drop",
"dropMeta": "dropInfo",
"quest": "quest",
"restrictions": "restrict",
"accessoryType": "type",
"identified": "fixID",
"skin": "skin",
"category": "category",
"attackSpeed": "ALLCAPS;atkSpd",
"base": "UNWRAP;item.base",
"requirements": "UNWRAP;requirements",
"identifications": "UNWRAP;identifications"
},
"item.base": {
"damage": "STR_RANGE;nDam",
"fireDamage": "STR_RANGE;fDam",
"waterDamage": "STR_RANGE;wDam",
"airDamage": "STR_RANGE;aDam",
"thunderDamage": "STR_RANGE;tDam",
"earthDamage": "STR_RANGE;eDam",
"health": "hp",
"fireDefence": "fDef",
"waterDefence": "wDef",
"airDefence": "aDef",
"thunderDefence": "tDef",
"earthDefence": "eDef",
"averageDPS": "DELETE;"
},
"requirements": {
"level": "lvl",
"levelRange": "lvRange",
"classRequirement": "classReq",
"strength": "strReq",
"dexterity": "dexReq",
"intelligence": "intReq",
"agility": "agiReq",
"defence": "defReq",
"tomeSeeking": "DELETE;"
},
"identifications": {
"healthRegen": "hprPct",
"manaRegen": "mr",
"spellDamage": "sdPct",
"elementalSpellDamage": "rSdPct",
"neutralSpellDamage": "nSdPct",
"fireSpellDamage": "fSdPct",
"waterSpellDamage": "wSdPct",
"airSpellDamage": "aSdPct",
"thunderSpellDamage": "tSdPct",
"earthSpellDamage": "eSdPct",
"mainAttackDamage": "mdPct",
"elementalMainAttackDamage": "rMdPct",
"neutralMainAttackDamage": "nMdPct",
"fireMainAttackDamage": "fMdPct",
"waterMainAttackDamage": "wMdPct",
"airMainAttackDamage": "aMdPct",
"thunderMainAttackDamage": "tMdPct",
"earthMainAttackDamage": "eMdPct",
"lifeSteal": "ls",
"manaSteal": "ms",
"xpBonus": "xpb",
"lootBonus": "lb",
"leveledXpBonus": "lxpb",
"leveledLootBonus": "llb",
"reflection": "ref",
"rawStrength": "str",
"rawDexterity": "dex",
"rawIntelligence": "int",
"rawDefence": "def",
"rawAgility": "agi",
"thorns": "thorns",
"poison": "poison",
"exploding": "expd",
"walkSpeed": "spd",
"rawAttackSpeed": "atkTier",
"rawHealth": "hpBonus",
"soulPointRegen": "spRegen",
"stealing": "eSteal",
"healthRegenRaw": "hprRaw",
"rawSpellDamage": "sdRaw",
"rawElementalSpellDamage": "rSdRaw",
"rawNeutralSpellDamage": "nSdRaw",
"rawFireSpellDamage": "fSdRaw",
"rawWaterSpellDamage": "wSdRaw",
"rawAirSpellDamage": "aSdRaw",
"rawThunderSpellDamage": "tSdRaw",
"rawEarthSpellDamage": "eSdRaw",
"rawMainAttackDamage": "mdRaw",
"rawElementalMainAttackDamage": "rMdRaw",
"rawNeutralMainAttackDamage": "nMdRaw",
"rawFireMainAttackDamage": "fMdRaw",
"rawWaterMainAttackDamage": "wMdRaw",
"rawAirMainAttackDamage": "aMdRaw",
"rawThunderMainAttackDamage": "tMdRaw",
"rawEarthMainAttackDamage": "eMdRaw",
"damage": "damPct",
"neutralDamage": "nDamPct",
"fireDamage": "fDamPct",
"waterDamage": "wDamPct",
"airDamage": "aDamPct",
"thunderDamage": "tDamPct",
"earthDamage": "eDamPct",
"elementalDamage": "rDamPct",
"rawDamage": "damRaw",
"rawNeutralDamage": "nDamRaw",
"rawFireDamage": "fDamRaw",
"rawWaterDamage": "wDamRaw",
"rawAirDamage": "aDamRaw",
"rawThunderDamage": "tDamRaw",
"rawEarthDamage": "eDamRaw",
"rawElementalDamage": "rDamRaw",
"fireDefence": "fDefPct",
"waterDefence": "wDefPct",
"airDefence": "aDefPct",
"thunderDefence": "tDefPct",
"earthDefence": "eDefPct",
"elementalDefence": "rDefPct",
"1stSpellCost": "spPct1",
"raw1stSpellCost": "spRaw1",
"2ndSpellCost": "spPct2",
"raw2ndSpellCost": "spRaw2",
"3rdSpellCost": "spPct3",
"raw3rdSpellCost": "spRaw3",
"4thSpellCost": "spPct4",
"raw4thSpellCost": "spRaw4",
"sprint": "sprint",
"sprintRegen": "sprintReg",
"jumpHeight": "jh",
"lootQuality": "lq",
"gatherXpBonus": "gXp",
"gatherSpeed": "gSpd",
"healingEfficiency": "healPct",
"knockback": "kb",
"weakenEnemy": "weakenEnemy",
"slowEnemy": "slowEnemy",
"elementalDefense": "rDefPct",
"damageFromMobs": "selfWeakPct"
}
}

View file

@ -0,0 +1,286 @@
"""
Used to process the raw item data pulled from the API.
Usage:
- python process_items.py [infile] [outfile]
OR
- python process_items.py [infile and outfile]
NOTE: id_map.json is due for change. Should be updated manually when Wynn2.0/corresponding WB version drops.
"""
import json
import sys
import os
import base64
import argparse
from items_common import translate_mappings
parser = argparse.ArgumentParser(description="Process raw pulled item data.")
parser.add_argument('infile', help='input file to read data from', default=None, nargs='?')
#parser.add_argument('outfile', help='output file to dump clean data into')
args = parser.parse_args()
if args.infile is None:
print("Grabbing json data from wynn api")
from item_wrapper import Items
api_data = Items().get_all_items()
json.dump(api_data, open('dump.json', 'w'))
else:
with open(args.infile, "r") as in_file:
api_data = json.load(in_file)
def translate_single_item(key, entry, name, directives, accumulate):
ret = entry
try:
if 'min' in entry and 'max' in entry:
if 'raw' in entry:
ret = entry['raw']
else:
ret = [entry['min'], entry['max']]
except:
pass
i = 0
while i < len(directives):
directive = directives[i]
if directive == 'DELETE':
ret = None
elif directive == 'CAPS':
ret = ret[0].upper() + ret[1:]
elif directive == 'ALLCAPS':
ret = ret.upper()
elif directive == 'STR_RANGE':
if 'min' in entry and 'max' in entry:
ret = f"{entry['min']}-{entry['max']}"
elif directive == 'UNWRAP':
recursive_translate(entry, accumulate, name, translate_single_item)
ret = None
i += 1
return ret
def translate_single_ing(key, entry, name, directives, accumulate):
ret = entry
try:
if 'min' in entry and 'max' in entry:
ret = {
'minimum': entry['min'],
'maximum': entry['max']
}
except:
pass
i = 0
while i < len(directives):
directive = directives[i]
if directive == 'DELETE':
ret = None
elif directive == 'UNWRAP':
recursive_translate(entry, accumulate, name, translate_single_ing)
ret = None
elif directive[:8] == 'RECURSE_':
ret = recursive_translate(entry, {}, directive[8:], translate_single_ing)
i += 1
return ret
def recursive_translate(entry, result, path, translate_single):
mapping = translate_mappings[path]
for k, v in entry.items():
# Translate the item.
if k in mapping:
tmp = mapping[k].split(';')
directives, translated_name = tmp[:-1], tmp[-1]
res = translate_single(k, v, translated_name, directives, result)
if res is not None:
result[translated_name] = res
continue
# pass it through unchanged.
result[k] = v
return result
armor_types = ['helmet', 'chestplate', 'leggings', 'boots']
tome_type_translation = {
'gatheringxp': 'gatherXpTome',
'dungeonxp': 'dungeonXpTome',
'slayingxp': 'mobXpTome',
'guildtome': 'guildTome',
'mobdefence': 'armorTome',
'mobdamage': 'weaponTome',
'lootrun': 'lootrunTome',
}
def translate_entry(entry):
"""
Convert an api entry into an appropriate parsed item.
Returns a pair: (converted, type)
where `type` is "item", "ingredient", "tome", "material", "charm", or None
and converted might be None if the conversion failed.
"""
# sketchily infer what kind of item we're dealing with, and translate it appropriately.
if "type" in entry:
# only items have this field.
res = recursive_translate(entry, {}, "item", translate_single_item)
if res['type'] in armor_types:
res['category'] = 'armor'
else:
res['category'] = 'weapon'
for element in 'netwfa':
damage_key = element + 'Dam'
if damage_key not in res:
res[damage_key] = '0-0'
return res, 'item'
if "accessoryType" in entry:
# only accessories have this field.
return recursive_translate(entry, {'category': 'accessory'}, "item", translate_single_item), "item"
if "itemOnlyIDs" in entry:
# only ingredients have this field.
res = recursive_translate(entry, {}, "ingredient", translate_single_ing)
return res, "ingredient"
#return recursive_translate(entry, {}, "ing"), "ingredient"
if "tomeType" in entry:
# only tomes have this field.
print(entry)
res = recursive_translate(entry, {}, "tome", translate_single_item)
res['category'] = 'tome'
res['fixID'] = False
res['type'] = tome_type_translation[res['type']]
print(res)
return res, "tome"
if "craftable" in entry:
return None, "material"
# I think the only things left are charms, we just don't classify them.
return None, None
with open("id_map.json", "r") as id_map_file:
id_map = json.load(id_map_file)
used_ids = set([v for k, v in id_map.items()])
max_id = 0
with open("ing_map.json","r") as ing_map_file:
ing_map = json.load(ing_map_file)
with open("../tome_map.json","r") as tome_map_file:
tome_map = json.load(tome_map_file)
items = []
ingreds = []
tomes = []
for name, entry in api_data.items():
entry['name'] = name
res, entry_type = translate_entry(entry)
print(f"Parsed {name}, type {entry_type}")
if res is None:
continue
# TODO: make this a map or smth less ugly code
if entry_type == 'item':
items.append(res)
elif entry_type == 'ingredient':
ingreds.append(res)
elif entry_type == 'tome':
tomes.append(res)
with open("../clean.json", "r") as oldfile:
old_data = json.load(oldfile)
old_items = old_data['items']
with open("../ingreds_clean.json", "r") as ingfile:
old_ingreds = json.load(ingfile)
with open("../tomes.json", "r") as tomefile:
old_tome_data = json.load(tomefile)
old_tomes = old_tome_data['tomes']
known_item_names = set()
known_ingred_names = set()
known_tome_names = set()
for item in items:
known_item_names.add(item["name"])
for ingred in ingreds:
known_ingred_names.add(ingred["name"])
for tome in tomes:
known_tome_names.add(tome["name"])
tome_value_map = {}
for item in old_items:
if item["name"] not in known_item_names:
print(f'Unknown old item: {item["name"]}!!!')
for ingred in old_ingreds:
if ingred["name"] not in known_ingred_names:
print(f'Unknown old ingred: {ingred["name"]}!!!')
for tome in old_tomes:
if tome["name"] not in known_tome_names:
print(f'Unknown old tome: {tome["name"]}!!!')
tome_value_map[tome['name']] = tome
# TODO hack pull the major id file
major_ids_filename = "../js/builder/major_ids_clean.json"
with open(major_ids_filename, 'r') as major_ids_file:
major_ids_map = json.load(major_ids_file)
major_ids_reverse_map = { v['displayName'] : k for k, v in major_ids_map.items() }
for item in items:
# NOTE: HACKY ITEM FIXES!
if 'majorIds' in item:
item['majorIds'] = [ major_ids_reverse_map[item['majorIds']['name']] ]
if item['tier'] == 'Common':
item['tier'] = 'Normal'
if not (item["name"] in id_map):
while max_id in used_ids:
max_id += 1
used_ids.add(max_id)
id_map[item["name"]] = max_id
print(f'New item: {item["name"]} (id: {max_id})')
item["id"] = id_map[item["name"]]
for ingred in ingreds:
# HACKY ING FIXES!
ingred['itemIDs']['dura'] = int(ingred['itemIDs']['dura'] / 1000)
ingred['skills'] = [x.upper() for x in ingred['skills']]
if 'ids' not in ingred:
ingred['ids'] = dict()
print(f"ing missing 'ids': {ingred['name']}")
if 'consumableIDs' not in ingred:
ingred['consumableIDs'] = {'dura': 0, 'charges': 0}
print(f"ing missing 'consumableIDs': {ingred['name']}")
if not (ingred["name"] in ing_map):
new_id = len(ing_map)
ing_map[ingred["name"]] = new_id
print(f'New ingred: {ingred["name"]} (id: {new_id})')
ingred["id"] = ing_map[ingred["name"]]
for tome in tomes:
if not (tome['name'] in tome_map):
new_id = len(tome_map)
tome_map[tome['name']] = new_id
print(f'New tome: {tome["name"]} (id: {new_id})')
tome['alias'] = 'NO_ALIAS'
else:
old_tome = tome_value_map[tome['name']]
if 'alias' in old_tome:
tome['alias'] = old_tome['alias']
tome['id'] = tome_map[tome['name']]
#write items back into data
old_data["items"] = items
#save id map
with open("id_map.json","w") as id_map_file:
json.dump(id_map, id_map_file, indent=2)
with open("ing_map.json","w") as ing_map_file:
json.dump(ing_map, ing_map_file, indent=2)
with open("../tome_map.json","w") as tome_map_file:
json.dump(tome_map, tome_map_file, indent=2)
#write the data back to the outfile
with open('item_out.json', "w+") as out_file:
json.dump(old_data, out_file, ensure_ascii=False, separators=(',', ':'))
with open('ing_out.json', "w+") as out_file:
json.dump(ingreds, out_file, ensure_ascii=False, separators=(',', ':'))
with open('tome_out.json', "w+") as out_file:
json.dump({'tomes': tomes}, out_file, ensure_ascii=False, separators=(',', ':'))

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

0
regression_tests/v6.png Executable file → Normal file
View file

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

0
regression_tests/v7.png Executable file → Normal file
View file

Before

Width:  |  Height:  |  Size: 304 KiB

After

Width:  |  Height:  |  Size: 304 KiB

View file

@ -60,6 +60,9 @@
"Harvester's Tome of Weapon Mastery I": 58,
"Harvester's Tome of Weapon Mastery II": 59,
"Harvester's Tome of Weapon Mastery III": 60,
"No Weapon Tome": 61,
"No Armor": 62,
"No Guild Tome": 63,
"Earthbound Tome of Weapon Mastery III": 64,
"Nimble Tome of Weapon Mastery III": 65,
"Mystical Tome of Weapon Mastery III": 66,
@ -71,5 +74,23 @@
"Abyssal Tome of Weapon Mastery III": 72,
"Infernal Tome of Weapon Mastery III": 73,
"Cyclonic Tome of Weapon Mastery III": 74,
"Astral Tome of Weapon Mastery III": 75
}
"Astral Tome of Weapon Mastery III": 75,
"Tome of Gathering Mastery III": 76,
"Mastermind's Tome of Lootrun Mastery": 77,
"Tome of Armour Mastery I": 78,
"Pickpocket's Tome of Lootrun Mastery": 79,
"Tome of Slaying Mastery III": 80,
"Tome of Slaying Mastery I": 81,
"Tome of Gathering Mastery I": 82,
"Scoundrel's Tome of Lootrun Mastery": 83,
"Spelunker's Tome of Lootrun Mastery": 84,
"Tome of Slaying Mastery II": 85,
"Tome of Dungeoneering Mastery II": 86,
"Tome of Gathering Mastery II": 87,
"Tome of Dungeoneering Mastery I": 88,
"Manipulator's Tome of Lootrun Mastery": 89,
"Plunderer's Tome of Lootrun Mastery": 90,
"Thief's Tome of Lootrun Mastery": 91,
"Tome of Dungeoneering Mastery III": 92,
"No Lootrun Tome": 93
}

2162
tomes.json

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,8 @@
<a href = ""><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">
@ -68,6 +70,14 @@
<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>