Files
aowow/includes/game/chrstatistics.php
Sarjuuk dd838fa994 Misc/Doc
* add several ItemMods unusd by client but still found in item_template as comment
2025-10-12 00:57:09 +02:00

701 lines
39 KiB
PHP

<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
abstract class Stat // based on g_statToJson
{
public const HEALTH = 1;
public const MANA = 2; // note: mana is on idx 0 from 5.0 onwards; idx 2 is empty
public const AGILITY = 3;
public const STRENGTH = 4;
public const INTELLECT = 5;
public const SPIRIT = 6;
public const STAMINA = 7;
public const ENERGY = 8;
public const RAGE = 9;
public const FOCUS = 10;
public const RUNIC_POWER = 11;
public const DEFENSE_RTG = 12;
public const DODGE_RTG = 13;
public const PARRY_RTG = 14;
public const BLOCK_RTG = 15;
public const MELEE_HIT_RTG = 16;
public const RANGED_HIT_RTG = 17;
public const SPELL_HIT_RTG = 18;
public const MELEE_CRIT_RTG = 19;
public const RANGED_CRIT_RTG = 20;
public const SPELL_CRIT_RTG = 21;
public const MELEE_HIT_TAKEN_RTG = 22;
public const RANGED_HIT_TAKEN_RTG = 23;
public const SPELL_HIT_TAKEN_RTG = 24;
public const MELEE_CRIT_TAKEN_RTG = 25;
public const RANGED_CRIT_TAKEN_RTG = 26;
public const SPELL_CRIT_TAKEN_RTG = 27;
public const MELEE_HASTE_RTG = 28;
public const RANGED_HASTE_RTG = 29;
public const SPELL_HASTE_RTG = 30;
public const HIT_RTG = 31;
public const CRIT_RTG = 32;
public const HIT_TAKEN_RTG = 33;
public const CRIT_TAKEN_RTG = 34;
public const RESILIENCE_RTG = 35;
public const HASTE_RTG = 36;
public const EXPERTISE_RTG = 37;
public const ATTACK_POWER = 38;
public const RANGED_ATTACK_POWER = 39;
public const FERAL_ATTACK_POWER = 40; // unused in wow-3.3.5
public const HEALING_SPELL_POWER = 41; // deprecated
public const DAMAGE_SPELL_POWER = 42; // deprecated
public const MANA_REGENERATION = 43;
public const ARMOR_PENETRATION_RTG = 44;
public const SPELL_POWER = 45;
public const HEALTH_REGENERATION = 46; // no differentiation between IC (item mods / spells) and OOC (from spirit) for Profiler
public const SPELL_PENETRATION = 47;
public const BLOCK = 48;
// public const MASTERY_RTG = 49; // not in wow-3.3.5
public const ARMOR = 50;
public const FIRE_RESISTANCE = 51;
public const FROST_RESISTANCE = 52;
public const HOLY_RESISTANCE = 53;
public const SHADOW_RESISTANCE = 54;
public const NATURE_RESISTANCE = 55;
public const ARCANE_RESISTANCE = 56;
public const FIRE_SPELL_POWER = 57;
public const FROST_SPELL_POWER = 58;
public const HOLY_SPELL_POWER = 59;
public const SHADOW_SPELL_POWER = 60;
public const NATURE_SPELL_POWER = 61;
public const ARCANE_SPELL_POWER = 62;
// v for stats lookups v
public const WEAPON_DAMAGE = 200; // +weapon dmg from enchantments
public const WEAPON_DAMAGE_TYPE = 201;
public const WEAPON_DAMAGE_MIN = 202;
public const WEAPON_DAMAGE_MAX = 203;
public const WEAPON_SPEED = 204;
public const WEAPON_DPS = 205; // also +weapon dps from enchantments (rockbiter)
public const MELEE_DAMAGE_MIN = 206;
public const MELEE_DAMAGE_MAX = 207;
public const MELEE_SPEED = 208;
public const MELEE_DPS = 209;
public const RANGED_DAMAGE_MIN = 210;
public const RANGED_DAMAGE_MAX = 211;
public const RANGED_SPEED = 212;
public const RANGED_DPS = 213;
public const EXTRA_SOCKETS = 214;
public const ARMOR_BONUS = 215;
public const MELEE_ATTACK_POWER = 216;
// v only seen in profiler v
public const EXPERTISE = 500;
public const ARMOR_PENETRATION_PCT = 501;
public const MELEE_HIT_PCT = 502;
public const MELEE_CRIT_PCT = 503;
public const MELEE_HASTE_PCT = 504;
public const RANGED_HIT_PCT = 505;
public const RANGED_CRIT_PCT = 506;
public const RANGED_HASTE_PCT = 507;
public const SPELL_HIT_PCT = 508;
public const SPELL_CRIT_PCT = 509;
public const SPELL_HASTE_PCT = 510;
public const MANA_REGENERATION_SPI = 511; // mp5 from spirit, excluding other sources
public const MANA_REGENERATION_OC = 512; // mp5 out of combat, excluding other sources
public const MANA_REGENERATION_IC = 513; // mp5 in combat, excluding other sources
public const ARMOR_TOTAL = 514; // ARMOR + ARMOR_BONUS meta category .. can be skipped here like pet* stats?
public const DEFENSE = 515;
public const DODGE_PCT = 516;
public const PARRY_PCT = 517;
public const BLOCK_PCT = 518;
public const RESILIENCE_PCT = 519;
public const FLAG_NONE = 0x00;
public const FLAG_ITEM = 0x01; // found on items
public const FLAG_SERVERSIDE = 0x02; // not included in g_statToJson
public const FLAG_PROFILER = 0x04; // stat used in profiler only
public const FLAG_LVL_SCALING = 0x08; // rating effectivenes scales with level
public const FLAG_FLOAT_VALUE = 0x10; // not an int
public const IDX_JSON_STR = 0;
public const IDX_ITEM_MOD = 1; // granted by items
public const IDX_COMBAT_RATING = 2; // granted by spells + enchantments
public const IDX_FILTER_CR_ID = 3; // also references listview cols
public const IDX_FLAGS = 4;
private static /* array */ $data = array(
self::HEALTH => ['health', ITEM_MOD_HEALTH, null, 115, self::FLAG_ITEM],
self::MANA => ['mana', ITEM_MOD_MANA, null, 116, self::FLAG_ITEM],
self::AGILITY => ['agi', ITEM_MOD_AGILITY, null, 21, self::FLAG_ITEM],
self::STRENGTH => ['str', ITEM_MOD_STRENGTH, null, 20, self::FLAG_ITEM],
self::INTELLECT => ['int', ITEM_MOD_INTELLECT, null, 23, self::FLAG_ITEM],
self::SPIRIT => ['spi', ITEM_MOD_SPIRIT, null, 24, self::FLAG_ITEM],
self::STAMINA => ['sta', ITEM_MOD_STAMINA, null, 22, self::FLAG_ITEM],
self::ENERGY => ['energy', null, null, null, self::FLAG_ITEM],
self::RAGE => ['rage', null, null, null, self::FLAG_ITEM],
self::FOCUS => ['focus', null, null, null, self::FLAG_ITEM],
self::RUNIC_POWER => ['runic', null, null, null, self::FLAG_ITEM | self::FLAG_SERVERSIDE],
self::DEFENSE_RTG => ['defrtng', ITEM_MOD_DEFENSE_SKILL_RATING, CR_DEFENSE_SKILL, 42, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::DODGE_RTG => ['dodgertng', ITEM_MOD_DODGE_RATING, CR_DODGE, 45, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::PARRY_RTG => ['parryrtng', ITEM_MOD_PARRY_RATING, CR_PARRY, 46, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::BLOCK_RTG => ['blockrtng', ITEM_MOD_BLOCK_RATING, CR_BLOCK, 44, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_HIT_RTG => ['mlehitrtng', ITEM_MOD_HIT_MELEE_RATING, CR_HIT_MELEE, 95, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_HIT_RTG => ['rgdhitrtng', ITEM_MOD_HIT_RANGED_RATING, CR_HIT_RANGED, 39, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_HIT_RTG => ['splhitrtng', ITEM_MOD_HIT_SPELL_RATING, CR_HIT_SPELL, 48, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_CRIT_RTG => ['mlecritstrkrtng', ITEM_MOD_CRIT_MELEE_RATING, CR_CRIT_MELEE, 84, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_CRIT_RTG => ['rgdcritstrkrtng', ITEM_MOD_CRIT_RANGED_RATING, CR_CRIT_RANGED, 40, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_CRIT_RTG => ['splcritstrkrtng', ITEM_MOD_CRIT_SPELL_RATING, CR_CRIT_SPELL, 49, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_HIT_TAKEN_RTG => ['_mlehitrtng', ITEM_MOD_HIT_TAKEN_MELEE_RATING, CR_HIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_HIT_TAKEN_RTG => ['_rgdhitrtng', ITEM_MOD_HIT_TAKEN_RANGED_RATING, CR_HIT_TAKEN_RANGED, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_HIT_TAKEN_RTG => ['_splhitrtng', ITEM_MOD_HIT_TAKEN_SPELL_RATING, CR_HIT_TAKEN_SPELL, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_CRIT_TAKEN_RTG => ['_mlecritstrkrtng', ITEM_MOD_CRIT_TAKEN_MELEE_RATING, CR_CRIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_CRIT_TAKEN_RTG => ['_rgdcritstrkrtng', ITEM_MOD_CRIT_TAKEN_RANGED_RATING, CR_CRIT_TAKEN_RANGED, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_CRIT_TAKEN_RTG => ['_splcritstrkrtng', ITEM_MOD_CRIT_TAKEN_SPELL_RATING, CR_CRIT_TAKEN_SPELL, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::MELEE_HASTE_RTG => ['mlehastertng', ITEM_MOD_HASTE_MELEE_RATING, CR_HASTE_MELEE, 78, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RANGED_HASTE_RTG => ['rgdhastertng', ITEM_MOD_HASTE_RANGED_RATING, CR_HASTE_RANGED, 101, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::SPELL_HASTE_RTG => ['splhastertng', ITEM_MOD_HASTE_SPELL_RATING, CR_HASTE_SPELL, 102, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::HIT_RTG => ['hitrtng', ITEM_MOD_HIT_RATING, -CR_HIT_MELEE, 119, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::CRIT_RTG => ['critstrkrtng', ITEM_MOD_CRIT_RATING, -CR_CRIT_MELEE, 96, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::HIT_TAKEN_RTG => ['_hitrtng', ITEM_MOD_HIT_TAKEN_RATING, -CR_HIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::CRIT_TAKEN_RTG => ['_critstrkrtng', ITEM_MOD_CRIT_TAKEN_RATING, -CR_CRIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::RESILIENCE_RTG => ['resirtng', ITEM_MOD_RESILIENCE_RATING, -CR_CRIT_TAKEN_MELEE, 79, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::HASTE_RTG => ['hastertng', ITEM_MOD_HASTE_RATING, -CR_HASTE_MELEE, 103, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
self::EXPERTISE_RTG => ['exprtng', ITEM_MOD_EXPERTISE_RATING, CR_EXPERTISE, 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, CR_ARMOR_PENETRATION, 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', ITEM_MOD_MASTERY_RATING, CR_MASTERY, null, self::FLAG_NONE],
self::ARMOR => ['armor', null,/*ITEM_MOD_EXTRA_ARMOR */null, 41, self::FLAG_ITEM],
self::FIRE_RESISTANCE => ['firres', null,/*ITEM_MOD_FIRE_RESISTANCE */null, 26, self::FLAG_ITEM],
self::FROST_RESISTANCE => ['frores', null,/*ITEM_MOD_FROST_RESISTANCE */null, 28, self::FLAG_ITEM],
self::HOLY_RESISTANCE => ['holres', null,/*ITEM_MOD_HOLY_RESISTANCE */null, 30, self::FLAG_ITEM],
self::SHADOW_RESISTANCE => ['shares', null,/*ITEM_MOD_SHADOW_RESISTANCE*/null, 29, self::FLAG_ITEM],
self::NATURE_RESISTANCE => ['natres', null,/*ITEM_MOD_NATURE_RESISTANCE*/null, 27, self::FLAG_ITEM],
self::ARCANE_RESISTANCE => ['arcres', null,/*ITEM_MOD_ARCANE_RESISTANCE*/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]
);
/* Combat Rating needed for 1% effect at level 60 (Note: Shaman, Druid, Paladin and Death Knight have a /1.3 modifier on HASTE not set here)
* Data taken from gtcombatratings.dbc for level 60 [idx % 100 = 59]
* Corrections from gtoctclasscombatratingscalar.dbc with Warrior as base [idx = ratingId + 1]
* Maybe create this data during setup, but then again it will never change for 3.3.5a
*/
private static $crPerPctPoint = array(
CR_WEAPON_SKILL => 2.50, CR_DEFENSE_SKILL => 1.50, CR_DODGE => 13.80, CR_PARRY => 13.80, CR_BLOCK => 5.00,
CR_HIT_MELEE => 10.00, CR_HIT_RANGED => 10.00, CR_HIT_SPELL => 8.00, CR_CRIT_MELEE => 14.00, CR_CRIT_RANGED => 14.00,
CR_CRIT_SPELL => 14.00, CR_HIT_TAKEN_MELEE => 10.00, CR_HIT_TAKEN_RANGED => 10.00, CR_HIT_TAKEN_SPELL => 8.00, CR_CRIT_TAKEN_MELEE => 28.75,
CR_CRIT_TAKEN_RANGED => 28.75, CR_CRIT_TAKEN_SPELL => 28.75, CR_HASTE_MELEE => 10.00, CR_HASTE_RANGED => 10.00, CR_HASTE_SPELL => 10.00,
CR_WEAPON_SKILL_MAINHAND => 2.50, CR_WEAPON_SKILL_OFFHAND => 2.50, CR_WEAPON_SKILL_RANGED => 2.50, CR_EXPERTISE => 2.50, CR_ARMOR_PENETRATION => 4.69512 / 1.1,
);
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 getRatingPctFactor(int $stat) : float
{
// Note: this makes the weapon skill related combat ratings inaccessible. Is this relevant..?
if (!isset(self::$data[$stat]) || self::$data[$stat][self::IDX_COMBAT_RATING] === null)
return 0.0;
// note: originally any CRIT_TAKEN_RTG stat was set to 0 in favor of RESILIENCE_RTG
// we keep the dbc value and just link RESILIENCE_RTG to CRIT_TAKEN_RTG
// note2: the js expects some stats to be directly mapped to a combat rating that doesn't exist
// picked the next best one in this case and denoted it with a negative value in the $data dump
return self::$crPerPctPoint[abs(self::$data[$stat][self::IDX_COMBAT_RATING])];
}
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 > 0 && (!$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 implements \Countable
{
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;
// if spells grant an equal, non-zero amount of SPELL_DAMAGE and SPELL_HEALING, combine them to SPELL_POWER
// this probably does not affect enchantments
$tmpStore = [];
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($tmpStore, [$idx => $amt]);
}
if (!empty($tmpStore[Stat::HEALING_SPELL_POWER]) && !empty($tmpStore[Stat::DAMAGE_SPELL_POWER]) && $tmpStore[Stat::HEALING_SPELL_POWER] == $tmpStore[Stat::DAMAGE_SPELL_POWER])
{
Util::arraySumByKey($tmpStore, [Stat::SPELL_POWER => $tmpStore[Stat::HEALING_SPELL_POWER]]);
unset($tmpStore[Stat::HEALING_SPELL_POWER]);
unset($tmpStore[Stat::DAMAGE_SPELL_POWER]);
}
Util::arraySumByKey($this->store, $tmpStore);
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!
{
if (Stat::getFlags($idx) & Stat::FLAG_FLOAT_VALUE)
Util::arraySumByKey($this->store, [$idx => (float)$json[$key]]);
else
Util::arraySumByKey($this->store, [$idx => (int)$json[$key]]);
}
if ($pruneFromSrc)
unset($json[$key]);
}
return $this;
}
public function fromDB(int $type, int $typeId, int $fieldFlags = Stat::FLAG_NONE) : 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 = Stat::FLAG_NONE, bool $includeEmpty = true) : array
{
$out = [];
foreach ($this->store as $stat => $amt)
if ((!$outFlags || (Stat::getFlags($stat) & $outFlags)) && ($amt || $includeEmpty))
$out[Stat::getJsonString($stat)] = $amt;
return $out;
}
public function toRaw() : array
{
return $this->store;
}
public function filter(?callable $filterFn = null) : self
{
$this->store = array_filter($this->store, $filterFn, ARRAY_FILTER_USE_BOTH);
return $this;
}
public function count() : int
{
return count($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:
return match ($object)
{
SPELL_SCHOOL_NORMAL => [Stat::ARMOR],
SPELL_SCHOOL_HOLY => [Stat::HOLY_RESISTANCE],
SPELL_SCHOOL_FIRE => [Stat::FIRE_RESISTANCE],
SPELL_SCHOOL_NATURE => [Stat::NATURE_RESISTANCE],
SPELL_SCHOOL_FROST => [Stat::FROST_RESISTANCE],
SPELL_SCHOOL_SHADOW => [Stat::SHADOW_RESISTANCE],
SPELL_SCHOOL_ARCANE => [Stat::ARCANE_RESISTANCE],
default => []
};
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
{
$hitMask = (1 << CR_HIT_MELEE) | (1 << CR_HIT_RANGED) | (1 << CR_HIT_SPELL);
if (($mask & $hitMask) == $hitMask)
return [Stat::HIT_RTG]; // generic hit rating
$critMask = (1 << CR_CRIT_MELEE) | (1 << CR_CRIT_RANGED) | (1 << CR_CRIT_SPELL);
if (($mask & $critMask) == $critMask)
return [Stat::CRIT_RTG]; // generic crit rating
$takentMask = (1 << CR_CRIT_TAKEN_MELEE) | (1 << CR_CRIT_TAKEN_RANGED) | (1 << CR_CRIT_TAKEN_SPELL);
if (($mask & $takentMask) == $takentMask)
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:
return match ($miscValue)
{
STAT_STRENGTH => [Stat::STRENGTH],
STAT_AGILITY => [Stat::AGILITY],
STAT_STAMINA => [Stat::STAMINA],
STAT_INTELLECT => [Stat::INTELLECT],
STAT_SPIRIT => [Stat::SPIRIT],
default => $miscValue < 0 ? [Stat::AGILITY, Stat::STRENGTH, Stat::INTELLECT, Stat::SPIRIT, Stat::STAMINA] : []
};
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
if ($miscValue == SPELL_MAGIC_SCHOOLS)
return [Stat::DAMAGE_SPELL_POWER];
if ($miscValue & (1 << SPELL_SCHOOL_HOLY))
$stats[] = Stat::HOLY_SPELL_POWER;
if ($miscValue & (1 << SPELL_SCHOOL_FIRE))
$stats[] = Stat::FIRE_SPELL_POWER;
if ($miscValue & (1 << SPELL_SCHOOL_NATURE))
$stats[] = Stat::NATURE_SPELL_POWER;
if ($miscValue & (1 << SPELL_SCHOOL_FROST))
$stats[] = Stat::FROST_SPELL_POWER;
if ($miscValue & (1 << SPELL_SCHOOL_SHADOW))
$stats[] = Stat::SHADOW_SPELL_POWER;
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
return match ($miscValue)
{
POWER_ENERGY => [Stat::ENERGY],
POWER_RAGE => [Stat::RAGE],
POWER_MANA => [Stat::MANA],
POWER_RUNIC_POWER => [Stat::RUNIC_POWER],
default => []
};
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 [];
}
}
?>