Core/CharStats

* unify stat handling. If there are discrepancies left at least they are now centralized.
   - health should no longer pretend to be mana
   - stats are no longer capped to 32.7k points as items can have multiple of the same stat and handily exceed smallints
 * weight scale fixes:
   - "repair cost" is no longer a weight scale option. Why was it one in the first place? Also it wasn't even accessible before. (that was a bug)
   - "bonus armor" is now searchable and only applied to armor pieces
 * removed unused parsed stats from itemsets
This commit is contained in:
Sarjuuk
2024-04-02 23:06:09 +02:00
parent cb3c7d4ef0
commit d16b08bb29
21 changed files with 1140 additions and 875 deletions

View File

@@ -422,6 +422,52 @@ define('STAT_STAMINA', 2);
define('STAT_INTELLECT', 3); define('STAT_INTELLECT', 3);
define('STAT_SPIRIT', 4); define('STAT_SPIRIT', 4);
// ItemMods
define('ITEM_MOD_MANA', 0);
define('ITEM_MOD_HEALTH', 1);
define('ITEM_MOD_AGILITY', 3);
define('ITEM_MOD_STRENGTH', 4);
define('ITEM_MOD_INTELLECT', 5);
define('ITEM_MOD_SPIRIT', 6);
define('ITEM_MOD_STAMINA', 7);
define('ITEM_MOD_DEFENSE_SKILL_RATING', 12);
define('ITEM_MOD_DODGE_RATING', 13);
define('ITEM_MOD_PARRY_RATING', 14);
define('ITEM_MOD_BLOCK_RATING', 15);
define('ITEM_MOD_HIT_MELEE_RATING', 16);
define('ITEM_MOD_HIT_RANGED_RATING', 17);
define('ITEM_MOD_HIT_SPELL_RATING', 18);
define('ITEM_MOD_CRIT_MELEE_RATING', 19);
define('ITEM_MOD_CRIT_RANGED_RATING', 20);
define('ITEM_MOD_CRIT_SPELL_RATING', 21);
define('ITEM_MOD_HIT_TAKEN_MELEE_RATING', 22);
define('ITEM_MOD_HIT_TAKEN_RANGED_RATING', 23);
define('ITEM_MOD_HIT_TAKEN_SPELL_RATING', 24);
define('ITEM_MOD_CRIT_TAKEN_MELEE_RATING', 25);
define('ITEM_MOD_CRIT_TAKEN_RANGED_RATING', 26);
define('ITEM_MOD_CRIT_TAKEN_SPELL_RATING', 27);
define('ITEM_MOD_HASTE_MELEE_RATING', 28);
define('ITEM_MOD_HASTE_RANGED_RATING', 29);
define('ITEM_MOD_HASTE_SPELL_RATING', 30);
define('ITEM_MOD_HIT_RATING', 31);
define('ITEM_MOD_CRIT_RATING', 32);
define('ITEM_MOD_HIT_TAKEN_RATING', 33);
define('ITEM_MOD_CRIT_TAKEN_RATING', 34);
define('ITEM_MOD_RESILIENCE_RATING', 35);
define('ITEM_MOD_HASTE_RATING', 36);
define('ITEM_MOD_EXPERTISE_RATING', 37);
define('ITEM_MOD_ATTACK_POWER', 38);
define('ITEM_MOD_RANGED_ATTACK_POWER', 39);
define('ITEM_MOD_FERAL_ATTACK_POWER', 40);
define('ITEM_MOD_SPELL_HEALING_DONE', 41);
define('ITEM_MOD_SPELL_DAMAGE_DONE', 42);
define('ITEM_MOD_MANA_REGENERATION', 43);
define('ITEM_MOD_ARMOR_PENETRATION_RATING', 44);
define('ITEM_MOD_SPELL_POWER', 45);
define('ITEM_MOD_HEALTH_REGEN', 46);
define('ITEM_MOD_SPELL_PENETRATION', 47);
define('ITEM_MOD_BLOCK_VALUE', 48);
// Powers // Powers
define('POWER_MANA', 0); define('POWER_MANA', 0);
define('POWER_RAGE', 1); define('POWER_RAGE', 1);
@@ -777,70 +823,16 @@ define('ITEM_FLAG_SMARTLOOT', 0x02000000);
define('ITEM_FLAG_ACCOUNTBOUND', 0x08000000); define('ITEM_FLAG_ACCOUNTBOUND', 0x08000000);
define('ITEM_FLAG_MILLABLE', 0x20000000); define('ITEM_FLAG_MILLABLE', 0x20000000);
// ItemMod (differ slightly from client, see g_statToJson) // ItemEnchantment types
define('ITEM_MOD_WEAPON_DMG', 0); // < custom define('ENCHANTMENT_TYPE_NONE', 0);
define('ITEM_MOD_MANA', 1); define('ENCHANTMENT_TYPE_COMBAT_SPELL', 1);
define('ITEM_MOD_HEALTH', 2); define('ENCHANTMENT_TYPE_DAMAGE', 2);
define('ITEM_MOD_AGILITY', 3); // stats v define('ENCHANTMENT_TYPE_EQUIP_SPELL', 3);
define('ITEM_MOD_STRENGTH', 4); define('ENCHANTMENT_TYPE_RESISTANCE', 4);
define('ITEM_MOD_INTELLECT', 5); define('ENCHANTMENT_TYPE_STAT', 5);
define('ITEM_MOD_SPIRIT', 6); define('ENCHANTMENT_TYPE_TOTEM', 6);
define('ITEM_MOD_STAMINA', 7); define('ENCHANTMENT_TYPE_USE_SPELL', 7);
define('ITEM_MOD_ENERGY', 8); // powers v define('ENCHANTMENT_TYPE_PRISMATIC_SOCKET', 8);
define('ITEM_MOD_RAGE', 9);
define('ITEM_MOD_FOCUS', 10);
define('ITEM_MOD_RUNIC_POWER', 11);
define('ITEM_MOD_DEFENSE_SKILL_RATING', 12); // ratings v
define('ITEM_MOD_DODGE_RATING', 13);
define('ITEM_MOD_PARRY_RATING', 14);
define('ITEM_MOD_BLOCK_RATING', 15);
define('ITEM_MOD_HIT_MELEE_RATING', 16);
define('ITEM_MOD_HIT_RANGED_RATING', 17);
define('ITEM_MOD_HIT_SPELL_RATING', 18);
define('ITEM_MOD_CRIT_MELEE_RATING', 19);
define('ITEM_MOD_CRIT_RANGED_RATING', 20);
define('ITEM_MOD_CRIT_SPELL_RATING', 21);
define('ITEM_MOD_HIT_TAKEN_MELEE_RATING', 22);
define('ITEM_MOD_HIT_TAKEN_RANGED_RATING', 23);
define('ITEM_MOD_HIT_TAKEN_SPELL_RATING', 24);
define('ITEM_MOD_CRIT_TAKEN_MELEE_RATING', 25);
define('ITEM_MOD_CRIT_TAKEN_RANGED_RATING', 26);
define('ITEM_MOD_CRIT_TAKEN_SPELL_RATING', 27);
define('ITEM_MOD_HASTE_MELEE_RATING', 28);
define('ITEM_MOD_HASTE_RANGED_RATING', 29);
define('ITEM_MOD_HASTE_SPELL_RATING', 30);
define('ITEM_MOD_HIT_RATING', 31);
define('ITEM_MOD_CRIT_RATING', 32);
define('ITEM_MOD_HIT_TAKEN_RATING', 33);
define('ITEM_MOD_CRIT_TAKEN_RATING', 34);
define('ITEM_MOD_RESILIENCE_RATING', 35);
define('ITEM_MOD_HASTE_RATING', 36);
define('ITEM_MOD_EXPERTISE_RATING', 37);
define('ITEM_MOD_ATTACK_POWER', 38);
define('ITEM_MOD_RANGED_ATTACK_POWER', 39);
define('ITEM_MOD_FERAL_ATTACK_POWER', 40);
define('ITEM_MOD_SPELL_HEALING_DONE', 41); // deprecated
define('ITEM_MOD_SPELL_DAMAGE_DONE', 42); // deprecated
define('ITEM_MOD_MANA_REGENERATION', 43);
define('ITEM_MOD_ARMOR_PENETRATION_RATING', 44);
define('ITEM_MOD_SPELL_POWER', 45);
define('ITEM_MOD_HEALTH_REGEN', 46);
define('ITEM_MOD_SPELL_PENETRATION', 47);
define('ITEM_MOD_BLOCK_VALUE', 48);
// define('ITEM_MOD_MASTERY_RATING', 49);
define('ITEM_MOD_ARMOR', 50); // resistances v
define('ITEM_MOD_FIRE_RESISTANCE', 51);
define('ITEM_MOD_FROST_RESISTANCE', 52);
define('ITEM_MOD_HOLY_RESISTANCE', 53);
define('ITEM_MOD_SHADOW_RESISTANCE', 54);
define('ITEM_MOD_NATURE_RESISTANCE', 55);
define('ITEM_MOD_ARCANE_RESISTANCE', 56); // custom v
define('ITEM_MOD_FIRE_POWER', 57);
define('ITEM_MOD_FROST_POWER', 58);
define('ITEM_MOD_HOLY_POWER', 59);
define('ITEM_MOD_SHADOW_POWER', 60);
define('ITEM_MOD_NATURE_POWER', 61);
define('ITEM_MOD_ARCANE_POWER', 62);
// Spell Effects and Auras // Spell Effects and Auras
define('SPELL_EFFECT_NONE', 0); define('SPELL_EFFECT_NONE', 0);

View File

@@ -33,17 +33,6 @@ class Game
null, 'warrior', 'paladin', 'hunter', 'rogue', 'priest', 'deathknight', 'shaman', 'mage', 'warlock', null, 'druid' null, 'warrior', 'paladin', 'hunter', 'rogue', 'priest', 'deathknight', 'shaman', 'mage', 'warlock', null, 'druid'
); );
private static $combatRatingToItemMod = array( // zero-indexed idx:CR; val:Mod
null, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, null, null, null, 37, 44
);
public static $lvlIndepRating = array( // rating doesn't scale with level
ITEM_MOD_MANA, ITEM_MOD_HEALTH, ITEM_MOD_ATTACK_POWER, ITEM_MOD_MANA_REGENERATION, ITEM_MOD_SPELL_POWER,
ITEM_MOD_HEALTH_REGEN, ITEM_MOD_SPELL_PENETRATION, ITEM_MOD_BLOCK_VALUE
);
public static $questClasses = array( public static $questClasses = array(
-2 => [ 0], -2 => [ 0],
0 => [ 1, 3, 4, 8, 9, 10, 11, 12, 25, 28, 33, 36, 38, 40, 41, 44, 45, 46, 47, 51, 85, 130, 132, 139, 154, 267, 1497, 1519, 1537, 2257, 3430, 3431, 3433, 3487, 4080, 4298], 0 => [ 1, 3, 4, 8, 9, 10, 11, 12, 25, 28, 33, 36, 38, 40, 41, 44, 45, 46, 47, 51, 85, 130, 132, 139, 154, 267, 1497, 1519, 1537, 2257, 3430, 3431, 3433, 3487, 4080, 4298],
@@ -146,19 +135,6 @@ class Game
'meta', 'red', 'yellow', 'blue' 'meta', 'red', 'yellow', 'blue'
); );
// 'replicates' $WH.g_statToJson
public static $itemMods = array( // zero-indexed; "mastrtng": unused mastery; _[a-z] => taken mods..
'dmg', 'mana', 'health', 'agi', 'str', 'int', 'spi',
'sta', 'energy', 'rage', 'focus', 'runicpwr', 'defrtng', 'dodgertng',
'parryrtng', 'blockrtng', 'mlehitrtng', 'rgdhitrtng', 'splhitrtng', 'mlecritstrkrtng', 'rgdcritstrkrtng',
'splcritstrkrtng', '_mlehitrtng', '_rgdhitrtng', '_splhitrtng', '_mlecritstrkrtng', '_rgdcritstrkrtng', '_splcritstrkrtng',
'mlehastertng', 'rgdhastertng', 'splhastertng', 'hitrtng', 'critstrkrtng', '_hitrtng', '_critstrkrtng',
'resirtng', 'hastertng', 'exprtng', 'atkpwr', 'rgdatkpwr', 'feratkpwr', 'splheal',
'spldmg', 'manargn', 'armorpenrtng', 'splpwr', 'healthrgn', 'splpen', 'block', // ITEM_MOD_BLOCK_VALUE
'mastrtng', 'armor', 'firres', 'frores', 'holres', 'shares', 'natres',
'arcres', 'firsplpwr', 'frosplpwr', 'holsplpwr', 'shasplpwr', 'natsplpwr', 'arcsplpwr'
);
public static $class2SpellFamily = array( public static $class2SpellFamily = array(
// null Warrior Paladin Hunter Rogue Priest DK Shaman Mage Warlock null Druid // null Warrior Paladin Hunter Rogue Priest DK Shaman Mage Warlock null Druid
null, 4, 10, 9, 8, 6, 15, 11, 3, 5, null, 7 null, 4, 10, 9, 8, 6, 15, 11, 3, 5, null, 7
@@ -171,31 +147,6 @@ class Game
4273 => 6, 4277 => 3, 4395 => 2, 4494 => 2, 4722 => 2, 4812 => 8 4273 => 6, 4277 => 3, 4395 => 2, 4494 => 2, 4722 => 2, 4812 => 8
); );
public static function itemModByRatingMask($mask)
{
if (($mask & 0x1C000) == 0x1C000) // special case resilience
return ITEM_MOD_RESILIENCE_RATING;
if (($mask & 0x00E0) == 0x00E0) // hit rating - all subcats (mle, rgd, spl)
return ITEM_MOD_HIT_RATING;
if (($mask & 0x0700) == 0x0700) // crit rating - all subcats (mle, rgd, spl)
return ITEM_MOD_CRIT_RATING;
for ($j = 0; $j < count(self::$combatRatingToItemMod); $j++)
{
if (!self::$combatRatingToItemMod[$j])
continue;
if (!($mask & (1 << $j)))
continue;
return self::$combatRatingToItemMod[$j];
}
return 0;
}
public static function sideByRaceMask($race) public static function sideByRaceMask($race)
{ {
// Any // Any

View File

@@ -23,6 +23,7 @@ if ($error)
require_once 'includes/defines.php'; require_once 'includes/defines.php';
require_once 'includes/stats.class.php'; // Game entity statistics conversion
require_once 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master) require_once 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master)
require_once 'includes/utilities.php'; // helper functions require_once 'includes/utilities.php'; // helper functions
require_once 'includes/config.class.php'; // Config holder require_once 'includes/config.class.php'; // Config holder

668
includes/stats.class.php Normal file
View File

@@ -0,0 +1,668 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
abstract class Stat // based on g_statToJson
{
public const HEALTH = 1;
public const MANA = 2; // note: mana is on idx 0 from 5.0 onwards; idx 2 is empty
public const AGILITY = 3;
public const STRENGTH = 4;
public const INTELLECT = 5;
public const SPIRIT = 6;
public const STAMINA = 7;
public const ENERGY = 8;
public const RAGE = 9;
public const FOCUS = 10;
public const RUNIC_POWER = 11;
public const DEFENSE_RTG = 12;
public const DODGE_RTG = 13;
public const PARRY_RTG = 14;
public const BLOCK_RTG = 15;
public const MELEE_HIT_RTG = 16;
public const RANGED_HIT_RTG = 17;
public const SPELL_HIT_RTG = 18;
public const MELEE_CRIT_RTG = 19;
public const RANGED_CRIT_RTG = 20;
public const SPELL_CRIT_RTG = 21;
public const MELEE_HIT_TAKEN_RTG = 22;
public const RANGED_HIT_TAKEN_RTG = 23;
public const SPELL_HIT_TAKEN_RTG = 24;
public const MELEE_CRIT_TAKEN_RTG = 25;
public const RANGED_CRIT_TAKEN_RTG = 26;
public const SPELL_CRIT_TAKEN_RTG = 27;
public const MELEE_HASTE_RTG = 28;
public const RANGED_HASTE_RTG = 29;
public const SPELL_HASTE_RTG = 30;
public const HIT_RTG = 31;
public const CRIT_RTG = 32;
public const HIT_TAKEN_RTG = 33;
public const CRIT_TAKEN_RTG = 34;
public const RESILIENCE_RTG = 35;
public const HASTE_RTG = 36;
public const EXPERTISE_RTG = 37;
public const ATTACK_POWER = 38;
public const RANGED_ATTACK_POWER = 39;
public const FERAL_ATTACK_POWER = 40; // unused in wow-3.3.5
public const HEALING_SPELL_POWER = 41; // deprecated
public const DAMAGE_SPELL_POWER = 42; // deprecated
public const MANA_REGENERATION = 43;
public const ARMOR_PENETRATION_RTG = 44;
public const SPELL_POWER = 45;
public const HEALTH_REGENERATION = 46; // no differentiation between IC (item mods / spells) and OOC (from spirit) for Profiler
public const SPELL_PENETRATION = 47;
public const BLOCK = 48;
// public const MASTERY_RTG = 49; // not in wow-3.3.5
public const ARMOR = 50;
public const FIRE_RESISTANCE = 51;
public const FROST_RESISTANCE = 52;
public const HOLY_RESISTANCE = 53;
public const SHADOW_RESISTANCE = 54;
public const NATURE_RESISTANCE = 55;
public const ARCANE_RESISTANCE = 56;
public const FIRE_SPELL_POWER = 57;
public const FROST_SPELL_POWER = 58;
public const HOLY_SPELL_POWER = 59;
public const SHADOW_SPELL_POWER = 60;
public const NATURE_SPELL_POWER = 61;
public const ARCANE_SPELL_POWER = 62;
// v for stats lookups v
public const WEAPON_DAMAGE = 200; // +weapon dmg from enchantments
public const WEAPON_DAMAGE_TYPE = 201;
public const WEAPON_DAMAGE_MIN = 202;
public const WEAPON_DAMAGE_MAX = 203;
public const WEAPON_SPEED = 204;
public const WEAPON_DPS = 205; // also +weapon dps from enchantments (rockbiter)
public const MELEE_DAMAGE_MIN = 206;
public const MELEE_DAMAGE_MAX = 207;
public const MELEE_SPEED = 208;
public const MELEE_DPS = 209;
public const RANGED_DAMAGE_MIN = 210;
public const RANGED_DAMAGE_MAX = 211;
public const RANGED_SPEED = 212;
public const RANGED_DPS = 213;
public const EXTRA_SOCKETS = 214;
public const ARMOR_BONUS = 215;
public const MELEE_ATTACK_POWER = 216;
// v only seen in profiler v
public const EXPERTISE = 500;
public const ARMOR_PENETRATION_PCT = 501;
public const MELEE_HIT_PCT = 502;
public const MELEE_CRIT_PCT = 503;
public const MELEE_HASTE_PCT = 504;
public const RANGED_HIT_PCT = 505;
public const RANGED_CRIT_PCT = 506;
public const RANGED_HASTE_PCT = 507;
public const SPELL_HIT_PCT = 508;
public const SPELL_CRIT_PCT = 509;
public const SPELL_HASTE_PCT = 510;
public const MANA_REGENERATION_SPI = 511; // mp5 from spirit, excluding other sources
public const MANA_REGENERATION_OC = 512; // mp5 out of combat, excluding other sources
public const MANA_REGENERATION_IC = 513; // mp5 in combat, excluding other sources
public const ARMOR_TOTAL = 514; // ARMOR + ARMOR_BONUS meta category .. can be skipped here like pet* stats?
public const DEFENSE = 515;
public const DODGE_PCT = 516;
public const PARRY_PCT = 517;
public const BLOCK_PCT = 518;
public const RESILIENCE_PCT = 519;
public const FLAG_NONE = 0x00;
public const FLAG_ITEM = 0x01; // found on items
public const FLAG_SERVERSIDE = 0x02; // not included in g_statToJson
public const FLAG_PROFILER = 0x04; // stat used in profiler only
public const FLAG_LVL_SCALING = 0x08; // rating effectivenes scales with level
public const FLAG_FLOAT_VALUE = 0x10; // not an int
public const IDX_JSON_STR = 0;
public const IDX_ITEM_MOD = 1; // granted by items
public const IDX_COMBAT_RATING = 2; // granted by spells + enchantments
public const IDX_FILTER_CR_ID = 3; // also references listview cols
public const IDX_FLAGS = 4;
private static /* array */ $data = array(
self::HEALTH => ['health', ITEM_MOD_HEALTH, null, 115, self::FLAG_ITEM],
self::MANA => ['mana', ITEM_MOD_MANA, null, 116, self::FLAG_ITEM],
self::AGILITY => ['agi', ITEM_MOD_AGILITY, null, 21, self::FLAG_ITEM],
self::STRENGTH => ['str', ITEM_MOD_STRENGTH, null, 20, self::FLAG_ITEM],
self::INTELLECT => ['int', ITEM_MOD_INTELLECT, null, 23, self::FLAG_ITEM],
self::SPIRIT => ['spi', ITEM_MOD_SPIRIT, null, 24, self::FLAG_ITEM],
self::STAMINA => ['sta', ITEM_MOD_STAMINA, null, 22, self::FLAG_ITEM],
self::ENERGY => ['energy', null, null, null, self::FLAG_ITEM],
self::RAGE => ['rage', null, null, null, self::FLAG_ITEM],
self::FOCUS => ['focus', null, null, null, self::FLAG_ITEM],
self::RUNIC_POWER => ['runic', null, null, null, self::FLAG_ITEM | self::FLAG_SERVERSIDE],
self::DEFENSE_RTG => ['defrtng', ITEM_MOD_DEFENSE_SKILL_RATING, 1, 42, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::DODGE_RTG => ['dodgertng', ITEM_MOD_DODGE_RATING, 2, 45, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::PARRY_RTG => ['parryrtng', ITEM_MOD_PARRY_RATING, 3, 46, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::BLOCK_RTG => ['blockrtng', ITEM_MOD_BLOCK_RATING, 4, 44, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_HIT_RTG => ['mlehitrtng', ITEM_MOD_HIT_MELEE_RATING, 5, 95, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_HIT_RTG => ['rgdhitrtng', ITEM_MOD_HIT_RANGED_RATING, 6, 39, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_HIT_RTG => ['splhitrtng', ITEM_MOD_HIT_SPELL_RATING, 7, 48, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_CRIT_RTG => ['mlecritstrkrtng', ITEM_MOD_CRIT_MELEE_RATING, 8, 84, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_CRIT_RTG => ['rgdcritstrkrtng', ITEM_MOD_CRIT_RANGED_RATING, 9, 40, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_CRIT_RTG => ['splcritstrkrtng', ITEM_MOD_CRIT_SPELL_RATING, 10, 49, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_HIT_TAKEN_RTG => ['_mlehitrtng', ITEM_MOD_HIT_TAKEN_MELEE_RATING, 11, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_HIT_TAKEN_RTG => ['_rgdhitrtng', ITEM_MOD_HIT_TAKEN_RANGED_RATING, 12, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_HIT_TAKEN_RTG => ['_splhitrtng', ITEM_MOD_HIT_TAKEN_SPELL_RATING, 13, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_CRIT_TAKEN_RTG => ['_mlecritstrkrtng', ITEM_MOD_CRIT_TAKEN_MELEE_RATING, 14, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_CRIT_TAKEN_RTG => ['_rgdcritstrkrtng', ITEM_MOD_CRIT_TAKEN_RANGED_RATING, 15, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_CRIT_TAKEN_RTG => ['_splcritstrkrtng', ITEM_MOD_CRIT_TAKEN_SPELL_RATING, 16, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_HASTE_RTG => ['mlehastertng', ITEM_MOD_HASTE_MELEE_RATING, 17, 78, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_HASTE_RTG => ['rgdhastertng', ITEM_MOD_HASTE_RANGED_RATING, 18, 101, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_HASTE_RTG => ['splhastertng', ITEM_MOD_HASTE_SPELL_RATING, 19, 102, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::HIT_RTG => ['hitrtng', ITEM_MOD_HIT_RATING, null, 119, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::CRIT_RTG => ['critstrkrtng', ITEM_MOD_CRIT_RATING, null, 96, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::HIT_TAKEN_RTG => ['_hitrtng', ITEM_MOD_HIT_TAKEN_RATING, null, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::CRIT_TAKEN_RTG => ['_critstrkrtng', ITEM_MOD_CRIT_TAKEN_RATING, null, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RESILIENCE_RTG => ['resirtng', ITEM_MOD_RESILIENCE_RATING, null, 79, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::HASTE_RTG => ['hastertng', ITEM_MOD_HASTE_RATING, null, 103, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::EXPERTISE_RTG => ['exprtng', ITEM_MOD_EXPERTISE_RATING, 23, 117, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::ATTACK_POWER => ['atkpwr', ITEM_MOD_ATTACK_POWER, null, 77, self::FLAG_ITEM],
self::RANGED_ATTACK_POWER => ['rgdatkpwr', ITEM_MOD_RANGED_ATTACK_POWER, null, 38, self::FLAG_ITEM],
self::FERAL_ATTACK_POWER => ['feratkpwr', ITEM_MOD_FERAL_ATTACK_POWER, null, 97, self::FLAG_ITEM],
self::HEALING_SPELL_POWER => ['splheal', ITEM_MOD_SPELL_HEALING_DONE, null, 50, self::FLAG_ITEM],
self::DAMAGE_SPELL_POWER => ['spldmg', ITEM_MOD_SPELL_DAMAGE_DONE, null, 51, self::FLAG_ITEM],
self::MANA_REGENERATION => ['manargn', ITEM_MOD_MANA_REGENERATION, null, 61, self::FLAG_ITEM],
self::ARMOR_PENETRATION_RTG => ['armorpenrtng', ITEM_MOD_ARMOR_PENETRATION_RATING, 24, 114, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_POWER => ['splpwr', ITEM_MOD_SPELL_POWER, null, 123, self::FLAG_ITEM],
self::HEALTH_REGENERATION => ['healthrgn', ITEM_MOD_HEALTH_REGEN, null, 60, self::FLAG_ITEM],
self::SPELL_PENETRATION => ['splpen', ITEM_MOD_SPELL_PENETRATION, null, 94, self::FLAG_ITEM],
self::BLOCK => ['block', ITEM_MOD_BLOCK_VALUE, null, 43, self::FLAG_ITEM],
// self::MASTERY_RTG => ['mastrtng', null, null, null, self::FLAG_NONE],
self::ARMOR => ['armor', null, null, 41, self::FLAG_ITEM],
self::FIRE_RESISTANCE => ['firres', null, null, 26, self::FLAG_ITEM],
self::FROST_RESISTANCE => ['frores', null, null, 28, self::FLAG_ITEM],
self::HOLY_RESISTANCE => ['holres', null, null, 30, self::FLAG_ITEM],
self::SHADOW_RESISTANCE => ['shares', null, null, 29, self::FLAG_ITEM],
self::NATURE_RESISTANCE => ['natres', null, null, 27, self::FLAG_ITEM],
self::ARCANE_RESISTANCE => ['arcres', null, null, 25, self::FLAG_ITEM],
self::FIRE_SPELL_POWER => ['firsplpwr', null, null, 53, self::FLAG_ITEM],
self::FROST_SPELL_POWER => ['frosplpwr', null, null, 54, self::FLAG_ITEM],
self::HOLY_SPELL_POWER => ['holsplpwr', null, null, 55, self::FLAG_ITEM],
self::SHADOW_SPELL_POWER => ['shasplpwr', null, null, 57, self::FLAG_ITEM],
self::NATURE_SPELL_POWER => ['natsplpwr', null, null, 56, self::FLAG_ITEM],
self::ARCANE_SPELL_POWER => ['arcsplpwr', null, null, 52, self::FLAG_ITEM],
// v not part of g_statToJson v
self::WEAPON_DAMAGE => ['dmg', null, null, null, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
self::WEAPON_DAMAGE_TYPE => ['damagetype', null, null, 35, self::FLAG_SERVERSIDE],
self::WEAPON_DAMAGE_MIN => ['dmgmin1', null, null, 33, self::FLAG_SERVERSIDE],
self::WEAPON_DAMAGE_MAX => ['dmgmax1', null, null, 34, self::FLAG_SERVERSIDE],
self::WEAPON_SPEED => ['speed', null, null, 36, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
self::WEAPON_DPS => ['dps', null, null, 32, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
self::MELEE_DAMAGE_MIN => ['mledmgmin', null, null, 135, self::FLAG_SERVERSIDE],
self::MELEE_DAMAGE_MAX => ['mledmgmax', null, null, 136, self::FLAG_SERVERSIDE],
self::MELEE_SPEED => ['mlespeed', null, null, 137, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
self::MELEE_DPS => ['mledps', null, null, 134, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE | self::FLAG_PROFILER],
self::RANGED_DAMAGE_MIN => ['rgddmgmin', null, null, 139, self::FLAG_SERVERSIDE],
self::RANGED_DAMAGE_MAX => ['rgddmgmax', null, null, 140, self::FLAG_SERVERSIDE],
self::RANGED_SPEED => ['rgdspeed', null, null, 141, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
self::RANGED_DPS => ['rgddps', null, null, 138, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE | self::FLAG_PROFILER],
self::EXTRA_SOCKETS => ['nsockets', null, null, 100, self::FLAG_SERVERSIDE],
self::ARMOR_BONUS => ['armorbonus', null, null, 109, self::FLAG_SERVERSIDE],
self::MELEE_ATTACK_POWER => ['mleatkpwr', null, null, 37, self::FLAG_SERVERSIDE | self::FLAG_PROFILER],
// v Profiler only v
self::EXPERTISE => ['exp', null, null, null, self::FLAG_PROFILER],
self::ARMOR_PENETRATION_PCT => ['armorpenpct', null, null, null, self::FLAG_PROFILER],
self::MELEE_HIT_PCT => ['mlehitpct', null, null, null, self::FLAG_PROFILER],
self::MELEE_CRIT_PCT => ['mlecritstrkpct', null, null, null, self::FLAG_PROFILER],
self::MELEE_HASTE_PCT => ['mlehastepct', null, null, null, self::FLAG_PROFILER],
self::RANGED_HIT_PCT => ['rgdhitpct', null, null, null, self::FLAG_PROFILER],
self::RANGED_CRIT_PCT => ['rgdcritstrkpct', null, null, null, self::FLAG_PROFILER],
self::RANGED_HASTE_PCT => ['rgdhastepct', null, null, null, self::FLAG_PROFILER],
self::SPELL_HIT_PCT => ['splhitpct', null, null, null, self::FLAG_PROFILER],
self::SPELL_CRIT_PCT => ['splcritstrkpct', null, null, null, self::FLAG_PROFILER],
self::SPELL_HASTE_PCT => ['splhastepct', null, null, null, self::FLAG_PROFILER],
self::MANA_REGENERATION_SPI => ['spimanargn', null, null, null, self::FLAG_PROFILER],
self::MANA_REGENERATION_OC => ['oocmanargn', null, null, null, self::FLAG_PROFILER],
self::MANA_REGENERATION_IC => ['icmanargn', null, null, null, self::FLAG_PROFILER],
self::ARMOR_TOTAL => ['fullarmor', null, null, null, self::FLAG_PROFILER],
self::DEFENSE => ['def', null, null, null, self::FLAG_PROFILER],
self::DODGE_PCT => ['dodgepct', null, null, null, self::FLAG_PROFILER],
self::PARRY_PCT => ['parrypct', null, null, null, self::FLAG_PROFILER],
self::BLOCK_PCT => ['blockpct', null, null, null, self::FLAG_PROFILER],
self::RESILIENCE_PCT => ['resipct', null, null, null, self::FLAG_PROFILER]
);
public static function isLevelIndependent(int $stat) : bool
{
if (!isset(self::$data[$stat]))
return false;
return !(self::$data[$stat][self::IDX_FLAGS] & self::FLAG_LVL_SCALING);
}
public static function getJsonString(int $stat) : string
{
if (!isset(self::$data[$stat]))
return '';
return self::$data[$stat][self::IDX_JSON_STR];
}
public static function getFilterCriteriumId(int $stat) : ?int
{
if (!isset(self::$data[$stat]))
return null;
return self::$data[$stat][self::IDX_FILTER_CR_ID];
}
public static function getFlags(int $stat) : int
{
if (!isset(self::$data[$stat]))
return 0;
return self::$data[$stat][self::IDX_FLAGS];
}
public static function getJsonStringsFor(int $flags = Stat::FLAG_NONE) : array
{
$x = [];
foreach (self::$data as $k => [$s, , , , $f])
if ($s && (!$flags || $flags & $f))
$x[$k] = $s;
return $x;
}
public static function getCombatRatingsFor(int $flags = Stat::FLAG_NONE) : array
{
$x = [];
foreach (self::$data as $k => [, , $c, , $f])
if ($c && (!$flags || $flags & $f))
$x[$k] = $c;
return $x;
}
public static function getFilterCriteriumIdFor(int $flags = Stat::FLAG_NONE) : array
{
$x = [];
foreach (self::$data as $k => [, , , $cr, $f])
if ($cr && (!$flags || $flags & $f))
$x[$k] = $cr;
return $x;
}
public static function getIndexFrom(int $idx, string $match) : int
{
$i = array_search($match, array_column(self::$data, $idx));
if ($i === false)
return 0;
return array_keys(self::$data)[$i];
}
}
class StatsContainer
{
private $store = [];
private $relSpells = [];
private $relEnchantments = [];
public function __construct(array $relSpells = [], array $relEnchantments = [])
{
if ($relSpells)
$this->relSpells = $relSpells;
if ($relEnchantments)
$this->relEnchantments = $relEnchantments;
}
/**********/
/* Source */
/**********/
public function fromItem(array $item) : self
{
if (!$item)
return $this;
// convert itemMods to stats
for ($i = 1; $i <= 10; $i++)
{
$mod = $item['statType'.$i];
$val = $item['statValue'.$i];
if (!$mod || !$val)
continue;
if ($idx = Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $mod))
Util::arraySumByKey($this->store, [$idx => $val]);
}
// also occurs as seperate field (gets summed in calculation but not in tooltip)
if ($item['tplBlock'])
Util::arraySumByKey($this->store, [Stat::BLOCK => $item['tplBlock']]);
// convert spells to stats
for ($i = 1; $i <= 5; $i++)
if (in_array($item['spellTrigger'.$i], [SPELL_TRIGGER_EQUIP, SPELL_TRIGGER_USE, SPELL_TRIGGER_USE_NODELAY]))
if ($relS = $this->relS($item['spellId'.$i]))
$this->fromSpell($relS);
// for ITEM_CLASS_GEM get stats from enchantment
if ($relE = $this->relE($item['gemEnchantmentId']))
$this->fromEnchantment($relE);
return $this;
}
public function fromEnchantment(array $enchantment) : self
{
if (!$enchantment)
return $this;
for ($i = 1; $i <= 3; $i++)
{
$type = $enchantment['type'.$i];
$object = $enchantment['object'.$i];
$amount = $enchantment['amount'.$i]; // !CAUTION! scaling enchantments are initialized with "0" as amount. 0 is a valid amount!
if ($type == ENCHANTMENT_TYPE_EQUIP_SPELL && ($relS = $this->relS($object)))
$this->fromSpell($relS);
else
foreach ($this->convertEnchantment($type, $object) as $idx)
Util::arraySumByKey($this->store, [$idx => $amount]);
}
return $this;
}
public function fromSpell(array $spell) : self
{
if (!$spell)
return $this;
for ($i = 1; $i <= 3; $i++)
{
$eff = $spell['effect'.$i.'Id'];
$aura = $spell['effect'.$i.'AuraId'];
$mVal = $spell['effect'.$i.'MiscValue'];
$amt = $spell['effect'.$i.'BasePoints'] + $spell['effect'.$i.'DieSides'];
if (in_array($eff, SpellList::EFFECTS_ENCHANTMENT) && ($relE = $this->relE($mVal)))
$this->fromEnchantment($relE);
else
foreach ($this->convertSpellEffect($aura, $mVal, $amt) as $idx)
Util::arraySumByKey($this->store, [$idx => $amt]);
}
return $this;
}
public function fromJson(array &$json, bool $pruneFromSrc = false) : self
{
if (!$json)
return $this;
foreach (Stat::getJsonStringsFor() as $idx => $key)
{
if (isset($json[$key])) // 0 is a valid amount!
{
$float = Stat::getFlags($idx) & Stat::FLAG_FLOAT_VALUE;
if (Util::checkNumeric($json[$key], $float ? NUM_CAST_FLOAT : NUM_CAST_INT))
Util::arraySumByKey($this->store, [$idx => $json[$key]]);
}
if ($pruneFromSrc)
unset($json[$key]);
}
return $this;
}
public function fromDB(int $type, int $typeId, int $fieldFlags = 0x0) : self
{
foreach (DB::Aowow()->selectRow('SELECT (?#) FROM ?_item_stats WHERE `type` = ?d AND `typeId` = ?d', Stat::getJsonStringsFor($fieldFlags ?: (Stat::FLAG_ITEM | Stat::FLAG_SERVERSIDE)), $type, $typeId) as $key => $amt)
{
if ($amt === null)
continue;
$idx = Stat::getIndexFrom(Stat::IDX_JSON_STR, $key);
$float = Stat::getFlags($idx) & Stat::FLAG_FLOAT_VALUE;
if (Util::checkNumeric($amt, $float ? NUM_CAST_FLOAT : NUM_CAST_INT))
Util::arraySumByKey($this->store, [$idx => $amt]);
}
return $this;
}
public function fromContainer(StatsContainer ...$container) : self
{
foreach ($container as $c)
Util::arraySumByKey($this->store, $c->toRaw());
return $this;
}
/**********/
/* Output */
/**********/
public function toJson(int $outFlags = 0x0) : array
{
$out = [];
foreach ($this->store as $stat => $amt)
if (!$outFlags || (Stat::getFlags($stat) & $outFlags))
$out[Stat::getJsonString($stat)] = $amt;
return $out;
}
public function toRaw() : array
{
return $this->store;
}
/****************/
/* internal use */
/****************/
private function relE(int $enchantmentId) : array
{
if ($enchantmentId <= 0 || !isset($this->relEnchantments[$enchantmentId]))
return [];
return $this->relEnchantments[$enchantmentId];
}
private function relS(int $spellId) : array
{
if ($spellId <= 0 || !isset($this->relSpells[$spellId]))
return [];
return $this->relSpells[$spellId];
}
private static function convertEnchantment(int $type, int $object) : array
{
switch ($type)
{
case ENCHANTMENT_TYPE_PRISMATIC_SOCKET:
return [Stat::EXTRA_SOCKETS];
case ENCHANTMENT_TYPE_DAMAGE:
return [Stat::WEAPON_DAMAGE];
case ENCHANTMENT_TYPE_TOTEM:
return [Stat::WEAPON_DPS];
case ENCHANTMENT_TYPE_STAT: // ITEM_MOD_*
return [Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $object)];
case ENCHANTMENT_TYPE_RESISTANCE:
if ($object == SPELL_SCHOOL_NORMAL)
return [Stat::ARMOR];
if ($object == SPELL_SCHOOL_HOLY)
return [Stat::HOLY_RESISTANCE];
if ($object == SPELL_SCHOOL_FIRE)
return [Stat::FIRE_RESISTANCE];
if ($object == SPELL_SCHOOL_NATURE)
return [Stat::NATURE_RESISTANCE];
if ($object == SPELL_SCHOOL_FROST)
return [Stat::FROST_RESISTANCE];
if ($object == SPELL_SCHOOL_SHADOW)
return [Stat::SHADOW_RESISTANCE];
if ($object == SPELL_SCHOOL_ARCANE)
return [Stat::ARCANE_RESISTANCE];
return [];
case ENCHANTMENT_TYPE_EQUIP_SPELL: // handled one level up
case ENCHANTMENT_TYPE_COMBAT_SPELL: // we do not average effects, so skip
case ENCHANTMENT_TYPE_USE_SPELL:
default:
return [];
}
return [];
}
public static function convertCombatRating(int $mask) : array
{
if (($mask & 0x00E0) == 0x00E0) // hit rating - all subcats (5:mle, 6:rgd, 7:spl)
return [Stat::HIT_RTG]; // generic hit rating
if (($mask & 0x0700) == 0x0700) // crit done rating - all subcats (8:mle, 9:rgd, 10:spl)
return [Stat::CRIT_RTG]; // generic crit rating
if (($mask & 0x1C000) == 0x1C000) // crit taken rating - all subcats (14:mle, 15:rgd, 16:spl)
return [Stat::RESILIENCE_RTG]; // resilience
$result = []; // there really shouldn't be multiple ratings in that mask besides the cases above, but who knows..
foreach (Stat::getCombatRatingsFor() as $stat => $cr)
if ($mask & (1 << $cr))
$result[] = $stat;
return $result;
}
private static function convertSpellEffect(int $auraId, int $miscValue, int &$amount) : array
{
$stats = [];
switch ($auraId)
{
case SPELL_AURA_MOD_STAT:
if ($miscValue < 0) // all stats
return [Stat::AGILITY, Stat::STRENGTH, Stat::INTELLECT, Stat::SPIRIT, Stat::STAMINA];
if ($miscValue == STAT_STRENGTH) // one stat
return [Stat::STRENGTH];
if ($miscValue == STAT_AGILITY)
return [Stat::AGILITY];
if ($miscValue == STAT_STAMINA)
return [Stat::STAMINA];
if ($miscValue == STAT_INTELLECT)
return [Stat::INTELLECT];
if ($miscValue == STAT_SPIRIT)
return [Stat::SPIRIT];
return []; // one bullshit
case SPELL_AURA_MOD_INCREASE_HEALTH:
case SPELL_AURA_MOD_INCREASE_HEALTH_NONSTACK:
case SPELL_AURA_MOD_INCREASE_HEALTH_2:
return [Stat::HEALTH];
case SPELL_AURA_MOD_DAMAGE_DONE:
// + weapon damage
if ($miscValue == (1 << SPELL_SCHOOL_NORMAL))
return [Stat::WEAPON_DAMAGE];
// full magic mask, also counts towards healing
if ($miscValue == SPELL_MAGIC_SCHOOLS)
return [Stat::SPELL_POWER, Stat::DAMAGE_SPELL_POWER, Stat::HEALING_SPELL_POWER];
// HolySpellpower (deprecated; still used in randomproperties)
if ($miscValue & (1 << SPELL_SCHOOL_HOLY))
$stats[] = Stat::HOLY_SPELL_POWER;
// FireSpellpower (deprecated; still used in randomproperties)
if ($miscValue & (1 << SPELL_SCHOOL_FIRE))
$stats[] = Stat::FIRE_SPELL_POWER;
// NatureSpellpower (deprecated; still used in randomproperties)
if ($miscValue & (1 << SPELL_SCHOOL_NATURE))
$stats[] = Stat::NATURE_SPELL_POWER;
// FrostSpellpower (deprecated; still used in randomproperties)
if ($miscValue & (1 << SPELL_SCHOOL_FROST))
$stats[] = Stat::FROST_SPELL_POWER;
// ShadowSpellpower (deprecated; still used in randomproperties)
if ($miscValue & (1 << SPELL_SCHOOL_SHADOW))
$stats[] = Stat::SHADOW_SPELL_POWER;
// ArcaneSpellpower (deprecated; still used in randomproperties)
if ($miscValue & (1 << SPELL_SCHOOL_ARCANE))
$stats[] = Stat::ARCANE_SPELL_POWER;
return $stats;
case SPELL_AURA_MOD_HEALING_DONE: // not as a mask..
return [Stat::HEALING_SPELL_POWER];
case SPELL_AURA_MOD_INCREASE_ENERGY: // MiscVal:type see defined Powers only energy/mana in use
if ($miscValue == POWER_ENERGY)
return [Stat::ENERGY];
if ($miscValue == POWER_RAGE)
return [Stat::RAGE];
if ($miscValue == POWER_MANA)
return [Stat::MANA];
if ($miscValue == POWER_RUNIC_POWER)
return [Stat::RUNIC_POWER];
return [];
case SPELL_AURA_MOD_RATING:
case SPELL_AURA_MOD_RATING_FROM_STAT:
if ($stat = self::convertCombatRating($miscValue))
return $stat;
return [];
case SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE:
case SPELL_AURA_MOD_BASE_RESISTANCE:
case SPELL_AURA_MOD_RESISTANCE:
// Armor only if explicitly specified
if ($miscValue == (1 << SPELL_SCHOOL_NORMAL))
return [Stat::ARMOR];
// Holy resistance only if explicitly specified (should it even exist...?)
if ($miscValue == (1 << SPELL_SCHOOL_HOLY))
return [Stat::HOLY_RESISTANCE];
if ($miscValue & (1 << SPELL_SCHOOL_FIRE))
$stats[] = Stat::FIRE_RESISTANCE;
if ($miscValue & (1 << SPELL_SCHOOL_NATURE))
$stats[] = Stat::NATURE_RESISTANCE;
if ($miscValue & (1 << SPELL_SCHOOL_FROST))
$stats[] = Stat::FROST_RESISTANCE;
if ($miscValue & (1 << SPELL_SCHOOL_SHADOW))
$stats[] = Stat::SHADOW_RESISTANCE;
if ($miscValue & (1 << SPELL_SCHOOL_ARCANE))
$stats[] = Stat::ARCANE_RESISTANCE;
return $stats;
case SPELL_AURA_PERIODIC_HEAL: // hp5
case SPELL_AURA_MOD_REGEN:
case SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT:
return [Stat::HEALTH_REGENERATION];
case SPELL_AURA_MOD_POWER_REGEN: // mp5
return [Stat::MANA_REGENERATION];
case SPELL_AURA_MOD_ATTACK_POWER:
return [Stat::ATTACK_POWER/*, Stat::RANGED_ATTACK_POWER*/];
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
return [Stat::RANGED_ATTACK_POWER];
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
return [Stat::BLOCK];
case SPELL_AURA_MOD_EXPERTISE:
return [Stat::EXPERTISE];
case SPELL_AURA_MOD_TARGET_RESISTANCE:
$amount = abs($amount); // functionally negative, but we work with the absolute amount
if ($miscValue == 0x7C) // SPELL_MAGIC_SCHOOLS & ~SPELL_SCHOOL_HOLY
return [Stat::SPELL_PENETRATION];
}
return [];
}
}
?>

View File

@@ -35,35 +35,25 @@ class EnchantmentList extends BaseType
if ($curTpl['object'.$i] <= 0) if ($curTpl['object'.$i] <= 0)
continue; continue;
switch ($curTpl['type'.$i]) switch ($curTpl['type'.$i]) // SPELL_TRIGGER_* just reused for wording
{ {
case 1: case ENCHANTMENT_TYPE_COMBAT_SPELL:
$proc = -$this->getField('ppmRate') ?: ($this->getField('procChance') ?: $this->getField('amount'.$i)); $proc = -$this->getField('ppmRate') ?: ($this->getField('procChance') ?: $this->getField('amount'.$i));
$curTpl['spells'][$i] = [$curTpl['object'.$i], 2, $curTpl['charges'], $proc]; $curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_HIT, $curTpl['charges'], $proc];
$this->relSpells[] = $curTpl['object'.$i]; $this->relSpells[] = $curTpl['object'.$i];
break; break;
case 3: case ENCHANTMENT_TYPE_EQUIP_SPELL:
$curTpl['spells'][$i] = [$curTpl['object'.$i], 1, $curTpl['charges'], 0]; $curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_EQUIP, $curTpl['charges'], 0];
$this->relSpells[] = $curTpl['object'.$i]; $this->relSpells[] = $curTpl['object'.$i];
break; break;
case 7: case ENCHANTMENT_TYPE_USE_SPELL:
$curTpl['spells'][$i] = [$curTpl['object'.$i], 0, $curTpl['charges'], 0]; $curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_USE, $curTpl['charges'], 0];
$this->relSpells[] = $curTpl['object'.$i]; $this->relSpells[] = $curTpl['object'.$i];
break; break;
} }
} }
// floats are fetched as string from db :< $this->jsonStats[$this->id] = (new StatsContainer)->fromJson($curTpl, true);
$curTpl['dmg'] = floatVal($curTpl['dmg']);
$curTpl['dps'] = floatVal($curTpl['dps']);
// remove zero-stats
foreach (Game::$itemMods as $str)
if ($curTpl[$str] == 0) // empty(0.0f) => true .. yeah, sure
unset($curTpl[$str]);
if ($curTpl['dps'] == 0)
unset($curTpl['dps']);
} }
if ($this->relSpells) if ($this->relSpells)
@@ -99,18 +89,18 @@ class EnchantmentList extends BaseType
if ($this->curTpl['requiredLevel'] > 0) if ($this->curTpl['requiredLevel'] > 0)
$data[$this->id]['reqlevel'] = $this->curTpl['requiredLevel']; $data[$this->id]['reqlevel'] = $this->curTpl['requiredLevel'];
foreach ($this->curTpl['spells'] as $s) foreach ($this->curTpl['spells'] as [$spellId, $trigger, $charges, $procChance])
{ {
// enchant is procing or onUse // enchant is procing or onUse
if ($s[1] == 2 || $s[1] == 0) if ($trigger == SPELL_TRIGGER_HIT || $trigger == SPELL_TRIGGER_USE)
$data[$this->id]['spells'][$s[0]] = $s[2]; $data[$this->id]['spells'][$spellId] = $charges;
// spell is procing // spell is procing
else if ($this->relSpells && $this->relSpells->getEntry($s[0]) && ($_ = $this->relSpells->canTriggerSpell())) else if ($this->relSpells && $this->relSpells->getEntry($spellId) && ($_ = $this->relSpells->canTriggerSpell()))
{ {
foreach ($_ as $idx) foreach ($_ as $idx)
{ {
$this->triggerIds[] = $this->relSpells->getField('effect'.$idx.'TriggerSpell'); $this->triggerIds[] = $this->relSpells->getField('effect'.$idx.'TriggerSpell');
$data[$this->id]['spells'][$this->relSpells->getField('effect'.$idx.'TriggerSpell')] = $s[2]; $data[$this->id]['spells'][$this->relSpells->getField('effect'.$idx.'TriggerSpell')] = $charges;
} }
} }
} }
@@ -118,88 +108,15 @@ class EnchantmentList extends BaseType
if (!$data[$this->id]['spells']) if (!$data[$this->id]['spells'])
unset($data[$this->id]['spells']); unset($data[$this->id]['spells']);
Util::arraySumByKey($data[$this->id], $this->getStatGain()); Util::arraySumByKey($data[$this->id], $this->jsonStats[$this->id]->toJson());
} }
return $data; return $data;
} }
public function getStatGain($addScalingKeys = false) public function getStatGainForCurrent() : array
{ {
$data = []; return $this->jsonStats[$this->id]->toJson();
foreach (Game::$itemMods as $str)
if (isset($this->curTpl[$str]))
$data[$str] = $this->curTpl[$str];
if (isset($this->curTpl['dps']))
$data['dps'] = $this->curTpl['dps'];
// scaling enchantments are saved as 0 to item_stats, thus return empty
if ($addScalingKeys)
{
$spellStats = [];
if ($this->relSpells)
$spellStats = $this->relSpells->getStatGain();
for ($h = 1; $h <= 3; $h++)
{
$obj = (int)$this->curTpl['object'.$h];
switch ($this->curTpl['type'.$h])
{
case 3: // TYPE_EQUIP_SPELL Spells from ObjectX (use of amountX?)
if (!empty($spellStats[$obj]))
foreach ($spellStats[$obj] as $mod => $_)
if ($str = Game::$itemMods[$mod])
Util::arraySumByKey($data, [$str => 0]);
$obj = null;
break;
case 4: // TYPE_RESISTANCE +AmountX resistance for ObjectX School
switch ($obj)
{
case 0: // Physical
$obj = ITEM_MOD_ARMOR;
break;
case 1: // Holy
$obj = ITEM_MOD_HOLY_RESISTANCE;
break;
case 2: // Fire
$obj = ITEM_MOD_FIRE_RESISTANCE;
break;
case 3: // Nature
$obj = ITEM_MOD_NATURE_RESISTANCE;
break;
case 4: // Frost
$obj = ITEM_MOD_FROST_RESISTANCE;
break;
case 5: // Shadow
$obj = ITEM_MOD_SHADOW_RESISTANCE;
break;
case 6: // Arcane
$obj = ITEM_MOD_ARCANE_RESISTANCE;
break;
default:
$obj = null;
}
break;
case 5: // TYPE_STAT +AmountX for Statistic by type of ObjectX
if ($obj < 2) // [mana, health] are on [0, 1] respectively and are expected on [1, 2] ..
$obj++; // 0 is weaponDmg .. ehh .. i messed up somewhere
break; // stats are directly assigned below
default: // TYPE_NONE dnd stuff; skip assignment below
$obj = null;
}
if ($obj !== null)
if ($str = Game::$itemMods[$obj]) // check if we use these mods
Util::arraySumByKey($data, [$str => 0]);
}
}
return $data;
} }
public function getRelSpell($id) public function getRelSpell($id)

View File

@@ -13,7 +13,7 @@ class ItemList extends BaseType
public static $dataTable = '?_items'; public static $dataTable = '?_items';
public $json = []; public $json = [];
public $itemMods = []; public $jsonStats = [];
public $rndEnchIds = []; public $rndEnchIds = [];
public $subItems = []; public $subItems = [];
@@ -48,7 +48,9 @@ class ItemList extends BaseType
// fix missing icons // fix missing icons
$_curTpl['iconString'] = $_curTpl['iconString'] ?: DEFAULT_ICON; $_curTpl['iconString'] = $_curTpl['iconString'] ?: DEFAULT_ICON;
// from json to json .. the gentle fuckups of legacy code integration
$this->initJsonStats(); $this->initJsonStats();
$this->jsonStats[$this->id] = (new StatsContainer())->fromJson($_curTpl, true)->toJson(Stat::FLAG_ITEM /* | Stat::FLAG_SERVERSIDE */);
if ($miscData) if ($miscData)
{ {
@@ -280,7 +282,7 @@ class ItemList extends BaseType
public function getListviewData($addInfoMask = 0x0, $miscData = null) public function getListviewData($addInfoMask = 0x0, $miscData = null)
{ {
/* /*
* ITEMINFO_JSON (0x01): itemMods (including spells) and subitems parsed * ITEMINFO_JSON (0x01): jsonStats (including spells) and subitems parsed
* ITEMINFO_SUBITEMS (0x02): searched by comparison * ITEMINFO_SUBITEMS (0x02): searched by comparison
* ITEMINFO_VENDOR (0x04): costs-obj, when displayed as vendor * ITEMINFO_VENDOR (0x04): costs-obj, when displayed as vendor
* ITEMINFO_GEM (0x10): gem infos and score * ITEMINFO_GEM (0x10): gem infos and score
@@ -294,7 +296,10 @@ class ItemList extends BaseType
$this->initSubItems(); $this->initSubItems();
if ($addInfoMask & ITEMINFO_JSON) if ($addInfoMask & ITEMINFO_JSON)
{
$this->extendJsonStats(); $this->extendJsonStats();
Util::arraySumByKey($data, $this->jsonStats);
}
$extCosts = []; $extCosts = [];
if ($addInfoMask & ITEMINFO_VENDOR) if ($addInfoMask & ITEMINFO_VENDOR)
@@ -321,9 +326,6 @@ class ItemList extends BaseType
if ($addInfoMask & ITEMINFO_JSON) if ($addInfoMask & ITEMINFO_JSON)
{ {
foreach ($this->itemMods[$this->id] as $k => $v)
$data[$this->id][$k] = $v;
if ($_ = intVal(($this->curTpl['minMoneyLoot'] + $this->curTpl['maxMoneyLoot']) / 2)) if ($_ = intVal(($this->curTpl['minMoneyLoot'] + $this->curTpl['maxMoneyLoot']) / 2))
$data[$this->id]['avgmoney'] = $_; $data[$this->id]['avgmoney'] = $_;
@@ -679,7 +681,7 @@ class ItemList extends BaseType
$x .= sprintf(Lang::item('damage', 'single', $sc2 ? 3 : 2), $this->curTpl['dmgMin2'], $sc2 ? Lang::game('sc', $sc2) : null).'<br />'; $x .= sprintf(Lang::item('damage', 'single', $sc2 ? 3 : 2), $this->curTpl['dmgMin2'], $sc2 ? Lang::game('sc', $sc2) : null).'<br />';
if ($_class == ITEM_CLASS_WEAPON) if ($_class == ITEM_CLASS_WEAPON)
$x .= '<!--dps-->'.sprintf(Lang::item('dps'), $dps).'<br />'; // do not use localized format here! $x .= '<!--dps-->'.Lang::item('dps', [$dps]).'<br />';
// display FeralAttackPower if set // display FeralAttackPower if set
if ($fap = $this->getFeralAP()) if ($fap = $this->getFeralAP())
@@ -1126,7 +1128,7 @@ class ItemList extends BaseType
{ {
$xCraft = ''; $xCraft = '';
if ($desc = $this->getField('description', true)) if ($desc = $this->getField('description', true))
$x .= '<span class="q2">'.Lang::item('trigger', 0).' <a href="?spell='.$this->curTpl['spellId2'].'">'.$desc.'</a></span><br />'; $x .= '<span class="q2">'.Lang::item('trigger', SPELL_TRIGGER_USE).' <a href="?spell='.$this->curTpl['spellId2'].'">'.$desc.'</a></span><br />';
// recipe handling (some stray Techniques have subclass == 0), place at bottom of tooltipp // recipe handling (some stray Techniques have subclass == 0), place at bottom of tooltipp
if ($_class == ITEM_CLASS_RECIPE || $this->curTpl['bagFamily'] == 16) if ($_class == ITEM_CLASS_RECIPE || $this->curTpl['bagFamily'] == 16)
@@ -1208,7 +1210,7 @@ class ItemList extends BaseType
$this->curTpl['scalingStatValue'] // scaleFlags $this->curTpl['scalingStatValue'] // scaleFlags
); );
} }
else // may still use level dependant ratings else // may still use level dependent ratings
{ {
array_push($link, array_push($link,
$causesScaling ? MAX_LEVEL : 1, // scaleMaxLevel $causesScaling ? MAX_LEVEL : 1, // scaleMaxLevel
@@ -1311,12 +1313,6 @@ class ItemList extends BaseType
foreach ($this->iterate() as $__) foreach ($this->iterate() as $__)
{ {
$this->itemMods[$this->id] = [];
foreach (Game::$itemMods as $mod)
if ($_ = floatVal($this->curTpl[$mod]))
Util::arraySumByKey($this->itemMods[$this->id], [$mod => $_]);
// fetch and add socketbonusstats // fetch and add socketbonusstats
if (!empty($this->json[$this->id]['socketbonus'])) if (!empty($this->json[$this->id]['socketbonus']))
$enchantments[$this->json[$this->id]['socketbonus']][] = $this->id; $enchantments[$this->json[$this->id]['socketbonus']][] = $this->id;
@@ -1353,28 +1349,28 @@ class ItemList extends BaseType
unset($this->json[$item][$k]); unset($this->json[$item][$k]);
} }
public function getOnUseStats() public function getOnUseStats() : ?StatsContainer
{ {
$onUseStats = []; if ($this->curTpl['class'] != ITEM_CLASS_CONSUMABLE)
return null;
$onUseStats = new StatsContainer();
// convert Spells // convert Spells
$useSpells = [];
for ($h = 1; $h <= 5; $h++) for ($h = 1; $h <= 5; $h++)
{ {
if ($this->curTpl['spellId'.$h] <= 0) if ($this->curTpl['spellId'.$h] <= 0)
continue; continue;
if ($this->curTpl['class'] != ITEM_CLASS_CONSUMABLE || $this->curTpl['spellTrigger'.$h]) if ($this->curTpl['spellTrigger'.$h] != SPELL_TRIGGER_USE)
continue; continue;
$useSpells[] = $this->curTpl['spellId'.$h]; if ($spell = DB::Aowow()->selectRow(
} 'SELECT effect1AuraId, effect1MiscValue, effect1BasePoints, effect1DieSides, effect2AuraId, effect2MiscValue, effect2BasePoints, effect2DieSides, effect3AuraId, effect3MiscValue, effect3BasePoints, effect3DieSides
FROM ?_spell
if ($useSpells) WHERE id = ?d',
{ $this->curTpl['spellId'.$h]))
$eqpSplList = new SpellList(array(['s.id', $useSpells])); $onUseStats->fromSpell($spell);
foreach ($eqpSplList->getStatGain() as $stat)
Util::arraySumByKey($onUseStats, $stat);
} }
return $onUseStats; return $onUseStats;
@@ -1414,32 +1410,33 @@ class ItemList extends BaseType
return true; return true;
} }
private function getFeralAP() private function getFeralAP() : float
{ {
// must be weapon // must be weapon
if ($this->curTpl['class'] != ITEM_CLASS_WEAPON) if ($this->curTpl['class'] != ITEM_CLASS_WEAPON)
return 0; return 0.0;
$subClasses = [14]; // Misc Weapons
$druid = new CharClassList(array(['id', log(CLASS_DRUID, 2) + 1]));
if (!$druid->error)
for ($i = 0; $i < 21; $i++)
if ($druid->getField('weaponTypeMask') & (1 << $i))
$subClasses[] = $i;
if (!in_array($this->curTpl['subClass'], $subClasses))
return 0;
// thats fucked up.. // thats fucked up..
if (!$this->curTpl['delay']) if (!$this->curTpl['delay'])
return 0; return 0.0;
// must have enough damage // must have enough damage
$dps = ($this->curTpl['tplDmgMin1'] + $this->curTpl['dmgMin2'] + $this->curTpl['tplDmgMax1'] + $this->curTpl['dmgMax2']) / (2 * $this->curTpl['delay'] / 1000); $dps = ($this->curTpl['tplDmgMin1'] + $this->curTpl['dmgMin2'] + $this->curTpl['tplDmgMax1'] + $this->curTpl['dmgMax2']) / (2 * $this->curTpl['delay'] / 1000);
if ($dps < 54.8) if ($dps <= 54.8)
return 0; return 0.0;
return round(($dps - 54.8) * 14, 0); $subClasses = [14]; // Misc Weapons
$weaponTypeMask = DB::Aowow()->selectCell('SELECT `weaponTypeMask` FROM ?_classes WHERE `id` = ?d', log(CLASS_DRUID, 2) + 1);
if ($weaponTypeMask)
for ($i = 0; $i < 21; $i++)
if ($weaponTypeMask & (1 << $i))
$subClasses[] = $i;
// cannot be used by druids
if (!in_array($this->curTpl['subClass'], $subClasses))
return 0.0;
return round(($dps - 54.8) * 14);
} }
private function parseRating($type, $value, $interactive = false, &$scaling = false) private function parseRating($type, $value, $interactive = false, &$scaling = false)
@@ -1449,29 +1446,28 @@ class ItemList extends BaseType
$reqLvl = $this->curTpl['requiredLevel'] > 1 ? $this->curTpl['requiredLevel'] : MAX_LEVEL; $reqLvl = $this->curTpl['requiredLevel'] > 1 ? $this->curTpl['requiredLevel'] : MAX_LEVEL;
$level = min(max($reqLvl, $ssdLvl), MAX_LEVEL); $level = min(max($reqLvl, $ssdLvl), MAX_LEVEL);
// unknown rating // unknown rating
if (in_array($type, [2, 8, 9, 10, 11]) || $type > ITEM_MOD_BLOCK_VALUE || $type < 0) if (!Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $type))
{ {
if (User::isInGroup(U_GROUP_EMPLOYEE)) if (User::isInGroup(U_GROUP_EMPLOYEE))
return sprintf(Lang::item('statType', count(Lang::item('statType')) - 1), $type, $value); return sprintf(Lang::item('statType', count(Lang::item('statType')) - 1), $type, $value);
else else
return null; return null;
} }
// level independant Bonus
else if (in_array($type, Game::$lvlIndepRating)) // level independent Bonus
return Lang::item('trigger', 1).str_replace('%d', '<!--rtg'.$type.'-->'.$value, Lang::item('statType', $type)); if (Stat::isLevelIndependent($type))
return Lang::item('trigger', SPELL_TRIGGER_EQUIP).str_replace('%d', '<!--rtg'.$type.'-->'.$value, Lang::item('statType', $type));
// rating-Bonuses // rating-Bonuses
$scaling = true;
if ($interactive)
$js = '&nbsp;<small>('.sprintf(Util::$changeLevelString, Util::setRatingLevel($level, $type, $value)).')</small>';
else else
{ $js = '&nbsp;<small>('.Util::setRatingLevel($level, $type, $value).')</small>';
$scaling = true;
if ($interactive) return Lang::item('trigger', SPELL_TRIGGER_EQUIP).str_replace('%d', '<!--rtg'.$type.'-->'.$value.$js, Lang::item('statType', $type));
$js = '&nbsp;<small>('.sprintf(Util::$changeLevelString, Util::setRatingLevel($level, $type, $value)).')</small>';
else
$js = '&nbsp;<small>('.Util::setRatingLevel($level, $type, $value).')</small>';
return Lang::item('trigger', 1).str_replace('%d', '<!--rtg'.$type.'-->'.$value.$js, Lang::item('statType', $type));
}
} }
private function getSSDMod($type) private function getSSDMod($type)
@@ -1583,7 +1579,7 @@ class ItemList extends BaseType
{ {
$this->rndEnchIds[$eId] = array( $this->rndEnchIds[$eId] = array(
'text' => $enchants->getField('name', true), 'text' => $enchants->getField('name', true),
'stats' => $enchants->getStatGain(true) 'stats' => $enchants->getStatGainForCurrent()
); );
} }
@@ -1677,7 +1673,7 @@ class ItemList extends BaseType
'subclass' => $this->curTpl['subClass'], 'subclass' => $this->curTpl['subClass'],
'subsubclass' => $this->curTpl['subSubClass'], 'subsubclass' => $this->curTpl['subSubClass'],
'heroic' => ($this->curTpl['flags'] & 0x8) >> 3, 'heroic' => ($this->curTpl['flags'] & 0x8) >> 3,
'side' => $this->curTpl['flagsExtra'] & 0x3 ? 3 - ($this->curTpl['flagsExtra'] & 0x3) : Game::sideByRaceMask($this->curTpl['requiredRace']), 'side' => $this->curTpl['flagsExtra'] & 0x3 ? SIDE_BOTH - ($this->curTpl['flagsExtra'] & 0x3) : Game::sideByRaceMask($this->curTpl['requiredRace']),
'slot' => $this->curTpl['slot'], 'slot' => $this->curTpl['slot'],
'slotbak' => $this->curTpl['slotBak'], 'slotbak' => $this->curTpl['slotBak'],
'level' => $this->curTpl['itemLevel'], 'level' => $this->curTpl['itemLevel'],
@@ -1690,7 +1686,7 @@ class ItemList extends BaseType
'frores' => $this->curTpl['resFrost'], 'frores' => $this->curTpl['resFrost'],
'shares' => $this->curTpl['resShadow'], 'shares' => $this->curTpl['resShadow'],
'arcres' => $this->curTpl['resArcane'], 'arcres' => $this->curTpl['resArcane'],
'armorbonus' => max(0, intVal($this->curTpl['armorDamageModifier'])), 'armorbonus' => $this->curTpl['class'] != ITEM_CLASS_ARMOR || $this->curTpl['armorDamageModifier'] <= 0 ? 0 : intVal($this->curTpl['armorDamageModifier']),
'armor' => $this->curTpl['tplArmor'], 'armor' => $this->curTpl['tplArmor'],
'dura' => $this->curTpl['durability'], 'dura' => $this->curTpl['durability'],
'itemset' => $this->curTpl['itemset'], 'itemset' => $this->curTpl['itemset'],
@@ -1709,8 +1705,8 @@ class ItemList extends BaseType
$json['dmgtype1'] = $this->curTpl['dmgType1']; $json['dmgtype1'] = $this->curTpl['dmgType1'];
$json['dmgmin1'] = $this->curTpl['tplDmgMin1'] + $this->curTpl['dmgMin2']; $json['dmgmin1'] = $this->curTpl['tplDmgMin1'] + $this->curTpl['dmgMin2'];
$json['dmgmax1'] = $this->curTpl['tplDmgMax1'] + $this->curTpl['dmgMax2']; $json['dmgmax1'] = $this->curTpl['tplDmgMax1'] + $this->curTpl['dmgMax2'];
$json['speed'] = number_format($this->curTpl['delay'] / 1000, 2); $json['speed'] = round($this->curTpl['delay'] / 1000, 2);
$json['dps'] = !floatVal($json['speed']) ? 0 : number_format(($json['dmgmin1'] + $json['dmgmax1']) / (2 * $json['speed']), 1); $json['dps'] = $json['speed'] ? round(($json['dmgmin1'] + $json['dmgmax1']) / (2 * $json['speed']), 1) : 0;
if (in_array($json['subclass'], [2, 3, 18, 19])) if (in_array($json['subclass'], [2, 3, 18, 19]))
{ {
@@ -1734,7 +1730,7 @@ class ItemList extends BaseType
if ($this->curTpl['class'] == ITEM_CLASS_ARMOR || $this->curTpl['class'] == ITEM_CLASS_WEAPON) if ($this->curTpl['class'] == ITEM_CLASS_ARMOR || $this->curTpl['class'] == ITEM_CLASS_WEAPON)
$json['gearscore'] = Util::getEquipmentScore($json['level'], $this->getField('quality'), $json['slot'], $json['nsockets']); $json['gearscore'] = Util::getEquipmentScore($json['level'], $this->getField('quality'), $json['slot'], $json['nsockets']);
else if ($this->curTpl['class'] == ITEM_CLASS_GEM) else if ($this->curTpl['class'] == ITEM_CLASS_GEM)
$json['gearscore'] = Util::getGemScore($json['level'], $this->getField('quality'), $this->getField('requiredSkill') == 755, $this->id); $json['gearscore'] = Util::getGemScore($json['level'], $this->getField('quality'), $this->getField('requiredSkill') == SKILL_JEWELCRAFTING, $this->id);
// clear zero-values afterwards // clear zero-values afterwards
foreach ($json as $k => $v) foreach ($json as $k => $v)
@@ -1809,7 +1805,7 @@ class ItemListFilter extends Filter
14 => -1, 14 => -1,
15 => -1 15 => -1
), ),
128 => array( // source 128 => array( // source
1 => true, // Any 1 => true, // Any
2 => false, // None 2 => false, // None
3 => 1, // Crafted 3 => 1, // Crafted
@@ -2036,18 +2032,15 @@ class ItemListFilter extends Filter
foreach ($this->fiData['v']['wt'] as $k => $v) foreach ($this->fiData['v']['wt'] as $k => $v)
{ {
$str = Util::$itemFilter[$v]; if ($idx = Stat::getIndexFrom(Stat::IDX_FILTER_CR_ID, $v))
$qty = intVal($this->fiData['v']['wtv'][$k]); {
$str = Stat::getJsonString($idx);
$qty = intVal($this->fiData['v']['wtv'][$k]);
if ($str == 'rgdspeed') // dont need no duplicate column $select[] = '(IFNULL(`is`.`'.$str.'`, 0) * '.$qty.')';
$str = 'speed'; $this->wtCnd[] = ['is.'.$str, 0, '>'];
$wtSum += $qty;
if ($str == 'mledps') // todo (med): unify rngdps and mledps to dps }
$str = 'dps';
$select[] = '(`is`.`'.$str.'` * '.$qty.')';
$this->wtCnd[] = ['is.'.$str, 0, '>'];
$wtSum += $qty;
} }
if (count($this->wtCnd) > 1) if (count($this->wtCnd) > 1)
@@ -2681,7 +2674,7 @@ class ItemListFilter extends Filter
if (preg_match('/\W/i', $v)) if (preg_match('/\W/i', $v))
return false; return false;
return isset(Util::$itemFilter[$v]); return Stat::getIndexFrom(Stat::IDX_FILTER_CR_ID, $v) > 0;
} }
} }

View File

@@ -58,6 +58,9 @@ class SpellList extends BaseType
SPELL_EFFECT_SCHOOL_DAMAGE, SPELL_EFFECT_ENVIRONMENTAL_DAMAGE, SPELL_EFFECT_POWER_DRAIN, SPELL_EFFECT_HEALTH_LEECH, SPELL_EFFECT_POWER_BURN, SPELL_EFFECT_SCHOOL_DAMAGE, SPELL_EFFECT_ENVIRONMENTAL_DAMAGE, SPELL_EFFECT_POWER_DRAIN, SPELL_EFFECT_HEALTH_LEECH, SPELL_EFFECT_POWER_BURN,
SPELL_EFFECT_HEAL_MAX_HEALTH SPELL_EFFECT_HEAL_MAX_HEALTH
); );
public const EFFECTS_ENCHANTMENT = array(
SPELL_EFFECT_ENCHANT_ITEM, SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY, SPELL_EFFECT_ENCHANT_HELD_ITEM, SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC
);
public const AURAS_HEAL = array( public const AURAS_HEAL = array(
SPELL_AURA_DUMMY, SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_HEALTH_FUNNEL, SPELL_AURA_SCHOOL_ABSORB, SPELL_AURA_MANA_SHIELD, SPELL_AURA_DUMMY, SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_HEALTH_FUNNEL, SPELL_AURA_SCHOOL_ABSORB, SPELL_AURA_MANA_SHIELD,
@@ -190,196 +193,19 @@ class SpellList extends BaseType
// end static use // end static use
// required for item-comparison // required for item-comparison
public function getStatGain() public function getStatGain() : array
{ {
$data = []; $data = [];
foreach ($this->iterate() as $__) foreach ($this->iterate() as $__)
{ {
$stats = []; $data[$this->id] = new StatsContainer();
for ($i = 1; $i <= 3; $i++) foreach ($this->canEnchantmentItem() as $i)
{ $data[$this->id]->fromDB(Type::ENCHANTMENT, $this->curTpl['effect'.$i.'MiscValue']);
$pts = $this->calculateAmountForCurrent($i)[1];
$mv = $this->curTpl['effect'.$i.'MiscValue'];
$au = $this->curTpl['effect'.$i.'AuraId'];
if (in_array($this->curTpl['effect'.$i.'Id'], [SPELL_EFFECT_ENCHANT_ITEM, SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY])) // todo: should enchantments be included here...?
{ $data[$this->id]->fromSpell($this->curTpl);
if ($mv && ($json = DB::Aowow()->selectRow('SELECT * FROM ?_item_stats WHERE `type` = ?d AND `typeId` = ?d', Type::ENCHANTMENT, $mv)))
{
$mods = [];
foreach ($json as $str => $val)
if ($val && ($idx = array_search($str, Game::$itemMods)))
$mods[$idx] = $val;
if ($mods)
Util::arraySumByKey($stats, $mods);
}
continue;
}
switch ($au)
{
case SPELL_AURA_MOD_STAT:
if ($mv < 0) // all stats
{
for ($iMod = ITEM_MOD_AGILITY; $iMod <= ITEM_MOD_STAMINA; $iMod++)
Util::arraySumByKey($stats, [$iMod => $pts]);
}
else if ($mv == STAT_STRENGTH) // one stat
Util::arraySumByKey($stats, [ITEM_MOD_STRENGTH => $pts]);
else if ($mv == STAT_AGILITY)
Util::arraySumByKey($stats, [ITEM_MOD_AGILITY => $pts]);
else if ($mv == STAT_STAMINA)
Util::arraySumByKey($stats, [ITEM_MOD_STAMINA => $pts]);
else if ($mv == STAT_INTELLECT)
Util::arraySumByKey($stats, [ITEM_MOD_INTELLECT => $pts]);
else if ($mv == STAT_SPIRIT)
Util::arraySumByKey($stats, [ITEM_MOD_SPIRIT => $pts]);
else // one bullshit
trigger_error('AuraId 29 of spell #'.$this->id.' has wrong statId #'.$mv, E_USER_WARNING);
break;
case SPELL_AURA_MOD_INCREASE_HEALTH:
case SPELL_AURA_MOD_INCREASE_HEALTH_NONSTACK:
case SPELL_AURA_MOD_INCREASE_HEALTH_2:
Util::arraySumByKey($stats, [ITEM_MOD_HEALTH => $pts]);
break;
case SPELL_AURA_MOD_DAMAGE_DONE:
// + weapon damage
if ($mv == (1 << SPELL_SCHOOL_NORMAL))
{
Util::arraySumByKey($stats, [ITEM_MOD_WEAPON_DMG => $pts]);
break;
}
// full magic mask, also counts towards healing
if ($mv == SPELL_MAGIC_SCHOOLS)
{
Util::arraySumByKey($stats, [ITEM_MOD_SPELL_POWER => $pts]);
Util::arraySumByKey($stats, [ITEM_MOD_SPELL_DAMAGE_DONE => $pts]);
}
else
{
// HolySpellpower (deprecated; still used in randomproperties)
if ($mv & (1 << SPELL_SCHOOL_HOLY))
Util::arraySumByKey($stats, [ITEM_MOD_HOLY_POWER => $pts]);
// FireSpellpower (deprecated; still used in randomproperties)
if ($mv & (1 << SPELL_SCHOOL_FIRE))
Util::arraySumByKey($stats, [ITEM_MOD_FIRE_POWER => $pts]);
// NatureSpellpower (deprecated; still used in randomproperties)
if ($mv & (1 << SPELL_SCHOOL_NATURE))
Util::arraySumByKey($stats, [ITEM_MOD_NATURE_POWER => $pts]);
// FrostSpellpower (deprecated; still used in randomproperties)
if ($mv & (1 << SPELL_SCHOOL_FROST))
Util::arraySumByKey($stats, [ITEM_MOD_FROST_POWER => $pts]);
// ShadowSpellpower (deprecated; still used in randomproperties)
if ($mv & (1 << SPELL_SCHOOL_SHADOW))
Util::arraySumByKey($stats, [ITEM_MOD_SHADOW_POWER => $pts]);
// ArcaneSpellpower (deprecated; still used in randomproperties)
if ($mv & (1 << SPELL_SCHOOL_ARCANE))
Util::arraySumByKey($stats, [ITEM_MOD_ARCANE_POWER => $pts]);
}
break;
case SPELL_AURA_MOD_HEALING_DONE: // not as a mask..
Util::arraySumByKey($stats, [ITEM_MOD_SPELL_HEALING_DONE => $pts]);
break;
case SPELL_AURA_MOD_INCREASE_ENERGY: // MiscVal:type see defined Powers only energy/mana in use
if ($mv == POWER_HEALTH)
Util::arraySumByKey($stats, [ITEM_MOD_HEALTH => $pts]);
else if ($mv == POWER_ENERGY)
Util::arraySumByKey($stats, [ITEM_MOD_ENERGY => $pts]);
else if ($mv == POWER_RAGE)
Util::arraySumByKey($stats, [ITEM_MOD_RAGE => $pts]);
else if ($mv == POWER_MANA)
Util::arraySumByKey($stats, [ITEM_MOD_MANA => $pts]);
else if ($mv == POWER_RUNIC_POWER)
Util::arraySumByKey($stats, [ITEM_MOD_RUNIC_POWER => $pts]);
break;
case SPELL_AURA_MOD_RATING:
case SPELL_AURA_MOD_RATING_FROM_STAT:
if ($mod = Game::itemModByRatingMask($mv))
Util::arraySumByKey($stats, [$mod => $pts]);
break;
case SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE:
case SPELL_AURA_MOD_BASE_RESISTANCE:
case SPELL_AURA_MOD_RESISTANCE:
// Armor only if explicitly specified
if ($mv == (1 << SPELL_SCHOOL_NORMAL))
{
Util::arraySumByKey($stats, [ITEM_MOD_ARMOR => $pts]);
break;
}
// Holy resistance only if explicitly specified (shouldn't even exist...?)
if ($mv == (1 << SPELL_SCHOOL_HOLY))
{
Util::arraySumByKey($stats, [ITEM_MOD_HOLY_RESISTANCE => $pts]);
break;
}
for ($j = 0; $j < 7; $j++)
{
if (($mv & (1 << $j)) == 0)
continue;
switch ($j)
{
case SPELL_SCHOOL_FIRE:
Util::arraySumByKey($stats, [ITEM_MOD_FIRE_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_NATURE:
Util::arraySumByKey($stats, [ITEM_MOD_NATURE_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_FROST:
Util::arraySumByKey($stats, [ITEM_MOD_FROST_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_SHADOW:
Util::arraySumByKey($stats, [ITEM_MOD_SHADOW_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_ARCANE:
Util::arraySumByKey($stats, [ITEM_MOD_ARCANE_RESISTANCE => $pts]);
break;
}
}
break;
case SPELL_AURA_PERIODIC_HEAL: // hp5
case SPELL_AURA_MOD_REGEN:
case SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT:
Util::arraySumByKey($stats, [ITEM_MOD_HEALTH_REGEN => $pts]);
break;
case SPELL_AURA_MOD_POWER_REGEN: // mp5
Util::arraySumByKey($stats, [ITEM_MOD_MANA_REGENERATION => $pts]);
break;
case SPELL_AURA_MOD_ATTACK_POWER:
Util::arraySumByKey($stats, [ITEM_MOD_ATTACK_POWER => $pts]);
break; // ?carries over to rngatkpwr?
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
Util::arraySumByKey($stats, [ITEM_MOD_RANGED_ATTACK_POWER => $pts]);
break;
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
Util::arraySumByKey($stats, [ITEM_MOD_BLOCK_VALUE => $pts]);
break;
case SPELL_AURA_MOD_EXPERTISE:
Util::arraySumByKey($stats, [ITEM_MOD_EXPERTISE_RATING => $pts]);
break;
case SPELL_AURA_MOD_TARGET_RESISTANCE:
if ($mv == 0x7C && $pts < 0)
Util::arraySumByKey($stats, [ITEM_MOD_SPELL_PENETRATION => -$pts]);
break;
}
}
$data[$this->id] = $stats;
} }
return $data; return $data;
@@ -390,21 +216,10 @@ class SpellList extends BaseType
// weapon hand check: param: slot, class, subclass, value // weapon hand check: param: slot, class, subclass, value
$whCheck = '$function() { var j, w = _inventory.getInventory()[%d]; if (!w[0] || !g_items[w[0]]) { return 0; } j = g_items[w[0]].jsonequip; return (j.classs == %d && (%d & (1 << (j.subclass)))) ? %d : 0; }'; $whCheck = '$function() { var j, w = _inventory.getInventory()[%d]; if (!w[0] || !g_items[w[0]]) { return 0; } j = g_items[w[0]].jsonequip; return (j.classs == %d && (%d & (1 << (j.subclass)))) ? %d : 0; }';
$data = $this->getStatGain(); // flat gains $data = []; // flat gains
foreach ($data as $id => &$spellData) foreach ($this->getStatGain() as $id => $spellData)
{ {
foreach ($spellData as $modId => $val) $data[$id] = $spellData->toJson(STAT::FLAG_ITEM | STAT::FLAG_PROFILER);
{
if (!isset(Game::$itemMods[$modId]))
continue;
if ($modId == ITEM_MOD_EXPERTISE_RATING) // not a rating .. pure expertise
$spellData['exp'] = $val;
else
$spellData[Game::$itemMods[$modId]] = $val;
unset($spellData[$modId]);
}
// apply weapon restrictions // apply weapon restrictions
$this->getEntry($id); $this->getEntry($id);
@@ -414,8 +229,8 @@ class SpellList extends BaseType
if ($class != ITEM_CLASS_WEAPON || !$subClass) if ($class != ITEM_CLASS_WEAPON || !$subClass)
continue; continue;
foreach ($spellData as $json => $pts) foreach ($data[$id] as $key => $amt)
$spellData[$json] = [1, 'functionOf', sprintf($whCheck, $slot, $class, $subClass, $pts)]; $data[$id][$key] = [1, 'functionOf', sprintf($whCheck, $slot, $class, $subClass, $amt)];
} }
// 4 possible modifiers found // 4 possible modifiers found
@@ -470,23 +285,10 @@ class SpellList extends BaseType
foreach ($this->iterate() as $id => $__) foreach ($this->iterate() as $id => $__)
{ {
// Priest: Spirit of Redemption is a spell but also a passive. *yaaayyyy* // kept for reference - if (($this->getField('cuFlags') & SPELL_CU_TALENTSPELL) && $id != 20711)
if (($this->getField('cuFlags') & SPELL_CU_TALENTSPELL) && $id != 20711) if (!($this->getField('attributes0') & SPELL_ATTR0_PASSIVE))
continue; continue;
// curious cases of OH MY FUCKING GOD WHY?!
if ($id == 16268) // Shaman - Spirit Weapons (parry is normaly stored in g_statistics)
{
$data[$id]['parrypct'] = [5, 'add'];
continue;
}
if ($id == 20550) // Tauren - Endurance (dependant on base health) ... if you are looking for something elegant, look away!
{
$data[$id]['health'] = [0.05, 'functionOf', '$function(p) { return g_statistics.combo[p.classs][p.level][5]; }'];
continue;
}
for ($i = 1; $i < 4; $i++) for ($i = 1; $i < 4; $i++)
{ {
$pts = $this->calculateAmountForCurrent($i)[1]; $pts = $this->calculateAmountForCurrent($i)[1];
@@ -502,6 +304,15 @@ class SpellList extends BaseType
as a flat value (that is equal to the percentage, like they should be). So the stats-table won't show the actual deficit as a flat value (that is equal to the percentage, like they should be). So the stats-table won't show the actual deficit
*/ */
// Shaman - Spirit Weapons (16268) (parry is normaly stored in g_statistics)
// i should recurse into SPELL_EFFECT_LEARN_SPELL and apply SPELL_EFFECT_PARRY from there
if ($id = 16268)
{
$data[$id]['parrypct'] = [5, 'add'];
continue;
}
switch ($au) switch ($au)
{ {
case SPELL_AURA_MOD_RESISTANCE_PCT: case SPELL_AURA_MOD_RESISTANCE_PCT:
@@ -524,7 +335,9 @@ class SpellList extends BaseType
$modXByStat($data[$id], null, $pts); $modXByStat($data[$id], null, $pts);
else if ($mv < 0) // all stats else if ($mv < 0) // all stats
for ($iMod = ITEM_MOD_AGILITY; $iMod <= ITEM_MOD_STAMINA; $iMod++) for ($iMod = ITEM_MOD_AGILITY; $iMod <= ITEM_MOD_STAMINA; $iMod++)
$data[$id][Game::$itemMods[$iMod]] = [$pts / 100, 'percentOf', Game::$itemMods[$iMod]]; if ($idx = Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $iMod))
if ($key = Stat::getJsonString($idx))
$data[$id][$key] = [$pts / 100, 'percentOf', $key];
break; break;
case SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT: case SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT:
$mv = $mv ?: SPELL_MAGIC_SCHOOLS; $mv = $mv ?: SPELL_MAGIC_SCHOOLS;
@@ -579,15 +392,18 @@ class SpellList extends BaseType
else if ($mv == POWER_ENERGY) else if ($mv == POWER_ENERGY)
$data[$id]['energy'] = [$pts / 100, 'percentOf', 'energy']; $data[$id]['energy'] = [$pts / 100, 'percentOf', 'energy'];
else if ($mv == POWER_MANA) else if ($mv == POWER_MANA)
$data[$id]['mana'] = [$pts / 100, 'percentOf', 'mana']; $data[$id]['mana'] = [$pts / 100, 'percentOf', 'mana'];
else if ($mv == POWER_RAGE) else if ($mv == POWER_RAGE)
$data[$id]['rage'] = [$pts / 100, 'percentOf', 'rage']; $data[$id]['rage'] = [$pts / 100, 'percentOf', 'rage'];
else if ($mv == POWER_RUNIC_POWER) else if ($mv == POWER_RUNIC_POWER)
$data[$id]['runic'] = [$pts / 100, 'percentOf', 'runic']; $data[$id]['runic'] = [$pts / 100, 'percentOf', 'runic'];
break; break;
case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT: case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT:
$data[$id]['health'] = [$pts / 100, 'percentOf', 'health']; $data[$id]['health'] = [$pts / 100, 'percentOf', 'health'];
break; break;
case SPELL_AURA_MOD_BASE_HEALTH_PCT: // only Tauren - Endurance (20550) ... if you are looking for something elegant, look away!
$data[$id]['health'] = [$pts / 100, 'functionOf', '$function(p) { return g_statistics.combo[p.classs][p.level][5]; }'];
break;
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT: case SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT:
$data[$id]['block'] = [$pts / 100, 'percentOf', 'block']; $data[$id]['block'] = [$pts / 100, 'percentOf', 'block'];
break; break;
@@ -910,7 +726,7 @@ class SpellList extends BaseType
]; ];
} }
public function canCreateItem() public function canCreateItem() : array
{ {
$idx = []; $idx = [];
for ($i = 1; $i < 4; $i++) for ($i = 1; $i < 4; $i++)
@@ -921,7 +737,7 @@ class SpellList extends BaseType
return $idx; return $idx;
} }
public function canTriggerSpell() public function canTriggerSpell() : array
{ {
$idx = []; $idx = [];
for ($i = 1; $i < 4; $i++) for ($i = 1; $i < 4; $i++)
@@ -932,7 +748,7 @@ class SpellList extends BaseType
return $idx; return $idx;
} }
public function canTeachSpell() public function canTeachSpell() : array
{ {
$idx = []; $idx = [];
for ($i = 1; $i < 4; $i++) for ($i = 1; $i < 4; $i++)
@@ -943,6 +759,16 @@ class SpellList extends BaseType
return $idx; return $idx;
} }
public function canEnchantmentItem() : array
{
$idx = [];
for ($i = 1; $i < 4; $i++)
if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_ENCHANTMENT))
$idx[] = $i;
return $idx;
}
public function isChanneledSpell() public function isChanneledSpell()
{ {
return $this->curTpl['attributes1'] & (SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2); return $this->curTpl['attributes1'] & (SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2);
@@ -1243,16 +1069,16 @@ class SpellList extends BaseType
} }
// Aura giving combat ratings // Aura giving combat ratings
$rType = 0; $rType = [];
if ($aura == SPELL_AURA_MOD_RATING) if ($aura == SPELL_AURA_MOD_RATING)
if ($rType = Game::itemModByRatingMask($mv)) if ($rType = StatsContainer::convertCombatRating($mv))
$this->scaling[$this->id] = true; $this->scaling[$this->id] = true;
// Aura end // Aura end
if ($rType) if ($rType)
{ {
$result[2] = '<!--rtg%s-->%s&nbsp;<small>(%s)</small>'; $result[2] = '<!--rtg%s-->%s&nbsp;<small>(%s)</small>';
$result[4] = $rType; $result[4] = $rType[0]; // could be multiple ratings in theory, but not expected to be
} }
/* todo: export to and solve formulas in javascript e.g.: spell 10187 - ${$42213m1*8*$<mult>} with $mult = ${${$?s31678[${1.05}][${${$?s31677[${1.04}][${${$?s31676[${1.03}][${${$?s31675[${1.02}][${${$?s31674[${1.01}][${1}]}}]}}]}}]}}]}*${$?s12953[${1.06}][${${$?s12952[${1.04}][${${$?s11151[${1.02}][${1}]}}]}}]}} /* todo: export to and solve formulas in javascript e.g.: spell 10187 - ${$42213m1*8*$<mult>} with $mult = ${${$?s31678[${1.05}][${${$?s31677[${1.04}][${${$?s31676[${1.03}][${${$?s31675[${1.02}][${${$?s31674[${1.01}][${1}]}}]}}]}}]}}]}*${$?s12953[${1.06}][${${$?s12952[${1.04}][${${$?s11151[${1.02}][${1}]}}]}}]}}
else if ($this->interactive && ($modStrMin || $modStrMax)) else if ($this->interactive && ($modStrMin || $modStrMax))
@@ -1336,16 +1162,16 @@ class SpellList extends BaseType
eval("\$max = $max $op $oparg;"); eval("\$max = $max $op $oparg;");
} }
// Aura giving combat ratings // Aura giving combat ratings
$rType = 0; $rType = [];
if ($aura == SPELL_AURA_MOD_RATING) if ($aura == SPELL_AURA_MOD_RATING)
if ($rType = Game::itemModByRatingMask($mv)) if ($rType = StatsContainer::convertCombatRating($mv))
$this->scaling[$this->id] = true; $this->scaling[$this->id] = true;
// Aura end // Aura end
if ($rType) if ($rType)
{ {
$result[2] = '<!--rtg%s-->%s&nbsp;<small>(%s)</small>'; $result[2] = '<!--rtg%s-->%s&nbsp;<small>(%s)</small>';
$result[4] = $rType; $result[4] = $rType[0]; // could be multiple ratings in theory, but not expected to be
} }
else if (($modStrMin || $modStrMax) && $this->interactive) else if (($modStrMin || $modStrMax) && $this->interactive)
{ {
@@ -1659,7 +1485,7 @@ class SpellList extends BaseType
// step 4: find and eliminate regular variables // step 4: find and eliminate regular variables
$data = $this->handleVariables($data, true); $data = $this->handleVariables($data, true);
// step 5: variable-dependant variable-text // step 5: variable-dependent variable-text
// special case $lONE:ELSE[:ELSE2]; or $|ONE:ELSE[:ELSE2]; // special case $lONE:ELSE[:ELSE2]; or $|ONE:ELSE[:ELSE2];
while (preg_match('/([\d\.]+)([^\d]*)(\$[l|]:*)([^:]*):([^;]*);/i', $data, $m)) while (preg_match('/([\d\.]+)([^\d]*)(\$[l|]:*)([^:]*):([^;]*);/i', $data, $m))
{ {

View File

@@ -501,17 +501,6 @@ abstract class Util
30 => 10, 31 => 10, 32 => 14, 33 => 0, 34 => 0, 35 => 28.75, 36 => 10, 37 => 2.5, 44 => 4.268292513760655 30 => 10, 31 => 10, 32 => 14, 33 => 0, 34 => 0, 35 => 28.75, 36 => 10, 37 => 2.5, 44 => 4.268292513760655
); );
public static $itemFilter = array(
20 => 'str', 21 => 'agi', 23 => 'int', 22 => 'sta', 24 => 'spi', 25 => 'arcres', 26 => 'firres', 27 => 'natres',
28 => 'frores', 29 => 'shares', 30 => 'holres', 37 => 'mleatkpwr', 32 => 'dps', 35 => 'damagetype', 33 => 'dmgmin1', 34 => 'dmgmax1',
36 => 'speed', 38 => 'rgdatkpwr', 39 => 'rgdhitrtng', 40 => 'rgdcritstrkrtng', 41 => 'armor', 44 => 'blockrtng', 43 => 'block', 42 => 'defrtng',
45 => 'dodgertng', 46 => 'parryrtng', 48 => 'splhitrtng', 49 => 'splcritstrkrtng', 50 => 'splheal', 51 => 'spldmg', 52 => 'arcsplpwr', 53 => 'firsplpwr',
54 => 'frosplpwr', 55 => 'holsplpwr', 56 => 'natsplpwr', 60 => 'healthrgn', 61 => 'manargn', 57 => 'shasplpwr', 77 => 'atkpwr', 78 => 'mlehastertng',
79 => 'resirtng', 84 => 'mlecritstrkrtng', 94 => 'splpen', 95 => 'mlehitrtng', 96 => 'critstrkrtng', 97 => 'feratkpwr', 100 => 'nsockets', 101 => 'rgdhastertng',
102 => 'splhastertng', 103 => 'hastertng', 114 => 'armorpenrtng', 115 => 'health', 116 => 'mana', 117 => 'exprtng', 119 => 'hitrtng', 123 => 'splpwr',
134 => 'mledps', 135 => 'mledmgmin', 136 => 'mledmgmax', 137 => 'mlespeed', 138 => 'rgddps', 139 => 'rgddmgmin', 140 => 'rgddmgmax', 141 => 'rgdspeed'
);
public static $ssdMaskFields = array( public static $ssdMaskFields = array(
'shoulderMultiplier', 'trinketMultiplier', 'weaponMultiplier', 'primBudged', 'shoulderMultiplier', 'trinketMultiplier', 'weaponMultiplier', 'primBudged',
'rangedMultiplier', 'clothShoulderArmor', 'leatherShoulderArmor', 'mailShoulderArmor', 'rangedMultiplier', 'clothShoulderArmor', 'leatherShoulderArmor', 'mailShoulderArmor',

View File

@@ -98,30 +98,29 @@ class EnchantmentPage extends GenericPage
switch ($_ty) switch ($_ty)
{ {
case 1: case ENCHANTMENT_TYPE_COMBAT_SPELL:
case 3: case ENCHANTMENT_TYPE_EQUIP_SPELL:
case 7: case ENCHANTMENT_TYPE_USE_SPELL:
$sArr = $this->subject->getField('spells')[$i]; [$spellId, $trigger, $charges, $procChance] = $this->subject->getField('spells')[$i];
$spl = $this->subject->getRelSpell($sArr[0]); $spl = $this->subject->getRelSpell($spellId);
$this->effects[$i]['name'] = User::isInGroup(U_GROUP_EMPLOYEE) ? sprintf(Util::$dfnString, 'Type: '.$_ty, Lang::item('trigger', $sArr[1])) : Lang::item('trigger', $sArr[1]); $this->effects[$i]['name'] = User::isInGroup(U_GROUP_EMPLOYEE) ? sprintf(Util::$dfnString, 'Type: '.$_ty, Lang::item('trigger', $trigger)) : Lang::item('trigger', $trigger);
$this->effects[$i]['proc'] = $sArr[3]; $this->effects[$i]['proc'] = $procChance;
$this->effects[$i]['value'] = $_qty ?: null; $this->effects[$i]['value'] = $_qty ?: null;
$this->effects[$i]['icon'] = array( $this->effects[$i]['icon'] = array(
'name' => !$spl ? Util::ucFirst(Lang::game('spell')).' #'.$sArr[0] : Util::localizedString($spl, 'name'), 'name' => !$spl ? Util::ucFirst(Lang::game('spell')).' #'.$spellId : Util::localizedString($spl, 'name'),
'id' => $sArr[0], 'id' => $spellId,
'count' => $sArr[2] 'count' => $charges
); );
break; break;
case 5: case ENCHANTMENT_TYPE_STAT:
if ($_obj < 2) // [mana, health] are on [0, 1] respectively and are expected on [1, 2] .. if ($idx = Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $_obj))
$_obj++; // 0 is weaponDmg .. ehh .. i messed up somewhere if ($jsonStat = Stat::getJsonString($idx))
$this->effects[$i]['tip'] = [$_obj, $jsonStat];
$this->effects[$i]['tip'] = [$_obj, Game::$itemMods[$_obj]];
// DO NOT BREAK! // DO NOT BREAK!
case 2: case ENCHANTMENT_TYPE_DAMAGE:
case 6: case ENCHANTMENT_TYPE_TOTEM:
case 8: case ENCHANTMENT_TYPE_PRISMATIC_SOCKET:
case 4: case ENCHANTMENT_TYPE_RESISTANCE:
$this->effects[$i]['name'] = User::isInGroup(U_GROUP_EMPLOYEE) ? sprintf(Util::$dfnString, 'Type: '.$_ty, Lang::enchantment('types', $_ty)) : Lang::enchantment('types', $_ty); $this->effects[$i]['name'] = User::isInGroup(U_GROUP_EMPLOYEE) ? sprintf(Util::$dfnString, 'Type: '.$_ty, Lang::enchantment('types', $_ty)) : Lang::enchantment('types', $_ty);
$this->effects[$i]['value'] = $_qty; $this->effects[$i]['value'] = $_qty;
if ($_ty == 4) if ($_ty == 4)

View File

@@ -59,8 +59,8 @@ class EnchantmentsPage extends GenericPage
$this->filter['initData']['sc'] = $x; $this->filter['initData']['sc'] = $x;
$xCols = $this->filterObj->getExtraCols(); $xCols = $this->filterObj->getExtraCols();
foreach (Util::$itemFilter as $fiId => $str) foreach (Stat::getFilterCriteriumIdFor() as $idx => $fiId)
if (array_column($tabData['data'], $str)) if (array_column($tabData['data'], Stat::getJsonString($idx)))
$xCols[] = $fiId; $xCols[] = $fiId;
if (array_column($tabData['data'], 'dmg')) if (array_column($tabData['data'], 'dmg'))

View File

@@ -1112,12 +1112,12 @@ class ItemPage extends genericPage
if ($_ = $this->subject->getField('cooldown')) // cooldown if ($_ = $this->subject->getField('cooldown')) // cooldown
$json['cooldown'] = $_ / 1000; $json['cooldown'] = $_ / 1000;
foreach ($this->subject->itemMods[$this->typeId] as $mod => $qty) Util::arraySumByKey($json, $this->subject->jsonStats[$this->typeId] ?? []);
$json[$mod] = $qty;
foreach ($this->subject->json[$this->typeId] as $name => $qty) foreach ($this->subject->json[$this->typeId] as $name => $qty)
if (in_array($name, Util::$itemFilter)) if ($idx = Stat::getIndexFrom(Stat::IDX_JSON_STR, $name))
$json[$name] = $qty; if (Stat::getFilterCriteriumId($idx))
$json[$name] = $qty;
$xml->addChild('jsonEquip')->addCData(substr(json_encode($json), 1, -1)); $xml->addChild('jsonEquip')->addCData(substr(json_encode($json), 1, -1));
@@ -1125,8 +1125,8 @@ class ItemPage extends genericPage
if ($onUse = $this->subject->getOnUseStats()) if ($onUse = $this->subject->getOnUseStats())
{ {
$j = ''; $j = '';
foreach ($onUse as $idx => $qty) foreach ($onUse->toJson() as $key => $amt)
$j .= ',"'.Game::$itemMods[$idx].'":'.$qty; $j .= ',"'.$key.'":'.$amt;
$xml->addChild('jsonUse')->addCData(substr($j, 1)); $xml->addChild('jsonUse')->addCData(substr($j, 1));
} }

View File

@@ -1103,88 +1103,88 @@ DROP TABLE IF EXISTS `aowow_item_stats`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `aowow_item_stats` ( CREATE TABLE `aowow_item_stats` (
`type` smallint unsigned NOT NULL, `type` smallint(5) unsigned NOT NULL,
`typeId` mediumint unsigned NOT NULL, `typeId` mediumint(8) NOT NULL,
`nsockets` tinyint unsigned NOT NULL DEFAULT 0, `nsockets` tinyint(3) unsigned NULL,
`dmgmin1` smallint unsigned NOT NULL DEFAULT 0, `dps` float(8,2) NULL,
`dmgmax1` smallint unsigned NOT NULL DEFAULT 0, `damagetype` tinyint(4) NULL,
`speed` float(8,2) NOT NULL DEFAULT 0.00, `dmgmin1` mediumint(5) unsigned NULL,
`dps` float(8,2) NOT NULL DEFAULT 0.00, `dmgmax1` mediumint(5) unsigned NULL,
`mledmgmin` smallint unsigned NOT NULL DEFAULT 0, `speed` float(8,2) NULL,
`mledmgmax` smallint unsigned NOT NULL DEFAULT 0, `mledps` float(8,2) NULL,
`mlespeed` float(8,2) NOT NULL DEFAULT 0.00, `mledmgmin` mediumint(5) unsigned NULL,
`mledps` float(8,2) NOT NULL DEFAULT 0.00, `mledmgmax` mediumint(5) unsigned NULL,
`rgddmgmin` smallint unsigned NOT NULL DEFAULT 0, `mlespeed` float(8,2) NULL,
`rgddmgmax` smallint unsigned NOT NULL DEFAULT 0, `rgddps` float(8,2) NULL,
`rgdspeed` float(8,2) NOT NULL DEFAULT 0.00, `rgddmgmin` mediumint(5) unsigned NULL,
`rgddps` float(8,2) NOT NULL DEFAULT 0.00, `rgddmgmax` mediumint(5) unsigned NULL,
`dmg` float(8,2) NOT NULL DEFAULT 0.00, `rgdspeed` float(8,2) NULL,
`damagetype` tinyint NOT NULL DEFAULT 0, `dmg` float(8,2) NULL,
`mana` mediumint NOT NULL DEFAULT 0, `mana` mediumint(6) NULL,
`health` mediumint NOT NULL DEFAULT 0, `health` mediumint(6) NULL,
`agi` mediumint NOT NULL DEFAULT 0, `agi` mediumint(6) NULL,
`str` mediumint NOT NULL DEFAULT 0, `str` mediumint(6) NULL,
`int` mediumint NOT NULL DEFAULT 0, `int` mediumint(6) NULL,
`spi` mediumint NOT NULL DEFAULT 0, `spi` mediumint(6) NULL,
`sta` mediumint NOT NULL DEFAULT 0, `sta` mediumint(6) NULL,
`energy` mediumint NOT NULL DEFAULT 0, `energy` mediumint(6) NULL,
`rage` mediumint NOT NULL DEFAULT 0, `rage` mediumint(6) NULL,
`focus` mediumint NOT NULL DEFAULT 0, `focus` mediumint(6) NULL,
`runicpwr` mediumint NOT NULL DEFAULT 0, `runic` mediumint(6) NULL,
`defrtng` mediumint NOT NULL DEFAULT 0, `defrtng` mediumint(6) NULL,
`dodgertng` mediumint NOT NULL DEFAULT 0, `dodgertng` mediumint(6) NULL,
`parryrtng` mediumint NOT NULL DEFAULT 0, `parryrtng` mediumint(6) NULL,
`blockrtng` mediumint NOT NULL DEFAULT 0, `blockrtng` mediumint(6) NULL,
`mlehitrtng` mediumint NOT NULL DEFAULT 0, `mlehitrtng` mediumint(6) NULL,
`rgdhitrtng` mediumint NOT NULL DEFAULT 0, `rgdhitrtng` mediumint(6) NULL,
`splhitrtng` mediumint NOT NULL DEFAULT 0, `splhitrtng` mediumint(6) NULL,
`mlecritstrkrtng` mediumint NOT NULL DEFAULT 0, `mlecritstrkrtng` mediumint(6) NULL,
`rgdcritstrkrtng` mediumint NOT NULL DEFAULT 0, `rgdcritstrkrtng` mediumint(6) NULL,
`splcritstrkrtng` mediumint NOT NULL DEFAULT 0, `splcritstrkrtng` mediumint(6) NULL,
`_mlehitrtng` mediumint NOT NULL DEFAULT 0, `_mlehitrtng` mediumint(6) NULL,
`_rgdhitrtng` mediumint NOT NULL DEFAULT 0, `_rgdhitrtng` mediumint(6) NULL,
`_splhitrtng` mediumint NOT NULL DEFAULT 0, `_splhitrtng` mediumint(6) NULL,
`_mlecritstrkrtng` mediumint NOT NULL DEFAULT 0, `_mlecritstrkrtng` mediumint(6) NULL,
`_rgdcritstrkrtng` mediumint NOT NULL DEFAULT 0, `_rgdcritstrkrtng` mediumint(6) NULL,
`_splcritstrkrtng` mediumint NOT NULL DEFAULT 0, `_splcritstrkrtng` mediumint(6) NULL,
`mlehastertng` mediumint NOT NULL DEFAULT 0, `mlehastertng` mediumint(6) NULL,
`rgdhastertng` mediumint NOT NULL DEFAULT 0, `rgdhastertng` mediumint(6) NULL,
`splhastertng` mediumint NOT NULL DEFAULT 0, `splhastertng` mediumint(6) NULL,
`hitrtng` mediumint NOT NULL DEFAULT 0, `hitrtng` mediumint(6) NULL,
`critstrkrtng` mediumint NOT NULL DEFAULT 0, `critstrkrtng` mediumint(6) NULL,
`_hitrtng` mediumint NOT NULL DEFAULT 0, `_hitrtng` mediumint(6) NULL,
`_critstrkrtng` mediumint NOT NULL DEFAULT 0, `_critstrkrtng` mediumint(6) NULL,
`resirtng` mediumint NOT NULL DEFAULT 0, `resirtng` mediumint(6) NULL,
`hastertng` mediumint NOT NULL DEFAULT 0, `hastertng` mediumint(6) NULL,
`exprtng` mediumint NOT NULL DEFAULT 0, `exprtng` mediumint(6) NULL,
`atkpwr` mediumint NOT NULL DEFAULT 0, `atkpwr` mediumint(6) NULL,
`mleatkpwr` mediumint NOT NULL DEFAULT 0, `mleatkpwr` mediumint(6) NULL,
`rgdatkpwr` mediumint NOT NULL DEFAULT 0, `rgdatkpwr` mediumint(6) NULL,
`feratkpwr` mediumint NOT NULL DEFAULT 0, `feratkpwr` mediumint(6) NULL,
`splheal` mediumint NOT NULL DEFAULT 0, `splheal` mediumint(6) NULL,
`spldmg` mediumint NOT NULL DEFAULT 0, `spldmg` mediumint(6) NULL,
`manargn` mediumint NOT NULL DEFAULT 0, `manargn` mediumint(6) NULL,
`armorpenrtng` mediumint NOT NULL DEFAULT 0, `armorpenrtng` mediumint(6) NULL,
`splpwr` mediumint NOT NULL DEFAULT 0, `splpwr` mediumint(6) NULL,
`healthrgn` mediumint NOT NULL DEFAULT 0, `healthrgn` mediumint(6) NULL,
`splpen` mediumint NOT NULL DEFAULT 0, `splpen` mediumint(6) NULL,
`block` mediumint NOT NULL DEFAULT 0, `block` mediumint(6) NULL,
`mastrtng` mediumint NOT NULL DEFAULT 0, `mastrtng` mediumint(6) NULL,
`armor` mediumint NOT NULL DEFAULT 0, `armor` mediumint(6) NULL,
`armorbonus` mediumint NOT NULL DEFAULT 0, `armorbonus` mediumint(6) NULL,
`firres` mediumint NOT NULL DEFAULT 0, `firres` mediumint(6) NULL,
`frores` mediumint NOT NULL DEFAULT 0, `frores` mediumint(6) NULL,
`holres` mediumint NOT NULL DEFAULT 0, `holres` mediumint(6) NULL,
`shares` mediumint NOT NULL DEFAULT 0, `shares` mediumint(6) NULL,
`natres` mediumint NOT NULL DEFAULT 0, `natres` mediumint(6) NULL,
`arcres` mediumint NOT NULL DEFAULT 0, `arcres` mediumint(6) NULL,
`firsplpwr` mediumint NOT NULL DEFAULT 0, `firsplpwr` mediumint(6) NULL,
`frosplpwr` mediumint NOT NULL DEFAULT 0, `frosplpwr` mediumint(6) NULL,
`holsplpwr` mediumint NOT NULL DEFAULT 0, `holsplpwr` mediumint(6) NULL,
`shasplpwr` mediumint NOT NULL DEFAULT 0, `shasplpwr` mediumint(6) NULL,
`natsplpwr` mediumint NOT NULL DEFAULT 0, `natsplpwr` mediumint(6) NULL,
`arcsplpwr` mediumint NOT NULL DEFAULT 0, `arcsplpwr` mediumint(6) NULL,
PRIMARY KEY (`typeId`,`type`) PRIMARY KEY (`type`,`typeId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
@@ -1601,7 +1601,6 @@ CREATE TABLE `aowow_itemset` (
`bonusText_loc4` text DEFAULT NULL, `bonusText_loc4` text DEFAULT NULL,
`bonusText_loc6` text DEFAULT NULL, `bonusText_loc6` text DEFAULT NULL,
`bonusText_loc8` text DEFAULT NULL, `bonusText_loc8` text DEFAULT NULL,
`bonusParsed` varchar(256) DEFAULT NULL COMMENT 'serialized itemMods',
`npieces` tinyint NOT NULL DEFAULT 0, `npieces` tinyint NOT NULL DEFAULT 0,
`minLevel` smallint NOT NULL DEFAULT 0, `minLevel` smallint NOT NULL DEFAULT 0,
`maxLevel` smallint NOT NULL DEFAULT 0, `maxLevel` smallint NOT NULL DEFAULT 0,
@@ -3228,7 +3227,7 @@ UNLOCK TABLES;
LOCK TABLES `aowow_dbversion` WRITE; LOCK TABLES `aowow_dbversion` WRITE;
/*!40000 ALTER TABLE `aowow_dbversion` DISABLE KEYS */; /*!40000 ALTER TABLE `aowow_dbversion` DISABLE KEYS */;
INSERT INTO `aowow_dbversion` VALUES (1717354215,0,NULL,NULL); INSERT INTO `aowow_dbversion` VALUES (1718468661,0,NULL,NULL);
/*!40000 ALTER TABLE `aowow_dbversion` ENABLE KEYS */; /*!40000 ALTER TABLE `aowow_dbversion` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;

View File

@@ -145,7 +145,7 @@ if (!CLI)
'skill' => -1, // modified if skill 'skill' => -1, // modified if skill
'slots' => [], // determined per spell but set per item 'slots' => [], // determined per spell but set per item
'enchantment' => $enchantments->getField('name', true), 'enchantment' => $enchantments->getField('name', true),
'jsonequip' => $enchantments->getStatGain(), 'jsonequip' => $enchantments->getStatGainForCurrent(),
'temp' => 0, // always 0 'temp' => 0, // always 0
'classes' => 0, // modified by item 'classes' => 0, // modified by item
'gearscore' => 0 // set later 'gearscore' => 0 // set later

View File

@@ -73,7 +73,7 @@ if (!CLI)
{ {
if (!$enchantments->getEntry($pop['enchId'])) if (!$enchantments->getEntry($pop['enchId']))
{ {
CLI::write(' * could not find enchantment #'.$pop['enchId'].' referenced by item #'.$gem['itemId'], CLI::LOG_WARN); CLI::write(' * could not find enchantment #'.$pop['enchId'].' referenced by item #'.$pop['itemId'], CLI::LOG_WARN);
continue; continue;
} }
@@ -82,10 +82,10 @@ if (!CLI)
'quality' => $pop['quality'], 'quality' => $pop['quality'],
'icon' => strToLower($pop['icon']), 'icon' => strToLower($pop['icon']),
'enchantment' => $enchantments->getField('name', true), 'enchantment' => $enchantments->getField('name', true),
'jsonequip' => $enchantments->getStatGain(), 'jsonequip' => $enchantments->getStatGainForCurrent(),
'colors' => $pop['colors'], 'colors' => $pop['colors'],
'expansion' => $pop['expansion'], 'expansion' => $pop['expansion'],
'gearscore' => Util::getGemScore($pop['itemLevel'], $pop['quality'], $pop['requiredSkill'] == 755, $pop['itemId']) 'gearscore' => Util::getGemScore($pop['itemLevel'], $pop['quality'], $pop['requiredSkill'] == SKILL_JEWELCRAFTING, $pop['itemId'])
); );
} }

View File

@@ -81,40 +81,38 @@ if (!CLI)
if ($set['item'.$i]) if ($set['item'.$i])
$setOut['pieces'][] = $set['item'.$i]; $setOut['pieces'][] = $set['item'.$i];
$_spells = [];
for ($i = 1; $i < 9; $i++) for ($i = 1; $i < 9; $i++)
{ {
if (!$set['bonus'.$i] || !$set['spell'.$i]) if (!$set['spell'.$i] || isset($jsonBonus[$set['spell'.$i]]))
continue; continue;
// costy and locale-independant -> cache $_spells[] = $set['spell'.$i];
if (!isset($jsonBonus[$set['spell'.$i]]))
$jsonBonus[$set['spell'.$i]] = (new SpellList(array(['s.id', (int)$set['spell'.$i]])))->getStatGain()[$set['spell'.$i]];
if (!isset($setOut['setbonus'][$set['bonus'.$i]]))
$setOut['setbonus'][$set['bonus'.$i]] = $jsonBonus[$set['spell'.$i]];
else
Util::arraySumByKey($setOut['setbonus'][$set['bonus'.$i]], $jsonBonus[$set['spell'.$i]]);
} }
foreach ($setOut['setbonus'] as $k => $v) // costy and locale-independant -> cache
if ($_spells)
$jsonBonus += (new SpellList(array(['s.id', $_spells])))->getStatGain();
$setbonus = [];
for ($i = 1; $i < 9; $i++)
{ {
if (empty($v)) $itemQty = $set['bonus'.$i];
unset($setOut['setbonus'][$k]); $itemSpl = $set['spell'.$i];
else if (!$itemQty || !$itemSpl || !isset($jsonBonus[$itemSpl]))
continue;
if ($x = $jsonBonus[$itemSpl]->toJson(Stat::FLAG_ITEM))
{ {
foreach ($v as $sk => $sv) if (!isset($setbonus[$itemQty]))
{ $setbonus[$itemQty] = [];
if ($str = Game::$itemMods[$sk])
{ Util::arraySumByKey($setbonus[$itemQty], $x);
$setOut['setbonus'][$k][$str] = $sv;
unset($setOut['setbonus'][$k][$sk]);
}
}
} }
} }
if (empty($setOut['setbonus'])) if ($setbonus)
unset($setOut['setbonus']); $setOut['setbonus'] = $setbonus;
$itemsetOut[$setOut['id']] = $setOut; $itemsetOut[$setOut['id']] = $setOut;
} }

View File

@@ -122,23 +122,23 @@ if (!CLI)
} }
$result[$tabIdx]['t'][$talentIdx] = array( $result[$tabIdx]['t'][$talentIdx] = array(
'i' => $i, 'i' => $i, // talent id
'n' => $n, 'n' => $n, // talent name
'm' => $m, 'm' => $m, // maxRank
'd' => $d, 'd' => $d, // [descriptions]
's' => $s, 's' => $s, // [spellIds]
'x' => $x, 'x' => $x, // col #
'y' => $y, 'y' => $y, // row #
'j' => $j 'j' => $j // spell mods applied when used in profiler
); );
if (isset($r)) if (isset($r)) // [reqTalentId, reqRank]
$result[$tabIdx]['t'][$talentIdx]['r'] = $r; $result[$tabIdx]['t'][$talentIdx]['r'] = $r;
if (!empty($t)) if (!empty($t)) // talentspell tooltip
$result[$tabIdx]['t'][$talentIdx]['t'] = $t; $result[$tabIdx]['t'][$talentIdx]['t'] = $t;
if (!empty($f)) if (!empty($f)) // [petFamilyId]
$result[$tabIdx]['t'][$talentIdx]['f'] = $f; $result[$tabIdx]['t'][$talentIdx]['f'] = $f;
if ($class) if ($class)

View File

@@ -9,17 +9,17 @@ if (!CLI)
class ItemStatSetup extends ItemList class ItemStatSetup extends ItemList
{ {
private $statCols = []; private $relSpells = [];
private $relEnchants = [];
public function __construct($start, $limit, array $ids, array $enchStats) public function __construct($start, $limit, array $ids, array $relEnchants, array $relSpells)
{ {
$this->statCols = DB::Aowow()->selectCol('SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_NAME` LIKE "%item_stats"');
$this->queryOpts['i']['o'] = 'i.id ASC'; $this->queryOpts['i']['o'] = 'i.id ASC';
unset($this->queryOpts['is']); // do not reference the stats table we are going to write to unset($this->queryOpts['is']); // do not reference the stats table we are going to write to
$conditions = array( $conditions = array(
['i.id', $start, '>'], ['i.id', $start, '>'],
['class', [ITEM_CLASS_WEAPON, ITEM_CLASS_GEM, ITEM_CLASS_ARMOR, ITEM_CLASS_CONSUMABLE]], ['class', [ITEM_CLASS_WEAPON, ITEM_CLASS_GEM, ITEM_CLASS_ARMOR, ITEM_CLASS_CONSUMABLE, ITEM_CLASS_AMMUNITION]],
$limit $limit
); );
@@ -28,102 +28,26 @@ class ItemStatSetup extends ItemList
parent::__construct($conditions); parent::__construct($conditions);
$this->enchParsed = $enchStats; $this->relSpells = $relSpells;
$this->relEnchants = $relEnchants;
} }
public function writeStatsTable() public function writeStatsTable()
{ {
$enchantments = []; // buffer Ids for lookup id => src; src>0: socketBonus; src<0: gemEnchant foreach ($this->iterate() as $id => $curTpl)
foreach ($this->iterate() as $__)
{ {
$this->itemMods[$this->id] = []; $spellIds = [];
// also occurs as seperate field (gets summed in calculation but not in tooltip) for ($i = 1; $i <= 5; $i++)
if ($_ = $this->getField('block')) if ($this->curTpl['spellId'.$i] > 0 && !isset($this->relSpells[$this->curTpl['spellId'.$i]]) && (($this->curTpl['class'] == ITEM_CLASS_CONSUMABLE && $this->curTpl['spellTrigger'.$i] == SPELL_TRIGGER_USE) || $this->curTpl['spellTrigger'.$i] == SPELL_TRIGGER_EQUIP))
$this->itemMods[$this->id][ITEM_MOD_BLOCK_VALUE] = $_; $spellIds[] = $this->curTpl['spellId'.$i];
// convert itemMods to stats if ($spellIds) // array_merge kills the keys
for ($h = 1; $h <= 10; $h++) $this->relSpells = array_replace($this->relSpells, DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM ?_spell WHERE id IN (?a)', $spellIds));
{
$mod = $this->curTpl['statType'.$h];
$val = $this->curTpl['statValue'.$h];
if (!$mod || !$val)
continue;
Util::arraySumByKey($this->itemMods[$this->id], [$mod => $val]); // fromItem: itemMods, spell, enchants from template - fromJson: calculated stats (feralAP, dps, ...)
} if ($stats = (new StatsContainer($this->relSpells, $this->relEnchants))->fromItem($curTpl)->fromJson($this->json[$id])->toJson(Stat::FLAG_ITEM | Stat::FLAG_SERVERSIDE))
DB::Aowow()->query('INSERT INTO ?_item_stats (?#) VALUES (?a)', array_merge(['type', 'typeId'], array_keys($stats)), array_merge([Type::ITEM, $this->id], array_values($stats)));
// convert spells to stats
$equipSpells = [];
for ($h = 1; $h <= 5; $h++)
{
if ($this->curTpl['spellId'.$h] <= 0)
continue;
// armor & weapons only onEquip && consumables only onUse
if (!(in_array($this->curTpl['class'], [ITEM_CLASS_WEAPON, ITEM_CLASS_ARMOR]) && $this->curTpl['spellTrigger'.$h] == SPELL_TRIGGER_EQUIP) &&
!( $this->curTpl['class'] == ITEM_CLASS_CONSUMABLE && $this->curTpl['spellTrigger'.$h] == SPELL_TRIGGER_USE))
continue;
$equipSpells[] = $this->curTpl['spellId'.$h];
}
if ($equipSpells)
{
$eqpSplList = new SpellList(array(['s.id', $equipSpells]));
foreach ($eqpSplList->getStatGain() as $stats)
Util::arraySumByKey($this->itemMods[$this->id], $stats);
}
// prepare: convert enchantments to stats
if (!empty($this->json[$this->id]['socketbonus']))
$enchantments[$this->json[$this->id]['socketbonus']][] = $this->id;
if ($geId = $this->curTpl['gemEnchantmentId'])
$enchantments[$geId][] = -$this->id;
}
// execute: convert enchantments to stats
// and merge enchantments back
foreach ($enchantments as $eId => $items)
{
if (empty($this->enchParsed[$eId]))
continue;
foreach ($items as $item)
{
if ($item > 0) // apply socketBonus
$this->json[$item]['socketbonusstat'] = $this->enchParsed[$eId];
else /* if ($item < 0) */ // apply gemEnchantment
Util::arraySumByKey($this->json[-$item], $this->enchParsed[$eId]);
}
}
// collect data and write to DB
foreach ($this->iterate() as $__)
{
$updateFields = ['type' => Type::ITEM, 'typeId' => $this->id];
foreach (@$this->json[$this->id] as $k => $v)
{
if (!in_array($k, $this->statCols) || !$v || $k == 'id')
continue;
$updateFields[$k] = number_format($v, 2, '.', '');
}
if (isset($this->itemMods[$this->id]))
{
foreach ($this->itemMods[$this->id] as $k => $v)
{
if (!$v)
continue;
if ($str = Game::$itemMods[$k])
$updateFields[$str] = number_format($v, 2, '.', '');
}
}
DB::Aowow()->query('REPLACE INTO ?_item_stats (?#) VALUES (?a)', array_keys($updateFields), array_values($updateFields));
} }
} }
} }
@@ -135,127 +59,50 @@ SqlGen::register(new class extends SetupScript
protected $tblDependencyAowow = ['items', 'spell']; protected $tblDependencyAowow = ['items', 'spell'];
protected $dbcSourceFiles = ['spellitemenchantment']; protected $dbcSourceFiles = ['spellitemenchantment'];
private function enchantment_stats() : array private $relSpells = [];
private function enchantment_stats(?int &$total = 0, ?int &$effective = 0) : array
{ {
$statCols = DB::Aowow()->selectCol('SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_NAME` LIKE "%item_stats"'); $enchants = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM dbc_spellitemenchantment');
$enchants = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM dbc_spellitemenchantment'); $spells = [];
$spells = []; $result = [];
$spellStats = []; $effective = 0;
$total = count($enchants);
foreach ($enchants as $eId => $e) foreach ($enchants as $eId => $e)
{ for ($i = 1; $i <= 3; $i++)
for ($i = 1; $i <=3; $i++) if ($e['object'.$i] > 0 && $e['type'.$i] == ENCHANTMENT_TYPE_EQUIP_SPELL)
{
// trigger: onEquip + valid SpellId
if ($e['object'.$i] > 0 && $e['type'.$i] == 3)
$spells[] = $e['object'.$i]; $spells[] = $e['object'.$i];
}
}
if ($spells) if ($spells)
$spellStats = (new SpellList(array(['id', $spells], Cfg::get('SQL_LIMIT_NONE'))))->getStatGain(); $this->relSpells = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM ?_spell WHERE id IN (?a)', $spells);
$result = [];
foreach ($enchants as $eId => $e) foreach ($enchants as $eId => $e)
{ if ($result[$eId] = (new StatsContainer($this->relSpells))->fromEnchantment($e)->toJson(Stat::FLAG_ITEM | Stat::FLAG_SERVERSIDE))
// parse stats
$result[$eId] = [];
for ($h = 1; $h <= 3; $h++)
{ {
$obj = (int)$e['object'.$h]; DB::Aowow()->query('INSERT INTO ?_item_stats (?#) VALUES (?a)', array_merge(['type', 'typeId'], array_keys($result[$eId])), array_merge([Type::ENCHANTMENT, $eId], array_values($result[$eId])));
$val = (int)$e['amount'.$h]; $effective++;
switch ($e['type'.$h])
{
case 6: // TYPE_TOTEM +AmountX as DPS (Rockbiter)
$result[$eId]['dps'] = $val; // we do not use dps as itemMod, so apply it directly
$obj = null;
break;
case 2: // TYPE_DAMAGE +AmountX damage
$obj = ITEM_MOD_WEAPON_DMG;
break;
// case 1: // TYPE_COMBAT_SPELL proc spell from ObjectX (amountX == procChance)
// case 7: // TYPE_USE_SPELL Engineering gadgets
case 3: // TYPE_EQUIP_SPELL Spells from ObjectX (use of amountX?)
if (!empty($spellStats[$obj]))
foreach ($spellStats[$obj] as $mod => $val)
if ($str = Game::$itemMods[$mod])
Util::arraySumByKey($result[$eId], [$str => $val]);
$obj = null;
break;
case 4: // TYPE_RESISTANCE +AmountX resistance for ObjectX School
switch ($obj)
{
case 0: // Physical
$obj = ITEM_MOD_ARMOR;
break;
case 1: // Holy
$obj = ITEM_MOD_HOLY_RESISTANCE;
break;
case 2: // Fire
$obj = ITEM_MOD_FIRE_RESISTANCE;
break;
case 3: // Nature
$obj = ITEM_MOD_NATURE_RESISTANCE;
break;
case 4: // Frost
$obj = ITEM_MOD_FROST_RESISTANCE;
break;
case 5: // Shadow
$obj = ITEM_MOD_SHADOW_RESISTANCE;
break;
case 6: // Arcane
$obj = ITEM_MOD_ARCANE_RESISTANCE;
break;
default:
$obj = null;
}
break;
case 5: // TYPE_STAT +AmountX for Statistic by type of ObjectX
if ($obj < 2) // [mana, health] are on [0, 1] respectively and are expected on [1, 2] ..
$obj++; // 0 is weaponDmg .. ehh .. i messed up somewhere
break; // stats are directly assigned below
case 8: // TYPE_PRISMATIC_SOCKET Extra Sockets AmountX as socketCount (ignore)
$result[$eId]['nsockets'] = $val; // there is no itemmod for sockets, so apply it directly
default: // TYPE_NONE dnd stuff; skip assignment below
$obj = null;
}
if ($obj !== null)
if ($str = Game::$itemMods[$obj]) // check if we use these mods
Util::arraySumByKey($result[$eId], [$str => $val]);
} }
$updateCols = ['type' => Type::ENCHANTMENT, 'typeId' => $eId]; return $enchants;
foreach ($result[$eId] as $k => $v)
{
if (!in_array($k, $statCols) || !$v || $k == 'id')
continue;
$updateCols[$k] = number_format($v, 2, '.', '');
}
DB::Aowow()->query('REPLACE INTO ?_item_stats (?#) VALUES (?a)', array_keys($updateCols), array_values($updateCols));
}
return $result;
} }
public function generate(array $ids = []) : bool public function generate(array $ids = []) : bool
{ {
$offset = 0; DB::Aowow()->query('TRUNCATE ?_item_stats');
CLI::write(' - applying stats for enchantments'); CLI::write(' - applying stats for enchantments');
$enchStats = $this->enchantment_stats();
CLI::write(' '.count($enchStats).' enchantments parsed'); $enchStats = $this->enchantment_stats($total, $effective);
CLI::write(' '.$effective.'+'.($total - $effective).' enchantments parsed');
CLI::write(' - applying stats for items'); CLI::write(' - applying stats for items');
$i = 0; $i = 0;
$offset = 0;
while (true) while (true)
{ {
$items = new ItemStatSetup($offset, SqlGen::$sqlBatchSize, $ids, $enchStats); $items = new ItemStatSetup($offset, SqlGen::$sqlBatchSize, $ids, $enchStats, $this->relSpells);
if ($items->error) if ($items->error)
break; break;

View File

@@ -213,22 +213,16 @@ SqlGen::register(new class extends SetupScript
/* calc statbonuses */ /* calc statbonuses */
/********************/ /********************/
$gains = $spells = $mods = []; $spells = [];
for ($i = 1; $i < 9; $i++) for ($i = 1; $i < 9; $i++)
if ($setData['spellId'.$i] > 0 && $setData['itemCount'.$i] > 0) if ($setData['spellId'.$i] > 0 && $setData['itemCount'.$i] > 0)
$spells[$i] = [$setData['spellId'.$i], $setData['itemCount'.$i]]; $spells[$i] = [$setData['spellId'.$i], $setData['itemCount'.$i]];
$bonusSpells = new SpellList(array(['s.id', array_column($spells, 0)])); $bonusSpells = new SpellList(array(['s.id', array_column($spells, 0)]));
$mods = $bonusSpells->getStatGain();
$spells = array_pad($spells, 8, [0, 0]); $spells = array_pad($spells, 8, [0, 0]);
for ($i = 1; $i < 9; $i++)
if ($setData['itemCount'.$i] > 0 && !empty($mods[$setData['spellId'.$i]]))
$gains[$setData['itemCount'.$i]] = $mods[$setData['spellId'.$i]];
$row['bonusParsed'] = serialize($gains);
foreach (array_column($spells, 0) as $idx => $spellId) foreach (array_column($spells, 0) as $idx => $spellId)
$row['spell'.($idx+1)] = $spellId; $row['spell'.($idx+1)] = $spellId;
foreach (array_column($spells, 1) as $idx => $nItems) foreach (array_column($spells, 1) as $idx => $nItems)

View File

@@ -0,0 +1,91 @@
ALTER TABLE `aowow_itemset`
DROP COLUMN `bonusParsed`;
DROP TABLE IF EXISTS `aowow_item_stats`;
CREATE TABLE `aowow_item_stats` (
`type` smallint(5) unsigned NOT NULL,
`typeId` mediumint(8) NOT NULL,
`nsockets` tinyint(3) unsigned NULL,
`dps` float(8,2) NULL,
`damagetype` tinyint(4) NULL,
`dmgmin1` mediumint(5) unsigned NULL,
`dmgmax1` mediumint(5) unsigned NULL,
`speed` float(8,2) NULL,
`mledps` float(8,2) NULL,
`mledmgmin` mediumint(5) unsigned NULL,
`mledmgmax` mediumint(5) unsigned NULL,
`mlespeed` float(8,2) NULL,
`rgddps` float(8,2) NULL,
`rgddmgmin` mediumint(5) unsigned NULL,
`rgddmgmax` mediumint(5) unsigned NULL,
`rgdspeed` float(8,2) NULL,
`dmg` float(8,2) NULL,
`mana` mediumint(6) NULL,
`health` mediumint(6) NULL,
`agi` mediumint(6) NULL,
`str` mediumint(6) NULL,
`int` mediumint(6) NULL,
`spi` mediumint(6) NULL,
`sta` mediumint(6) NULL,
`energy` mediumint(6) NULL,
`rage` mediumint(6) NULL,
`focus` mediumint(6) NULL,
`runic` mediumint(6) NULL,
`defrtng` mediumint(6) NULL,
`dodgertng` mediumint(6) NULL,
`parryrtng` mediumint(6) NULL,
`blockrtng` mediumint(6) NULL,
`mlehitrtng` mediumint(6) NULL,
`rgdhitrtng` mediumint(6) NULL,
`splhitrtng` mediumint(6) NULL,
`mlecritstrkrtng` mediumint(6) NULL,
`rgdcritstrkrtng` mediumint(6) NULL,
`splcritstrkrtng` mediumint(6) NULL,
`_mlehitrtng` mediumint(6) NULL,
`_rgdhitrtng` mediumint(6) NULL,
`_splhitrtng` mediumint(6) NULL,
`_mlecritstrkrtng` mediumint(6) NULL,
`_rgdcritstrkrtng` mediumint(6) NULL,
`_splcritstrkrtng` mediumint(6) NULL,
`mlehastertng` mediumint(6) NULL,
`rgdhastertng` mediumint(6) NULL,
`splhastertng` mediumint(6) NULL,
`hitrtng` mediumint(6) NULL,
`critstrkrtng` mediumint(6) NULL,
`_hitrtng` mediumint(6) NULL,
`_critstrkrtng` mediumint(6) NULL,
`resirtng` mediumint(6) NULL,
`hastertng` mediumint(6) NULL,
`exprtng` mediumint(6) NULL,
`atkpwr` mediumint(6) NULL,
`mleatkpwr` mediumint(6) NULL,
`rgdatkpwr` mediumint(6) NULL,
`feratkpwr` mediumint(6) NULL,
`splheal` mediumint(6) NULL,
`spldmg` mediumint(6) NULL,
`manargn` mediumint(6) NULL,
`armorpenrtng` mediumint(6) NULL,
`splpwr` mediumint(6) NULL,
`healthrgn` mediumint(6) NULL,
`splpen` mediumint(6) NULL,
`block` mediumint(6) NULL,
`mastrtng` mediumint(6) NULL,
`armor` mediumint(6) NULL,
`armorbonus` mediumint(6) NULL,
`firres` mediumint(6) NULL,
`frores` mediumint(6) NULL,
`holres` mediumint(6) NULL,
`shares` mediumint(6) NULL,
`natres` mediumint(6) NULL,
`arcres` mediumint(6) NULL,
`firsplpwr` mediumint(6) NULL,
`frosplpwr` mediumint(6) NULL,
`holsplpwr` mediumint(6) NULL,
`shasplpwr` mediumint(6) NULL,
`natsplpwr` mediumint(6) NULL,
`arcsplpwr` mediumint(6) NULL,
PRIMARY KEY (`type`,`typeId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
UPDATE `aowow_dbversion`
SET `sql` = CONCAT(IFNULL(`sql`, ''), ' item_stats');

View File

@@ -169,7 +169,7 @@ var fi_filters = {
{ id: 87, name: 'reagentforability', type: 'profession' }, { id: 87, name: 'reagentforability', type: 'profession' },
{ id: 63, name: 'buyprice', type: 'num', noweights: 1 }, { id: 63, name: 'buyprice', type: 'num', noweights: 1 },
{ id: 154, name: 'refundable', type: 'yn' }, { id: 154, name: 'refundable', type: 'yn' },
{ id: 165, name: 'repaircost', type: 'num' }, { id: 165, name: 'repaircost', type: 'num', noweights: 1 },
{ id: 64, name: 'sellprice', type: 'num', noweights: 1 }, { id: 64, name: 'sellprice', type: 'num', noweights: 1 },
{ id: 157, name: 'smartloot', type: 'yn' }, { id: 157, name: 'smartloot', type: 'yn' },
{ id: 6, name: 'startsquest', type: 'side' }, { id: 6, name: 'startsquest', type: 'side' },

View File

@@ -4772,7 +4772,7 @@ var LANG = {
su_addscale: "Gewichtung", su_addscale: "Gewichtung",
su_additem: "Gegenstand", su_additem: "Gegenstand",
su_addset: "Set", su_addset: "Set",
su_toggle: "Klickt, um die Darstellung anzuzeigen", su_toggle: "Klickt, um die Darstellung umzuschalten",
su_preset: "Vorlage: ", su_preset: "Vorlage: ",
su_name: "Name: ", su_name: "Name: ",