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>
This commit is contained in:
hppeng-wynn 2024-05-31 00:54:23 -07:00 committed by GitHub
parent 4147bd813e
commit dec0a95279
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 119163 additions and 116154 deletions

File diff suppressed because one or more lines are too long

View file

@ -827,6 +827,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 +1352,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>

177467
clean.json

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

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

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

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

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

1387
data/2.0.4.4/tomes.json Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -56,6 +56,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

@ -152,7 +152,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.'],

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

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
}
*/
@ -476,14 +477,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 +569,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

View file

@ -3589,7 +3589,7 @@ const atrees = {
},
"properties": {
"duration": 3,
"tick": 0.6
"rate": 1.66666666666666666666666666666
},
"effects": [
{
@ -3604,9 +3604,7 @@ const atrees = {
},
{
"name": "DPS",
"hits": {
"Damage Tick": 1.66666666666666666666666666666
}
"hits": { "Damage Tick": "Uppercut.rate" }
},
{
"name": "Total Damage",
@ -3846,7 +3844,7 @@ const atrees = {
},
{
"display_name": "Counter",
"desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back",
"desc": "Gain a 30% chance to take no damage and instantly attack back when dodging an enemy attack with Agility.",
"archetype": "Battle Monk",
"parents": ["Aerodynamics", "Cheaper Uppercut", "Manachism"],
"dependencies": [],
@ -3990,7 +3988,7 @@ const atrees = {
},
{
"display_name": "Sacred Surge",
"desc": "Gain the ability to unleash a Sacred Surge. Whenever any of your spells or abilities are triggered, increase your holy power by 1%. Bash and Uppercut will spend 20% of Sacred Surge to smite enemies with holy energy, dealing extra damage.",
"desc": "Gain the ability to unleash a Sacred Surge. Whenever any of your spells or abilities are triggered, increase your holy power by 1%. Bash and Uppercut will spend 25% of Sacred Surge to smite enemies with holy energy, dealing extra damage.",
"archetype": "Paladin",
"archetype_req": 5,
"parents": ["Stronger Mantle", "Provoke"],
@ -4031,14 +4029,23 @@ const atrees = {
"col": 1,
"icon": "node_1"
},
"properties": {},
"properties": {
"duration": 3,
"rate": 2.5
},
"effects": [
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Boiling Blood",
"target_part": "Boiling Blood Tick",
"cost": 0,
"multipliers": [25, 0, 0, 0, 5, 0]
},
{
"type": "add_spell_prop",
"base_spell": 1,
"target_part": "Boiling Blood DPS",
"hits": { "Boiling Blood Tick": "Bash.rate" }
}
]
},
@ -4542,7 +4549,7 @@ const atrees = {
"type": "add_spell_prop",
"base_spell": 4,
"target_part": "Total Damage",
"hits": { "Tempest": 3 }
"hits": { "Tempest Total Damage": 1 }
}
]
},
@ -5856,7 +5863,8 @@ const atrees = {
"dependencies": [ "Heal" ],
"blockers": [
"Larger Heal",
"Orphion's Pulse"
"Orphion's Pulse",
"Timelock"
],
"cost": 2,
"display": {
@ -7258,7 +7266,7 @@ const atrees = {
"More Winded II"
],
"dependencies": [],
"blockers": [],
"blockers": ["Arcane Transfer"],
"cost": 2,
"display": {
"row": 34,
@ -8088,15 +8096,9 @@ const atrees = {
{
"type": "add_spell_prop",
"base_spell": 4,
"behavior": "modify",
"target_part": "Per Tick",
"multipliers": [
0,
0,
0,
0,
10,
0
]
"multipliers": [0, 0, 0, 0, 10, 0]
}
]
},
@ -8320,23 +8322,16 @@ const atrees = {
{
"type": "add_spell_prop",
"base_spell": 4,
"behavior": "modify",
"target_part": "Total Damage",
"hits": {
"Per Bomb": 2
}
"hits": { "Per Bomb": 2 }
},
{
"type": "add_spell_prop",
"base_spell": 4,
"behavior": "modify",
"target_part": "Per Tick",
"multipliers": [
-10,
0,
0,
0,
0,
0
]
"multipliers": [ -10, 0, 0, 0, 0, 0 ]
}
]
},
@ -8869,13 +8864,17 @@ const atrees = {
"type": "stat",
"name": "damMult.EchoCast:4.Per Tick"
},
{
"type": "stat",
"name": "damMult.EchoCast:4.Single Bomb"
},
{
"type": "stat",
"name": "damMult.EchoCast:6.Per Shuriken"
}
],
"scaling": [
100, 100, 100, 100, 100, 100, 100
100
]
}
]
@ -10626,6 +10625,7 @@ const atrees = {
"slider_name": "Active Puppets",
"slider_step": 1,
"slider_max": 3,
"slider_default": 3,
"output": [
{
"type": "prop",
@ -10835,6 +10835,7 @@ const atrees = {
"slider": true,
"slider_name": "Active Puppets",
"slider_max": 1,
"slider_default": 1,
"output": [
{
"type": "prop",
@ -11001,7 +11002,7 @@ const atrees = {
"bonuses": [
{
"type": "stat",
"name": "damMult.Bullwhip:5.Puppet Hit",
"name": "damMult.Bullwhip:6.Puppet Hit",
"value": 20
},
{
@ -11034,6 +11035,7 @@ const atrees = {
"slider": true,
"slider_name": "Active Puppets",
"slider_max": 2,
"slider_default": 2,
"output": [
{
"type": "prop",

File diff suppressed because one or more lines are too long

View file

@ -34,7 +34,8 @@ const wynn_version_names = [
'2.0.2.3',
'2.0.3.1',
'2.0.4.1',
'2.0.4.3'
'2.0.4.3',
'2.0.4.4'
];
const WYNN_VERSION_LATEST = wynn_version_names.length - 1;
// Default to the newest version.
@ -66,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];
@ -151,7 +152,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 ) {
@ -192,7 +193,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;
@ -211,7 +212,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);
@ -220,7 +221,16 @@ 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);
@ -257,7 +267,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 = "";

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);
@ -1072,7 +1074,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

@ -160,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": {
@ -230,7 +237,7 @@
},
"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",
@ -240,31 +247,25 @@
},
"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 }
}
]
}]
@ -528,5 +529,73 @@
"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

@ -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

@ -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

@ -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

@ -1,4 +1,4 @@
const DB_VERSION = 130;
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 = 31;
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 = 8;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
let tdb;
@ -136,7 +136,8 @@ 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", "guildTome", "No Guild Tome"],
["tome", "lootrunTome", "No Lootrun Tome"]
];
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];

View file

@ -77,6 +77,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);
@ -106,13 +108,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 +163,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);

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

@ -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

@ -3868,5 +3868,7 @@
"Scarlet Wynnter Sweater": 3866,
"Orange Wynnter Sweater": 3867,
"Pine Wynnter Sweater": 3868,
"Indigo Wynnter Sweater": 3869
"Indigo Wynnter Sweater": 3869,
"Air In A Can": 3870,
"Iosis": 3871
}

View file

@ -42,6 +42,7 @@
"airSpellDamage",
"poison",
"elementalDamage",
"healingEfficiency",
"raw4thSpellCost",
"raw2ndSpellCost",
"sprintRegen",
@ -51,7 +52,6 @@
"elementalSpellDamage",
"rawNeutralSpellDamage",
"4thSpellCost",
"healingEfficiency",
"knockback",
"waterSpellDamage",
"fireSpellDamage",
@ -72,6 +72,7 @@
"earthMainAttackDamage",
"rawFireSpellDamage",
"rawElementalSpellDamage",
"healing",
"rawElementalMainAttackDamage",
"airMainAttackDamage",
"thunderMainAttackDamage",
@ -86,7 +87,8 @@
"lootQuality",
"gatherXpBonus",
"gatherSpeed",
"rawWaterMainAttackDamage"
"rawWaterMainAttackDamage",
"rawEarthMainAttackDamage"
],
"majorIds": [
"Divine Honor",
@ -121,6 +123,7 @@
"Festive Spirit",
"Freerunner",
"Hawkeye",
"Temblor",
"Windsurf",
"Juggle",
"Roving Assassin",
@ -130,8 +133,8 @@
"Heart of the Pack",
"Coagulate",
"Guardian",
"Hellfire",
"Overwhelm",
"Temblor",
"Lunge",
"Madness"
],

View file

@ -28,7 +28,7 @@ if __name__ == "__main__":
with open(local_metadata_file, 'r') as infile:
metadata_check = json.load(infile)
checklist = set(x for x in translate_mappings.keys())
checklist = set(x for x in translate_mappings['identifications'].keys())
debug(f"Checking {len(checklist)} identifications")
n = 0
for identification in metadata_check['identifications']:

View file

@ -1,4 +1,26 @@
{
"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",
@ -80,7 +102,8 @@
"dexterity": "dexReq",
"intelligence": "intReq",
"agility": "agiReq",
"defence": "defReq"
"defence": "defReq",
"tomeSeeking": "DELETE;"
},
"identifications": {
"healthRegen": "hprPct",

View file

@ -101,6 +101,15 @@ def recursive_translate(entry, result, path, translate_single):
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):
"""
@ -133,7 +142,13 @@ def translate_entry(entry):
#return recursive_translate(entry, {}, "ing"), "ingredient"
if "tomeType" in entry:
# only tomes have this field.
return None, "tome"
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"
@ -144,11 +159,15 @@ 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)
@ -160,26 +179,39 @@ for name, entry in api_data.items():
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"
@ -188,9 +220,11 @@ with open(major_ids_filename, 'r') as major_ids_file:
major_ids_reverse_map = { v['displayName'] : k for k, v in major_ids_map.items() }
for item in items:
# HACKY ITEM FIXES!
# 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:
@ -214,9 +248,21 @@ for ingred in ingreds:
if not (ingred["name"] in ing_map):
new_id = len(ing_map)
ing_map[ingred["name"]] = new_id
print(f'New item: {item["name"]} (id: {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
@ -225,6 +271,8 @@ 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
@ -234,3 +282,5 @@ with open('item_out.json', "w+") as out_file:
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=(',', ':'))

View file

@ -71,5 +71,22 @@
"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": 73,
"Mastermind's Tome of Lootrun Mastery": 74,
"Tome of Armour Mastery I": 75,
"Pickpocket's Tome of Lootrun Mastery": 76,
"Tome of Slaying Mastery III": 77,
"Tome of Slaying Mastery I": 78,
"Tome of Gathering Mastery I": 79,
"Scoundrel's Tome of Lootrun Mastery": 80,
"Spelunker's Tome of Lootrun Mastery": 81,
"Tome of Slaying Mastery II": 82,
"Tome of Dungeoneering Mastery II": 83,
"Tome of Gathering Mastery II": 84,
"Tome of Dungeoneering Mastery I": 85,
"Manipulator's Tome of Lootrun Mastery": 86,
"Plunderer's Tome of Lootrun Mastery": 87,
"Thief's Tome of Lootrun Mastery": 88,
"Tome of Dungeoneering Mastery III": 89
}

2160
tomes.json

File diff suppressed because it is too large Load diff