Core/CharStats

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

View File

@@ -35,35 +35,25 @@ class EnchantmentList extends BaseType
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 @@ class EnchantmentList extends BaseType
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 @@ class EnchantmentList extends BaseType
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)

View File

@@ -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 @@ class ItemList extends BaseType
// 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 @@ class ItemList extends BaseType
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 @@ class ItemList extends BaseType
$this->initSubItems();
if ($addInfoMask & ITEMINFO_JSON)
{
$this->extendJsonStats();
Util::arraySumByKey($data, $this->jsonStats);
}
$extCosts = [];
if ($addInfoMask & ITEMINFO_VENDOR)
@@ -321,9 +326,6 @@ class ItemList extends BaseType
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 @@ class ItemList extends BaseType
$x .= sprintf(Lang::item('damage', 'single', $sc2 ? 3 : 2), $this->curTpl['dmgMin2'], $sc2 ? Lang::game('sc', $sc2) : null).'<br />';
if ($_class == ITEM_CLASS_WEAPON)
$x .= '<!--dps-->'.sprintf(Lang::item('dps'), $dps).'<br />'; // do not use localized format here!
$x .= '<!--dps-->'.Lang::item('dps', [$dps]).'<br />';
// display FeralAttackPower if set
if ($fap = $this->getFeralAP())
@@ -1126,7 +1128,7 @@ class ItemList extends BaseType
{
$xCraft = '';
if ($desc = $this->getField('description', true))
$x .= '<span class="q2">'.Lang::item('trigger', 0).' <a href="?spell='.$this->curTpl['spellId2'].'">'.$desc.'</a></span><br />';
$x .= '<span class="q2">'.Lang::item('trigger', SPELL_TRIGGER_USE).' <a href="?spell='.$this->curTpl['spellId2'].'">'.$desc.'</a></span><br />';
// recipe handling (some stray Techniques have subclass == 0), place at bottom of tooltipp
if ($_class == ITEM_CLASS_RECIPE || $this->curTpl['bagFamily'] == 16)
@@ -1208,7 +1210,7 @@ class ItemList extends BaseType
$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 @@ class ItemList extends BaseType
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 @@ class ItemList extends BaseType
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 @@ class ItemList extends BaseType
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;
return round(($dps - 54.8) * 14, 0);
$subClasses = [14]; // Misc Weapons
$weaponTypeMask = DB::Aowow()->selectCell('SELECT `weaponTypeMask` FROM ?_classes WHERE `id` = ?d', log(CLASS_DRUID, 2) + 1);
if ($weaponTypeMask)
for ($i = 0; $i < 21; $i++)
if ($weaponTypeMask & (1 << $i))
$subClasses[] = $i;
// cannot be used by druids
if (!in_array($this->curTpl['subClass'], $subClasses))
return 0.0;
return round(($dps - 54.8) * 14);
}
private function parseRating($type, $value, $interactive = false, &$scaling = false)
@@ -1449,29 +1446,28 @@ class ItemList extends BaseType
$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', '<!--rtg'.$type.'-->'.$value, Lang::item('statType', $type));
// level independent Bonus
if (Stat::isLevelIndependent($type))
return Lang::item('trigger', SPELL_TRIGGER_EQUIP).str_replace('%d', '<!--rtg'.$type.'-->'.$value, Lang::item('statType', $type));
// rating-Bonuses
$scaling = true;
if ($interactive)
$js = '&nbsp;<small>('.sprintf(Util::$changeLevelString, Util::setRatingLevel($level, $type, $value)).')</small>';
else
{
$scaling = true;
$js = '&nbsp;<small>('.Util::setRatingLevel($level, $type, $value).')</small>';
if ($interactive)
$js = '&nbsp;<small>('.sprintf(Util::$changeLevelString, Util::setRatingLevel($level, $type, $value)).')</small>';
else
$js = '&nbsp;<small>('.Util::setRatingLevel($level, $type, $value).')</small>';
return Lang::item('trigger', 1).str_replace('%d', '<!--rtg'.$type.'-->'.$value.$js, Lang::item('statType', $type));
}
return Lang::item('trigger', SPELL_TRIGGER_EQUIP).str_replace('%d', '<!--rtg'.$type.'-->'.$value.$js, Lang::item('statType', $type));
}
private function getSSDMod($type)
@@ -1583,7 +1579,7 @@ class ItemList extends BaseType
{
$this->rndEnchIds[$eId] = array(
'text' => $enchants->getField('name', true),
'stats' => $enchants->getStatGain(true)
'stats' => $enchants->getStatGainForCurrent()
);
}
@@ -1677,7 +1673,7 @@ class ItemList extends BaseType
'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 @@ class ItemList extends BaseType
'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 @@ class ItemList extends BaseType
$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 @@ class ItemList extends BaseType
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 @@ class ItemListFilter extends Filter
foreach ($this->fiData['v']['wt'] as $k => $v)
{
$str = Util::$itemFilter[$v];
$qty = intVal($this->fiData['v']['wtv'][$k]);
if ($idx = Stat::getIndexFrom(Stat::IDX_FILTER_CR_ID, $v))
{
$str = Stat::getJsonString($idx);
$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';
$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 @@ class ItemListFilter extends Filter
if (preg_match('/\W/i', $v))
return false;
return isset(Util::$itemFilter[$v]);
return Stat::getIndexFrom(Stat::IDX_FILTER_CR_ID, $v) > 0;
}
}

View File

@@ -58,6 +58,9 @@ class SpellList extends BaseType
SPELL_EFFECT_SCHOOL_DAMAGE, SPELL_EFFECT_ENVIRONMENTAL_DAMAGE, SPELL_EFFECT_POWER_DRAIN, SPELL_EFFECT_HEALTH_LEECH, SPELL_EFFECT_POWER_BURN,
SPELL_EFFECT_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 @@ class SpellList extends BaseType
// end static use
// required for item-comparison
public function getStatGain()
public function getStatGain() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$stats = [];
$data[$this->id] = new StatsContainer();
for ($i = 1; $i <= 3; $i++)
{
$pts = $this->calculateAmountForCurrent($i)[1];
$mv = $this->curTpl['effect'.$i.'MiscValue'];
$au = $this->curTpl['effect'.$i.'AuraId'];
foreach ($this->canEnchantmentItem() as $i)
$data[$this->id]->fromDB(Type::ENCHANTMENT, $this->curTpl['effect'.$i.'MiscValue']);
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;
}
// Holy resistance only if explicitly specified (shouldn't even exist...?)
if ($mv == (1 << SPELL_SCHOOL_HOLY))
{
Util::arraySumByKey($stats, [ITEM_MOD_HOLY_RESISTANCE => $pts]);
break;
}
for ($j = 0; $j < 7; $j++)
{
if (($mv & (1 << $j)) == 0)
continue;
switch ($j)
{
case SPELL_SCHOOL_FIRE:
Util::arraySumByKey($stats, [ITEM_MOD_FIRE_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_NATURE:
Util::arraySumByKey($stats, [ITEM_MOD_NATURE_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_FROST:
Util::arraySumByKey($stats, [ITEM_MOD_FROST_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_SHADOW:
Util::arraySumByKey($stats, [ITEM_MOD_SHADOW_RESISTANCE => $pts]);
break;
case SPELL_SCHOOL_ARCANE:
Util::arraySumByKey($stats, [ITEM_MOD_ARCANE_RESISTANCE => $pts]);
break;
}
}
break;
case SPELL_AURA_PERIODIC_HEAL: // hp5
case SPELL_AURA_MOD_REGEN:
case SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT:
Util::arraySumByKey($stats, [ITEM_MOD_HEALTH_REGEN => $pts]);
break;
case SPELL_AURA_MOD_POWER_REGEN: // mp5
Util::arraySumByKey($stats, [ITEM_MOD_MANA_REGENERATION => $pts]);
break;
case SPELL_AURA_MOD_ATTACK_POWER:
Util::arraySumByKey($stats, [ITEM_MOD_ATTACK_POWER => $pts]);
break; // ?carries over to rngatkpwr?
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
Util::arraySumByKey($stats, [ITEM_MOD_RANGED_ATTACK_POWER => $pts]);
break;
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
Util::arraySumByKey($stats, [ITEM_MOD_BLOCK_VALUE => $pts]);
break;
case SPELL_AURA_MOD_EXPERTISE:
Util::arraySumByKey($stats, [ITEM_MOD_EXPERTISE_RATING => $pts]);
break;
case SPELL_AURA_MOD_TARGET_RESISTANCE:
if ($mv == 0x7C && $pts < 0)
Util::arraySumByKey($stats, [ITEM_MOD_SPELL_PENETRATION => -$pts]);
break;
}
}
$data[$this->id] = $stats;
// todo: should enchantments be included here...?
$data[$this->id]->fromSpell($this->curTpl);
}
return $data;
@@ -390,21 +216,10 @@ class SpellList extends BaseType
// 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 @@ class SpellList extends BaseType
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,23 +285,10 @@ class SpellList extends BaseType
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)
// kept for reference - if (($this->getField('cuFlags') & SPELL_CU_TALENTSPELL) && $id != 20711)
if (!($this->getField('attributes0') & SPELL_ATTR0_PASSIVE))
continue;
// curious cases of OH MY FUCKING GOD WHY?!
if ($id == 16268) // Shaman - Spirit Weapons (parry is normaly stored in g_statistics)
{
$data[$id]['parrypct'] = [5, 'add'];
continue;
}
if ($id == 20550) // Tauren - Endurance (dependant on base health) ... if you are looking for something elegant, look away!
{
$data[$id]['health'] = [0.05, 'functionOf', '$function(p) { return g_statistics.combo[p.classs][p.level][5]; }'];
continue;
}
for ($i = 1; $i < 4; $i++)
{
$pts = $this->calculateAmountForCurrent($i)[1];
@@ -502,6 +304,15 @@ class SpellList extends BaseType
as a flat value (that is equal to the percentage, like they should be). So the stats-table won't show the actual deficit
*/
// 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 @@ class SpellList extends BaseType
$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 @@ class SpellList extends BaseType
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 @@ class SpellList extends BaseType
];
}
public function canCreateItem()
public function canCreateItem() : array
{
$idx = [];
for ($i = 1; $i < 4; $i++)
@@ -921,7 +737,7 @@ class SpellList extends BaseType
return $idx;
}
public function canTriggerSpell()
public function canTriggerSpell() : array
{
$idx = [];
for ($i = 1; $i < 4; $i++)
@@ -932,7 +748,7 @@ class SpellList extends BaseType
return $idx;
}
public function canTeachSpell()
public function canTeachSpell() : array
{
$idx = [];
for ($i = 1; $i < 4; $i++)
@@ -943,6 +759,16 @@ class SpellList extends BaseType
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 @@ class SpellList extends BaseType
}
// 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] = '<!--rtg%s-->%s&nbsp;<small>(%s)</small>';
$result[4] = $rType;
$result[4] = $rType[0]; // could be multiple ratings in theory, but not expected to be
}
/* todo: export to and solve formulas in javascript e.g.: spell 10187 - ${$42213m1*8*$<mult>} with $mult = ${${$?s31678[${1.05}][${${$?s31677[${1.04}][${${$?s31676[${1.03}][${${$?s31675[${1.02}][${${$?s31674[${1.01}][${1}]}}]}}]}}]}}]}*${$?s12953[${1.06}][${${$?s12952[${1.04}][${${$?s11151[${1.02}][${1}]}}]}}]}}
else if ($this->interactive && ($modStrMin || $modStrMax))
@@ -1336,16 +1162,16 @@ class SpellList extends BaseType
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] = '<!--rtg%s-->%s&nbsp;<small>(%s)</small>';
$result[4] = $rType;
$result[4] = $rType[0]; // could be multiple ratings in theory, but not expected to be
}
else if (($modStrMin || $modStrMax) && $this->interactive)
{
@@ -1659,7 +1485,7 @@ class SpellList extends BaseType
// 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))
{