Wynn versioning method (#238)

* Update tomes.json with 2.0 tomes

Added the 18 new weapon tomes coming in update 2.0.
Also fixed fabled t1 weapon tomes having level 80 instead of 60.

* Update tome_map.json with 2.0 tomes

Added the 18 new weapon tomes coming in update 2.0.

* Add missing regression tests

* Encode and decode V8, initial files

* Adding shortcut load

use json directly meme

* Dummy data for 2.0.1.2 (same as 2.0.1.1)

update documentation slightly

* Tier 3 tomes, and update files from api

.... tech debt... when binary encode

* Change versioning system to use query instead of hash

hopefully less breaking change; also just prettier

* More misc bugfix

Fix race condition in builder graph load
Fix loading empty hash
Add boundless set tag and set bonus
Update mage atree

* Data files for wynn version 2.0.1.2, and updated atree const json

* Add ability for nodes to have (single) archetype req but no archetype

Possibly we want to make a more general req system

* Finish inputting rest of the atree changes

hopefully

* Minify index.html

* Add tree decoding pseudocode to dev/index.html

* Update V8 docs

* Style fixes, bump db versions

* missing... semicolons...

Co-authored-by: FrozenEarth <39888817+FrozenEarth-git@users.noreply.github.com>
Co-authored-by: hppeng <hppeng>
This commit is contained in:
hppeng-wynn 2022-12-16 10:29:01 +00:00 committed by GitHub
parent ba1f23e7ab
commit 2561feb621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 35583 additions and 32047 deletions

File diff suppressed because one or more lines are too long

View file

@ -1313,13 +1313,12 @@
<script type="text/javascript" src="../js/load_tome.js"></script>
<script type="text/javascript" src="../js/custom.js"></script>
<script type="text/javascript" src="../js/craft.js"></script>
<script type="text/javascript" src="../js/builder/atree_constants_min.js"></script>
<script type="text/javascript" src="../js/builder/build.js"></script>
<script type="text/javascript" src="../js/builder/builder_constants.js"></script>
<script type="text/javascript" src="../js/builder/build_encode_decode.js"></script>
<script type="text/javascript" src="../js/builder/atree.js"></script>
<script type="text/javascript" src="../js/builder/builder.js"></script>
<script type="text/javascript" src="../js/builder/builder_graph.js"></script>
<script type="text/javascript" src="../js/builder/builder.js"></script>
<!--script type="text/javascript" src="../js/builder/optimize.js"></script-->
</body>
</html>

62740
clean.json

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

1
data/2.0.1.1/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.1.1/items.json Normal file

File diff suppressed because one or more lines are too long

809
data/2.0.1.1/tomes.json Normal file
View file

@ -0,0 +1,809 @@
{
"tomes": [
{
"name": "Retaliating Tome of Armour Mastery I",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"defmobs": 3,
"thorns": 6,
"ref": 6,
"hpBonus": 120,
"fixID": false,
"id": 0,
"alias": "Thorns I"
},
{
"name": "Retaliating Tome of Armour Mastery II",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 5,
"thorns": 8,
"ref": 8,
"fixID": false,
"id": 1,
"alias": "Thorns II"
},
{
"name": "Destructive Tome of Armour Mastery I",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"defMobs": 3,
"exploding": 5,
"mdPct": 5,
"hpBonus": 120,
"fixID": false,
"id": 2,
"alias": "Melee I"
},
{
"name": "Destructive Tome of Armour Mastery II",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 5,
"exploding": 6,
"mdPct": 6,
"fixID": false,
"id": 3,
"alias": "Melee II"
},
{
"name": "Sorcerer's Tome of Armour Mastery I",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"defMobs": 3,
"sdPct": 5,
"hpBonus": 120,
"fixID": false,
"id": 4,
"alias": "Spell Damage I"
},
{
"name": "Sorcerer's Tome of Armour Mastery II",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 5,
"sdPct": 6,
"fixID": false,
"id": 5,
"alias": "Spell Damage II"
},
{
"name": "Everlasting Tome of Armour Mastery I",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"defMobs": 3,
"hprRaw": 15,
"hpBonus": 120,
"fixID": false,
"id": 6,
"alias": "Health Regen I"
},
{
"name": "Everlasting Tome of Armour Mastery II",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 5,
"hprRaw": 60,
"fixID": false,
"id": 7,
"alias": "Health Regen II"
},
{
"name": "Vampiric Tome of Armour Mastery I",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"defMobs": 3,
"ls": 25,
"hpBonus": 120,
"fixID": false,
"id": 8,
"alias": "Life Steal I"
},
{
"name": "Vampiric Tome of Armour Mastery II",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 5,
"ls": 85,
"fixID": false,
"id": 9,
"alias": "Life Steal II"
},
{
"name": "Greedy Tome of Armour Mastery I",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"defMobs": 3,
"lb": 5,
"hpBonus": 120,
"fixID": false,
"id": 10,
"alias": "Loot Bonus I"
},
{
"name": "Greedy Tome of Armour Mastery II",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 5,
"lb": 6,
"fixID": false,
"id": 11,
"alias": "Loot Bonus II"
},
{
"name": "Weightless Tome of Armour Mastery I",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"defMobs": 3,
"spd": 5,
"hpBonus": 120,
"fixID": false,
"id": 12,
"alias": "Walk Speed I"
},
{
"name": "Weightless Tome of Armour Mastery II",
"tier": "Fabled",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 5,
"spd": 6,
"fixID": false,
"id": 13,
"alias": "Walk Speed II"
},
{
"name": "Blooming Tome of Armour Mastery II",
"tier": "Mythic",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 8,
"eDefPct": 10,
"hpBonus": 150,
"fixID": false,
"id": 14,
"alias": "Earth Defense II"
},
{
"name": "Pulsing Tome of Armour Mastery II",
"tier": "Mythic",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 8,
"tDefPct": 10,
"hpBonus": 150,
"fixID": false,
"id": 15,
"alias": "Thunder Defense II"
},
{
"name": "Oceanic Tome of Armour Mastery II",
"tier": "Mythic",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 8,
"wDefPct": 10,
"hpBonus": 150,
"fixID": false,
"id": 16,
"alias": "Water Defense II"
},
{
"name": "Courageous Tome of Armour Mastery II",
"tier": "Mythic",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 8,
"fDefPct": 10,
"hpBonus": 150,
"fixID": false,
"id": 17,
"alias": "Fire Defense II"
},
{
"name": "Clouded Tome of Armour Mastery II",
"tier": "Mythic",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 8,
"aDefPct": 10,
"hpBonus": 150,
"fixID": false,
"id": 18,
"alias": "Air Defense II"
},
{
"name": "Radiant Tome of Armour Mastery II",
"tier": "Mythic",
"type": "armorTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 100,
"defMobs": 8,
"eDefPct": 6,
"tDefPct": 6,
"wDefPct": 6,
"fDefPct": 6,
"aDefPct": 6,
"hpBonus": 150,
"fixID": false,
"id": 19,
"alias": "Rainbow Defense II"
},
{
"name": "Tome of Weapon Mastery I",
"tier": "Legendary",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"damMobs": 6,
"fixID": false,
"id": 20,
"alias": "Weapon Mastery I"
},
{
"name": "Earthbound Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 7,
"str": 3,
"fixID": false,
"id": 21,
"alias": "Strength I"
},
{
"name": "Earthbound Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"str": 3,
"fixID": false,
"id": 22,
"alias": "Strength II"
},
{
"name": "Nimble Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 7,
"dex": 3,
"fixID": false,
"id": 23,
"alias": "Dexterity I"
},
{
"name": "Nimble Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"dex": 3,
"fixID": false,
"id": 24,
"alias": "Dexterity II"
},
{
"name": "Mystical Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 7,
"int": 3,
"fixID": false,
"id": 25,
"alias": "Intelligence I"
},
{
"name": "Mystical Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"int": 3,
"fixID": false,
"id": 26,
"alias": "Intelligence II"
},
{
"name": "Warding Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 7,
"def": 3,
"fixID": false,
"id": 27,
"alias": "Defense I"
},
{
"name": "Warding Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"def": 3,
"fixID": false,
"id": 28,
"alias": "Defense II"
},
{
"name": "Athletic Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 7,
"agi": 3,
"fixID": false,
"id": 29,
"alias": "Agility I"
},
{
"name": "Athletic Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"agi": 3,
"fixID": false,
"id": 30,
"alias": "Agility II"
},
{
"name": "Cosmic Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 7,
"str": 1,
"dex": 1,
"int": 1,
"def": 1,
"agi": 1,
"fixID": false,
"id": 31,
"alias": "Rainbow Skillpoint I"
},
{
"name": "Cosmic Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"str": 1,
"dex": 1,
"int": 1,
"def": 1,
"agi": 1,
"fixID": false,
"id": 32,
"alias": "Rainbow Skillpoint II"
},
{
"name": "Seismic Tome of Weapon Mastery II",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 12,
"eDamPct": 7,
"fixID": false,
"id": 33,
"alias": "Earth Damage II"
},
{
"name": "Voltaic Tome of Weapon Mastery II",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 12,
"tDamPct": 7,
"fixID": false,
"id": 34,
"alias": "Thunder Damage II"
},
{
"name": "Abyssal Tome of Weapon Mastery II",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 12,
"wDamPct": 7,
"fixID": false,
"id": 35,
"alias": "Water Damage II"
},
{
"name": "Infernal Tome of Weapon Mastery II",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 12,
"fDamPct": 7,
"fixID": false,
"id": 36,
"alias": "Fire Damage II"
},
{
"name": "Cyclonic Tome of Weapon Mastery II",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 12,
"aDamPct": 7,
"fixID": false,
"id": 37,
"alias": "Air Damage II"
},
{
"name": "Astral Tome of Weapon Mastery II",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 12,
"eDamPct": 6,
"tDamPct": 6,
"wDamPct": 6,
"fDamPct": 6,
"aDamPct": 6,
"fixID": false,
"id": 38,
"alias": "Rainbow Damage II"
},
{
"name": "Brute's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"str": 3,
"eDamPct": 2,
"fixID": false,
"id": 39,
"alias": "Strength"
},
{
"name": "Sadist's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"dex": 3,
"tDamPct": 2,
"fixID": false,
"id": 40,
"alias": "Dexterity"
},
{
"name": "Mastermind's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"int": 3,
"wDamPct": 2,
"fixID": false,
"id": 41,
"alias": "Intelligence"
},
{
"name": "Arsonist's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"def": 3,
"fDamPct": 2,
"fixID": false,
"id": 42,
"alias": "Defense"
},
{
"name": "Ghost's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"agi": 3,
"aDamPct": 2,
"fixID": false,
"id": 43,
"alias": "Agility"
},
{
"name": "Psychopath's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"str": 2,
"dex": 2,
"fixID": false,
"id": 44,
"alias": "ET"
},
{
"name": "Loner's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"str": 2,
"int": 2,
"fixID": false,
"id": 45,
"alias": "EW"
},
{
"name": "Warlock's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"dex": 2,
"int": 2,
"fixID": false,
"id": 46,
"alias": "TW"
},
{
"name": "Destroyer's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"str": 2,
"def": 2,
"fixID": false,
"id": 47,
"alias": "EF"
},
{
"name": "Devil's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"dex": 2,
"def": 2,
"fixID": false,
"id": 48,
"alias": "TF"
},
{
"name": "Alchemist's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"int": 2,
"def": 2,
"fixID": false,
"id": 49,
"alias": "WF"
},
{
"name": "Barbarian's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"str": 2,
"agi": 2,
"fixID": false,
"id": 50,
"alias": "EA"
},
{
"name": "Freelancer's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"dex": 2,
"agi": 2,
"fixID": false,
"id": 51,
"alias": "TA"
},
{
"name": "Sycophant's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"int": 2,
"agi": 2,
"fixID": false,
"id": 52,
"alias": "WA"
},
{
"name": "Fanatic's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"def": 2,
"agi": 2,
"fixID": false,
"id": 53,
"alias": "FA"
},
{
"name": "Assimilator's Tome of Allegiance",
"tier": "Legendary",
"type": "guildTome",
"category": "tome",
"drop": "never",
"restrict": "Untradable",
"lvl": 100,
"str": 1,
"dex": 1,
"int": 1,
"def": 1,
"agi": 1,
"fixID": false,
"id": 54,
"alias": "Rainbow"
}
]
}

1
data/2.0.1.2/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.1.2/items.json Normal file

File diff suppressed because one or more lines are too long

1069
data/2.0.1.2/tomes.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -143,11 +143,82 @@
</div>
<p>
All build links will end in "#[version number]_[build hash]".
All build links starting from Version 8 may look like "?v=[wynn version number]#[version number]_[build hash]".
The query section is optional.
</p>
<p>
All build links older than Version 8 look like "#[version number]_[build hash]".
</p>
<div class="row section" title="Version 8">
<p>
Version 8 was made to account for an oversight made when designing the tome encoding, and to signal the beginning of versions that support wynn version history. Version 8 and higher links may start with a query indicating the wynn version to use.
</p>
<p>
Additionally, Version 8 uses 2 characters instead of 1 character to encode the ID of each tome. (The IDs representing NONE tomes does not change.)
</p>
</div>
<div class="row section" title="Version 7">
<p>
Version 7 was made to account for ability trees in wynncraft 2.0. Version 7 introduces a new field at the end of the build hash, for ability tree data. Atree data is compressed using a depth first search algorithm into a binary blob, which is then base-64 encoded and appended to the V6 hash. The binry blob is decompressed by using the corresponding traversal.
</p>
<p>
The tree structure defining the ability tree can be found in a json file (by default, <code>js/builder/atree_constants_min.json</code>). The file defines connections between the ability tree nodes: Each node has an unordered list of parent nodes, and an ordered list of child nodes.
</p>
<p>
A reference implementation of the encoding/decoding algorithms can be found in <code>js/builder/build_encode_decode.js</code>. A pseudocode description of the algorithms is given here:
</p>
<code style="white-space: pre">
// Encode an ability tree configuration into a binary blob.
// NOTE: this algorithm only works for "connected" (valid) ability trees.
// Its behavior is not well defined otherwise.
//
// Parameters:
// tree_data: Object containing ability tree structure.
// tree_state: Object containing info about which abilities are selected.
function encode(tree_data, tree_state):
return_vector = BitVector()
visited = Set()
function recursive_traverse(head_node):
for each child of head_node, in order:
if child is not in visited:
add child to visited set
if tree_state.is_active(child):
append bit 1 to return_vector
recursive_traverse(child)
else:
append bit 0 to return_vector
recursive_traverse(tree_data.root)
return return_vector
// Decode a binary blob into an ability tree configuration
//
// Parameters:
// tree_data: Object containing ability tree structure.
// tree_state: Object containing info about which abilities are selected.
// tree_bitvector: raw binary data, accessed one bit at a time
function decode(tree_data, tree_state, tree_bitvector):
i = 0
visited = Set()
function recursive_traverse(head_node):
for each child of head_node, in order:
if child is not in visited:
add child to visited set
if tree_bitvector[i]:
child.active = True
recursive_traverse(child)
else:
child.active = False
i = i + 1
</code>
</div>
<div class="row section" title="Version 6">
<p>
Version 6 was made to account for the desire to save tomes in a build. As of the last version of this documentation, version 6 is used for encoding whenever there are tomes in the build.
Version 6 was made to account for the desire to save tomes in a build.
</p>
<div class = "row section" title = "Example 1: With Tomes">
<code class="full-width">

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -18,6 +18,7 @@ atree_node: {
desc: str
archetype: Optional[str] // not present or empty string = no arch
archetype_req: Optional[int] // default: 0
req_archetype: Optional[str] // what the req is for if no archetype defined... maybe clean up this data format later...?
base_abil: Optional[int] // Modify another abil? poorly defined...
parents: List[int]
dependencies: List[int] // Hard reqs
@ -107,6 +108,21 @@ scaling_target: {
*/
// Space for big json data
let atrees;
let atree_load_complete = false;
/*
* Load atree info remote DB (aka a big json file).
*/
async function load_atree_data(version_str) {
let getUrl = window.location;
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// No random string -- we want to use caching
let url = `${baseUrl}/data/${version_str}/atree.json`;
atrees = await (await fetch(url)).json();
atree_load_complete = true;
}
const elem_mastery_abil = { display_name: "Elemental Mastery", id: 998, properties: {}, effects: [] };
// TODO: Range numbers
@ -276,12 +292,17 @@ function abil_can_activate(atree_node, atree_state, reachable, archetype_count,
if (!node_reachable) {
return [false, false, 'not reachable'];
}
if ('archetype' in ability && ability.archetype !== "") {
if ('archetype_req' in ability && ability.archetype_req !== 0) {
const others = (archetype_count.get(ability.archetype) || 0);
if (others < ability.archetype_req) {
return [false, false, ability.archetype+': '+others+' < '+ability.archetype_req];
let req_archetype;
if ('req_archetype' in ability && ability.req_archetype !== "") {
req_archetype = ability.req_archetype;
}
else {
req_archetype = ability.archetype;
}
const others = (archetype_count.get(req_archetype) || 0);
if (others < ability.archetype_req) {
return [false, false, req_archetype+': '+others+' < '+ability.archetype_req];
}
}
if (ability.cost > points_remain) {
@ -1259,7 +1280,8 @@ function generateTooltip(container, node_elem, ability, atree_map) {
// calculate if requirements are satisfied
let apUsed = 0;
let maxAP = parseInt(document.getElementById("active_AP_cap").innerHTML);
let archChosen = 0;
let arch_chosen = 0;
const node_arch = ability.req_archetype || ability.archetype;
let satisfiedDependencies = [];
let blockedBy = [];
for (let [id, node_wrap] of atree_map.entries()) {
@ -1267,8 +1289,8 @@ function generateTooltip(container, node_elem, ability, atree_map) {
continue; // we don't want to count abilities that are not selected, and an ability should not count towards itself
}
apUsed += node_wrap.ability.cost;
if (node_wrap.ability.archetype == ability.archetype) {
archChosen++;
if (node_wrap.ability.archetype == node_arch) {
arch_chosen++;
}
if (ability.dependencies.includes(id)) {
satisfiedDependencies.push(id);
@ -1292,15 +1314,21 @@ function generateTooltip(container, node_elem, ability, atree_map) {
container.appendChild(cost);
// archetype req
if (ability.archetype_req > 0 && ability.archetype != null) {
let archReq = make_elem("p", ["scaled-font", "my-0", "mx-1"], {});
if (archChosen >= ability.archetype_req) {
archReq.innerHTML = reqYes;
} else {
archReq.innerHTML = reqNo;
if (ability.archetype_req > 0) {
let arch_req = make_elem("p", ["scaled-font", "my-0", "mx-1"], {});
if ('req_archetype' in ability && ability.req_archetype !== "") {
req_archetype = ability.req_archetype;
}
archReq.innerHTML += "<span class = 'mc-gray'>Min " + ability.archetype + " Archetype:</span> " + archChosen + "<span class = 'mc-gray'>/" + ability.archetype_req;
container.appendChild(archReq);
else {
req_archetype = ability.archetype;
}
if (arch_chosen >= ability.archetype_req) {
arch_req.innerHTML = reqYes;
} else {
arch_req.innerHTML = reqNo;
}
arch_req.innerHTML += "<span class = 'mc-gray'>Min " + req_archetype+ " Archetype:</span> " + arch_chosen + "<span class = 'mc-gray'>/" + ability.archetype_req;
container.appendChild(arch_req);
}
// dependencies

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -27,12 +27,26 @@ function parsePowdering(powder_info) {
}
let atree_data = null;
const wynn_version_names = [
'2.0.1.1',
'2.0.1.2'
];
const WYNN_VERSION_LATEST = wynn_version_names.length - 1;
// Default to the newest version.
let wynn_version_id = WYNN_VERSION_LATEST;
/*
* Populate fields based on url, and calculate build.
* TODO: THIS CODE IS GOD AWFUL result of being lazy
* fix all the slice() and break into functions or do something about it... its inefficient, ugly and error prone
*/
function decodeBuild(url_tag) {
if (url_tag) {
async function parse_hash(url_tag) {
const default_load_promises = [ load_atree_data(wynn_version_names[WYNN_VERSION_LATEST]),
load_init(), load_ing_init(), load_tome_init() ];
if (!url_tag) {
await Promise.all(default_load_promises);
return;
}
//default values
let equipment = [null, null, null, null, null, null, null, null, null];
let tomes = [null, null, null, null, null, null, null];
@ -43,7 +57,55 @@ function decodeBuild(url_tag) {
let skillpoints = [0, 0, 0, 0, 0];
let level = 106;
let version_number = parseInt(version)
let version_number = parseInt(version);
let data_str = info[1];
if (version_number >= 8) {
// parse query parameters
// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
const url_params = new URLSearchParams(window.location.search);
const version_id = url_params.get('v');
wynn_version_id = parseInt(version_id);
if (isNaN(wynn_version_id) || wynn_version_id > WYNN_VERSION_LATEST || wynn_version_id < 0) {
// TODO: maybe make the NAN try to use the human readable version?
// NOTE: Failing silently... do we want to raise a loud error?
console.log("Explicit version not found or invalid, using latest version");
wynn_version_id = WYNN_VERSION_LATEST;
}
else {
console.log(`Build link for wynn version ${wynn_version_id} (${wynn_version_names[wynn_version_id]})`);
}
}
else {
// Change the default to oldest. (A time before v8)
wynn_version_id = 0;
}
// the deal with this is because old versions should default to 0 (oldest wynn item version), and v8+ defaults to latest.
// its ugly... but i think this is the behavior we want...
if (wynn_version_id != WYNN_VERSION_LATEST) {
// force reload item database and such.
// TODO MUST: display a warning showing older version!
const msg = 'This build was created in an older version of wynncraft '
+ `(${wynn_version_names[wynn_version_id]} < ${wynn_version_names[WYNN_VERSION_LATEST]}). `
+ 'Would you like to update to the latest version? Updating may break the build and ability tree.';
if (confirm(msg)) {
wynn_version_id = WYNN_VERSION_LATEST;
}
else {
version_name = wynn_version_names[wynn_version_id];
const load_promises = [ load_atree_data(version_name),
load_old_version(version_name),
load_ings_old_version(version_name),
load_tome_old_version(version_name) ];
console.log("Loading old version data...", version_name)
await Promise.all(load_promises);
}
}
if (wynn_version_id == WYNN_VERSION_LATEST) {
await Promise.all(default_load_promises);
}
//equipment (items)
// TODO: use filters
if (version_number < 4) {
@ -52,10 +114,10 @@ function decodeBuild(url_tag) {
let equipment_str = equipments.slice(i*3,i*3+3);
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
}
info[1] = equipments.slice(27);
data_str = equipments.slice(27);
}
else if (version_number == 4) {
let info_str = info[1];
let info_str = data_str;
let start_idx = 0;
for (let i = 0; i < 9; ++i ) {
if (info_str.charAt(start_idx) === "-") {
@ -68,10 +130,10 @@ function decodeBuild(url_tag) {
start_idx += 3;
}
}
info[1] = info_str.slice(start_idx);
data_str = info_str.slice(start_idx);
}
else if (version_number <= 7) {
let info_str = info[1];
else if (version_number <= 8) {
let info_str = data_str;
let start_idx = 0;
for (let i = 0; i < 9; ++i ) {
if (info_str.slice(start_idx,start_idx+3) === "CR-") {
@ -87,7 +149,7 @@ function decodeBuild(url_tag) {
start_idx += 3;
}
}
info[1] = info_str.slice(start_idx);
data_str = info_str.slice(start_idx);
}
//constant in all versions
for (let i in equipment) {
@ -96,48 +158,59 @@ function decodeBuild(url_tag) {
//level, skill point assignments, and powdering
if (version_number == 1) {
let powder_info = info[1];
let powder_info = data_str;
let res = parsePowdering(powder_info);
powdering = res[0];
} else if (version_number == 2) {
save_skp = true;
let skillpoint_info = info[1].slice(0, 10);
let skillpoint_info = data_str.slice(0, 10);
for (let i = 0; i < 5; ++i ) {
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
}
let powder_info = info[1].slice(10);
let powder_info = data_str.slice(10);
let res = parsePowdering(powder_info);
powdering = res[0];
} else if (version_number <= 7){
level = Base64.toInt(info[1].slice(10,12));
} else if (version_number <= 8){
level = Base64.toInt(data_str.slice(10,12));
setValue("level-choice",level);
save_skp = true;
let skillpoint_info = info[1].slice(0, 10);
let skillpoint_info = data_str.slice(0, 10);
for (let i = 0; i < 5; ++i ) {
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
}
let powder_info = info[1].slice(12);
let powder_info = data_str.slice(12);
let res = parsePowdering(powder_info);
powdering = res[0];
info[1] = res[1];
data_str = res[1];
}
// Tomes.
if (version >= 6) {
if (version_number >= 6) {
//tome values do not appear in anything before v6.
if (version_number < 8) {
for (let i in tomes) {
let tome_str = info[1].charAt(i);
let tome_str = data_str.charAt(i);
let tome_name = getTomeNameFromID(Base64.toInt(tome_str));
setValue(tomeInputs[i], tome_name);
}
info[1] = info[1].slice(7);
data_str = data_str.slice(7);
}
else {
// 2chr tome encoding to allow for more tomes.
for (let i in tomes) {
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);
}
}
if (version >= 7) {
if (version_number >= 7) {
// ugly af. only works since its the last thing. will be fixed with binary decode
atree_data = new BitVector(info[1]);
atree_data = new BitVector(data_str);
}
else {
atree_data = null;
@ -149,7 +222,6 @@ function decodeBuild(url_tag) {
for (let i in skillpoints) {
setValue(skp_order[i] + "-skp", skillpoints[i]);
}
}
}
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
@ -161,7 +233,8 @@ function encodeBuild(build, powders, skillpoints, atree, atree_state) {
//V6 encoding - Tomes
//V7 encoding - ATree
build_version = 5;
//V8 encoding - wynn version
build_version = 8;
build_string = "";
tome_string = "";
@ -169,16 +242,16 @@ function encodeBuild(build, powders, skillpoints, atree, atree_state) {
if (item.statMap.get("custom")) {
let custom = "CI-"+encodeCustom(item, true);
build_string += Base64.fromIntN(custom.length, 3) + custom;
build_version = Math.max(build_version, 5);
//build_version = Math.max(build_version, 5);
} else if (item.statMap.get("crafted")) {
build_string += "CR-"+encodeCraft(item);
} else if (item.statMap.get("category") === "tome") {
let tome_id = item.statMap.get("id");
if (tome_id <= 60) {
// valid normal tome. ID 61-63 is for NONE tomes.
build_version = Math.max(build_version, 6);
//build_version = Math.max(build_version, 6);
}
tome_string += Base64.fromIntN(tome_id, 1);
tome_string += Base64.fromIntN(tome_id, 2);
} else {
build_string += Base64.fromIntN(item.statMap.get("id"), 3);
}
@ -206,7 +279,7 @@ function encodeBuild(build, powders, skillpoints, atree, atree_state) {
build_string += tome_string;
if (atree.length > 0 && atree_state.get(atree[0].ability.id).active) {
build_version = Math.max(build_version, 7);
//build_version = Math.max(build_version, 7);
const bitvec = encode_atree(atree, atree_state);
build_string += bitvec.toB64();
}
@ -215,14 +288,18 @@ function encodeBuild(build, powders, skillpoints, atree, atree_state) {
}
}
function get_full_url() {
return `${url_base}?v=${wynn_version_id.toString()}${location.hash}`
}
function copyBuild() {
copyTextToClipboard(url_base+location.hash);
copyTextToClipboard();
document.getElementById("copy-button").textContent = "Copied!";
}
function shareBuild(build) {
if (build) {
let text = url_base+location.hash+"\n"+
let text = url_base+'?v='+wynn_version_id.toString()+location.hash+"\n"+
"WynnBuilder build:\n"+
"> "+build.items[0].statMap.get("displayName")+"\n"+
"> "+build.items[1].statMap.get("displayName")+"\n"+

View file

@ -47,6 +47,8 @@ function loadBuild() {
let saveName = document.getElementById("build-name").value;
if (Object.keys(savedBuilds).includes(saveName)) {
// NOTE: this is broken since decodeBuild is async func.
// Doubly broken because of versioning... lets just kill this for now
decodeBuild(savedBuilds[saveName])
document.getElementById("loaded-error").textContent = "";
document.getElementById("loaded-build").textContent = "Build loaded";
@ -305,9 +307,8 @@ function collapse_element(elmnt) {
document.querySelector(elmnt).style.removeProperty('display');
}
function init() {
async function init() {
console.log("builder.js init");
init_autocomplete();
// Other "main" stuff
// Spell dropdowns
@ -329,6 +330,7 @@ function init() {
}
// Masonry setup
try {
let masonry = Macy({
container: "#masonry-container",
columns: 1,
@ -340,7 +342,6 @@ function init() {
x: 20,
y: 20,
}
});
let search_masonry = Macy({
@ -354,9 +355,16 @@ function init() {
x: 20,
y: 20,
}
});
decodeBuild(url_tag);
} catch (e) {
console.log("Could not initialize macy components. Maybe you're offline?");
}
await parse_hash(url_tag);
try {
init_autocomplete();
} catch (e) {
console.log("Could not initialize autocomplete. Maybe you're offline?");
}
builder_graph_init();
for (const item_node of item_nodes) {
if (item_node.get_value() === null) {
@ -364,6 +372,7 @@ function init() {
if (confirm('One or more items failed to load correctly. This could be due to a corrupted build link, or (more likely) a database load failure. Would you like to reload?')) {
hardReload();
}
console.log(item_node);
break;
}
}
@ -375,7 +384,5 @@ window.onerror = function(message, source, lineno, colno, error) {
};
(async function() {
let load_promises = [ load_init(), load_ing_init(), load_tome_init() ];
await Promise.all(load_promises);
init();
await init();
})();

View file

@ -1,4 +1,4 @@
const DB_VERSION = 117;
const DB_VERSION = 118;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
let db;
@ -92,13 +92,31 @@ function clean_item(item) {
}
}
async function load_old_version(version_str) {
load_in_progress = true;
let getUrl = window.location;
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// No random string -- we want to use caching
let url = `${baseUrl}/data/${version_str}/items.json`;
let result = await (await fetch(url)).json();
items = result.items;
for (const item of items) {
clean_item(item);
}
let sets_ = result.sets;
for (const set in sets_) {
sets.set(set, sets_[set]);
}
init_maps();
load_complete = true;
}
/*
* Load item set from remote DB (aka a big json file). Calls init() on success.
*/
async function load() {
let getUrl = window.location;
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// "Random" string to prevent caching!
let url = baseUrl + "/compress.json?"+new Date();
let result = await (await fetch(url)).json();
@ -150,7 +168,7 @@ async function load_init() {
console.log("Skipping load...")
}
else {
load_in_progress = true
load_in_progress = true;
if (reload) {
console.log("Using new data...")
await load();

View file

@ -1,4 +1,4 @@
const ING_DB_VERSION = 18;
const ING_DB_VERSION = 19;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.js
@ -59,22 +59,41 @@ function clean_ing(ing) {
}
}
async function load_ings_old_version(version_str) {
iload_in_progress = true;
let getUrl = window.location;
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// No random string -- we want to use caching
let url = `${baseUrl}/data/${version_str}/ingreds.json`;
let result = await (await fetch(url)).json();
ings = result;
for (const id in ings) {
clean_ing(ings[id]);
}
url = baseUrl + "/recipes_compress.json";
result = await (await fetch(url)).json();
recipes = result.recipes;
init_maps();
iload_complete = true;
}
/*
* Load item set from remote DB (aka a big json file). Calls init() on success.
*/
async function load_ings() {
let getUrl = window.location;
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// "Random" string to prevent caching!
let url = baseUrl + "/ingreds_compress.json?"+new Date();
url = url.replace(/\w+.html/, "") ;
let result = await (await fetch(url)).json();
result = await (await fetch(url)).json();
ings = result;
url = url.replace("/ingreds_compress.json", "/recipes_compress.json");
url = baseUrl + "/recipes_compress.json?"+new Date();
result = await (await fetch(url)).json();
recipes = result.recipes;

View file

@ -1,4 +1,4 @@
const TOME_DB_VERSION = 5;
const TOME_DB_VERSION = 6;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
let tdb;
@ -34,13 +34,27 @@ async function load_tome_local() {
});
}
async function load_tome_old_version(version_str) {
tload_in_progress = true;
let getUrl = window.location;
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// No random string -- we want to use caching
let url = `${baseUrl}/data/${version_str}/tomes.json`;
let result = await (await fetch(url)).json();
tomes = result.tomes;
for (const tome of tomes) {
//dependency on clean_item in load.js
clean_item(tome);
}
init_tome_maps();
tload_complete = true;
}
/*
* Load tome set from remote DB (json). Calls init() on success.
*/
async function load_tome() {
let getUrl = window.location;
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
let baseUrl = `${getUrl.protocol}//${getUrl.host}/`;
// "Random" string to prevent caching!
let url = baseUrl + "/tomes.json?"+new Date();
let result = await (await fetch(url)).json();

View file

@ -3647,10 +3647,6 @@
"Mask of the Spirits": 3649,
"Inhibitor": 3650,
"Spear of Testiness": 3651,
"Blue Wynnter Sweater": 3648,
"Green Wynnter Sweater": 3649,
"Purple Wynnter Sweater": 3650,
"Red Wynnter Sweater": 3651,
"Snowtread Boots": 3652,
"White Wynnter Sweater": 3653,
"Keratoconus": 3579,
@ -3768,5 +3764,35 @@
"Sharpened Seaglass": 3762,
"Storyteller": 3763,
"Midnight Sun": 3764,
"Lachesism": 3765
"Lachesism": 3765,
"Bison Tipper": 3766,
"Decaying Headdress": 3767,
"Despondence": 3768,
"Expedition's End": 3769,
"Fire Sword Style-2": 3770,
"Black Wynnter Sweater": 3771,
"Brown Wynnter Sweater": 3772,
"Cerulean Wynnter Sweater": 3773,
"Pink Wynnter Sweater": 3774,
"Yellow Wynnter Sweater": 3775,
"Poison-Tipped Poleaxe": 3776,
"Compliance": 3777,
"Dispersion": 3778,
"Obstinance": 3779,
"Succession": 3780,
"Continuum": 3781,
"Divergence": 3782,
"Aleph Null": 3783,
"Fractal": 3784,
"Nonexistence": 3785,
"Infinitesimal": 3786,
"Recursion": 3787,
"Soul Ink": 3788,
"Swimmer Net": 3789,
"Unusual": 3790,
"Santa Earmuffs": 3791,
"Blue Wynnter Sweater": 3792,
"Green Wynnter Sweater": 3793,
"Purple Wynnter Sweater": 3794,
"Red Wynnter Sweater": 3795
}

View file

@ -717,5 +717,18 @@
"Windswept Roots": 705,
"Yale Leather": 706,
"Contraband Hibiscus": 717,
"Myocardial Leg": 718
"Myocardial Leg": 718,
"Broken Ram Horn": 719,
"Climber's Padding": 720,
"Rusty Spurs": 721,
"Sheep Hoof": 722,
"Consecrated Ivory": 723,
"Intermittenstirrups": 724,
"Natural Nitrates": 725,
"Pigman Ivory": 726,
"Runner's Bandages": 727,
"Blazing Stimulants": 728,
"Icy Crampons": 729,
"Sanctified Sheep Soul": 730,
"Solcrystal Horn": 731
}

View file

@ -5,3 +5,5 @@ Version 3: http://localhost:8000/#3_0250px0uX0K50K20OK0OJ00A0Qe1z+m21001M1g00001
Version 3: http://localhost:8000/#3_0K60iv0CE0Qt0BK0BK0K40Jc0uG160V050o1L1g00001003C6
Version 4: http://localhost:8000/#4_-1+W+W+W+W+W+W9g91-1+W+W+W+W+W+W9d91-1+W+W+W+W+W+W9i9--1+W+W+W+W+W+W9a91-1+W+W+W+W+W+W9m91-1+W+W+W+W+W+W9m91-1+W+W+W+W+W+W9c91-1+W+W+W+W+W+W9n91-1+W+W+W+W+W+W9q9100000000001g000010036C
Version 5 (CI): http://localhost:8000/index.html#5_0lS0JX0u50tv00nCI-10000MFlipped%20Lapis%20Ring0200540M01010V010m0K20OL0og00-CI-110230401030570A07287-3530F40M0201d0c07287-3530w023zu100202e-Y0i211q-Y1g00000
Version 6: (tomes): https://localhost:8000/builder/#6_02509-0p00Qj0K2CR-19i7n9i7n9C7J9m910T40Ji0R1-t+f1k-P2W1g00001007lUbW51I6p
Version 7 (atree): http://localhost:8000/builder/#7_0ZP0iv0uR0Lg00502C0K40uY0BE161a+W0o161g00001001Z6zz++++--6l-VU6

BIN
regression_tests/v6.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
regression_tests/v7.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

View file

@ -53,5 +53,23 @@
"Freelancer's Tome of Allegiance": 51,
"Sycophant's Tome of Allegiance": 52,
"Fanatic's Tome of Allegiance": 53,
"Assimilator's Tome of Allegiance": 54
"Assimilator's Tome of Allegiance": 54,
"Ephemeral Tome of Weapon Mastery I": 55,
"Ephemeral Tome of Weapon Mastery II": 56,
"Ephemeral Tome of Weapon Mastery III": 57,
"Harvester's Tome of Weapon Mastery I": 58,
"Harvester's Tome of Weapon Mastery II": 59,
"Harvester's Tome of Weapon Mastery III": 60,
"Earthbound Tome of Weapon Mastery III": 64,
"Nimble Tome of Weapon Mastery III": 65,
"Mystical Tome of Weapon Mastery III": 66,
"Warding Tome of Weapon Mastery III": 67,
"Athletic Tome of Weapon Mastery III": 68,
"Cosmic Tome of Weapon Mastery III": 69,
"Seismic Tome of Weapon Mastery III": 70,
"Voltaic Tome of Weapon Mastery III": 71,
"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
}

View file

@ -321,7 +321,7 @@
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"lvl": 60,
"damMobs": 7,
"str": 3,
"fixID": false,
@ -349,7 +349,7 @@
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"lvl": 60,
"damMobs": 7,
"dex": 3,
"fixID": false,
@ -377,7 +377,7 @@
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"lvl": 60,
"damMobs": 7,
"int": 3,
"fixID": false,
@ -405,7 +405,7 @@
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"lvl": 60,
"damMobs": 7,
"def": 3,
"fixID": false,
@ -433,7 +433,7 @@
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"lvl": 60,
"damMobs": 7,
"agi": 3,
"fixID": false,
@ -461,7 +461,7 @@
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"lvl": 60,
"damMobs": 7,
"str": 1,
"dex": 1,
@ -804,6 +804,266 @@
"fixID": false,
"id": 54,
"alias": "Rainbow"
},
{
"name": "Ephemeral Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"damMobs": 7,
"mr": 1,
"fixID": false,
"id": 55,
"alias": "Mana Regen I"
},
{
"name": "Ephemeral Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"mr": 1,
"fixID": false,
"id": 56,
"alias": "Mana Regen II"
},
{
"name": "Ephemeral Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"mr": 1,
"fixID": false,
"id": 57,
"alias": "Mana Regen III"
},
{
"name": "Harvester's Tome of Weapon Mastery I",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 60,
"damMobs": 7,
"ms": 1,
"fixID": false,
"id": 58,
"alias": "Mana Steal I"
},
{
"name": "Harvester's Tome of Weapon Mastery II",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 80,
"damMobs": 8,
"ms": 1,
"fixID": false,
"id": 59,
"alias": "Mana Steal II"
},
{
"name": "Harvester's Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"ms": 1,
"fixID": false,
"id": 60,
"alias": "Mana Steal III"
},
{
"name": "Earthbound Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"str": 3,
"fixID": false,
"id": 64,
"alias": "Strength III"
},
{
"name": "Nimble Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"dex": 3,
"fixID": false,
"id": 65,
"alias": "Dexterity III"
},
{
"name": "Mystical Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"int": 3,
"fixID": false,
"id": 66,
"alias": "Intelligence III"
},
{
"name": "Warding Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"def": 3,
"fixID": false,
"id": 67,
"alias": "Defense III"
},
{
"name": "Athletic Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"agi": 3,
"fixID": false,
"id": 68,
"alias": "Agility III"
},
{
"name": "Cosmic Tome of Weapon Mastery III",
"tier": "Fabled",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 13,
"str": 1,
"dex": 1,
"int": 1,
"def": 1,
"agi": 1,
"fixID": false,
"id": 69,
"alias": "Rainbow Skillpoint III"
},
{
"name": "Seismic Tome of Weapon Mastery III",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 16,
"eDamPct": 8,
"fixID": false,
"id": 70,
"alias": "Earth Damage III"
},
{
"name": "Voltaic Tome of Weapon Mastery III",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 16,
"tDamPct": 8,
"fixID": false,
"id": 71,
"alias": "Thunder Damage III"
},
{
"name": "Abyssal Tome of Weapon Mastery III",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 16,
"wDamPct": 8,
"fixID": false,
"id": 72,
"alias": "Water Damage III"
},
{
"name": "Infernal Tome of Weapon Mastery III",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 16,
"fDamPct": 8,
"fixID": false,
"id": 73,
"alias": "Fire Damage III"
},
{
"name": "Cyclonic Tome of Weapon Mastery III",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 16,
"aDamPct": 8,
"fixID": false,
"id": 74,
"alias": "Air Damage III"
},
{
"name": "Astral Tome of Weapon Mastery III",
"tier": "Mythic",
"type": "weaponTome",
"category": "tome",
"drop": "never",
"restrict": "Soulbound Item",
"lvl": 105,
"damMobs": 16,
"eDamPct": 7,
"tDamPct": 7,
"wDamPct": 7,
"fDamPct": 7,
"aDamPct": 7,
"fixID": false,
"id": 75,
"alias": "Rainbow Damage III"
}
]
}