From d16b08bb297dd1522040a01d1ba8958fba52d155 Mon Sep 17 00:00:00 2001 From: Sarjuuk Date: Tue, 2 Apr 2024 23:06:09 +0200 Subject: [PATCH] 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 --- includes/defines.php | 120 ++--- includes/game.php | 49 -- includes/kernel.php | 1 + includes/stats.class.php | 668 ++++++++++++++++++++++++ includes/types/enchantment.class.php | 115 +--- includes/types/item.class.php | 143 +++-- includes/types/spell.class.php | 282 ++-------- includes/utilities.php | 11 - pages/enchantment.php | 37 +- pages/enchantments.php | 4 +- pages/item.php | 12 +- setup/db_structure.sql | 167 +++--- setup/tools/filegen/enchants.func.php | 2 +- setup/tools/filegen/gems.func.php | 6 +- setup/tools/filegen/itemsets.func.php | 44 +- setup/tools/filegen/talentCalc.func.php | 22 +- setup/tools/sqlgen/item_stats.func.php | 229 ++------ setup/tools/sqlgen/itemset.func.php | 8 +- setup/updates/1718468660_01.sql | 91 ++++ static/js/filters.js | 2 +- static/js/locale_dede.js | 2 +- 21 files changed, 1140 insertions(+), 875 deletions(-) create mode 100644 includes/stats.class.php create mode 100644 setup/updates/1718468660_01.sql diff --git a/includes/defines.php b/includes/defines.php index 3c5ab8203..6db2a4998 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -422,6 +422,52 @@ define('STAT_INTELLECT', 3); 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 define('POWER_MANA', 0); define('POWER_RAGE', 1); @@ -777,70 +823,16 @@ define('ITEM_FLAG_ACCOUNTBOUND', 0x08000000); define('ITEM_FLAG_MILLABLE', 0x20000000); -// ItemMod (differ slightly from client, see g_statToJson) -define('ITEM_MOD_WEAPON_DMG', 0); // < custom -define('ITEM_MOD_MANA', 1); -define('ITEM_MOD_HEALTH', 2); -define('ITEM_MOD_AGILITY', 3); // stats v -define('ITEM_MOD_STRENGTH', 4); -define('ITEM_MOD_INTELLECT', 5); -define('ITEM_MOD_SPIRIT', 6); -define('ITEM_MOD_STAMINA', 7); -define('ITEM_MOD_ENERGY', 8); // powers v -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); +// ItemEnchantment types +define('ENCHANTMENT_TYPE_NONE', 0); +define('ENCHANTMENT_TYPE_COMBAT_SPELL', 1); +define('ENCHANTMENT_TYPE_DAMAGE', 2); +define('ENCHANTMENT_TYPE_EQUIP_SPELL', 3); +define('ENCHANTMENT_TYPE_RESISTANCE', 4); +define('ENCHANTMENT_TYPE_STAT', 5); +define('ENCHANTMENT_TYPE_TOTEM', 6); +define('ENCHANTMENT_TYPE_USE_SPELL', 7); +define('ENCHANTMENT_TYPE_PRISMATIC_SOCKET', 8); // Spell Effects and Auras define('SPELL_EFFECT_NONE', 0); diff --git a/includes/game.php b/includes/game.php index 44e4f158d..532c065b6 100644 --- a/includes/game.php +++ b/includes/game.php @@ -33,17 +33,6 @@ class Game 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( -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], @@ -146,19 +135,6 @@ class Game '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( // null Warrior Paladin Hunter Rogue Priest DK Shaman Mage Warlock null Druid 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 ); - 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) { // Any diff --git a/includes/kernel.php b/includes/kernel.php index be8a6ae54..7a35b476e 100644 --- a/includes/kernel.php +++ b/includes/kernel.php @@ -23,6 +23,7 @@ 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/utilities.php'; // helper functions require_once 'includes/config.class.php'; // Config holder diff --git a/includes/stats.class.php b/includes/stats.class.php new file mode 100644 index 000000000..615d11b2e --- /dev/null +++ b/includes/stats.class.php @@ -0,0 +1,668 @@ + ['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 []; + } +} + +?> diff --git a/includes/types/enchantment.class.php b/includes/types/enchantment.class.php index b8af0c4df..8cddd361d 100644 --- a/includes/types/enchantment.class.php +++ b/includes/types/enchantment.class.php @@ -35,35 +35,25 @@ public function __construct($conditions = []) if ($curTpl['object'.$i] <= 0) 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)); - $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]; break; - case 3: - $curTpl['spells'][$i] = [$curTpl['object'.$i], 1, $curTpl['charges'], 0]; + case ENCHANTMENT_TYPE_EQUIP_SPELL: + $curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_EQUIP, $curTpl['charges'], 0]; $this->relSpells[] = $curTpl['object'.$i]; break; - case 7: - $curTpl['spells'][$i] = [$curTpl['object'.$i], 0, $curTpl['charges'], 0]; + case ENCHANTMENT_TYPE_USE_SPELL: + $curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_USE, $curTpl['charges'], 0]; $this->relSpells[] = $curTpl['object'.$i]; break; } } - // floats are fetched as string from db :< - $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']); + $this->jsonStats[$this->id] = (new StatsContainer)->fromJson($curTpl, true); } if ($this->relSpells) @@ -99,18 +89,18 @@ public function getListviewData($addInfoMask = 0x0) if ($this->curTpl['requiredLevel'] > 0) $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 - if ($s[1] == 2 || $s[1] == 0) - $data[$this->id]['spells'][$s[0]] = $s[2]; + if ($trigger == SPELL_TRIGGER_HIT || $trigger == SPELL_TRIGGER_USE) + $data[$this->id]['spells'][$spellId] = $charges; // 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) { $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 @@ public function getListviewData($addInfoMask = 0x0) if (!$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; } - public function getStatGain($addScalingKeys = false) + public function getStatGainForCurrent() : array { - $data = []; - - 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; + return $this->jsonStats[$this->id]->toJson(); } public function getRelSpell($id) diff --git a/includes/types/item.class.php b/includes/types/item.class.php index 7912c5c9c..7d8afa772 100644 --- a/includes/types/item.class.php +++ b/includes/types/item.class.php @@ -13,7 +13,7 @@ class ItemList extends BaseType public static $dataTable = '?_items'; public $json = []; - public $itemMods = []; + public $jsonStats = []; public $rndEnchIds = []; public $subItems = []; @@ -48,7 +48,9 @@ public function __construct($conditions = [], $miscData = null) // fix missing icons $_curTpl['iconString'] = $_curTpl['iconString'] ?: DEFAULT_ICON; + // from json to json .. the gentle fuckups of legacy code integration $this->initJsonStats(); + $this->jsonStats[$this->id] = (new StatsContainer())->fromJson($_curTpl, true)->toJson(Stat::FLAG_ITEM /* | Stat::FLAG_SERVERSIDE */); if ($miscData) { @@ -280,7 +282,7 @@ public function getExtendedCost($filter = [], &$reqRating = []) 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_VENDOR (0x04): costs-obj, when displayed as vendor * ITEMINFO_GEM (0x10): gem infos and score @@ -294,7 +296,10 @@ public function getListviewData($addInfoMask = 0x0, $miscData = null) $this->initSubItems(); if ($addInfoMask & ITEMINFO_JSON) + { $this->extendJsonStats(); + Util::arraySumByKey($data, $this->jsonStats); + } $extCosts = []; if ($addInfoMask & ITEMINFO_VENDOR) @@ -321,9 +326,6 @@ public function getListviewData($addInfoMask = 0x0, $miscData = null) 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)) $data[$this->id]['avgmoney'] = $_; @@ -679,7 +681,7 @@ public function renderTooltip($interactive = false, $subOf = 0, $enhance = []) $x .= sprintf(Lang::item('damage', 'single', $sc2 ? 3 : 2), $this->curTpl['dmgMin2'], $sc2 ? Lang::game('sc', $sc2) : null).'
'; if ($_class == ITEM_CLASS_WEAPON) - $x .= ''.sprintf(Lang::item('dps'), $dps).'
'; // do not use localized format here! + $x .= ''.Lang::item('dps', [$dps]).'
'; // display FeralAttackPower if set if ($fap = $this->getFeralAP()) @@ -1126,7 +1128,7 @@ public function renderTooltip($interactive = false, $subOf = 0, $enhance = []) { $xCraft = ''; if ($desc = $this->getField('description', true)) - $x .= ''.Lang::item('trigger', 0).' '.$desc.'
'; + $x .= ''.Lang::item('trigger', SPELL_TRIGGER_USE).' '.$desc.'
'; // recipe handling (some stray Techniques have subclass == 0), place at bottom of tooltipp if ($_class == ITEM_CLASS_RECIPE || $this->curTpl['bagFamily'] == 16) @@ -1208,7 +1210,7 @@ public function renderTooltip($interactive = false, $subOf = 0, $enhance = []) $this->curTpl['scalingStatValue'] // scaleFlags ); } - else // may still use level dependant ratings + else // may still use level dependent ratings { array_push($link, $causesScaling ? MAX_LEVEL : 1, // scaleMaxLevel @@ -1311,12 +1313,6 @@ public function extendJsonStats() 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 if (!empty($this->json[$this->id]['socketbonus'])) $enchantments[$this->json[$this->id]['socketbonus']][] = $this->id; @@ -1353,28 +1349,28 @@ public function extendJsonStats() 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 - $useSpells = []; for ($h = 1; $h <= 5; $h++) { if ($this->curTpl['spellId'.$h] <= 0) continue; - if ($this->curTpl['class'] != ITEM_CLASS_CONSUMABLE || $this->curTpl['spellTrigger'.$h]) + if ($this->curTpl['spellTrigger'.$h] != SPELL_TRIGGER_USE) continue; - $useSpells[] = $this->curTpl['spellId'.$h]; - } - - if ($useSpells) - { - $eqpSplList = new SpellList(array(['s.id', $useSpells])); - foreach ($eqpSplList->getStatGain() as $stat) - Util::arraySumByKey($onUseStats, $stat); + if ($spell = DB::Aowow()->selectRow( + 'SELECT effect1AuraId, effect1MiscValue, effect1BasePoints, effect1DieSides, effect2AuraId, effect2MiscValue, effect2BasePoints, effect2DieSides, effect3AuraId, effect3MiscValue, effect3BasePoints, effect3DieSides + FROM ?_spell + WHERE id = ?d', + $this->curTpl['spellId'.$h])) + $onUseStats->fromSpell($spell); } return $onUseStats; @@ -1414,32 +1410,33 @@ private function canTeachSpell() return true; } - private function getFeralAP() + private function getFeralAP() : float { // must be weapon if ($this->curTpl['class'] != ITEM_CLASS_WEAPON) - return 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; + return 0.0; // thats fucked up.. if (!$this->curTpl['delay']) - return 0; + return 0.0; // must have enough damage $dps = ($this->curTpl['tplDmgMin1'] + $this->curTpl['dmgMin2'] + $this->curTpl['tplDmgMax1'] + $this->curTpl['dmgMax2']) / (2 * $this->curTpl['delay'] / 1000); - if ($dps < 54.8) - return 0; + if ($dps <= 54.8) + return 0.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; - return round(($dps - 54.8) * 14, 0); + // 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) @@ -1449,29 +1446,28 @@ private function parseRating($type, $value, $interactive = false, &$scaling = fa $reqLvl = $this->curTpl['requiredLevel'] > 1 ? $this->curTpl['requiredLevel'] : MAX_LEVEL; $level = min(max($reqLvl, $ssdLvl), MAX_LEVEL); - // unknown rating - if (in_array($type, [2, 8, 9, 10, 11]) || $type > ITEM_MOD_BLOCK_VALUE || $type < 0) + // unknown rating + if (!Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $type)) { if (User::isInGroup(U_GROUP_EMPLOYEE)) return sprintf(Lang::item('statType', count(Lang::item('statType')) - 1), $type, $value); else return null; } - // level independant Bonus - else if (in_array($type, Game::$lvlIndepRating)) - return Lang::item('trigger', 1).str_replace('%d', ''.$value, Lang::item('statType', $type)); + + // level independent Bonus + if (Stat::isLevelIndependent($type)) + return Lang::item('trigger', SPELL_TRIGGER_EQUIP).str_replace('%d', ''.$value, Lang::item('statType', $type)); + // rating-Bonuses - else - { - $scaling = true; + $scaling = true; - if ($interactive) - $js = ' ('.sprintf(Util::$changeLevelString, Util::setRatingLevel($level, $type, $value)).')'; - else - $js = ' ('.Util::setRatingLevel($level, $type, $value).')'; + if ($interactive) + $js = ' ('.sprintf(Util::$changeLevelString, Util::setRatingLevel($level, $type, $value)).')'; + else + $js = ' ('.Util::setRatingLevel($level, $type, $value).')'; - return Lang::item('trigger', 1).str_replace('%d', ''.$value.$js, Lang::item('statType', $type)); - } + return Lang::item('trigger', SPELL_TRIGGER_EQUIP).str_replace('%d', ''.$value.$js, Lang::item('statType', $type)); } private function getSSDMod($type) @@ -1583,7 +1579,7 @@ public function initSubItems() { $this->rndEnchIds[$eId] = array( 'text' => $enchants->getField('name', true), - 'stats' => $enchants->getStatGain(true) + 'stats' => $enchants->getStatGainForCurrent() ); } @@ -1677,7 +1673,7 @@ private function initJsonStats() 'subclass' => $this->curTpl['subClass'], 'subsubclass' => $this->curTpl['subSubClass'], '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'], 'slotbak' => $this->curTpl['slotBak'], 'level' => $this->curTpl['itemLevel'], @@ -1690,7 +1686,7 @@ private function initJsonStats() 'frores' => $this->curTpl['resFrost'], 'shares' => $this->curTpl['resShadow'], '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'], 'dura' => $this->curTpl['durability'], 'itemset' => $this->curTpl['itemset'], @@ -1709,8 +1705,8 @@ private function initJsonStats() $json['dmgtype1'] = $this->curTpl['dmgType1']; $json['dmgmin1'] = $this->curTpl['tplDmgMin1'] + $this->curTpl['dmgMin2']; $json['dmgmax1'] = $this->curTpl['tplDmgMax1'] + $this->curTpl['dmgMax2']; - $json['speed'] = number_format($this->curTpl['delay'] / 1000, 2); - $json['dps'] = !floatVal($json['speed']) ? 0 : number_format(($json['dmgmin1'] + $json['dmgmax1']) / (2 * $json['speed']), 1); + $json['speed'] = round($this->curTpl['delay'] / 1000, 2); + $json['dps'] = $json['speed'] ? round(($json['dmgmin1'] + $json['dmgmax1']) / (2 * $json['speed']), 1) : 0; if (in_array($json['subclass'], [2, 3, 18, 19])) { @@ -1734,7 +1730,7 @@ private function initJsonStats() 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']); 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 foreach ($json as $k => $v) @@ -1809,7 +1805,7 @@ class ItemListFilter extends Filter 14 => -1, 15 => -1 ), - 128 => array( // source + 128 => array( // source 1 => true, // Any 2 => false, // None 3 => 1, // Crafted @@ -2036,18 +2032,15 @@ public function createConditionsForWeights() foreach ($this->fiData['v']['wt'] as $k => $v) { - $str = Util::$itemFilter[$v]; - $qty = intVal($this->fiData['v']['wtv'][$k]); - - if ($str == 'rgdspeed') // dont need no duplicate column - $str = 'speed'; - - if ($str == 'mledps') // todo (med): unify rngdps and mledps to dps - $str = 'dps'; + if ($idx = Stat::getIndexFrom(Stat::IDX_FILTER_CR_ID, $v)) + { + $str = Stat::getJsonString($idx); + $qty = intVal($this->fiData['v']['wtv'][$k]); - $select[] = '(`is`.`'.$str.'` * '.$qty.')'; - $this->wtCnd[] = ['is.'.$str, 0, '>']; - $wtSum += $qty; + $select[] = '(IFNULL(`is`.`'.$str.'`, 0) * '.$qty.')'; + $this->wtCnd[] = ['is.'.$str, 0, '>']; + $wtSum += $qty; + } } if (count($this->wtCnd) > 1) @@ -2681,7 +2674,7 @@ protected function cbWeightKeyCheck(&$v) if (preg_match('/\W/i', $v)) return false; - return isset(Util::$itemFilter[$v]); + return Stat::getIndexFrom(Stat::IDX_FILTER_CR_ID, $v) > 0; } } diff --git a/includes/types/spell.class.php b/includes/types/spell.class.php index 7eb332bdf..72745cfc2 100644 --- a/includes/types/spell.class.php +++ b/includes/types/spell.class.php @@ -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_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( SPELL_AURA_DUMMY, SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_HEALTH_FUNNEL, SPELL_AURA_SCHOOL_ABSORB, SPELL_AURA_MANA_SHIELD, @@ -190,196 +193,19 @@ public static function getName($id) // end static use // required for item-comparison - public function getStatGain() + public function getStatGain() : array { $data = []; foreach ($this->iterate() as $__) { - $stats = []; - - for ($i = 1; $i <= 3; $i++) - { - $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])) - { - 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; - } + $data[$this->id] = new StatsContainer(); - // 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; - } + foreach ($this->canEnchantmentItem() as $i) + $data[$this->id]->fromDB(Type::ENCHANTMENT, $this->curTpl['effect'.$i.'MiscValue']); - 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; + // todo: should enchantments be included here...? + $data[$this->id]->fromSpell($this->curTpl); } return $data; @@ -390,21 +216,10 @@ public function getProfilerMods() // 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; }'; - $data = $this->getStatGain(); // flat gains - foreach ($data as $id => &$spellData) + $data = []; // flat gains + foreach ($this->getStatGain() as $id => $spellData) { - foreach ($spellData as $modId => $val) - { - 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]); - } + $data[$id] = $spellData->toJson(STAT::FLAG_ITEM | STAT::FLAG_PROFILER); // apply weapon restrictions $this->getEntry($id); @@ -414,8 +229,8 @@ public function getProfilerMods() if ($class != ITEM_CLASS_WEAPON || !$subClass) continue; - foreach ($spellData as $json => $pts) - $spellData[$json] = [1, 'functionOf', sprintf($whCheck, $slot, $class, $subClass, $pts)]; + foreach ($data[$id] as $key => $amt) + $data[$id][$key] = [1, 'functionOf', sprintf($whCheck, $slot, $class, $subClass, $amt)]; } // 4 possible modifiers found @@ -470,22 +285,9 @@ public function getProfilerMods() foreach ($this->iterate() as $id => $__) { - // Priest: Spirit of Redemption is a spell but also a passive. *yaaayyyy* - if (($this->getField('cuFlags') & SPELL_CU_TALENTSPELL) && $id != 20711) - 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]; }']; + // kept for reference - if (($this->getField('cuFlags') & SPELL_CU_TALENTSPELL) && $id != 20711) + if (!($this->getField('attributes0') & SPELL_ATTR0_PASSIVE)) continue; - } for ($i = 1; $i < 4; $i++) { @@ -502,6 +304,15 @@ public function getProfilerMods() 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) { case SPELL_AURA_MOD_RESISTANCE_PCT: @@ -524,7 +335,9 @@ public function getProfilerMods() $modXByStat($data[$id], null, $pts); else if ($mv < 0) // all stats 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; case SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT: $mv = $mv ?: SPELL_MAGIC_SCHOOLS; @@ -579,15 +392,18 @@ public function getProfilerMods() else if ($mv == POWER_ENERGY) $data[$id]['energy'] = [$pts / 100, 'percentOf', 'energy']; else if ($mv == POWER_MANA) - $data[$id]['mana'] = [$pts / 100, 'percentOf', 'mana']; + $data[$id]['mana'] = [$pts / 100, 'percentOf', 'mana']; 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) - $data[$id]['runic'] = [$pts / 100, 'percentOf', 'runic']; + $data[$id]['runic'] = [$pts / 100, 'percentOf', 'runic']; break; case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT: $data[$id]['health'] = [$pts / 100, 'percentOf', 'health']; 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: $data[$id]['block'] = [$pts / 100, 'percentOf', 'block']; break; @@ -910,7 +726,7 @@ private function calculateAmountForCurrent(int $effIdx, ?SpellList $altTpl = nul ]; } - public function canCreateItem() + public function canCreateItem() : array { $idx = []; for ($i = 1; $i < 4; $i++) @@ -921,7 +737,7 @@ public function canCreateItem() return $idx; } - public function canTriggerSpell() + public function canTriggerSpell() : array { $idx = []; for ($i = 1; $i < 4; $i++) @@ -932,7 +748,7 @@ public function canTriggerSpell() return $idx; } - public function canTeachSpell() + public function canTeachSpell() : array { $idx = []; for ($i = 1; $i < 4; $i++) @@ -943,6 +759,16 @@ public function canTeachSpell() 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() { return $this->curTpl['attributes1'] & (SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2); @@ -1243,16 +1069,16 @@ private function resolveVariableString($varParts) } // Aura giving combat ratings - $rType = 0; + $rType = []; if ($aura == SPELL_AURA_MOD_RATING) - if ($rType = Game::itemModByRatingMask($mv)) + if ($rType = StatsContainer::convertCombatRating($mv)) $this->scaling[$this->id] = true; // Aura end if ($rType) { $result[2] = '%s (%s)'; - $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*$} 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)) @@ -1336,16 +1162,16 @@ private function resolveVariableString($varParts) eval("\$max = $max $op $oparg;"); } // Aura giving combat ratings - $rType = 0; + $rType = []; if ($aura == SPELL_AURA_MOD_RATING) - if ($rType = Game::itemModByRatingMask($mv)) + if ($rType = StatsContainer::convertCombatRating($mv)) $this->scaling[$this->id] = true; // Aura end if ($rType) { $result[2] = '%s (%s)'; - $result[4] = $rType; + $result[4] = $rType[0]; // could be multiple ratings in theory, but not expected to be } else if (($modStrMin || $modStrMax) && $this->interactive) { @@ -1659,7 +1485,7 @@ functions in use .. caseInsensitive // step 4: find and eliminate regular variables $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]; while (preg_match('/([\d\.]+)([^\d]*)(\$[l|]:*)([^:]*):([^;]*);/i', $data, $m)) { diff --git a/includes/utilities.php b/includes/utilities.php index 6fea7ee7e..b5a6571d4 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -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 ); - 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( 'shoulderMultiplier', 'trinketMultiplier', 'weaponMultiplier', 'primBudged', 'rangedMultiplier', 'clothShoulderArmor', 'leatherShoulderArmor', 'mailShoulderArmor', diff --git a/pages/enchantment.php b/pages/enchantment.php index 652a7c734..ef490bb68 100644 --- a/pages/enchantment.php +++ b/pages/enchantment.php @@ -98,30 +98,29 @@ protected function generateContent() switch ($_ty) { - case 1: - case 3: - case 7: - $sArr = $this->subject->getField('spells')[$i]; - $spl = $this->subject->getRelSpell($sArr[0]); - $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]['proc'] = $sArr[3]; + case ENCHANTMENT_TYPE_COMBAT_SPELL: + case ENCHANTMENT_TYPE_EQUIP_SPELL: + case ENCHANTMENT_TYPE_USE_SPELL: + [$spellId, $trigger, $charges, $procChance] = $this->subject->getField('spells')[$i]; + $spl = $this->subject->getRelSpell($spellId); + $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'] = $procChance; $this->effects[$i]['value'] = $_qty ?: null; $this->effects[$i]['icon'] = array( - 'name' => !$spl ? Util::ucFirst(Lang::game('spell')).' #'.$sArr[0] : Util::localizedString($spl, 'name'), - 'id' => $sArr[0], - 'count' => $sArr[2] + 'name' => !$spl ? Util::ucFirst(Lang::game('spell')).' #'.$spellId : Util::localizedString($spl, 'name'), + 'id' => $spellId, + 'count' => $charges ); break; - case 5: - 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 - - $this->effects[$i]['tip'] = [$_obj, Game::$itemMods[$_obj]]; + case ENCHANTMENT_TYPE_STAT: + if ($idx = Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $_obj)) + if ($jsonStat = Stat::getJsonString($idx)) + $this->effects[$i]['tip'] = [$_obj, $jsonStat]; // DO NOT BREAK! - case 2: - case 6: - case 8: - case 4: + case ENCHANTMENT_TYPE_DAMAGE: + case ENCHANTMENT_TYPE_TOTEM: + case ENCHANTMENT_TYPE_PRISMATIC_SOCKET: + 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]['value'] = $_qty; if ($_ty == 4) diff --git a/pages/enchantments.php b/pages/enchantments.php index 2f772610a..d66a3b8e2 100644 --- a/pages/enchantments.php +++ b/pages/enchantments.php @@ -59,8 +59,8 @@ protected function generateContent() $this->filter['initData']['sc'] = $x; $xCols = $this->filterObj->getExtraCols(); - foreach (Util::$itemFilter as $fiId => $str) - if (array_column($tabData['data'], $str)) + foreach (Stat::getFilterCriteriumIdFor() as $idx => $fiId) + if (array_column($tabData['data'], Stat::getJsonString($idx))) $xCols[] = $fiId; if (array_column($tabData['data'], 'dmg')) diff --git a/pages/item.php b/pages/item.php index feb51c01a..0b0f2ec36 100644 --- a/pages/item.php +++ b/pages/item.php @@ -1112,12 +1112,12 @@ protected function generateXML() if ($_ = $this->subject->getField('cooldown')) // cooldown $json['cooldown'] = $_ / 1000; - foreach ($this->subject->itemMods[$this->typeId] as $mod => $qty) - $json[$mod] = $qty; + Util::arraySumByKey($json, $this->subject->jsonStats[$this->typeId] ?? []); foreach ($this->subject->json[$this->typeId] as $name => $qty) - if (in_array($name, Util::$itemFilter)) - $json[$name] = $qty; + if ($idx = Stat::getIndexFrom(Stat::IDX_JSON_STR, $name)) + if (Stat::getFilterCriteriumId($idx)) + $json[$name] = $qty; $xml->addChild('jsonEquip')->addCData(substr(json_encode($json), 1, -1)); @@ -1125,8 +1125,8 @@ protected function generateXML() if ($onUse = $this->subject->getOnUseStats()) { $j = ''; - foreach ($onUse as $idx => $qty) - $j .= ',"'.Game::$itemMods[$idx].'":'.$qty; + foreach ($onUse->toJson() as $key => $amt) + $j .= ',"'.$key.'":'.$amt; $xml->addChild('jsonUse')->addCData(substr($j, 1)); } diff --git a/setup/db_structure.sql b/setup/db_structure.sql index 89f400ccb..fdb3ad0e5 100644 --- a/setup/db_structure.sql +++ b/setup/db_structure.sql @@ -1103,88 +1103,88 @@ DROP TABLE IF EXISTS `aowow_item_stats`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8mb4 */; CREATE TABLE `aowow_item_stats` ( - `type` smallint unsigned NOT NULL, - `typeId` mediumint unsigned NOT NULL, - `nsockets` tinyint unsigned NOT NULL DEFAULT 0, - `dmgmin1` smallint unsigned NOT NULL DEFAULT 0, - `dmgmax1` smallint unsigned NOT NULL DEFAULT 0, - `speed` float(8,2) NOT NULL DEFAULT 0.00, - `dps` float(8,2) NOT NULL DEFAULT 0.00, - `mledmgmin` smallint unsigned NOT NULL DEFAULT 0, - `mledmgmax` smallint unsigned NOT NULL DEFAULT 0, - `mlespeed` float(8,2) NOT NULL DEFAULT 0.00, - `mledps` float(8,2) NOT NULL DEFAULT 0.00, - `rgddmgmin` smallint unsigned NOT NULL DEFAULT 0, - `rgddmgmax` smallint unsigned NOT NULL DEFAULT 0, - `rgdspeed` float(8,2) NOT NULL DEFAULT 0.00, - `rgddps` float(8,2) NOT NULL DEFAULT 0.00, - `dmg` float(8,2) NOT NULL DEFAULT 0.00, - `damagetype` tinyint NOT NULL DEFAULT 0, - `mana` mediumint NOT NULL DEFAULT 0, - `health` mediumint NOT NULL DEFAULT 0, - `agi` mediumint NOT NULL DEFAULT 0, - `str` mediumint NOT NULL DEFAULT 0, - `int` mediumint NOT NULL DEFAULT 0, - `spi` mediumint NOT NULL DEFAULT 0, - `sta` mediumint NOT NULL DEFAULT 0, - `energy` mediumint NOT NULL DEFAULT 0, - `rage` mediumint NOT NULL DEFAULT 0, - `focus` mediumint NOT NULL DEFAULT 0, - `runicpwr` mediumint NOT NULL DEFAULT 0, - `defrtng` mediumint NOT NULL DEFAULT 0, - `dodgertng` mediumint NOT NULL DEFAULT 0, - `parryrtng` mediumint NOT NULL DEFAULT 0, - `blockrtng` mediumint NOT NULL DEFAULT 0, - `mlehitrtng` mediumint NOT NULL DEFAULT 0, - `rgdhitrtng` mediumint NOT NULL DEFAULT 0, - `splhitrtng` mediumint NOT NULL DEFAULT 0, - `mlecritstrkrtng` mediumint NOT NULL DEFAULT 0, - `rgdcritstrkrtng` mediumint NOT NULL DEFAULT 0, - `splcritstrkrtng` mediumint NOT NULL DEFAULT 0, - `_mlehitrtng` mediumint NOT NULL DEFAULT 0, - `_rgdhitrtng` mediumint NOT NULL DEFAULT 0, - `_splhitrtng` mediumint NOT NULL DEFAULT 0, - `_mlecritstrkrtng` mediumint NOT NULL DEFAULT 0, - `_rgdcritstrkrtng` mediumint NOT NULL DEFAULT 0, - `_splcritstrkrtng` mediumint NOT NULL DEFAULT 0, - `mlehastertng` mediumint NOT NULL DEFAULT 0, - `rgdhastertng` mediumint NOT NULL DEFAULT 0, - `splhastertng` mediumint NOT NULL DEFAULT 0, - `hitrtng` mediumint NOT NULL DEFAULT 0, - `critstrkrtng` mediumint NOT NULL DEFAULT 0, - `_hitrtng` mediumint NOT NULL DEFAULT 0, - `_critstrkrtng` mediumint NOT NULL DEFAULT 0, - `resirtng` mediumint NOT NULL DEFAULT 0, - `hastertng` mediumint NOT NULL DEFAULT 0, - `exprtng` mediumint NOT NULL DEFAULT 0, - `atkpwr` mediumint NOT NULL DEFAULT 0, - `mleatkpwr` mediumint NOT NULL DEFAULT 0, - `rgdatkpwr` mediumint NOT NULL DEFAULT 0, - `feratkpwr` mediumint NOT NULL DEFAULT 0, - `splheal` mediumint NOT NULL DEFAULT 0, - `spldmg` mediumint NOT NULL DEFAULT 0, - `manargn` mediumint NOT NULL DEFAULT 0, - `armorpenrtng` mediumint NOT NULL DEFAULT 0, - `splpwr` mediumint NOT NULL DEFAULT 0, - `healthrgn` mediumint NOT NULL DEFAULT 0, - `splpen` mediumint NOT NULL DEFAULT 0, - `block` mediumint NOT NULL DEFAULT 0, - `mastrtng` mediumint NOT NULL DEFAULT 0, - `armor` mediumint NOT NULL DEFAULT 0, - `armorbonus` mediumint NOT NULL DEFAULT 0, - `firres` mediumint NOT NULL DEFAULT 0, - `frores` mediumint NOT NULL DEFAULT 0, - `holres` mediumint NOT NULL DEFAULT 0, - `shares` mediumint NOT NULL DEFAULT 0, - `natres` mediumint NOT NULL DEFAULT 0, - `arcres` mediumint NOT NULL DEFAULT 0, - `firsplpwr` mediumint NOT NULL DEFAULT 0, - `frosplpwr` mediumint NOT NULL DEFAULT 0, - `holsplpwr` mediumint NOT NULL DEFAULT 0, - `shasplpwr` mediumint NOT NULL DEFAULT 0, - `natsplpwr` mediumint NOT NULL DEFAULT 0, - `arcsplpwr` mediumint NOT NULL DEFAULT 0, - PRIMARY KEY (`typeId`,`type`) + `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; /*!40101 SET character_set_client = @saved_cs_client */; @@ -1601,7 +1601,6 @@ CREATE TABLE `aowow_itemset` ( `bonusText_loc4` text DEFAULT NULL, `bonusText_loc6` text DEFAULT NULL, `bonusText_loc8` text DEFAULT NULL, - `bonusParsed` varchar(256) DEFAULT NULL COMMENT 'serialized itemMods', `npieces` tinyint NOT NULL DEFAULT 0, `minLevel` smallint NOT NULL DEFAULT 0, `maxLevel` smallint NOT NULL DEFAULT 0, @@ -3228,7 +3227,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_dbversion` WRITE; /*!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 */; UNLOCK TABLES; diff --git a/setup/tools/filegen/enchants.func.php b/setup/tools/filegen/enchants.func.php index 30c9b646f..efd043c86 100644 --- a/setup/tools/filegen/enchants.func.php +++ b/setup/tools/filegen/enchants.func.php @@ -145,7 +145,7 @@ function enchants() 'skill' => -1, // modified if skill 'slots' => [], // determined per spell but set per item 'enchantment' => $enchantments->getField('name', true), - 'jsonequip' => $enchantments->getStatGain(), + 'jsonequip' => $enchantments->getStatGainForCurrent(), 'temp' => 0, // always 0 'classes' => 0, // modified by item 'gearscore' => 0 // set later diff --git a/setup/tools/filegen/gems.func.php b/setup/tools/filegen/gems.func.php index 810631a43..7eb15f213 100644 --- a/setup/tools/filegen/gems.func.php +++ b/setup/tools/filegen/gems.func.php @@ -73,7 +73,7 @@ function gems() { 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; } @@ -82,10 +82,10 @@ function gems() 'quality' => $pop['quality'], 'icon' => strToLower($pop['icon']), 'enchantment' => $enchantments->getField('name', true), - 'jsonequip' => $enchantments->getStatGain(), + 'jsonequip' => $enchantments->getStatGainForCurrent(), 'colors' => $pop['colors'], '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']) ); } diff --git a/setup/tools/filegen/itemsets.func.php b/setup/tools/filegen/itemsets.func.php index a0ba2466e..80ccd69eb 100644 --- a/setup/tools/filegen/itemsets.func.php +++ b/setup/tools/filegen/itemsets.func.php @@ -81,40 +81,38 @@ function itemsets() if ($set['item'.$i]) $setOut['pieces'][] = $set['item'.$i]; + $_spells = []; for ($i = 1; $i < 9; $i++) { - if (!$set['bonus'.$i] || !$set['spell'.$i]) + if (!$set['spell'.$i] || isset($jsonBonus[$set['spell'.$i]])) continue; - // costy and locale-independant -> cache - 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]]); + $_spells[] = $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)) - unset($setOut['setbonus'][$k]); - else + $itemQty = $set['bonus'.$i]; + $itemSpl = $set['spell'.$i]; + if (!$itemQty || !$itemSpl || !isset($jsonBonus[$itemSpl])) + continue; + + if ($x = $jsonBonus[$itemSpl]->toJson(Stat::FLAG_ITEM)) { - foreach ($v as $sk => $sv) - { - if ($str = Game::$itemMods[$sk]) - { - $setOut['setbonus'][$k][$str] = $sv; - unset($setOut['setbonus'][$k][$sk]); - } - } + if (!isset($setbonus[$itemQty])) + $setbonus[$itemQty] = []; + + Util::arraySumByKey($setbonus[$itemQty], $x); } } - if (empty($setOut['setbonus'])) - unset($setOut['setbonus']); + if ($setbonus) + $setOut['setbonus'] = $setbonus; $itemsetOut[$setOut['id']] = $setOut; } diff --git a/setup/tools/filegen/talentCalc.func.php b/setup/tools/filegen/talentCalc.func.php index f3b75ab3c..a8e9865aa 100644 --- a/setup/tools/filegen/talentCalc.func.php +++ b/setup/tools/filegen/talentCalc.func.php @@ -122,23 +122,23 @@ function talentCalc() } $result[$tabIdx]['t'][$talentIdx] = array( - 'i' => $i, - 'n' => $n, - 'm' => $m, - 'd' => $d, - 's' => $s, - 'x' => $x, - 'y' => $y, - 'j' => $j + 'i' => $i, // talent id + 'n' => $n, // talent name + 'm' => $m, // maxRank + 'd' => $d, // [descriptions] + 's' => $s, // [spellIds] + 'x' => $x, // col # + 'y' => $y, // row # + 'j' => $j // spell mods applied when used in profiler ); - if (isset($r)) + if (isset($r)) // [reqTalentId, reqRank] $result[$tabIdx]['t'][$talentIdx]['r'] = $r; - if (!empty($t)) + if (!empty($t)) // talentspell tooltip $result[$tabIdx]['t'][$talentIdx]['t'] = $t; - if (!empty($f)) + if (!empty($f)) // [petFamilyId] $result[$tabIdx]['t'][$talentIdx]['f'] = $f; if ($class) diff --git a/setup/tools/sqlgen/item_stats.func.php b/setup/tools/sqlgen/item_stats.func.php index 30485c7d3..e635bc6b6 100644 --- a/setup/tools/sqlgen/item_stats.func.php +++ b/setup/tools/sqlgen/item_stats.func.php @@ -9,17 +9,17 @@ 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'; unset($this->queryOpts['is']); // do not reference the stats table we are going to write to $conditions = array( ['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 ); @@ -28,102 +28,26 @@ public function __construct($start, $limit, array $ids, array $enchStats) parent::__construct($conditions); - $this->enchParsed = $enchStats; + $this->relSpells = $relSpells; + $this->relEnchants = $relEnchants; } public function writeStatsTable() { - $enchantments = []; // buffer Ids for lookup id => src; src>0: socketBonus; src<0: gemEnchant - - foreach ($this->iterate() as $__) + foreach ($this->iterate() as $id => $curTpl) { - $this->itemMods[$this->id] = []; - - // also occurs as seperate field (gets summed in calculation but not in tooltip) - if ($_ = $this->getField('block')) - $this->itemMods[$this->id][ITEM_MOD_BLOCK_VALUE] = $_; - - // convert itemMods to stats - for ($h = 1; $h <= 10; $h++) - { - $mod = $this->curTpl['statType'.$h]; - $val = $this->curTpl['statValue'.$h]; - if (!$mod || !$val) - continue; + $spellIds = []; - Util::arraySumByKey($this->itemMods[$this->id], [$mod => $val]); - } - - // convert spells to stats - $equipSpells = []; - for ($h = 1; $h <= 5; $h++) - { - if ($this->curTpl['spellId'.$h] <= 0) - continue; + for ($i = 1; $i <= 5; $i++) + 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)) + $spellIds[] = $this->curTpl['spellId'.$i]; - // 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; + if ($spellIds) // array_merge kills the keys + $this->relSpells = array_replace($this->relSpells, DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM ?_spell WHERE id IN (?a)', $spellIds)); - $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)); + // 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))); } } } @@ -135,127 +59,50 @@ public function writeStatsTable() protected $tblDependencyAowow = ['items', 'spell']; 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'); - $spells = []; - $spellStats = []; + $enchants = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM dbc_spellitemenchantment'); + $spells = []; + $result = []; + $effective = 0; + $total = count($enchants); foreach ($enchants as $eId => $e) - { - for ($i = 1; $i <=3; $i++) - { - // trigger: onEquip + valid SpellId - if ($e['object'.$i] > 0 && $e['type'.$i] == 3) + for ($i = 1; $i <= 3; $i++) + if ($e['object'.$i] > 0 && $e['type'.$i] == ENCHANTMENT_TYPE_EQUIP_SPELL) $spells[] = $e['object'.$i]; - } - } 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) - { - // parse stats - $result[$eId] = []; - for ($h = 1; $h <= 3; $h++) - { - $obj = (int)$e['object'.$h]; - $val = (int)$e['amount'.$h]; - - 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]; - foreach ($result[$eId] as $k => $v) + if ($result[$eId] = (new StatsContainer($this->relSpells))->fromEnchantment($e)->toJson(Stat::FLAG_ITEM | Stat::FLAG_SERVERSIDE)) { - if (!in_array($k, $statCols) || !$v || $k == 'id') - continue; - - $updateCols[$k] = number_format($v, 2, '.', ''); + 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]))); + $effective++; } - DB::Aowow()->query('REPLACE INTO ?_item_stats (?#) VALUES (?a)', array_keys($updateCols), array_values($updateCols)); - } - - return $result; + return $enchants; } public function generate(array $ids = []) : bool { - $offset = 0; + DB::Aowow()->query('TRUNCATE ?_item_stats'); 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'); $i = 0; + $offset = 0; while (true) { - $items = new ItemStatSetup($offset, SqlGen::$sqlBatchSize, $ids, $enchStats); + $items = new ItemStatSetup($offset, SqlGen::$sqlBatchSize, $ids, $enchStats, $this->relSpells); if ($items->error) break; diff --git a/setup/tools/sqlgen/itemset.func.php b/setup/tools/sqlgen/itemset.func.php index b6f0f152f..93db167c3 100644 --- a/setup/tools/sqlgen/itemset.func.php +++ b/setup/tools/sqlgen/itemset.func.php @@ -213,22 +213,16 @@ public function generate(array $ids = []) : bool /* calc statbonuses */ /********************/ - $gains = $spells = $mods = []; + $spells = []; for ($i = 1; $i < 9; $i++) if ($setData['spellId'.$i] > 0 && $setData['itemCount'.$i] > 0) $spells[$i] = [$setData['spellId'.$i], $setData['itemCount'.$i]]; $bonusSpells = new SpellList(array(['s.id', array_column($spells, 0)])); - $mods = $bonusSpells->getStatGain(); $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) $row['spell'.($idx+1)] = $spellId; foreach (array_column($spells, 1) as $idx => $nItems) diff --git a/setup/updates/1718468660_01.sql b/setup/updates/1718468660_01.sql new file mode 100644 index 000000000..ba6fef739 --- /dev/null +++ b/setup/updates/1718468660_01.sql @@ -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'); diff --git a/static/js/filters.js b/static/js/filters.js index 7e5e3b693..a552b404d 100644 --- a/static/js/filters.js +++ b/static/js/filters.js @@ -169,7 +169,7 @@ var fi_filters = { { id: 87, name: 'reagentforability', type: 'profession' }, { id: 63, name: 'buyprice', type: 'num', noweights: 1 }, { 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: 157, name: 'smartloot', type: 'yn' }, { id: 6, name: 'startsquest', type: 'side' }, diff --git a/static/js/locale_dede.js b/static/js/locale_dede.js index 831286467..e00e27eb4 100644 --- a/static/js/locale_dede.js +++ b/static/js/locale_dede.js @@ -4772,7 +4772,7 @@ var LANG = { su_addscale: "Gewichtung", su_additem: "Gegenstand", su_addset: "Set", - su_toggle: "Klickt, um die Darstellung anzuzeigen", + su_toggle: "Klickt, um die Darstellung umzuschalten", su_preset: "Vorlage: ", su_name: "Name: ",