mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
* fix data types and data length and add default values where necessary * data should no longer get truncated * misc fixes
2842 lines
133 KiB
PHP
2842 lines
133 KiB
PHP
<?php
|
|
|
|
if (!defined('AOWOW_REVISION'))
|
|
die('illegal access');
|
|
|
|
|
|
class SpellList extends BaseType
|
|
{
|
|
use listviewHelper, sourceHelper;
|
|
|
|
public $ranks = [];
|
|
public $relItems = null;
|
|
|
|
public static $type = Type::SPELL;
|
|
public static $brickFile = 'spell';
|
|
public static $dataTable = '?_spell';
|
|
|
|
public static $skillLines = array(
|
|
6 => [ 43, 44, 45, 46, 54, 55, 95, 118, 136, 160, 162, 172, 173, 176, 226, 228, 229, 473], // Weapons
|
|
8 => [293, 413, 414, 415, 433], // Armor
|
|
9 => SKILLS_TRADE_SECONDARY, // sec. Professions
|
|
10 => [ 98, 109, 111, 113, 115, 137, 138, 139, 140, 141, 313, 315, 673, 759], // Languages
|
|
11 => SKILLS_TRADE_PRIMARY // prim. Professions
|
|
);
|
|
|
|
public static $spellTypes = array(
|
|
6 => 1,
|
|
8 => 2,
|
|
10 => 4
|
|
);
|
|
|
|
public const EFFECTS_HEAL = array(
|
|
SPELL_EFFECT_NONE, /*SPELL_EFFECT_DUMMY*/ SPELL_EFFECT_HEAL, SPELL_EFFECT_HEAL_MAX_HEALTH, SPELL_EFFECT_HEAL_MECHANICAL,
|
|
SPELL_EFFECT_HEAL_PCT
|
|
);
|
|
public const EFFECTS_DAMAGE = array(
|
|
SPELL_EFFECT_NONE, SPELL_EFFECT_DUMMY, SPELL_EFFECT_SCHOOL_DAMAGE, SPELL_EFFECT_HEALTH_LEECH, SPELL_EFFECT_POWER_BURN
|
|
);
|
|
public const EFFECTS_ITEM_CREATE = array(
|
|
SPELL_EFFECT_CREATE_ITEM, SPELL_EFFECT_SUMMON_CHANGE_ITEM, SPELL_EFFECT_CREATE_RANDOM_ITEM, SPELL_EFFECT_CREATE_MANA_GEM, SPELL_EFFECT_CREATE_ITEM_2
|
|
);
|
|
public const EFFECTS_TRIGGER = array(
|
|
SPELL_EFFECT_DUMMY, SPELL_EFFECT_TRIGGER_MISSILE, SPELL_EFFECT_TRIGGER_SPELL, SPELL_EFFECT_FEED_PET, SPELL_EFFECT_FORCE_CAST,
|
|
SPELL_EFFECT_FORCE_CAST_WITH_VALUE, SPELL_EFFECT_TRIGGER_SPELL_WITH_VALUE, SPELL_EFFECT_TRIGGER_MISSILE_SPELL_WITH_VALUE, SPELL_EFFECT_TRIGGER_SPELL_2, SPELL_EFFECT_SUMMON_RAF_FRIEND,
|
|
SPELL_EFFECT_TITAN_GRIP, SPELL_EFFECT_FORCE_CAST_2, SPELL_EFFECT_REMOVE_AURA
|
|
);
|
|
public const EFFECTS_TEACH = array(
|
|
SPELL_EFFECT_LEARN_SPELL, SPELL_EFFECT_LEARN_PET_SPELL /*SPELL_EFFECT_UNLEARN_SPECIALIZATION*/
|
|
);
|
|
public const EFFECTS_MODEL_OBJECT = array(
|
|
SPELL_EFFECT_TRANS_DOOR, SPELL_EFFECT_SUMMON_OBJECT_WILD, SPELL_EFFECT_SUMMON_OBJECT_SLOT1, SPELL_EFFECT_SUMMON_OBJECT_SLOT2, SPELL_EFFECT_SUMMON_OBJECT_SLOT3,
|
|
SPELL_EFFECT_SUMMON_OBJECT_SLOT4
|
|
);
|
|
public const EFFECTS_MODEL_NPC = array(
|
|
SPELL_EFFECT_SUMMON, SPELL_EFFECT_SUMMON_PET, SPELL_EFFECT_SUMMON_DEMON, SPELL_EFFECT_KILL_CREDIT, SPELL_EFFECT_KILL_CREDIT2
|
|
);
|
|
public const EFFECTS_DIRECT_SCALING = array(
|
|
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 AURAS_HEAL = array(
|
|
SPELL_AURA_DUMMY, SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_HEALTH_FUNNEL, SPELL_AURA_SCHOOL_ABSORB, SPELL_AURA_MANA_SHIELD,
|
|
SPELL_AURA_PERIODIC_DUMMY
|
|
);
|
|
public const AURAS_DAMAGE = array(
|
|
SPELL_AURA_PERIODIC_DAMAGE, SPELL_AURA_DUMMY, SPELL_AURA_DAMAGE_SHIELD, SPELL_AURA_PERIODIC_LEECH, SPELL_AURA_PERIODIC_DAMAGE_PERCENT,
|
|
SPELL_AURA_POWER_BURN, SPELL_AURA_PERIODIC_DUMMY
|
|
);
|
|
public const AURAS_ITEM_CREATE = array(
|
|
SPELL_AURA_CHANNEL_DEATH_ITEM
|
|
);
|
|
public const AURAS_TRIGGER = array(
|
|
SPELL_AURA_DUMMY, SPELL_AURA_PERIODIC_TRIGGER_SPELL, SPELL_AURA_PROC_TRIGGER_SPELL, SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, SPELL_AURA_ADD_TARGET_TRIGGER,
|
|
SPELL_AURA_PERIODIC_DUMMY, SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE, SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE, SPELL_AURA_CONTROL_VEHICLE, SPELL_AURA_LINKED
|
|
);
|
|
public const AURAS_MODEL_NPC = array(
|
|
SPELL_AURA_TRANSFORM, SPELL_AURA_MOUNTED, SPELL_AURA_CHANGE_MODEL_FOR_ALL_HUMANOIDS, SPELL_AURA_X_RAY,
|
|
SPELL_AURA_MOD_FAKE_INEBRIATE
|
|
);
|
|
public const AURAS_PERIODIC_SCALING = array(
|
|
SPELL_AURA_PERIODIC_DAMAGE, SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_LEECH
|
|
);
|
|
|
|
private $spellVars = [];
|
|
private $refSpells = [];
|
|
private $tools = [];
|
|
private $interactive = false;
|
|
private $charLevel = MAX_LEVEL;
|
|
private $scaling = [];
|
|
private $parsedText = [];
|
|
|
|
protected $queryBase = 'SELECT s.*, s.id AS ARRAY_KEY FROM ?_spell s';
|
|
protected $queryOpts = array(
|
|
's' => [['src', 'sr', 'ic', 'ica']], // 6: Type::SPELL
|
|
'ic' => ['j' => ['?_icons ic ON ic.id = s.iconId', true], 's' => ', ic.name AS iconString'],
|
|
'ica' => ['j' => ['?_icons ica ON ica.id = s.iconIdAlt', true], 's' => ', ica.name AS iconStringAlt'],
|
|
'sr' => ['j' => ['?_spellrange sr ON sr.id = s.rangeId'], 's' => ', sr.rangeMinHostile, sr.rangeMinFriend, sr.rangeMaxHostile, sr.rangeMaxFriend, sr.name_loc0 AS rangeText_loc0, sr.name_loc2 AS rangeText_loc2, sr.name_loc3 AS rangeText_loc3, sr.name_loc4 AS rangeText_loc4, sr.name_loc6 AS rangeText_loc6, sr.name_loc8 AS rangeText_loc8'],
|
|
'src' => ['j' => ['?_source src ON type = 6 AND typeId = s.id', true], 's' => ', moreType, moreTypeId, moreZoneId, moreMask, src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24']
|
|
);
|
|
|
|
public function __construct($conditions = [], $miscData = [])
|
|
{
|
|
parent::__construct($conditions);
|
|
|
|
if ($this->error)
|
|
return;
|
|
|
|
if (isset($miscData['interactive']))
|
|
$this->interactive = $miscData['interactive'];
|
|
|
|
if (isset($miscData['charLevel']))
|
|
$this->charLevel = $miscData['charLevel'];
|
|
|
|
// post processing
|
|
$foo = DB::World()->selectCol('SELECT perfectItemType FROM skill_perfect_item_template WHERE spellId IN (?a)', $this->getFoundIDs());
|
|
foreach ($this->iterate() as &$_curTpl)
|
|
{
|
|
// required for globals
|
|
if ($idx = $this->canCreateItem())
|
|
foreach ($idx as $i)
|
|
$foo[] = (int)$_curTpl['effect'.$i.'CreateItemId'];
|
|
|
|
for ($i = 1; $i <= 8; $i++)
|
|
if ($_curTpl['reagent'.$i] > 0)
|
|
$foo[] = (int)$_curTpl['reagent'.$i];
|
|
|
|
for ($i = 1; $i <= 2; $i++)
|
|
if ($_curTpl['tool'.$i] > 0)
|
|
$foo[] = (int)$_curTpl['tool'.$i];
|
|
|
|
// ranks
|
|
$this->ranks[$this->id] = $this->getField('rank', true);
|
|
|
|
// sources
|
|
for ($i = 1; $i < 25; $i++)
|
|
{
|
|
if ($_ = $_curTpl['src'.$i])
|
|
$this->sources[$this->id][$i][] = $_;
|
|
|
|
unset($_curTpl['src'.$i]);
|
|
}
|
|
|
|
// set full masks to 0
|
|
$_curTpl['reqClassMask'] &= CLASS_MASK_ALL;
|
|
if ($_curTpl['reqClassMask'] == CLASS_MASK_ALL)
|
|
$_curTpl['reqClassMask'] = 0;
|
|
|
|
$_curTpl['reqRaceMask'] &= RACE_MASK_ALL;
|
|
if ($_curTpl['reqRaceMask'] == RACE_MASK_ALL)
|
|
$_curTpl['reqRaceMask'] = 0;
|
|
|
|
// unpack skillLines
|
|
$_curTpl['skillLines'] = [];
|
|
if ($_curTpl['skillLine1'] < 0)
|
|
{
|
|
foreach (Game::$skillLineMask[$_curTpl['skillLine1']] as $idx => $pair)
|
|
if ($_curTpl['skillLine2OrMask'] & (1 << $idx))
|
|
$_curTpl['skillLines'][] = $pair[1];
|
|
}
|
|
else if ($sec = $_curTpl['skillLine2OrMask'])
|
|
{
|
|
if ($this->id == 818) // and another hack .. basic Campfire (818) has deprecated skill Survival (142) as first skillLine
|
|
$_curTpl['skillLines'] = [$sec, $_curTpl['skillLine1']];
|
|
else
|
|
$_curTpl['skillLines'] = [$_curTpl['skillLine1'], $sec];
|
|
}
|
|
else if ($prim = $_curTpl['skillLine1'])
|
|
$_curTpl['skillLines'] = [$prim];
|
|
|
|
unset($_curTpl['skillLine1']);
|
|
unset($_curTpl['skillLine2OrMask']);
|
|
|
|
// fix missing icons
|
|
$_curTpl['iconString'] = $_curTpl['iconString'] ?: DEFAULT_ICON;
|
|
|
|
$this->scaling[$this->id] = false;
|
|
}
|
|
|
|
if ($foo)
|
|
$this->relItems = new ItemList(array(['i.id', array_unique($foo)], CFG_SQL_LIMIT_NONE));
|
|
}
|
|
|
|
// use if you JUST need the name
|
|
public static function getName($id)
|
|
{
|
|
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_spell WHERE id = ?d', $id );
|
|
return Util::localizedString($n, 'name');
|
|
}
|
|
// end static use
|
|
|
|
// required for item-comparison
|
|
public function getStatGain()
|
|
{
|
|
$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;
|
|
}
|
|
|
|
// Holy resistance only if explicitly specified (shouldn't even exist...?)
|
|
if ($mv == (1 << SPELL_SCHOOL_HOLY))
|
|
{
|
|
Util::arraySumByKey($stats, [ITEM_MOD_HOLY_RESISTANCE => $pts]);
|
|
break;
|
|
}
|
|
|
|
for ($j = 0; $j < 7; $j++)
|
|
{
|
|
if (($mv & (1 << $j)) == 0)
|
|
continue;
|
|
|
|
switch ($j)
|
|
{
|
|
case SPELL_SCHOOL_FIRE:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_FIRE_RESISTANCE => $pts]);
|
|
break;
|
|
case SPELL_SCHOOL_NATURE:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_NATURE_RESISTANCE => $pts]);
|
|
break;
|
|
case SPELL_SCHOOL_FROST:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_FROST_RESISTANCE => $pts]);
|
|
break;
|
|
case SPELL_SCHOOL_SHADOW:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_SHADOW_RESISTANCE => $pts]);
|
|
break;
|
|
case SPELL_SCHOOL_ARCANE:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_ARCANE_RESISTANCE => $pts]);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SPELL_AURA_PERIODIC_HEAL: // hp5
|
|
case SPELL_AURA_MOD_REGEN:
|
|
case SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_HEALTH_REGEN => $pts]);
|
|
break;
|
|
case SPELL_AURA_MOD_POWER_REGEN: // mp5
|
|
Util::arraySumByKey($stats, [ITEM_MOD_MANA_REGENERATION => $pts]);
|
|
break;
|
|
case SPELL_AURA_MOD_ATTACK_POWER:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_ATTACK_POWER => $pts]);
|
|
break; // ?carries over to rngatkpwr?
|
|
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_RANGED_ATTACK_POWER => $pts]);
|
|
break;
|
|
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_BLOCK_VALUE => $pts]);
|
|
break;
|
|
case SPELL_AURA_MOD_EXPERTISE:
|
|
Util::arraySumByKey($stats, [ITEM_MOD_EXPERTISE_RATING => $pts]);
|
|
break;
|
|
case SPELL_AURA_MOD_TARGET_RESISTANCE:
|
|
if ($mv == 0x7C && $pts < 0)
|
|
Util::arraySumByKey($stats, [ITEM_MOD_SPELL_PENETRATION => -$pts]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
$data[$this->id] = $stats;
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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]);
|
|
}
|
|
|
|
// apply weapon restrictions
|
|
$this->getEntry($id);
|
|
$class = $this->getField('equippedItemClass');
|
|
$subClass = $this->getField('equippedItemSubClassMask');
|
|
$slot = $subClass & 0x5000C ? 18 : 16;
|
|
if ($class != ITEM_CLASS_WEAPON || !$subClass)
|
|
continue;
|
|
|
|
foreach ($spellData as $json => $pts)
|
|
$spellData[$json] = [1, 'functionOf', sprintf($whCheck, $slot, $class, $subClass, $pts)];
|
|
}
|
|
|
|
// 4 possible modifiers found
|
|
// <statistic> => [0.15, 'functionOf', <funcName:int>]
|
|
// <statistic> => [0.33, 'percentOf', <statistic>]
|
|
// <statistic> => [123, 'add']
|
|
// <statistic> => <value> ... as from getStatGain()
|
|
|
|
$modXByStat = function (&$arr, $stat, $pts) use (&$mv)
|
|
{
|
|
if ($mv == STAT_STRENGTH)
|
|
$arr[$stat ?: 'str'] = [$pts / 100, 'percentOf', 'str'];
|
|
else if ($mv == STAT_AGILITY)
|
|
$arr[$stat ?: 'agi'] = [$pts / 100, 'percentOf', 'agi'];
|
|
else if ($mv == STAT_STAMINA)
|
|
$arr[$stat ?: 'sta'] = [$pts / 100, 'percentOf', 'sta'];
|
|
else if ($mv == STAT_INTELLECT)
|
|
$arr[$stat ?: 'int'] = [$pts / 100, 'percentOf', 'int'];
|
|
else if ($mv == STAT_SPIRIT)
|
|
$arr[$stat ?: 'spi'] = [$pts / 100, 'percentOf', 'spi'];
|
|
};
|
|
|
|
$modXBySchool = function (&$arr, $stat, $val, $mask = null) use (&$mv)
|
|
{
|
|
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_HOLY))
|
|
$arr['hol'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'hol'.$stat];
|
|
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_FIRE))
|
|
$arr['fir'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fir'.$stat];
|
|
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_NATURE))
|
|
$arr['nat'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'nat'.$stat];
|
|
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_FROST))
|
|
$arr['fro'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fro'.$stat];
|
|
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_SHADOW))
|
|
$arr['sha'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'sha'.$stat];
|
|
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_ARCANE))
|
|
$arr['arc'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'arc'.$stat];
|
|
};
|
|
|
|
$jsonStat = function ($stat)
|
|
{
|
|
if ($stat == STAT_STRENGTH)
|
|
return 'str';
|
|
if ($stat == STAT_AGILITY)
|
|
return 'agi';
|
|
if ($stat == STAT_STAMINA)
|
|
return 'sta';
|
|
if ($stat == STAT_INTELLECT)
|
|
return 'int';
|
|
if ($stat == STAT_SPIRIT)
|
|
return 'spi';
|
|
};
|
|
|
|
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]; }'];
|
|
continue;
|
|
}
|
|
|
|
for ($i = 1; $i < 4; $i++)
|
|
{
|
|
$pts = $this->calculateAmountForCurrent($i)[1];
|
|
$mv = $this->getField('effect'.$i.'MiscValue');
|
|
$mvB = $this->getField('effect'.$i.'MiscValueB');
|
|
$au = $this->getField('effect'.$i.'AuraId');
|
|
$class = $this->getField('equippedItemClass');
|
|
$subClass = $this->getField('equippedItemSubClassMask');
|
|
|
|
|
|
/* ISSUE!
|
|
mods formated like ['<statName>' => [<points>, 'percentOf', '<statName>']] are applied as multiplier and not
|
|
as a flat value (that is equal to the percentage, like they should be). So the stats-table won't show the actual deficit
|
|
*/
|
|
|
|
switch ($au)
|
|
{
|
|
case SPELL_AURA_MOD_RESISTANCE_PCT:
|
|
case SPELL_AURA_MOD_BASE_RESISTANCE_PCT:
|
|
// Armor only if explicitly specified only affects armor from equippment
|
|
if ($mv == (1 << SPELL_SCHOOL_NORMAL))
|
|
$data[$id]['armor'] = [$pts / 100, 'percentOf', ['armor', 0]];
|
|
else if ($mv)
|
|
$modXBySchool($data[$id], 'res', $pts);
|
|
break;
|
|
case SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT:
|
|
// Armor only if explicitly specified
|
|
if ($mv == (1 << SPELL_SCHOOL_NORMAL))
|
|
$data[$id]['armor'] = [$pts / 100, 'percentOf', $jsonStat($mvB)];
|
|
else if ($mv)
|
|
$modXBySchool($data[$id], 'res', [$pts / 100, 'percentOf', $jsonStat($mvB)]);
|
|
break;
|
|
case SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE:
|
|
if ($mv > -1) // one stat
|
|
$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]];
|
|
break;
|
|
case SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT:
|
|
$mv = $mv ?: SPELL_MAGIC_SCHOOLS;
|
|
$modXBySchool($data[$id], 'spldmg', [$pts / 100, 'percentOf', $jsonStat($mvB)]);
|
|
break;
|
|
case SPELL_AURA_MOD_RANGED_ATTACK_POWER_OF_STAT_PERCENT:
|
|
$modXByStat($data[$id], 'rgdatkpwr', $pts);
|
|
break;
|
|
case SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT:
|
|
$modXByStat($data[$id], 'mleatkpwr', $pts);
|
|
break;
|
|
case SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT:
|
|
$modXByStat($data[$id], 'splheal', $pts);
|
|
break;
|
|
case SPELL_AURA_MOD_MANA_REGEN_FROM_STAT:
|
|
$modXByStat($data[$id], 'manargn', $pts);
|
|
break;
|
|
case SPELL_AURA_MOD_MANA_REGEN_INTERRUPT:
|
|
$data[$id]['icmanargn'] = [$pts / 100, 'percentOf', 'oocmanargn'];
|
|
break;
|
|
case SPELL_AURA_MOD_SPELL_CRIT_CHANCE:
|
|
case SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL:
|
|
$mv = $mv ?: SPELL_MAGIC_SCHOOLS;
|
|
$modXBySchool($data[$id], 'splcritstrkpct', [$pts, 'add']);
|
|
if (($mv & SPELL_MAGIC_SCHOOLS) == SPELL_MAGIC_SCHOOLS)
|
|
$data[$id]['splcritstrkpct'] = [$pts, 'add'];
|
|
break;
|
|
case SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR:
|
|
$data[$id]['mleatkpwr'] = [1 / $pts, 'percentOf', 'fullarmor'];
|
|
$data[$id]['rgdatkpwr'] = [1 / $pts, 'percentOf', 'fullarmor'];
|
|
break;
|
|
case SPELL_AURA_MOD_WEAPON_CRIT_PERCENT:
|
|
if ($class < 1 || ($class == ITEM_CLASS_WEAPON && ($subClass & 0x5000C)))
|
|
$data[$id]['rgdcritstrkpct'] = [1, 'functionOf', sprintf($whCheck, 18, $class, $subClass, $pts)];
|
|
// $data[$id]['rgdcritstrkpct'] = [$pts, 'add'];
|
|
if ($class < 1 || ($class == ITEM_CLASS_WEAPON && ($subClass & 0xA5F3)))
|
|
$data[$id]['mlecritstrkpct'] = [1, 'functionOf', sprintf($whCheck, 16, $class, $subClass, $pts)];
|
|
// $data[$id]['mlecritstrkpct'] = [$pts, 'add'];
|
|
break;
|
|
case SPELL_AURA_MOD_PARRY_PERCENT:
|
|
$data[$id]['parrypct'] = [$pts, 'add'];
|
|
break;
|
|
case SPELL_AURA_MOD_DODGE_PERCENT:
|
|
$data[$id]['dodgepct'] = [$pts, 'add'];
|
|
break;
|
|
case SPELL_AURA_MOD_BLOCK_PERCENT:
|
|
$data[$id]['blockpct'] = [$pts, 'add'];
|
|
break;
|
|
case SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT:
|
|
if ($mv == POWER_HEALTH)
|
|
$data[$id]['health'] = [$pts / 100, 'percentOf', 'health'];
|
|
else if ($mv == POWER_ENERGY)
|
|
$data[$id]['energy'] = [$pts / 100, 'percentOf', 'energy'];
|
|
else if ($mv == POWER_MANA)
|
|
$data[$id]['mana'] = [$pts / 100, 'percentOf', 'mana'];
|
|
else if ($mv == POWER_RAGE)
|
|
$data[$id]['rage'] = [$pts / 100, 'percentOf', 'rage'];
|
|
else if ($mv == POWER_RUNIC_POWER)
|
|
$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_SHIELD_BLOCKVALUE_PCT:
|
|
$data[$id]['block'] = [$pts / 100, 'percentOf', 'block'];
|
|
break;
|
|
case SPELL_AURA_MOD_CRIT_PCT:
|
|
$data[$id]['mlecritstrkpct'] = [$pts, 'add'];
|
|
$data[$id]['rgdcritstrkpct'] = [$pts, 'add'];
|
|
$data[$id]['splcritstrkpct'] = [$pts, 'add'];
|
|
break;
|
|
case SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER:
|
|
$mv = $mv ?: SPELL_MAGIC_SCHOOLS;
|
|
$modXBySchool($data[$id], 'spldmg', [$pts / 100, 'percentOf', 'mleatkpwr']);
|
|
break;
|
|
case SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER:
|
|
$data[$id]['splheal'] = [$pts / 100, 'percentOf', 'mleatkpwr'];
|
|
break;
|
|
case SPELL_AURA_MOD_ATTACK_POWER_PCT: // ingmae only melee..?
|
|
$data[$id]['mleatkpwr'] = [$pts / 100, 'percentOf', 'mleatkpwr'];
|
|
break;
|
|
case SPELL_AURA_MOD_HEALTH_REGEN_PERCENT:
|
|
$data[$id]['healthrgn'] = [$pts / 100, 'percentOf', 'healthrgn'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
// halper
|
|
public function getReagentsForCurrent()
|
|
{
|
|
$data = [];
|
|
|
|
for ($i = 1; $i <= 8; $i++)
|
|
if ($this->curTpl['reagent'.$i] > 0 && $this->curTpl['reagentCount'.$i])
|
|
$data[$this->curTpl['reagent'.$i]] = [$this->curTpl['reagent'.$i], $this->curTpl['reagentCount'.$i]];
|
|
|
|
return $data;
|
|
}
|
|
|
|
public function getToolsForCurrent()
|
|
{
|
|
if ($this->tools)
|
|
return $this->tools;
|
|
|
|
$tools = [];
|
|
for ($i = 1; $i <= 2; $i++)
|
|
{
|
|
// TotemCategory
|
|
if ($_ = $this->curTpl['toolCategory'.$i])
|
|
{
|
|
$tc = DB::Aowow()->selectRow('SELECT * FROM ?_totemcategory WHERE id = ?d', $_);
|
|
$tools[$i + 1] = array(
|
|
'id' => $_,
|
|
'name' => Util::localizedString($tc, 'name'));
|
|
}
|
|
|
|
// Tools
|
|
if (!$this->curTpl['tool'.$i])
|
|
continue;
|
|
|
|
foreach ($this->relItems->iterate() as $relId => $__)
|
|
{
|
|
if ($relId != $this->curTpl['tool'.$i])
|
|
continue;
|
|
|
|
$tools[$i - 1] = array(
|
|
'itemId' => $relId,
|
|
'name' => $this->relItems->getField('name', true),
|
|
'quality' => $this->relItems->getField('quality')
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->tools = array_reverse($tools);
|
|
|
|
return $this->tools;
|
|
}
|
|
|
|
public function getModelInfo($spellId = 0, $effIdx = 0)
|
|
{
|
|
$displays = [0 => []];
|
|
foreach ($this->iterate() as $id => $__)
|
|
{
|
|
if ($spellId && $spellId != $id)
|
|
continue;
|
|
|
|
for ($i = 1; $i < 4; $i++)
|
|
{
|
|
$effMV = $this->curTpl['effect'.$i.'MiscValue'];
|
|
if (!$effMV)
|
|
continue;
|
|
|
|
// GO Model from MiscVal
|
|
if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_MODEL_OBJECT))
|
|
{
|
|
if (isset($displays[Type::OBJECT][$id]))
|
|
$displays[Type::OBJECT][$id][0][] = $i;
|
|
else
|
|
$displays[Type::OBJECT][$id] = [[$i], $effMV];
|
|
}
|
|
// NPC Model from MiscVal
|
|
else if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_MODEL_NPC) || in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_MODEL_NPC))
|
|
{
|
|
if (isset($displays[Type::NPC][$id]))
|
|
$displays[Type::NPC][$id][0][] = $i;
|
|
else
|
|
$displays[Type::NPC][$id] = [[$i], $effMV];
|
|
}
|
|
// Shapeshift
|
|
else if ($this->curTpl['effect'.$i.'AuraId'] == SPELL_AURA_MOD_SHAPESHIFT)
|
|
{
|
|
$subForms = array(
|
|
892 => [892, 29407, 29406, 29408, 29405], // Cat - NE
|
|
8571 => [8571, 29410, 29411, 29412], // Cat - Tauren
|
|
2281 => [2281, 29413, 29414, 29416, 29417], // Bear - NE
|
|
2289 => [2289, 29415, 29418, 29419, 29420, 29421] // Bear - Tauren
|
|
);
|
|
|
|
if ($st = DB::Aowow()->selectRow('SELECT *, displayIdA as model1, displayIdH as model2 FROM ?_shapeshiftforms WHERE id = ?d', $effMV))
|
|
{
|
|
foreach ([1, 2] as $j)
|
|
if (isset($subForms[$st['model'.$j]]))
|
|
$st['model'.$j] = $subForms[$st['model'.$j]][array_rand($subForms[$st['model'.$j]])];
|
|
|
|
$displays[0][$id][$i] = array(
|
|
'typeId' => 0,
|
|
'displayId' => $st['model2'] ? $st['model'.rand(1, 2)] : $st['model1'],
|
|
'creatureType' => $st['creatureType'],
|
|
'displayName' => Lang::game('st', $effMV)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$results = $displays[0];
|
|
|
|
if (!empty($displays[Type::NPC]))
|
|
{
|
|
$nModels = new CreatureList(array(['id', array_column($displays[Type::NPC], 1)]));
|
|
foreach ($nModels->iterate() as $nId => $__)
|
|
{
|
|
foreach ($displays[Type::NPC] as $srcId => [$indizes, $npcId])
|
|
{
|
|
if ($npcId == $nId)
|
|
{
|
|
foreach ($indizes as $idx)
|
|
{
|
|
$res = array(
|
|
'typeId' => $nId,
|
|
'displayId' => $nModels->getRandomModelId(),
|
|
'displayName' => $nModels->getField('name', true)
|
|
);
|
|
|
|
if ($nModels->getField('humanoid'))
|
|
$res['humanoid'] = 1;
|
|
|
|
$results[$srcId][$idx] = $res;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($displays[Type::OBJECT]))
|
|
{
|
|
$oModels = new GameObjectList(array(['id', array_column($displays[Type::OBJECT], 1)]));
|
|
foreach ($oModels->iterate() as $oId => $__)
|
|
{
|
|
foreach ($displays[Type::OBJECT] as $srcId => [$indizes, $objId])
|
|
{
|
|
if ($objId == $oId)
|
|
{
|
|
foreach ($indizes as $idx)
|
|
{
|
|
$results[$srcId][$idx] = array(
|
|
'typeId' => $oId,
|
|
'displayId' => $oModels->getField('displayId'),
|
|
'displayName' => $oModels->getField('name', true)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($spellId && $effIdx)
|
|
return !empty($results[$spellId][$effIdx]) ? $results[$spellId][$effIdx] : 0;
|
|
|
|
return $results;
|
|
}
|
|
|
|
private function createRangesForCurrent()
|
|
{
|
|
if (!$this->curTpl['rangeMaxHostile'])
|
|
return '';
|
|
|
|
// minRange exists; show as range
|
|
if ($this->curTpl['rangeMinHostile'])
|
|
return sprintf(Lang::spell('range'), $this->curTpl['rangeMinHostile'].' - '.$this->curTpl['rangeMaxHostile']);
|
|
// friend and hostile differ; do color
|
|
else if ($this->curTpl['rangeMaxHostile'] != $this->curTpl['rangeMaxFriend'])
|
|
return sprintf(Lang::spell('range'), '<span class="q10">'.$this->curTpl['rangeMaxHostile'].'</span> - <span class="q2">'.$this->curTpl['rangeMaxFriend']. '</span>');
|
|
// hardcode: "melee range"
|
|
else if ($this->curTpl['rangeMaxHostile'] == 5)
|
|
return Lang::spell('meleeRange');
|
|
// hardcode "unlimited range"
|
|
else if ($this->curTpl['rangeMaxHostile'] == 50000)
|
|
return Lang::spell('unlimRange');
|
|
// regular case
|
|
else
|
|
return sprintf(Lang::spell('range'), $this->curTpl['rangeMaxHostile']);
|
|
}
|
|
|
|
public function createPowerCostForCurrent()
|
|
{
|
|
$str = '';
|
|
|
|
$pt = $this->curTpl['powerType'];
|
|
$pc = $this->curTpl['powerCost'];
|
|
$pcp = $this->curTpl['powerCostPercent'];
|
|
$pps = $this->curTpl['powerPerSecond'];
|
|
$pcpl = $this->curTpl['powerCostPerLevel'];
|
|
|
|
if ($pt == POWER_RAGE || $pt == POWER_RUNIC_POWER)
|
|
$pc /= 10;
|
|
|
|
if ($pt == POWER_RUNE && ($rCost = ($this->curTpl['powerCostRunes'] & 0x333)))
|
|
{ // Blood 2|1 - Unholy 2|1 - Frost 2|1
|
|
$runes = [];
|
|
|
|
for ($i = 0; $i < 3; $i++)
|
|
{
|
|
if ($rCost & 0x3)
|
|
$runes[] = Lang::spell('powerCostRunes', $i, [$rCost & 0x3]);
|
|
|
|
$rCost >>= 4;
|
|
}
|
|
|
|
$str .= implode(' ', $runes);
|
|
}
|
|
else if ($pcp > 0) // power cost: pct over static
|
|
$str .= $pcp."% ".sprintf(Lang::spell('pctCostOf'), mb_strtolower(Lang::spell('powerTypes', $pt)));
|
|
else if ($pc > 0 || $pps > 0 || $pcpl > 0)
|
|
{
|
|
if (Lang::exist('spell', 'powerCost', $pt))
|
|
$str .= Lang::spell('powerCost', $pt, intVal($pps > 0), [$pc, $pps]);
|
|
else
|
|
$str .= Lang::spell('powerDisplayCost', intVal($pps > 0), [$pc, Lang::spell('powerTypes', $pt), $pps]);
|
|
}
|
|
|
|
// append level cost (todo (low): work in as scaling cost)
|
|
if ($pcpl > 0)
|
|
$str .= sprintf(Lang::spell('costPerLevel'), $pcpl);
|
|
|
|
return $str;
|
|
}
|
|
|
|
public function createCastTimeForCurrent($short = true, $noInstant = true)
|
|
{
|
|
if (!$this->curTpl['castTime'] && $this->isChanneledSpell())
|
|
return Lang::spell('channeled');
|
|
// SPELL_ATTR0_ABILITY instant ability.. yeah, wording thing only (todo (low): rule is imperfect)
|
|
else if (!$this->curTpl['castTime'] && ($this->curTpl['damageClass'] != 1 || $this->curTpl['attributes0'] & SPELL_ATTR0_ABILITY))
|
|
return Lang::spell('instantPhys');
|
|
// show instant only for player/pet/npc abilities (todo (low): unsure when really hidden (like talent-case))
|
|
else if ($noInstant && !in_array($this->curTpl['typeCat'], [11, 7, -3, -6, -8, 0]) && !($this->curTpl['cuFlags'] & SPELL_CU_TALENTSPELL))
|
|
return '';
|
|
else
|
|
return $short ? Lang::formatTime($this->curTpl['castTime'] * 1000, 'spell', 'castTime') : Util::formatTime($this->curTpl['castTime'] * 1000);
|
|
}
|
|
|
|
private function createCooldownForCurrent()
|
|
{
|
|
if ($this->curTpl['recoveryTime'])
|
|
return Lang::formatTime($this->curTpl['recoveryTime'], 'spell', 'cooldown');
|
|
else if ($this->curTpl['recoveryCategory'])
|
|
return Lang::formatTime($this->curTpl['recoveryCategory'], 'spell', 'cooldown');
|
|
else
|
|
return '';
|
|
}
|
|
|
|
// formulae base from TC
|
|
private function calculateAmountForCurrent(int $effIdx, ?SpellList $altTpl = null, int $nTicks = 1) : array
|
|
{
|
|
$ref = $altTpl ?: $this;
|
|
$level = $this->charLevel;
|
|
$maxBase = 0;
|
|
$rppl = $ref->getField('effect'.$effIdx.'RealPointsPerLevel');
|
|
$base = $ref->getField('effect'.$effIdx.'BasePoints');
|
|
$add = $ref->getField('effect'.$effIdx.'DieSides');
|
|
$maxLvl = $ref->getField('maxLevel');
|
|
$baseLvl = $ref->getField('baseLevel');
|
|
|
|
if ($rppl)
|
|
{
|
|
if ($level > $maxLvl && $maxLvl > 0)
|
|
$level = $maxLvl;
|
|
else if ($level < $baseLvl)
|
|
$level = $baseLvl;
|
|
|
|
if (!$ref->getField('atributes0') & SPELL_ATTR0_PASSIVE)
|
|
$level -= $ref->getField('spellLevel');
|
|
|
|
$maxBase += (int)(($level - $baseLvl) * $rppl);
|
|
$maxBase *= $nTicks;
|
|
}
|
|
|
|
$min = $nTicks * ($add ? $base + 1 : $base);
|
|
$max = $nTicks * ($add + $base);
|
|
|
|
return [
|
|
$min + $maxBase,
|
|
$max + $maxBase,
|
|
$rppl ? '<!--ppl'.$baseLvl.':'.$level.':'.$min.':'.($rppl * 100 * $nTicks).'-->' : null,
|
|
$rppl ? '<!--ppl'.$baseLvl.':'.$level.':'.$max.':'.($rppl * 100 * $nTicks).'-->' : null
|
|
];
|
|
}
|
|
|
|
public function canCreateItem()
|
|
{
|
|
$idx = [];
|
|
for ($i = 1; $i < 4; $i++)
|
|
if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_ITEM_CREATE) || in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_ITEM_CREATE))
|
|
if ($this->curTpl['effect'.$i.'CreateItemId'] > 0)
|
|
$idx[] = $i;
|
|
|
|
return $idx;
|
|
}
|
|
|
|
public function canTriggerSpell()
|
|
{
|
|
$idx = [];
|
|
for ($i = 1; $i < 4; $i++)
|
|
if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_TRIGGER) || in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_TRIGGER))
|
|
if ($this->curTpl['effect'.$i.'AuraId'] == SPELL_AURA_DUMMY || $this->curTpl['effect'.$i.'TriggerSpell'] > 0 || ($this->curTpl['effect'.$i.'Id'] == SPELL_EFFECT_TITAN_GRIP && $this->curTpl['effect'.$i.'MiscValue'] > 0))
|
|
$idx[] = $i;
|
|
|
|
return $idx;
|
|
}
|
|
|
|
public function canTeachSpell()
|
|
{
|
|
$idx = [];
|
|
for ($i = 1; $i < 4; $i++)
|
|
if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_TEACH))
|
|
if ($this->curTpl['effect'.$i.'TriggerSpell'] > 0)
|
|
$idx[] = $i;
|
|
|
|
return $idx;
|
|
}
|
|
|
|
public function isChanneledSpell()
|
|
{
|
|
return $this->curTpl['attributes1'] & (SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2);
|
|
}
|
|
|
|
public function isHealingSpell()
|
|
{
|
|
for ($i = 1; $i < 4; $i++)
|
|
if (!in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_HEAL) && !in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_HEAL))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public function isDamagingSpell()
|
|
{
|
|
for ($i = 1; $i < 4; $i++)
|
|
if (!in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_DAMAGE) && !in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_DAMAGE))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public function periodicEffectsMask()
|
|
{
|
|
$effMask = 0x0;
|
|
|
|
for ($i = 1; $i < 4; $i++)
|
|
if ($this->curTpl['effect'.$i.'Periode'] > 0)
|
|
$effMask |= 1 << ($i - 1);
|
|
|
|
return $effMask;
|
|
}
|
|
|
|
// description-, buff-parsing component
|
|
private function resolveEvaluation($formula)
|
|
{
|
|
// see Traits in javascript locales
|
|
|
|
$PlayerName = Lang::main('name');
|
|
$pl = $PL = /* playerLevel set manually ? $this->charLevel : */ $this->interactive ? sprintf(Util::$dfnString, 'LANG.level', Lang::game('level')) : Lang::game('level');
|
|
$ap = $AP = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.atkpwr[0]', Lang::spell('traitShort', 'atkpwr')) : Lang::spell('traitShort', 'atkpwr');
|
|
$rap = $RAP = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.rgdatkpwr[0]', Lang::spell('traitShort', 'rgdatkpwr')) : Lang::spell('traitShort', 'rgdatkpwr');
|
|
$sp = $SP = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.splpwr[0]', Lang::spell('traitShort', 'splpwr')) : Lang::spell('traitShort', 'splpwr');
|
|
$spa = $SPA = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.arcsplpwr[0]', Lang::spell('traitShort', 'arcsplpwr')) : Lang::spell('traitShort', 'arcsplpwr');
|
|
$spfi = $SPFI = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.firsplpwr[0]', Lang::spell('traitShort', 'firsplpwr')) : Lang::spell('traitShort', 'firsplpwr');
|
|
$spfr = $SPFR = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.frosplpwr[0]', Lang::spell('traitShort', 'frosplpwr')) : Lang::spell('traitShort', 'frosplpwr');
|
|
$sph = $SPH = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.holsplpwr[0]', Lang::spell('traitShort', 'holsplpwr')) : Lang::spell('traitShort', 'holsplpwr');
|
|
$spn = $SPN = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.natsplpwr[0]', Lang::spell('traitShort', 'natsplpwr')) : Lang::spell('traitShort', 'natsplpwr');
|
|
$sps = $SPS = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.shasplpwr[0]', Lang::spell('traitShort', 'shasplpwr')) : Lang::spell('traitShort', 'shasplpwr');
|
|
$bh = $BH = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.splheal[0]', Lang::spell('traitShort', 'splheal')) : Lang::spell('traitShort', 'splheal');
|
|
$spi = $SPI = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.spi[0]', Lang::spell('traitShort', 'spi')) : Lang::spell('traitShort', 'spi');
|
|
$sta = $STA = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.sta[0]', Lang::spell('traitShort', 'sta')) : Lang::spell('traitShort', 'sta');
|
|
$str = $STR = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.str[0]', Lang::spell('traitShort', 'str')) : Lang::spell('traitShort', 'str');
|
|
$agi = $AGI = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.agi[0]', Lang::spell('traitShort', 'agi')) : Lang::spell('traitShort', 'agi');
|
|
$int = $INT = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.int[0]', Lang::spell('traitShort', 'int')) : Lang::spell('traitShort', 'int');
|
|
|
|
// only 'ron test spell', guess its %-dmg mod; no idea what bc2 might be
|
|
$pa = '<$PctArcane>'; // %arcane
|
|
$pfi = '<$PctFire>'; // %fire
|
|
$pfr = '<$PctFrost>'; // %frost
|
|
$ph = '<$PctHoly>'; // %holy
|
|
$pn = '<$PctNature>'; // %nature
|
|
$ps = '<$PctShadow>'; // %shadow
|
|
$pbh = '<$PctHeal>'; // %heal
|
|
$pbhd = '<$PctHealDone>'; // %heal done
|
|
$bc2 = '<$bc2>'; // bc2
|
|
|
|
$HND = $hnd = $this->interactive ? sprintf(Util::$dfnString, '[Hands required by weapon]', 'HND') : 'HND'; // todo (med): localize this one
|
|
$MWS = $mws = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.mlespeed[0]', 'MWS') : 'MWS';
|
|
$mw = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.dmgmin1[0]', 'mw') : 'mw';
|
|
$MW = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.dmgmax1[0]', 'MW') : 'MW';
|
|
$mwb = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.mledmgmin[0]', 'mwb') : 'mwb';
|
|
$MWB = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.mledmgmax[0]', 'MWB') : 'MWB';
|
|
$rwb = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.rgddmgmin[0]', 'rwb') : 'rwb';
|
|
$RWB = $this->interactive ? sprintf(Util::$dfnString, 'LANG.traits.rgddmgmax[0]', 'RWB') : 'RWB';
|
|
|
|
$cond = $COND = function($a, $b, $c) { return $a ? $b : $c; };
|
|
$eq = $EQ = function($a, $b) { return $a == $b; };
|
|
$gt = $GT = function($a, $b) { return $a > $b; };
|
|
$gte = $GTE = function($a, $b) { return $a >= $b; };
|
|
$floor = $FLOOR = function($a) { return floor($a); };
|
|
$max = $MAX = function($a, $b) { return max($a, $b); };
|
|
$min = $MIN = function($a, $b) { return min($a, $b); };
|
|
|
|
if (preg_match_all('/\$\w+\b/i', $formula, $vars))
|
|
{
|
|
|
|
$evalable = true;
|
|
|
|
foreach ($vars[0] as $var) // oh lord, forgive me this sin .. but is_callable seems to bug out and function_exists doesn't find lambda-functions >.<
|
|
{
|
|
$var = substr($var, 1);
|
|
|
|
if (isset($$var))
|
|
{
|
|
$eval = eval('return @$'.$var.';'); // attention: error suppression active here (will be logged anyway)
|
|
if (getType($eval) == 'object')
|
|
continue;
|
|
else if (is_numeric($eval))
|
|
continue;
|
|
}
|
|
else
|
|
$$var = '<UNK: $'.$var.'>';
|
|
|
|
$evalable = false;
|
|
break;
|
|
}
|
|
|
|
if (!$evalable)
|
|
{
|
|
// can't eval constructs because of strings present. replace constructs with strings
|
|
$cond = $COND = !$this->interactive ? 'COND' : sprintf(Util::$dfnString, 'COND(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>, <span class=\'q1\'>c</span>)<br /> <span class=\'q1\'>a</span> ? <span class=\'q1\'>b</span> : <span class=\'q1\'>c</span>', 'COND');
|
|
$eq = $EQ = !$this->interactive ? 'EQ' : sprintf(Util::$dfnString, 'EQ(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)<br /> <span class=\'q1\'>a</span> == <span class=\'q1\'>b</span>', 'EQ');
|
|
$gt = $GT = !$this->interactive ? 'GT' : sprintf(Util::$dfnString, 'GT(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)<br /> <span class=\'q1\'>a</span> > <span class=\'q1\'>b</span>', 'GT');
|
|
$gte = $GTE = !$this->interactive ? 'GTE' : sprintf(Util::$dfnString, 'GTE(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)<br /> <span class=\'q1\'>a</span> >= <span class=\'q1\'>b</span>', 'GTE');
|
|
$floor = $FLOOR = !$this->interactive ? 'FLOOR' : sprintf(Util::$dfnString, 'FLOOR(<span class=\'q1\'>a</span>)', 'FLOOR');
|
|
$min = $MIN = !$this->interactive ? 'MIN' : sprintf(Util::$dfnString, 'MIN(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)', 'MIN');
|
|
$max = $MAX = !$this->interactive ? 'MAX' : sprintf(Util::$dfnString, 'MAX(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)', 'MAX');
|
|
$pl = $PL = !$this->interactive ? 'PL' : sprintf(Util::$dfnString, 'LANG.level', 'PL');
|
|
|
|
// note the " !
|
|
return eval('return "'.$formula.'";');
|
|
}
|
|
else
|
|
return eval('return '.$formula.';');
|
|
}
|
|
|
|
// since this function may be called recursively, there are cases, where the already evaluated string is tried to be evaled again, throwing parse errors
|
|
// todo (med): also quit, if we replaced vars with non-interactive text
|
|
if (strstr($formula, '</dfn>') || strstr($formula, '<!--'))
|
|
return $formula;
|
|
|
|
// hm, minor eval-issue. eval doesnt understand two operators without a space between them (eg. spelll: 18126)
|
|
$formula = preg_replace('/(\+|-|\*|\/)(\+|-|\*|\/)/i', '\1 \2', $formula);
|
|
|
|
// there should not be any letters without a leading $
|
|
return eval('return '.$formula.';');
|
|
}
|
|
|
|
// description-, buff-parsing component
|
|
// a variables structure is pretty .. flexile. match in steps
|
|
private function matchVariableString($varString, &$len = 0)
|
|
{
|
|
$varParts = array(
|
|
'op' => null,
|
|
'oparg' => null,
|
|
'lookup' => null,
|
|
'var' => null,
|
|
'effIdx' => null,
|
|
'switch' => null
|
|
);
|
|
|
|
// last value or gender -switch $[lg]ifText:elseText;
|
|
if (preg_match('/^([lg])([^:]*:[^;]*);/i', $varString, $m))
|
|
{
|
|
$len = strlen($m[0]);
|
|
$varParts['var'] = $m[1];
|
|
$varParts['switch'] = explode(':', $m[2]);
|
|
}
|
|
// basic variable ref (most common case) $(refSpell)?(var)(effIdx)?
|
|
else if (preg_match('/^(\d*)([a-z])([123]?)\b/i', $varString, $m))
|
|
{
|
|
$len = strlen($m[0]);
|
|
$varParts['lookup'] = $m[1];
|
|
$varParts['var'] = $m[2];
|
|
$varParts['effIdx'] = $m[3];
|
|
}
|
|
// variable ref /w formula $( (op) (oparg); )? (refSpell) ( (var) (effIdx) ) OR $(refSpell) ( (op) (oparg); )? ( (var) (effIdx) )
|
|
else if (preg_match('/^(([\+\-\*\/])(\d+);)?(\d*)(([\+\-\*\/])(\d+);)?([a-z])([123]?)\b/i', $varString, $m))
|
|
{
|
|
$len = strlen($m[0]);
|
|
$varParts['lookup'] = $m[4];
|
|
$varParts['var'] = $m[8];
|
|
$varParts['effIdx'] = $m[9];
|
|
$varParts['op'] = $m[6] ?: $m[2];
|
|
$varParts['oparg'] = $m[7] ?: $m[3];
|
|
}
|
|
// something .. else?
|
|
else
|
|
return [];
|
|
|
|
return $varParts;
|
|
}
|
|
|
|
// description-, buff-parsing component
|
|
// returns [min, max, minFulltext, maxFulltext, ratingId]
|
|
private function resolveVariableString($varParts)
|
|
{
|
|
$signs = ['+', '-', '/', '*', '%', '^'];
|
|
|
|
foreach ($varParts as $k => $v)
|
|
$$k = $v;
|
|
|
|
$result = [null];
|
|
|
|
if (!$var)
|
|
return $result;
|
|
|
|
if (!$effIdx) // if EffectIdx is omitted, assume EffectIdx: 1
|
|
$effIdx = 1;
|
|
|
|
// cache at least some lookups.. should be moved to single spellList :/
|
|
if ($lookup && $lookup != $this->id && !isset($this->refSpells[$lookup]))
|
|
$this->refSpells[$lookup] = new SpellList(array(['s.id', $lookup]));
|
|
|
|
$srcSpell = $lookup && $lookup != $this->id ? $this->refSpells[$lookup] : $this;
|
|
if ($srcSpell->error)
|
|
return $result;
|
|
|
|
switch ($var)
|
|
{
|
|
case 'a': // EffectRadiusMin
|
|
case 'A': // EffectRadiusMax
|
|
$base = $srcSpell->getField('effect'.$effIdx.'RadiusMax');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'b': // PointsPerComboPoint
|
|
case 'B':
|
|
$base = $srcSpell->getField('effect'.$effIdx.'PointsPerComboPoint');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'd': // SpellDuration
|
|
case 'D': // todo (med): min/max?; /w unit?
|
|
$base = $srcSpell->getField('duration');
|
|
|
|
$result[2] = Lang::formatTime($srcSpell->getField('duration'), 'spell', 'duration');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base < 0 ? 0 : $base;
|
|
break;
|
|
case 'e': // EffectValueMultiplier
|
|
case 'E':
|
|
$base = $srcSpell->getField('effect'.$effIdx.'ValueMultiplier');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'f': // EffectDamageMultiplier
|
|
case 'F':
|
|
$base = $srcSpell->getField('effect'.$effIdx.'DamageMultiplier');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'g': // boolean choice with casters gender as condition $gX:Y;
|
|
case 'G':
|
|
$result[2] = '<'.$switch[0].'/'.$switch[1].'>';
|
|
break;
|
|
case 'h': // ProcChance
|
|
case 'H':
|
|
$base = $srcSpell->getField('procChance');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'i': // MaxAffectedTargets
|
|
case 'I':
|
|
$base = $srcSpell->getField('maxAffectedTargets');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'l': // boolean choice with last value as condition $lX:Y;
|
|
case 'L':
|
|
// resolve later by backtracking
|
|
$result[2] = '$l'.$switch[0].':'.$switch[1].';';
|
|
break;
|
|
case 'm': // BasePoints (minValue)
|
|
case 'M': // BasePoints (maxValue)
|
|
[$min, $max, $modStrMin, $modStrMax] = $this->calculateAmountForCurrent($effIdx, $srcSpell);
|
|
|
|
$mv = $srcSpell->getField('effect'.$effIdx.'MiscValue');
|
|
$aura = $srcSpell->getField('effect'.$effIdx.'AuraId');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg))
|
|
{
|
|
eval("\$min = $min $op $oparg;");
|
|
eval("\$max = $max $op $oparg;");
|
|
}
|
|
|
|
// Aura giving combat ratings
|
|
$rType = 0;
|
|
if ($aura == SPELL_AURA_MOD_RATING)
|
|
if ($rType = Game::itemModByRatingMask($mv))
|
|
$this->scaling[$this->id] = true;
|
|
// Aura end
|
|
|
|
if ($rType)
|
|
{
|
|
$result[2] = '<!--rtg%s-->%s <small>(%s)</small>';
|
|
$result[4] = $rType;
|
|
}
|
|
/* 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))
|
|
{
|
|
$this->scaling[$this->id] = true;
|
|
$result[2] = $modStrMin.'%s';
|
|
}
|
|
*/
|
|
$result[0] = ctype_lower($var) ? $min : $max;
|
|
break;
|
|
case 'n': // ProcCharges
|
|
case 'N':
|
|
$base = $srcSpell->getField('procCharges');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'o': // TotalAmount for periodic auras (with variance)
|
|
case 'O':
|
|
$periode = $srcSpell->getField('effect'.$effIdx.'Periode');
|
|
$duration = $srcSpell->getField('duration');
|
|
|
|
if (!$periode)
|
|
{
|
|
// Mod Power Regeneration & Mod Health Regeneration have an implicit periode of 5sec
|
|
$aura = $srcSpell->getField('effect'.$effIdx.'AuraId');
|
|
if ($aura == SPELL_AURA_MOD_REGEN || $aura == SPELL_AURA_MOD_POWER_REGEN)
|
|
$periode = 5000;
|
|
else
|
|
$periode = 3000;
|
|
}
|
|
|
|
[$min, $max, $modStrMin, $modStrMax] = $this->calculateAmountForCurrent($effIdx, $srcSpell, intVal($duration / $periode));
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg))
|
|
{
|
|
eval("\$min = $min $op $oparg;");
|
|
eval("\$max = $max $op $oparg;");
|
|
}
|
|
|
|
if ($this->interactive && ($modStrMin || $modStrMax))
|
|
{
|
|
$this->scaling[$this->id] = true;
|
|
|
|
$result[2] = $modStrMin.'%s';
|
|
$result[3] = $modStrMax.'%s';
|
|
}
|
|
|
|
$result[0] = $min;
|
|
$result[1] = $max;
|
|
break;
|
|
case 'q': // EffectMiscValue
|
|
case 'Q':
|
|
$base = $srcSpell->getField('effect'.$effIdx.'MiscValue');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'r': // SpellRange
|
|
case 'R':
|
|
$base = $srcSpell->getField('rangeMaxHostile');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 's': // BasePoints (with variance)
|
|
case 'S':
|
|
[$min, $max, $modStrMin, $modStrMax] = $this->calculateAmountForCurrent($effIdx, $srcSpell);
|
|
$mv = $srcSpell->getField('effect'.$effIdx.'MiscValue');
|
|
$aura = $srcSpell->getField('effect'.$effIdx.'AuraId');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg))
|
|
{
|
|
eval("\$min = $min $op $oparg;");
|
|
eval("\$max = $max $op $oparg;");
|
|
}
|
|
// Aura giving combat ratings
|
|
$rType = 0;
|
|
if ($aura == SPELL_AURA_MOD_RATING)
|
|
if ($rType = Game::itemModByRatingMask($mv))
|
|
$this->scaling[$this->id] = true;
|
|
// Aura end
|
|
|
|
if ($rType)
|
|
{
|
|
$result[2] = '<!--rtg%s-->%s <small>(%s)</small>';
|
|
$result[4] = $rType;
|
|
}
|
|
else if (($modStrMin || $modStrMax) && $this->interactive)
|
|
{
|
|
$this->scaling[$this->id] = true;
|
|
$result[2] = $modStrMin.'%s';
|
|
$result[3] = $modStrMax.'%s';
|
|
}
|
|
|
|
$result[0] = $min;
|
|
$result[1] = $max;
|
|
break;
|
|
case 't': // Periode
|
|
case 'T':
|
|
$base = $srcSpell->getField('effect'.$effIdx.'Periode') / 1000;
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'u': // StackCount
|
|
case 'U':
|
|
$base = $srcSpell->getField('stackAmount');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'v': // MaxTargetLevel
|
|
case 'V':
|
|
$base = $srcSpell->getField('MaxTargetLevel');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'x': // ChainTargetCount
|
|
case 'X':
|
|
$base = $srcSpell->getField('effect'.$effIdx.'ChainTarget');
|
|
|
|
if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base))
|
|
eval("\$base = $base $op $oparg;");
|
|
|
|
$result[0] = $base;
|
|
break;
|
|
case 'z': // HomeZone
|
|
$result[2] = Lang::spell('home');
|
|
break;
|
|
}
|
|
|
|
// handle excessively precise floats
|
|
if (is_float($result[0]))
|
|
$result[0] = round($result[0], 2);
|
|
if (isset($result[1]) && is_float($result[1]))
|
|
$result[1] = round($result[1], 2);
|
|
|
|
return $result; // minPoints, maxPoints, fmtStringMin, fmtStringMax, combatRatingId
|
|
}
|
|
|
|
// description-, buff-parsing component
|
|
private function resolveFormulaString($formula, $precision = 0)
|
|
{
|
|
$fSuffix = '%s';
|
|
$fRating = 0;
|
|
|
|
// step 1: formula unpacking redux
|
|
while (($formStartPos = strpos($formula, '${')) !== false)
|
|
{
|
|
$formBrktCnt = 0;
|
|
$formPrecision = 0;
|
|
$formCurPos = $formStartPos;
|
|
|
|
$formOutStr = '';
|
|
|
|
while ($formCurPos < strlen($formula))
|
|
{
|
|
$char = $formula[$formCurPos];
|
|
|
|
if ($char == '}')
|
|
$formBrktCnt--;
|
|
|
|
if ($formBrktCnt)
|
|
$formOutStr .= $char;
|
|
|
|
if ($char == '{')
|
|
$formBrktCnt++;
|
|
|
|
if (!$formBrktCnt && $formCurPos != $formStartPos)
|
|
break;
|
|
|
|
$formCurPos++;
|
|
}
|
|
|
|
if (isset($formula[++$formCurPos]) && $formula[$formCurPos] == '.')
|
|
{
|
|
$formPrecision = (int)$formula[++$formCurPos];
|
|
++$formCurPos; // for some odd reason the precision decimal survives if we dont increment further..
|
|
}
|
|
|
|
[$formOutStr, $fSuffix, $fRating] = $this->resolveFormulaString($formOutStr, $formPrecision);
|
|
|
|
$formula = substr_replace($formula, $formOutStr, $formStartPos, ($formCurPos - $formStartPos));
|
|
}
|
|
|
|
// note: broken tooltip on this one
|
|
// ${58644m1/-10} gets matched as a formula (ok), 58644m1 has no $ prefixed (not ok)
|
|
// the client scraps the m1 and prints -5864
|
|
if ($this->id == 58644)
|
|
$formula = '$'.$formula;
|
|
|
|
// step 2: resolve variables
|
|
$pos = 0; // continue strpos-search from this offset
|
|
$str = '';
|
|
while (($npos = strpos($formula, '$', $pos)) !== false)
|
|
{
|
|
if ($npos != $pos)
|
|
$str .= substr($formula, $pos, $npos - $pos);
|
|
|
|
$pos = $npos++;
|
|
|
|
if ($formula[$pos] == '$')
|
|
$pos++;
|
|
|
|
$varParts = $this->matchVariableString(substr($formula, $pos), $len);
|
|
if (!$varParts)
|
|
{
|
|
$str .= '#'; // mark as done, reset below
|
|
continue;
|
|
}
|
|
|
|
$pos += $len;
|
|
|
|
// we are resolving a formula -> omit ranges
|
|
$var = $this->resolveVariableString($varParts);
|
|
|
|
// time within formula -> rebase to seconds and omit timeUnit
|
|
if (strtolower($varParts['var']) == 'd')
|
|
{
|
|
$var[0] /= 1000;
|
|
unset($var[2]);
|
|
}
|
|
|
|
$str .= $var[0];
|
|
|
|
// overwrite eventually inherited strings
|
|
if (isset($var[2]))
|
|
$fSuffix = $var[2];
|
|
|
|
// overwrite eventually inherited ratings
|
|
if (isset($var[4]))
|
|
$fRating = $var[4];
|
|
}
|
|
$str .= substr($formula, $pos);
|
|
$str = str_replace('#', '$', $str); // reset marks
|
|
|
|
// step 3: try to evaluate result
|
|
$evaled = $this->resolveEvaluation($str);
|
|
|
|
$return = is_numeric($evaled) ? round($evaled, $precision) : $evaled;
|
|
|
|
return [$return, $fSuffix, $fRating];
|
|
}
|
|
|
|
// should probably used only once to create ?_spell. come to think of it, it yields the same results every time.. it absolutely has to!
|
|
// although it seems to be pretty fast, even on those pesky test-spells with extra complex tooltips (Ron Test Spell X))
|
|
public function parseText($type = 'description', $level = MAX_LEVEL)
|
|
{
|
|
// oooo..kaaayy.. parsing text in 6 or 7 easy steps
|
|
// we don't use the internal iterator here. This func has to be called for the individual template.
|
|
// otherwise it will get a bit messy, when we iterate, while we iterate *yo dawg!*
|
|
|
|
/* documentation .. sort of
|
|
bracket use
|
|
${}.x - formulas; .x is optional; x:[0-9] .. max-precision of a floatpoint-result; default: 0
|
|
$[] - conditionals ... like $?condition[true][false]; alternative $?!(cond1|cond2)[true]$?cond3[elseTrue][false]; ?a40120: has aura 40120; ?s40120: knows spell 40120(?)
|
|
$<> - variables
|
|
() - regular use for function-like calls
|
|
|
|
variables in use .. caseSensitive
|
|
|
|
game variables (optionally replace with textVars)
|
|
$PlayerName - Cpt. Obvious
|
|
$PL / $pl - PlayerLevel
|
|
$STR - Strength Attribute (not seen)
|
|
$AGI - Agility Attribute (not seen)
|
|
$STA - Stamina Attribute (not seen)
|
|
$INT - Intellect Attribute (not seen)
|
|
$SPI - Spirit Attribute
|
|
$AP - Atkpwr
|
|
$RAP - RngAtkPwr
|
|
$HND - hands used by weapon (1H, 2H) => (1, 2)
|
|
$MWS - MainhandWeaponSpeed
|
|
$mw / $MW - MainhandWeaponDamage Min/Max
|
|
$rwb / $RWB - RangedWeapon..Bonus? Min/Max
|
|
$sp - Spellpower
|
|
$spa - Spellpower Arcane
|
|
$spfi - Spellpower Fire
|
|
$spfr - Spellpower Frost
|
|
$sph - Spellpower Holy
|
|
$spn - Spellpower Nature
|
|
$sps - Spellpower Shadow
|
|
$bh - Bonus Healing
|
|
$pa - %-ArcaneDmg (as float) // V seems broken
|
|
$pfi - %-FireDmg (as float)
|
|
$pfr - %-FrostDmg (as float)
|
|
$ph - %-HolyDmg (as float)
|
|
$pn - %-NatureDmg (as float)
|
|
$ps - %-ShadowDmg (as float)
|
|
$pbh - %-HealingBonus (as float)
|
|
$pbhd - %-Healing Done (as float) // all above seem broken
|
|
$bc2 - baseCritChance? always 3.25 (unsure)
|
|
|
|
spell variables (the stuff we can actually parse) rounding... >5 up?
|
|
$a - SpellRadius; per EffectIdx
|
|
$b - PointsPerComboPoint; per EffectIdx
|
|
$d / $D - SpellDuration; appended timeShorthand; d/D maybe base/max duration?; interpret "0" as "until canceled"
|
|
$e - EffectValueMultiplier; per EffectIdx
|
|
$f / $F - EffectDamageMultiplier; per EffectIdx
|
|
$g / $G - Gender-Switch $Gmale:female;
|
|
$h / $H - ProcChance
|
|
$i - MaxAffectedTargets
|
|
$l - LastValue-Switch; last value as condition $Ltrue:false;
|
|
$m / $M - BasePoints; per EffectIdx; m/M +1/+effectDieSides
|
|
$n - ProcCharges
|
|
$o - TotalAmount (for periodic auras); per EffectIdx
|
|
$q - EffectMiscValue; per EffectIdx
|
|
$r - SpellRange (hostile)
|
|
$s / $S - BasePoints; per EffectIdx; as Range, if applicable
|
|
$t / $T - EffectPeriode; per EffectIdx
|
|
$u - StackAmount
|
|
$v - MaxTargetLevel
|
|
$x - MaxAffectedTargets
|
|
$z - no place like <Home>
|
|
|
|
deviations from standard procedures
|
|
division - example: $/10;2687s1 => $2687s1/10
|
|
- also: $61829/5;s1 => $61829s1/5
|
|
|
|
functions in use .. caseInsensitive
|
|
$cond(a, b, c) - like SQL, if A is met use B otherwise use C
|
|
$eq(a, b) - a == b
|
|
$floor(a) - floor()
|
|
$gt(a, b) - a > b
|
|
$gte(a, b) - a >= b
|
|
$min(a, b) - min()
|
|
$max(a, b) - max()
|
|
*/
|
|
|
|
$this->charLevel = $level;
|
|
|
|
// step -1: already handled?
|
|
if (isset($this->parsedText[$this->id][$type][User::$localeId][$this->charLevel][(int)$this->interactive]))
|
|
return $this->parsedText[$this->id][$type][User::$localeId][$this->charLevel][(int)$this->interactive];
|
|
|
|
// step 0: get text
|
|
$data = $this->getField($type, true);
|
|
if (empty($data) || $data == "[]") // empty tooltip shouldn't be displayed anyway
|
|
return ['', [], false];
|
|
|
|
// step 1: if the text is supplemented with text-variables, get and replace them
|
|
if ($this->curTpl['spellDescriptionVariableId'] > 0)
|
|
{
|
|
if (empty($this->spellVars[$this->id]))
|
|
{
|
|
$spellVars = DB::Aowow()->SelectCell('SELECT vars FROM ?_spellvariables WHERE id = ?d', $this->curTpl['spellDescriptionVariableId']);
|
|
$spellVars = explode("\n", $spellVars);
|
|
foreach ($spellVars as $sv)
|
|
if (preg_match('/\$(\w*\d*)=(.*)/i', trim($sv), $matches))
|
|
$this->spellVars[$this->id][$matches[1]] = $matches[2];
|
|
}
|
|
|
|
// replace self-references
|
|
$reset = true;
|
|
while ($reset)
|
|
{
|
|
$reset = false;
|
|
foreach ($this->spellVars[$this->id] as $k => $sv)
|
|
{
|
|
if (preg_match('/\$<(\w*\d*)>/i', $sv, $matches))
|
|
{
|
|
$this->spellVars[$this->id][$k] = str_replace('$<'.$matches[1].'>', '${'.$this->spellVars[$this->id][$matches[1]].'}', $sv);
|
|
$reset = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// finally, replace SpellDescVars
|
|
foreach ($this->spellVars[$this->id] as $k => $sv)
|
|
$data = str_replace('$<'.$k.'>', $sv, $data);
|
|
}
|
|
|
|
// step 2: resolving conditions
|
|
// aura- or spell-conditions cant be resolved for our purposes, so force them to false for now (todo (low): strg+f "know" in aowowPower.js ^.^)
|
|
|
|
/* sequences
|
|
a) simple - $?cond[A][B] // simple case of b)
|
|
b) elseif - $?cond[A]?cond[B]..[C] // can probably be repeated as often as you wanted
|
|
c) recursive - $?cond[A][$?cond[B][..]] // can probably be stacked as deep as you wanted
|
|
|
|
only case a) can be used for KNOW-parameter
|
|
*/
|
|
|
|
$relSpells = [];
|
|
$data = $this->handleConditions($data, $relSpells, true);
|
|
|
|
// step 3: unpack formulas ${ .. }.X
|
|
$data = $this->handleFormulas($data, true);
|
|
|
|
// step 4: find and eliminate regular variables
|
|
$data = $this->handleVariables($data, true);
|
|
|
|
// step 5: variable-dependant variable-text
|
|
// special case $lONE:ELSE[:ELSE2]; or $|ONE:ELSE[:ELSE2];
|
|
while (preg_match('/([\d\.]+)([^\d]*)(\$[l|]:*)([^:]*):([^;]*);/i', $data, $m))
|
|
{
|
|
$plurals = explode(':', $m[5]);
|
|
$replace = '';
|
|
|
|
if (count($plurals) == 2) // special case: ruRU
|
|
{
|
|
switch (substr($m[1], -1)) // check last digit of number
|
|
{
|
|
case 1:
|
|
// but not 11 (teen number)
|
|
if (!in_array($m[1], [11]))
|
|
{
|
|
$replace = $m[4];
|
|
break;
|
|
}
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
// but not 12, 13, 14 (teen number) [11 is passthrough]
|
|
if (!in_array($m[1], [11, 12, 13, 14]))
|
|
{
|
|
$replace = $plurals[0];
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
$replace = $plurals[1];
|
|
}
|
|
|
|
}
|
|
else
|
|
$replace = ($m[1] == 1 ? $m[4] : $plurals[0]);
|
|
|
|
$data = str_ireplace($m[1].$m[2].$m[3].$m[4].':'.$m[5].';', $m[1].$m[2].$replace, $data);
|
|
}
|
|
|
|
// step 6: HTMLize
|
|
// colors
|
|
$data = preg_replace('/\|cff([a-f0-9]{6})(.+?)\|r/i', '<span style="color: #$1;">$2</span>', $data);
|
|
|
|
// line endings
|
|
$data = strtr($data, ["\r" => '', "\n" => '<br />']);
|
|
|
|
// cache result
|
|
$this->parsedText[$this->id][$type][User::$localeId][$this->charLevel][(int)$this->interactive] = [$data, $relSpells, $this->scaling[$this->id]];
|
|
|
|
return [$data, $relSpells, $this->scaling[$this->id]];
|
|
}
|
|
|
|
private function handleFormulas($data, $topLevel = false)
|
|
{
|
|
// they are stacked recursively but should be balanced .. hf
|
|
while (($formStartPos = strpos($data, '${')) !== false)
|
|
{
|
|
$formBrktCnt = 0;
|
|
$formPrecision = 0;
|
|
$formCurPos = $formStartPos;
|
|
|
|
$formOutStr = '';
|
|
|
|
while ($formCurPos < strlen($data)) // only hard-exit condition, we'll hit those breaks eventually^^
|
|
{
|
|
$char = $data[$formCurPos];
|
|
|
|
if ($char == '}')
|
|
$formBrktCnt--;
|
|
|
|
if ($formBrktCnt)
|
|
$formOutStr .= $char;
|
|
|
|
if ($char == '{')
|
|
$formBrktCnt++;
|
|
|
|
if (!$formBrktCnt && $formCurPos != $formStartPos)
|
|
break;
|
|
|
|
// advance position
|
|
$formCurPos++;
|
|
}
|
|
|
|
$formCurPos++;
|
|
|
|
// check for precision-modifiers
|
|
if ($formCurPos + 1 < strlen($data) && $data[$formCurPos] == '.' && is_numeric($data[$formCurPos + 1]))
|
|
{
|
|
$formPrecision = $data[$formCurPos + 1];
|
|
$formCurPos += 2;
|
|
}
|
|
[$formOutVal, $formOutStr, $ratingId] = $this->resolveFormulaString($formOutStr, $formPrecision ?: ($topLevel ? 0 : 10));
|
|
|
|
if ($ratingId && Util::checkNumeric($formOutVal) && $this->interactive)
|
|
$resolved = sprintf($formOutStr, $ratingId, abs($formOutVal), sprintf(Util::$setRatingLevelString, $this->charLevel, $ratingId, abs($formOutVal), Util::setRatingLevel($this->charLevel, $ratingId, abs($formOutVal))));
|
|
else if ($ratingId && Util::checkNumeric($formOutVal))
|
|
$resolved = sprintf($formOutStr, $ratingId, abs($formOutVal), Util::setRatingLevel($this->charLevel, $ratingId, abs($formOutVal)));
|
|
else
|
|
$resolved = sprintf($formOutStr, Util::checkNumeric($formOutVal) ? abs($formOutVal) : $formOutVal);
|
|
|
|
$data = substr_replace($data, $resolved, $formStartPos, ($formCurPos - $formStartPos));
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
private function handleVariables($data, $topLevel = false)
|
|
{
|
|
$pos = 0; // continue strpos-search from this offset
|
|
$str = '';
|
|
while (($npos = strpos($data, '$', $pos)) !== false)
|
|
{
|
|
if ($npos != $pos)
|
|
$str .= substr($data, $pos, $npos - $pos);
|
|
|
|
$pos = $npos++;
|
|
|
|
if ($data[$pos] == '$')
|
|
$pos++;
|
|
|
|
$varParts = $this->matchVariableString(substr($data, $pos), $len);
|
|
if (!$varParts)
|
|
{
|
|
$str .= '#'; // mark as done, reset below
|
|
continue;
|
|
}
|
|
|
|
$pos += $len;
|
|
|
|
$var = $this->resolveVariableString($varParts);
|
|
$resolved = is_numeric($var[0]) ? abs($var[0]) : $var[0];
|
|
if (isset($var[2]))
|
|
{
|
|
if (isset($var[4]) && $this->interactive)
|
|
$resolved = sprintf($var[2], $var[4], abs($var[0]), sprintf(Util::$setRatingLevelString, $this->charLevel, $var[4], abs($var[0]), Util::setRatingLevel($this->charLevel, $var[4], abs($var[0]))));
|
|
else if (isset($var[4]))
|
|
$resolved = sprintf($var[2], $var[4], abs($var[0]), Util::setRatingLevel($this->charLevel, $var[4], abs($var[0])));
|
|
else
|
|
$resolved = sprintf($var[2], $resolved);
|
|
}
|
|
|
|
if (isset($var[1]) && $var[0] != $var[1] && !isset($var[4]))
|
|
{
|
|
$_ = is_numeric($var[1]) ? abs($var[1]) : $var[1];
|
|
$resolved .= Lang::game('valueDelim');
|
|
$resolved .= isset($var[3]) ? sprintf($var[3], $_) : $_;
|
|
}
|
|
|
|
$str .= $resolved;
|
|
}
|
|
$str .= substr($data, $pos);
|
|
$str = str_replace('#', '$', $str); // reset marker
|
|
|
|
return $str;
|
|
}
|
|
|
|
private function handleConditions($data, &$relSpells, $topLevel = false)
|
|
{
|
|
while (($condStartPos = strpos($data, '$?')) !== false)
|
|
{
|
|
$condBrktCnt = 0;
|
|
$condCurPos = $condStartPos + 2; // after the '$?'
|
|
$targetPart = 3; // we usually want the second pair of brackets
|
|
$curPart = 0; // parts: $? 0 [ 1 ] 2 [ 3 ] 4 ...
|
|
$condParts = [];
|
|
$isLastElse = false;
|
|
|
|
while ($condCurPos < strlen($data)) // only hard-exit condition, we'll hit those breaks eventually^^
|
|
{
|
|
$char = $data[$condCurPos];
|
|
|
|
// advance position
|
|
$condCurPos++;
|
|
|
|
if ($char == '[')
|
|
{
|
|
$condBrktCnt++;
|
|
|
|
if ($condBrktCnt == 1)
|
|
$curPart++;
|
|
|
|
// previously there was no condition -> last else
|
|
if ($condBrktCnt == 1)
|
|
if (($curPart && ($curPart % 2)) && (!isset($condParts[$curPart - 1]) || empty(trim($condParts[$curPart - 1]))))
|
|
$isLastElse = true;
|
|
|
|
if (empty($condParts[$curPart]))
|
|
continue;
|
|
}
|
|
|
|
if (empty($condParts[$curPart]))
|
|
$condParts[$curPart] = $char;
|
|
else
|
|
$condParts[$curPart] .= $char;
|
|
|
|
if ($char == ']')
|
|
{
|
|
$condBrktCnt--;
|
|
|
|
if (!$condBrktCnt)
|
|
{
|
|
$condParts[$curPart] = substr($condParts[$curPart], 0, -1);
|
|
$curPart++;
|
|
}
|
|
|
|
if ($condBrktCnt)
|
|
continue;
|
|
|
|
if ($isLastElse)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// this should be 0 if all went well
|
|
if ($condBrktCnt > 0)
|
|
{
|
|
trigger_error('SpellList::handleConditions() - string contains unbalanced condition', E_USER_WARNING);
|
|
$condParts[3] = $condParts[3] ?? '';
|
|
}
|
|
|
|
// check if it is know-compatible
|
|
$know = 0;
|
|
if (preg_match('/\(?(\!?)[as](\d+)\)?$/i', $condParts[0], $m))
|
|
{
|
|
if (!strstr($condParts[1], '$?'))
|
|
if (!strstr($condParts[3], '$?'))
|
|
if (!isset($condParts[5]))
|
|
$know = $m[2];
|
|
|
|
// found a negation -> switching condition target
|
|
if ($m[1] == '!')
|
|
$targetPart = 1;
|
|
}
|
|
// if not, what part of the condition should be used?
|
|
else if (preg_match('/(([\W\D]*[as]\d+)|([^\[]*))/i', $condParts[0], $m) && !empty($m[3]))
|
|
{
|
|
$cnd = $this->resolveEvaluation($m[3]);
|
|
if ((is_numeric($cnd) || is_bool($cnd)) && $cnd) // only case, deviating from normal; positive result -> use [true]
|
|
$targetPart = 1;
|
|
}
|
|
|
|
// recursive conditions
|
|
if (strstr($condParts[$targetPart], '$?'))
|
|
$condParts[$targetPart] = $this->handleConditions($condParts[$targetPart], $relSpells);
|
|
|
|
if ($know && $topLevel)
|
|
{
|
|
foreach ([1, 3] as $pos)
|
|
{
|
|
if (strstr($condParts[$pos], '${'))
|
|
$condParts[$pos] = $this->handleFormulas($condParts[$pos]);
|
|
|
|
if (strstr($condParts[$pos], '$'))
|
|
$condParts[$pos] = $this->handleVariables($condParts[$pos]);
|
|
}
|
|
|
|
// false condition first
|
|
if (!isset($relSpells[$know]))
|
|
$relSpells[$know] = [];
|
|
|
|
$relSpells[$know][] = [$condParts[3], $condParts[1]];
|
|
|
|
$data = substr_replace($data, '<!--sp'.$know.':0-->'.$condParts[$targetPart].'<!--sp'.$know.'-->', $condStartPos, ($condCurPos - $condStartPos));
|
|
}
|
|
else
|
|
$data = substr_replace($data, $condParts[$targetPart], $condStartPos, ($condCurPos - $condStartPos));
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
public function renderBuff($level = MAX_LEVEL, $interactive = false)
|
|
{
|
|
if (!$this->curTpl)
|
|
return ['', []];
|
|
|
|
// doesn't have a buff
|
|
if (!$this->getField('buff', true))
|
|
return ['', []];
|
|
|
|
$this->interactive = $interactive;
|
|
$this->charLevel = $level;
|
|
|
|
$x = '<table><tr>';
|
|
|
|
// spellName
|
|
$x .= '<td><b class="q">'.$this->getField('name', true).'</b></td>';
|
|
|
|
// dispelType (if applicable)
|
|
if ($this->curTpl['dispelType'])
|
|
if ($dispel = Lang::game('dt', $this->curTpl['dispelType']))
|
|
$x .= '<th><b class="q">'.$dispel.'</b></th>';
|
|
|
|
$x .= '</tr></table>';
|
|
|
|
$x .= '<table><tr><td>';
|
|
|
|
// parse Buff-Text
|
|
$btt = $this->parseText('buff');
|
|
$x .= $btt[0].'<br>';
|
|
|
|
// duration
|
|
if ($this->curTpl['duration'] > 0)
|
|
$x .= '<span class="q">'.Lang::formatTime($this->curTpl['duration'], 'spell', 'timeRemaining').'<span>';
|
|
|
|
$x .= '</td></tr></table>';
|
|
|
|
$min = $this->scaling[$this->id] ? ($this->getField('baseLevel') ?: 1) : 1;
|
|
$max = $this->scaling[$this->id] ? MAX_LEVEL : 1;
|
|
// scaling information - spellId:min:max:curr
|
|
$x .= '<!--?'.$this->id.':'.$min.':'.$max.':'.min($this->charLevel, $max).'-->';
|
|
|
|
return [$x, Util::parseHtmlText($btt[1])];
|
|
}
|
|
|
|
public function renderTooltip($level = MAX_LEVEL, $interactive = false)
|
|
{
|
|
if (!$this->curTpl)
|
|
return ['', []];
|
|
|
|
$this->interactive = $interactive;
|
|
$this->charLevel = $level;
|
|
|
|
// fetch needed texts
|
|
$name = $this->getField('name', true);
|
|
$rank = $this->getField('rank', true);
|
|
$desc = $this->parseText('description');
|
|
$tools = $this->getToolsForCurrent();
|
|
$cool = $this->createCooldownForCurrent();
|
|
$cast = $this->createCastTimeForCurrent();
|
|
$cost = $this->createPowerCostForCurrent();
|
|
$range = $this->createRangesForCurrent();
|
|
|
|
// get reagents
|
|
$reagents = $this->getReagentsForCurrent();
|
|
foreach ($reagents as &$r)
|
|
$r[2] = ItemList::getName($r[0]);
|
|
|
|
$reagents = array_reverse($reagents);
|
|
|
|
// get stances
|
|
$stances = '';
|
|
if ($this->curTpl['stanceMask'] && !($this->curTpl['attributes2'] & SPELL_ATTR2_NOT_NEED_SHAPESHIFT))
|
|
$stances = Lang::game('requires2').' '.Lang::getStances($this->curTpl['stanceMask']);
|
|
|
|
// get item requirement (skip for professions)
|
|
$reqItems = '';
|
|
if ($this->curTpl['typeCat'] != 11)
|
|
{
|
|
$class = $this->getField('equippedItemClass');
|
|
$mask = $this->getField('equippedItemSubClassMask');
|
|
$reqItems = Lang::getRequiredItems($class, $mask);
|
|
}
|
|
|
|
// get created items (may need improvement)
|
|
$createItem = '';
|
|
if (in_array($this->curTpl['typeCat'], [9, 11])) // only Professions
|
|
{
|
|
foreach ($this->canCreateItem() as $idx)
|
|
{
|
|
// has createItem Scroll of Enchantment
|
|
if ($this->curTpl['effect'.$idx.'Id'] == SPELL_EFFECT_ENCHANT_ITEM)
|
|
continue;
|
|
|
|
foreach ($this->relItems->iterate() as $cId => $__)
|
|
{
|
|
if ($cId != $this->curTpl['effect'.$idx.'CreateItemId'])
|
|
continue;
|
|
|
|
$createItem = $this->relItems->renderTooltip(true, $this->id);
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
$x = '';
|
|
$x .= '<table><tr><td>';
|
|
|
|
// name & rank
|
|
if ($rank)
|
|
$x .= '<table width="100%"><tr><td><b>'.$name.'</b></td><th><b class="q0">'.$rank.'</b></th></tr></table>';
|
|
else
|
|
$x .= '<b>'.$name.'</b><br />';
|
|
|
|
// powerCost & ranges
|
|
if ($range && $cost)
|
|
$x .= '<table width="100%"><tr><td>'.$cost.'</td><th>'.$range.'</th></tr></table>';
|
|
else if ($cost || $range)
|
|
$x .= $range.$cost.'<br />';
|
|
|
|
// castTime & cooldown
|
|
if ($cast && $cool) // tabled layout
|
|
{
|
|
$x .= '<table width="100%">';
|
|
$x .= '<tr><td>'.$cast.'</td><th>'.$cool.'</th></tr>';
|
|
if ($stances)
|
|
$x.= '<tr><td colspan="2">'.$stances.'</td></tr>';
|
|
|
|
$x .= '</table>';
|
|
}
|
|
else if ($cast || $cool) // line-break layout
|
|
{
|
|
$x .= $cast.$cool;
|
|
|
|
if ($stances)
|
|
$x .= '<br />'.$stances;
|
|
}
|
|
|
|
$x .= '</td></tr></table>';
|
|
|
|
$xTmp = [];
|
|
|
|
if ($tools)
|
|
{
|
|
$_ = Lang::spell('tools').':<br/><div class="indent q1">';
|
|
while ($tool = array_pop($tools))
|
|
{
|
|
if (isset($tool['itemId']))
|
|
$_ .= '<a href="?item='.$tool['itemId'].'">'.$tool['name'].'</a>';
|
|
else if (isset($tool['id']))
|
|
$_ .= '<a href="?items&filter=cr=91;crs='.$tool['id'].';crv=0">'.$tool['name'].'</a>';
|
|
else
|
|
$_ .= $tool['name'];
|
|
|
|
if (!empty($tools))
|
|
$_ .= ', ';
|
|
else
|
|
$_ .= '<br />';
|
|
}
|
|
|
|
$xTmp[] = $_.'</div>';
|
|
}
|
|
|
|
if ($reagents)
|
|
{
|
|
$_ = Lang::spell('reagents').':<br/><div class="indent q1">';
|
|
while ($reagent = array_pop($reagents))
|
|
{
|
|
$_ .= '<a href="?item='.$reagent[0].'">'.$reagent[2].'</a>';
|
|
if ($reagent[1] > 1)
|
|
$_ .= ' ('.$reagent[1].')';
|
|
|
|
$_ .= empty($reagents) ? '<br />' : ', ';
|
|
}
|
|
|
|
$xTmp[] = $_.'</div>';
|
|
}
|
|
|
|
if ($reqItems)
|
|
$xTmp[] = Lang::game('requires2').' '.$reqItems;
|
|
|
|
if ($desc[0])
|
|
$xTmp[] = '<span class="q">'.$desc[0].'</span>';
|
|
|
|
if ($createItem)
|
|
$xTmp[] = $createItem;
|
|
|
|
if ($xTmp)
|
|
$x .= '<table><tr><td>'.implode('<br />', $xTmp).'</td></tr></table>';
|
|
|
|
$min = $this->scaling[$this->id] ? ($this->getField('baseLevel') ?: 1) : 1;
|
|
$max = $this->scaling[$this->id] ? MAX_LEVEL : 1;
|
|
// scaling information - spellId:min:max:curr
|
|
$x .= '<!--?'.$this->id.':'.$min.':'.$max.':'.min($this->charLevel, $max).'-->';
|
|
|
|
return [$x, Util::parseHtmlText($desc[1])];
|
|
}
|
|
|
|
public function getTalentHeadForCurrent()
|
|
{
|
|
// power cost: pct over static
|
|
$cost = $this->createPowerCostForCurrent();
|
|
|
|
// ranges
|
|
$range = $this->createRangesForCurrent();
|
|
|
|
// cast times
|
|
$cast = $this->createCastTimeForCurrent();
|
|
|
|
// cooldown or categorycooldown
|
|
$cool = $this->createCooldownForCurrent();
|
|
|
|
// assemble parts
|
|
// upper: cost :: range
|
|
// lower: time :: cooldown
|
|
$x = '';
|
|
|
|
// upper
|
|
if ($cost && $range)
|
|
$x .= '<table width="100%"><tr><td>'.$cost.'</td><th>'.$range.'</th></tr></table>';
|
|
else
|
|
$x .= $cost.$range;
|
|
|
|
if (($cost xor $range) && ($cast xor $cool))
|
|
$x .= '<br />';
|
|
|
|
// lower
|
|
if ($cast && $cool)
|
|
$x .= '<table width="100%"><tr><td>'.$cast.'</td><th>'.$cool.'</th></tr></table>';
|
|
else
|
|
$x .= $cast.$cool;
|
|
|
|
return $x;
|
|
}
|
|
|
|
public function getColorsForCurrent() : array
|
|
{
|
|
$gry = $this->curTpl['skillLevelGrey'];
|
|
$ylw = $this->curTpl['skillLevelYellow'];
|
|
$grn = (int)(($ylw + $gry) / 2);
|
|
$org = $this->curTpl['learnedAt'];
|
|
|
|
if ($ylw < $org)
|
|
$ylw = 0;
|
|
|
|
if ($grn < $org || $grn < $ylw)
|
|
$grn = 0;
|
|
|
|
if ($org >= $ylw || $org >= $grn || $org >= $gry)
|
|
$org = 0;
|
|
|
|
return $gry > 1 ? [$org, $ylw, $grn, $gry] : [];
|
|
}
|
|
|
|
public function getListviewData($addInfoMask = 0x0)
|
|
{
|
|
$data = [];
|
|
|
|
if ($addInfoMask & ITEMINFO_MODEL)
|
|
$modelInfo = $this->getModelInfo();
|
|
|
|
foreach ($this->iterate() as $__)
|
|
{
|
|
$quality = ($this->curTpl['cuFlags'] & SPELL_CU_QUALITY_MASK) >> 8;
|
|
$talent = $this->curTpl['cuFlags'] & (SPELL_CU_TALENT | SPELL_CU_TALENTSPELL) && $this->curTpl['spellLevel'] <= 1;
|
|
|
|
$data[$this->id] = array(
|
|
'id' => $this->id,
|
|
'name' => ($quality ?: '@').$this->getField('name', true),
|
|
'icon' => $this->curTpl['iconStringAlt'] ?: $this->curTpl['iconString'],
|
|
'level' => $talent ? $this->curTpl['talentLevel'] : $this->curTpl['spellLevel'],
|
|
'school' => $this->curTpl['schoolMask'],
|
|
'cat' => $this->curTpl['typeCat'],
|
|
'trainingcost' => $this->curTpl['trainingCost'],
|
|
'skill' => count($this->curTpl['skillLines']) > 4 ? array_merge(array_splice($this->curTpl['skillLines'], 0, 4), [-1]): $this->curTpl['skillLines'], // display max 4 skillLines (fills max three lines in listview)
|
|
'reagents' => array_values($this->getReagentsForCurrent()),
|
|
'source' => []
|
|
// 'talentspec' => $this->curTpl['skillLines'][0] not used: g_chr_specs has the wrong structure for it; also setting .cat and .type does the same
|
|
);
|
|
|
|
// Sources
|
|
if ($this->getSources($s, $sm))
|
|
{
|
|
$data[$this->id]['source'] = $s;
|
|
if ($sm)
|
|
$data[$this->id]['sourcemore'] = $sm;
|
|
}
|
|
|
|
// Proficiencies
|
|
if ($this->curTpl['typeCat'] == -11)
|
|
foreach (self::$spellTypes as $cat => $type)
|
|
if (in_array($this->curTpl['skillLines'][0], self::$skillLines[$cat]))
|
|
$data[$this->id]['type'] = $type;
|
|
|
|
// creates item
|
|
foreach ($this->canCreateItem() as $idx)
|
|
{
|
|
$max = $this->curTpl['effect'.$idx.'DieSides'] + $this->curTpl['effect'.$idx.'BasePoints'];
|
|
$min = $this->curTpl['effect'.$idx.'DieSides'] > 1 ? 1 : $max;
|
|
|
|
$data[$this->id]['creates'] = [$this->curTpl['effect'.$idx.'CreateItemId'], $min, $max];
|
|
break;
|
|
}
|
|
|
|
// Profession
|
|
if (in_array($this->curTpl['typeCat'], [9, 11]))
|
|
{
|
|
if ($la = $this->curTpl['learnedAt'])
|
|
$data[$this->id]['learnedat'] = $la;
|
|
else if (($la = $this->curTpl['reqSkillLevel']) > 1)
|
|
$data[$this->id]['learnedat'] = $la;
|
|
|
|
$data[$this->id]['colors'] = $this->getColorsForCurrent();
|
|
}
|
|
|
|
// glyph
|
|
if ($this->curTpl['typeCat'] == -13)
|
|
$data[$this->id]['glyphtype'] = $this->curTpl['cuFlags'] & SPELL_CU_GLYPH_MAJOR ? 1 : 2;
|
|
|
|
if ($r = $this->getField('rank', true))
|
|
$data[$this->id]['rank'] = $r;
|
|
|
|
if ($mask = $this->curTpl['reqClassMask'])
|
|
$data[$this->id]['reqclass'] = $mask;
|
|
|
|
if ($mask = $this->curTpl['reqRaceMask'])
|
|
$data[$this->id]['reqrace'] = $mask;
|
|
|
|
|
|
if ($addInfoMask & ITEMINFO_MODEL)
|
|
{
|
|
// may have multiple models set, in this case i've no idea what should be picked
|
|
for ($i = 1; $i < 4; $i++)
|
|
{
|
|
if (!empty($modelInfo[$this->id][$i]))
|
|
{
|
|
$data[$this->id]['npcId'] = $modelInfo[$this->id][$i]['typeId'];
|
|
$data[$this->id]['displayId'] = $modelInfo[$this->id][$i]['displayId'];
|
|
$data[$this->id]['displayName'] = $modelInfo[$this->id][$i]['displayName'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
public function getJSGlobals($addMask = GLOBALINFO_SELF, &$extra = [])
|
|
{
|
|
$data = [];
|
|
|
|
if ($this->relItems && ($addMask & GLOBALINFO_RELATED))
|
|
$data = $this->relItems->getJSGlobals();
|
|
|
|
foreach ($this->iterate() as $id => $__)
|
|
{
|
|
if ($addMask & GLOBALINFO_RELATED)
|
|
{
|
|
if ($mask = $this->curTpl['reqClassMask'])
|
|
for ($i = 0; $i < 11; $i++)
|
|
if ($mask & (1 << $i))
|
|
$data[Type::CHR_CLASS][$i + 1] = $i + 1;
|
|
|
|
if ($mask = $this->curTpl['reqRaceMask'])
|
|
for ($i = 0; $i < 11; $i++)
|
|
if ($mask & (1 << $i))
|
|
$data[Type::CHR_RACE][$i + 1] = $i + 1;
|
|
|
|
// play sound effect
|
|
for ($i = 1; $i < 4; $i++)
|
|
if ($this->getField('effect'.$i.'Id') == SPELL_EFFECT_PLAY_SOUND || $this->getField('effect'.$i.'Id') == SPELL_EFFECT_PLAY_MUSIC)
|
|
$data[Type::SOUND][$this->getField('effect'.$i.'MiscValue')] = $this->getField('effect'.$i.'MiscValue');
|
|
}
|
|
|
|
if ($addMask & GLOBALINFO_SELF)
|
|
{
|
|
$iconString = $this->curTpl['iconStringAlt'] ? 'iconStringAlt' : 'iconString';
|
|
|
|
$data[Type::SPELL][$id] = array(
|
|
'icon' => $this->curTpl[$iconString],
|
|
'name' => $this->getField('name', true),
|
|
);
|
|
}
|
|
|
|
if ($addMask & GLOBALINFO_EXTRA)
|
|
{
|
|
$buff = $this->renderBuff(MAX_LEVEL, true);
|
|
$tTip = $this->renderTooltip(MAX_LEVEL, true);
|
|
|
|
foreach ($tTip[1] as $relId => $_)
|
|
if (empty($data[Type::SPELL][$relId]))
|
|
$data[Type::SPELL][$relId] = $relId;
|
|
|
|
foreach ($buff[1] as $relId => $_)
|
|
if (empty($data[Type::SPELL][$relId]))
|
|
$data[Type::SPELL][$relId] = $relId;
|
|
|
|
$extra[$id] = array(
|
|
'id' => $id,
|
|
'tooltip' => $tTip[0],
|
|
'buff' => !empty($buff[0]) ? $buff[0] : null,
|
|
'spells' => $tTip[1],
|
|
'buffspells' => !empty($buff[1]) ? $buff[1] : null
|
|
);
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
// mostly similar to TC
|
|
public function getCastingTimeForBonus($asDOT = false)
|
|
{
|
|
$areaTargets = [7, 8, 15, 16, 20, 24, 30, 31, 33, 34, 37, 54, 56, 59, 104, 108];
|
|
$castingTime = $this->IsChanneledSpell() ? $this->curTpl['duration'] : ($this->curTpl['castTime'] * 1000);
|
|
|
|
if (!$castingTime)
|
|
return 3500;
|
|
|
|
if ($castingTime > 7000)
|
|
$castingTime = 7000;
|
|
|
|
if ($castingTime < 1500)
|
|
$castingTime = 1500;
|
|
|
|
if ($asDOT && !$this->isChanneledSpell())
|
|
$castingTime = 3500;
|
|
|
|
$overTime = 0;
|
|
$nEffects = 0;
|
|
$isDirect = false;
|
|
$isArea = false;
|
|
|
|
for ($i = 1; $i <= 3; $i++)
|
|
{
|
|
if (in_array($this->curTpl['effect'.$i.'Id'], SpellLIst::EFFECTS_DIRECT_SCALING))
|
|
$isDirect = true;
|
|
else if (in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_PERIODIC_SCALING))
|
|
if ($_ = $this->curTpl['duration'])
|
|
$overTime = $_;
|
|
else if ($this->curTpl['effect'.$i.'AuraId'])
|
|
$nEffects++;
|
|
|
|
if (in_array($this->curTpl['effect'.$i.'ImplicitTargetA'], $areaTargets) || in_array($this->curTpl['effect'.$i.'ImplicitTargetB'], $areaTargets))
|
|
$isArea = true;
|
|
}
|
|
|
|
// Combined Spells with Both Over Time and Direct Damage
|
|
if ($overTime > 0 && $castingTime > 0 && $isDirect)
|
|
{
|
|
// mainly for DoTs which are 3500 here otherwise
|
|
$originalCastTime = $this->curTpl['castTime'] * 1000;
|
|
if ($this->curTpl['attributes0'] & SPELL_ATTR0_REQ_AMMO)
|
|
$originalCastTime += 500;
|
|
|
|
if ($originalCastTime > 7000)
|
|
$originalCastTime = 7000;
|
|
|
|
if ($originalCastTime < 1500)
|
|
$originalCastTime = 1500;
|
|
|
|
// Portion to Over Time
|
|
$PtOT = ($overTime / 15000) / (($overTime / 15000) + ($originalCastTime / 3500));
|
|
|
|
if ($asDOT)
|
|
$castingTime = $castingTime * $PtOT;
|
|
else if ($PtOT < 1)
|
|
$castingTime = $castingTime * (1 - $PtOT);
|
|
else
|
|
$castingTime = 0;
|
|
}
|
|
|
|
// Area Effect Spells receive only half of bonus
|
|
if ($isArea)
|
|
$castingTime /= 2;
|
|
|
|
// -5% of total per any additional effect
|
|
$castingTime -= ($nEffects * 175);
|
|
if ($castingTime < 0)
|
|
$castingTime = 0;
|
|
|
|
return $castingTime;
|
|
}
|
|
|
|
public function getSourceData(int $id = 0) : array
|
|
{
|
|
$data = [];
|
|
|
|
foreach ($this->iterate() as $__)
|
|
{
|
|
if ($id && $id != $this->id)
|
|
continue;
|
|
|
|
$data[$this->id] = array(
|
|
'n' => $this->getField('name', true),
|
|
't' => Type::SPELL,
|
|
'ti' => $this->id,
|
|
's' => empty($this->curTpl['skillLines']) ? 0 : $this->curTpl['skillLines'][0],
|
|
'c' => $this->curTpl['typeCat'],
|
|
'icon' => $this->curTpl['iconStringAlt'] ? $this->curTpl['iconStringAlt'] : $this->curTpl['iconString'],
|
|
);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
|
|
class SpellListFilter extends Filter
|
|
{
|
|
const MAX_SPELL_EFFECT = 167;
|
|
const MAX_SPELL_AURA = 316;
|
|
|
|
protected $enums = array(
|
|
9 => array( // sources index
|
|
1 => true, // Any
|
|
2 => false, // None
|
|
3 => 1, // Crafted
|
|
4 => 2, // Drop
|
|
6 => 4, // Quest
|
|
7 => 5, // Vendor
|
|
8 => 6, // Trainer
|
|
9 => 7, // Discovery
|
|
10 => 9 // Talent
|
|
),
|
|
40 => array( // damage class index
|
|
1 => 0, // none
|
|
2 => 1, // magic
|
|
3 => 2, // melee
|
|
4 => 3 // ranged
|
|
),
|
|
45 => array( // power type index
|
|
// 1 => ??, // burning embers
|
|
// 2 => ??, // chi
|
|
// 3 => ??, // demonic fury
|
|
4 => POWER_ENERGY, // energy
|
|
5 => POWER_FOCUS, // focus
|
|
6 => POWER_HEALTH, // health
|
|
// 7 => ??, // holy power
|
|
8 => POWER_MANA, // mana
|
|
9 => POWER_RAGE, // rage
|
|
10 => POWER_RUNE, // runes
|
|
11 => POWER_RUNIC_POWER, // runic power
|
|
// 12 => ??, // shadow orbs
|
|
// 13 => ??, // soul shard
|
|
14 => POWER_HAPPINESS, // happiness v custom v
|
|
15 => -1, // ammo
|
|
16 => -41, // pyrite
|
|
17 => -61, // steam pressure
|
|
18 => -101, // heat
|
|
19 => -121, // ooze
|
|
20 => -141, // blood power
|
|
21 => -142 // wrath
|
|
)
|
|
);
|
|
|
|
protected $genericFilter = array(
|
|
1 => [FILTER_CR_CALLBACK, 'cbCost', ], // costAbs [op] [int]
|
|
2 => [FILTER_CR_NUMERIC, 'powerCostPercent', NUM_CAST_INT ], // prcntbasemanarequired
|
|
3 => [FILTER_CR_BOOLEAN, 'spellFocusObject' ], // requiresnearbyobject
|
|
4 => [FILTER_CR_NUMERIC, 'trainingcost', NUM_CAST_INT ], // trainingcost
|
|
5 => [FILTER_CR_BOOLEAN, 'reqSpellId' ], // requiresprofspec
|
|
8 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
|
9 => [FILTER_CR_CALLBACK, 'cbSource', ], // source [enum]
|
|
10 => [FILTER_CR_FLAG, 'cuFlags', SPELL_CU_FIRST_RANK ], // firstrank
|
|
11 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
|
12 => [FILTER_CR_FLAG, 'cuFlags', SPELL_CU_LAST_RANK ], // lastrank
|
|
13 => [FILTER_CR_NUMERIC, 'rankNo', NUM_CAST_INT ], // rankno
|
|
14 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true ], // id
|
|
15 => [FILTER_CR_STRING, 'ic.name', ], // icon
|
|
17 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
|
19 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION ], // scaling
|
|
20 => [FILTER_CR_CALLBACK, 'cbReagents', ], // has Reagents [yn]
|
|
// 22 => [FILTER_CR_NYI_PH, null, null, null ], // proficiencytype [proficiencytype] pointless
|
|
25 => [FILTER_CR_BOOLEAN, 'skillLevelYellow' ], // rewardsskillups
|
|
27 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CHANNELED_1, true ], // channeled [yn]
|
|
28 => [FILTER_CR_NUMERIC, 'castTime', NUM_CAST_FLOAT ], // casttime [num]
|
|
29 => [FILTER_CR_CALLBACK, 'cbAuraNames', ], // appliesaura [effectauranames]
|
|
// 31 => [FILTER_CR_NYI_PH, null, null, null ], // usablewhenshapeshifted [yn] pointless
|
|
33 => [FILTER_CR_CALLBACK, 'cbInverseFlag', 'attributes0', SPELL_ATTR0_CANT_USED_IN_COMBAT], // combatcastable [yn]
|
|
34 => [FILTER_CR_CALLBACK, 'cbInverseFlag', 'attributes2', SPELL_ATTR2_CANT_CRIT ], // chancetocrit [yn]
|
|
35 => [FILTER_CR_CALLBACK, 'cbInverseFlag', 'attributes3', SPELL_ATTR3_IGNORE_HIT_RESULT ], // chancetomiss [yn]
|
|
36 => [FILTER_CR_FLAG, 'attributes3', SPELL_ATTR3_DEATH_PERSISTENT ], // persiststhroughdeath [yn]
|
|
38 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_ONLY_STEALTHED ], // requiresstealth [yn]
|
|
39 => [FILTER_CR_CALLBACK, 'cbSpellstealable', 'attributes4', SPELL_ATTR4_NOT_STEALABLE ], // spellstealable [yn]
|
|
40 => [FILTER_CR_ENUM, 'damageClass' ], // damagetype [damagetype]
|
|
41 => [FILTER_CR_FLAG, 'stanceMask', (1 << (22 - 1)) ], // requiresmetamorphosis [yn]
|
|
42 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_USABLE_WHILE_STUNNED ], // usablewhenstunned [yn]
|
|
44 => [FILTER_CR_CALLBACK, 'cbUsableInArena' ], // usableinarenas [yn]
|
|
45 => [FILTER_CR_ENUM, 'powerType' ], // resourcetype [resourcetype]
|
|
// 46 => [FILTER_CR_NYI_PH, null, null, null ], // disregardimmunity [yn]
|
|
47 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE ], // disregardschoolimmunity [yn]
|
|
48 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 0x0004000C, false ], // reqrangedweapon [yn]
|
|
49 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_ON_NEXT_SWING ], // onnextswingplayers [yn]
|
|
50 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_PASSIVE ], // passivespell [yn]
|
|
51 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR ], // hiddenaura [yn]
|
|
52 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_ON_NEXT_SWING_2 ], // onnextswingnpcs [yn]
|
|
53 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_DAYTIME_ONLY ], // daytimeonly [yn]
|
|
54 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_NIGHT_ONLY ], // nighttimeonly [yn]
|
|
55 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_INDOORS_ONLY ], // indoorsonly [yn]
|
|
56 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_OUTDOORS_ONLY ], // outdoorsonly [yn]
|
|
57 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CANT_CANCEL ], // uncancellableaura [yn]
|
|
58 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION ], // damagedependsonlevel [yn]
|
|
59 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_STOP_ATTACK_TARGET ], // stopsautoattack [yn]
|
|
60 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK ], // cannotavoid [yn]
|
|
61 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CASTABLE_WHILE_DEAD ], // usabledead [yn]
|
|
62 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CASTABLE_WHILE_MOUNTED ], // usablemounted [yn]
|
|
63 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_DISABLED_WHILE_ACTIVE ], // delayedrecoverystarttime [yn]
|
|
64 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CASTABLE_WHILE_SITTING ], // usablesitting [yn]
|
|
65 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_DRAIN_ALL_POWER ], // usesallpower [yn]
|
|
66 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CHANNELED_2, true ], // channeled [yn]
|
|
67 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CANT_BE_REFLECTED ], // cannotreflect [yn]
|
|
68 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_NOT_BREAK_STEALTH ], // usablestealthed [yn]
|
|
69 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_NEGATIVE_1 ], // harmful [yn]
|
|
70 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CANT_TARGET_IN_COMBAT ], // targetnotincombat [yn]
|
|
71 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_NO_THREAT ], // nothreat [yn]
|
|
72 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_IS_PICKPOCKET ], // pickpocket [yn]
|
|
73 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_DISPEL_AURAS_ON_IMMUNITY ], // dispelauraonimmunity [yn]
|
|
74 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 0x00100000, false ], // reqfishingpole [yn]
|
|
75 => [FILTER_CR_FLAG, 'attributes2', SPELL_ATTR2_CANT_TARGET_TAPPED ], // requntappedtarget [yn]
|
|
// 76 => [FILTER_CR_NYI_PH, null, null, null ], // targetownitem [yn] // the flag for this has to be somewhere....
|
|
77 => [FILTER_CR_FLAG, 'attributes2', SPELL_ATTR2_NOT_NEED_SHAPESHIFT ], // doesntreqshapeshift [yn]
|
|
78 => [FILTER_CR_FLAG, 'attributes2', SPELL_ATTR2_FOOD_BUFF ], // foodbuff [yn]
|
|
79 => [FILTER_CR_FLAG, 'attributes3', SPELL_ATTR3_ONLY_TARGET_PLAYERS ], // targetonlyplayer [yn]
|
|
80 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 1 << INVTYPE_WEAPONMAINHAND, true ], // reqmainhand [yn]
|
|
81 => [FILTER_CR_FLAG, 'attributes3', SPELL_ATTR3_NO_INITIAL_AGGRO ], // doesntengagetarget [yn]
|
|
82 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 0x00080000, false ], // reqwand [yn]
|
|
83 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 1 << INVTYPE_WEAPONOFFHAND, true ], // reqoffhand [yn]
|
|
84 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_HIDE_IN_COMBAT_LOG ], // nolog [yn]
|
|
85 => [FILTER_CR_FLAG, 'attributes4', SPELL_ATTR4_FADES_WHILE_LOGGED_OUT ], // auratickswhileloggedout [yn]
|
|
87 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_START_PERIODIC_AT_APPLY ], // startstickingatapplication [yn]
|
|
88 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_USABLE_WHILE_CONFUSED ], // usableconfused [yn]
|
|
89 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_USABLE_WHILE_FEARED ], // usablefeared [yn]
|
|
90 => [FILTER_CR_FLAG, 'attributes6', SPELL_ATTR6_ONLY_IN_ARENA ], // onlyarena [yn]
|
|
91 => [FILTER_CR_FLAG, 'attributes6', SPELL_ATTR6_NOT_IN_RAID_INSTANCE ], // notinraid [yn]
|
|
92 => [FILTER_CR_FLAG, 'attributes7', SPELL_ATTR7_REACTIVATE_AT_RESURRECT ], // paladinaura [yn]
|
|
93 => [FILTER_CR_FLAG, 'attributes7', SPELL_ATTR7_SUMMON_PLAYER_TOTEM ], // totemspell [yn]
|
|
95 => [FILTER_CR_CALLBACK, 'cbBandageSpell' ], // bandagespell [yn] ...don't ask
|
|
96 => [FILTER_CR_STAFFFLAG, 'attributes0' ], // flags1 [flags]
|
|
97 => [FILTER_CR_STAFFFLAG, 'attributes1' ], // flags2 [flags]
|
|
98 => [FILTER_CR_STAFFFLAG, 'attributes2' ], // flags3 [flags]
|
|
99 => [FILTER_CR_STAFFFLAG, 'attributes3' ], // flags4 [flags]
|
|
100 => [FILTER_CR_STAFFFLAG, 'attributes4' ], // flags5 [flags]
|
|
101 => [FILTER_CR_STAFFFLAG, 'attributes5' ], // flags6 [flags]
|
|
102 => [FILTER_CR_STAFFFLAG, 'attributes6' ], // flags7 [flags]
|
|
103 => [FILTER_CR_STAFFFLAG, 'attributes7' ], // flags8 [flags]
|
|
104 => [FILTER_CR_STAFFFLAG, 'targets' ], // flags9 [flags]
|
|
105 => [FILTER_CR_STAFFFLAG, 'stanceMaskNot' ], // flags10 [flags]
|
|
106 => [FILTER_CR_STAFFFLAG, 'spellFamilyFlags1' ], // flags11 [flags]
|
|
107 => [FILTER_CR_STAFFFLAG, 'spellFamilyFlags2' ], // flags12 [flags]
|
|
108 => [FILTER_CR_STAFFFLAG, 'spellFamilyFlags3' ], // flags13 [flags]
|
|
109 => [FILTER_CR_CALLBACK, 'cbEffectNames', ], // effecttype [effecttype]
|
|
// 110 => [FILTER_CR_NYI_PH, null, null, null ], // scalingap [yn] // unreasonably complex for now
|
|
// 111 => [FILTER_CR_NYI_PH, null, null, null ], // scalingsp [yn] // unreasonably complex for now
|
|
114 => [FILTER_CR_CALLBACK, 'cbReqFaction' ], // requiresfaction [side]
|
|
116 => [FILTER_CR_BOOLEAN, 'startRecoveryTime' ] // onGlobalCooldown [yn]
|
|
);
|
|
|
|
protected $inputFields = array(
|
|
'cr' => [FILTER_V_RANGE, [1, 116], true ], // criteria ids
|
|
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 99999]], true ], // criteria operators
|
|
'crv' => [FILTER_V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiters
|
|
'na' => [FILTER_V_REGEX, parent::PATTERN_NAME, false], // name / text - only printable chars, no delimiter
|
|
'ex' => [FILTER_V_EQUAL, 'on', false], // extended name search
|
|
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
|
'minle' => [FILTER_V_RANGE, [1, 99], false], // spell level min
|
|
'maxle' => [FILTER_V_RANGE, [1, 99], false], // spell level max
|
|
'minrs' => [FILTER_V_RANGE, [1, 999], false], // required skill level min
|
|
'maxrs' => [FILTER_V_RANGE, [1, 999], false], // required skill level max
|
|
'ra' => [FILTER_V_LIST, [[1, 8], 10, 11], false], // races
|
|
'cl' => [FILTER_V_CALLBACK, 'cbClasses', true ], // classes
|
|
'gl' => [FILTER_V_CALLBACK, 'cbGlyphs', true ], // glyph type
|
|
'sc' => [FILTER_V_RANGE, [0, 6], true ], // magic schools
|
|
'dt' => [FILTER_V_LIST, [[1, 6], 9], false], // dispel types
|
|
'me' => [FILTER_V_RANGE, [1, 31], false] // mechanics
|
|
);
|
|
|
|
protected function createSQLForValues()
|
|
{
|
|
$parts = [];
|
|
$_v = &$this->fiData['v'];
|
|
|
|
//string (extended)
|
|
if (isset($_v['na']))
|
|
{
|
|
$_ = [];
|
|
if (isset($_v['ex']) && $_v['ex'] == 'on')
|
|
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'buff_loc'.User::$localeId, 'description_loc'.User::$localeId]);
|
|
else
|
|
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
|
|
|
|
if ($_)
|
|
$parts[] = $_;
|
|
}
|
|
|
|
// spellLevel min todo (low): talentSpells (typeCat -2) commonly have spellLevel 1 (and talentLevel >1) -> query is inaccurate
|
|
if (isset($_v['minle']))
|
|
$parts[] = ['spellLevel', $_v['minle'], '>='];
|
|
|
|
// spellLevel max
|
|
if (isset($_v['maxle']))
|
|
$parts[] = ['spellLevel', $_v['maxle'], '<='];
|
|
|
|
// skillLevel min
|
|
if (isset($_v['minrs']))
|
|
$parts[] = ['learnedAt', $_v['minrs'], '>='];
|
|
|
|
// skillLevel max
|
|
if (isset($_v['maxrs']))
|
|
$parts[] = ['learnedAt', $_v['maxrs'], '<='];
|
|
|
|
// race
|
|
if (isset($_v['ra']))
|
|
$parts[] = ['AND', [['reqRaceMask', RACE_MASK_ALL, '&'], RACE_MASK_ALL, '!'], ['reqRaceMask', $this->list2Mask([$_v['ra']]), '&']];
|
|
|
|
// class [list]
|
|
if (isset($_v['cl']))
|
|
$parts[] = ['reqClassMask', $this->list2Mask($_v['cl']), '&'];
|
|
|
|
// school [list]
|
|
if (isset($_v['sc']))
|
|
$parts[] = ['schoolMask', $this->list2Mask($_v['sc'], true), '&'];
|
|
|
|
// glyph type [list] wonky, admittedly, but consult SPELL_CU_* in defines and it makes sense
|
|
if (isset($_v['gl']))
|
|
$parts[] = ['cuFlags', ($this->list2Mask($_v['gl']) << 6), '&'];
|
|
|
|
// dispel type
|
|
if (isset($_v['dt']))
|
|
$parts[] = ['dispelType', $_v['dt']];
|
|
|
|
// mechanic
|
|
if (isset($_v['me']))
|
|
$parts[] = ['OR', ['mechanic', $_v['me']], ['effect1Mechanic', $_v['me']], ['effect2Mechanic', $_v['me']], ['effect3Mechanic', $_v['me']]];
|
|
|
|
return $parts;
|
|
}
|
|
|
|
public function getGenericFilter($cr) // access required by SpellDetailPage's SpellAttributes list
|
|
{
|
|
return $this->genericFilter[$cr] ?? [];
|
|
}
|
|
|
|
protected function cbClasses(&$val)
|
|
{
|
|
if (!$this->parentCats || !in_array($this->parentCats[0], [-13, -2, 7]))
|
|
return false;
|
|
|
|
if (!Util::checkNumeric($val, NUM_REQ_INT))
|
|
return false;
|
|
|
|
$type = FILTER_V_LIST;
|
|
$valid = [[1, 9], 11];
|
|
|
|
return $this->checkInput($type, $valid, $val);
|
|
}
|
|
|
|
protected function cbGlyphs(&$val)
|
|
{
|
|
if (!$this->parentCats || $this->parentCats[0] != -13)
|
|
return false;
|
|
|
|
if (!Util::checkNumeric($val, NUM_REQ_INT))
|
|
return false;
|
|
|
|
$type = FILTER_V_LIST;
|
|
$valid = [1, 2];
|
|
|
|
return $this->checkInput($type, $valid, $val);
|
|
}
|
|
|
|
protected function cbCost($cr)
|
|
{
|
|
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
|
return false;
|
|
|
|
return ['OR',
|
|
['AND', ['powerType', [POWER_RAGE, POWER_RUNIC_POWER]], ['powerCost', (10 * $cr[2]), $cr[1]]],
|
|
['AND', ['powerType', [POWER_RAGE, POWER_RUNIC_POWER], '!'], ['powerCost', $cr[2], $cr[1]]]
|
|
];
|
|
}
|
|
|
|
protected function cbSource($cr)
|
|
{
|
|
if (!isset($this->enums[$cr[0]][$cr[1]]))
|
|
return false;
|
|
|
|
$_ = $this->enums[$cr[0]][$cr[1]];
|
|
if (is_int($_)) // specific
|
|
return ['src.src'.$_, null, '!'];
|
|
else if ($_) // any
|
|
return ['OR', ['src.src1', null, '!'], ['src.src2', null, '!'], ['src.src4', null, '!'], ['src.src5', null, '!'], ['src.src6', null, '!'], ['src.src7', null, '!'], ['src.src9', null, '!'], ['src.src10', null, '!']];
|
|
else if (!$_) // none
|
|
return ['AND', ['src.src1', null], ['src.src2', null], ['src.src4', null], ['src.src5', null], ['src.src6', null], ['src.src7', null], ['src.src9', null], ['src.src10', null]];
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function cbReagents($cr)
|
|
{
|
|
if (!$this->int2Bool($cr[1]))
|
|
return false;
|
|
|
|
if ($cr[1])
|
|
return ['OR', ['reagent1', 0, '>'], ['reagent2', 0, '>'], ['reagent3', 0, '>'], ['reagent4', 0, '>'], ['reagent5', 0, '>'], ['reagent6', 0, '>'], ['reagent7', 0, '>'], ['reagent8', 0, '>']];
|
|
else
|
|
return ['AND', ['reagent1', 0], ['reagent2', 0], ['reagent3', 0], ['reagent4', 0], ['reagent5', 0], ['reagent6', 0], ['reagent7', 0], ['reagent8', 0]];
|
|
}
|
|
|
|
protected function cbAuraNames($cr)
|
|
{
|
|
if (!$this->checkInput(FILTER_V_RANGE, [1, self::MAX_SPELL_AURA], $cr[1]))
|
|
return false;
|
|
|
|
return ['OR', ['effect1AuraId', $cr[1]], ['effect2AuraId', $cr[1]], ['effect3AuraId', $cr[1]]];
|
|
}
|
|
|
|
protected function cbEffectNames($cr)
|
|
{
|
|
if (!$this->checkInput(FILTER_V_RANGE, [1, self::MAX_SPELL_EFFECT], $cr[1]))
|
|
return false;
|
|
|
|
return ['OR', ['effect1Id', $cr[1]], ['effect2Id', $cr[1]], ['effect3Id', $cr[1]]];
|
|
}
|
|
|
|
protected function cbInverseFlag($cr, $field, $flag)
|
|
{
|
|
if (!$this->int2Bool($cr[1]))
|
|
return false;
|
|
|
|
if ($cr[1])
|
|
return [[$field, $flag, '&'], 0];
|
|
else
|
|
return [$field, $flag, '&'];
|
|
}
|
|
|
|
protected function cbSpellstealable($cr, $field, $flag)
|
|
{
|
|
if (!$this->int2Bool($cr[1]))
|
|
return false;
|
|
|
|
if ($cr[1])
|
|
return ['AND', [[$field, $flag, '&'], 0], ['dispelType', 1]];
|
|
else
|
|
return ['OR', [$field, $flag, '&'], ['dispelType', 1, '!']];
|
|
}
|
|
|
|
protected function cbReqFaction($cr)
|
|
{
|
|
switch ($cr[1])
|
|
{
|
|
case 1: // yes
|
|
return ['reqRaceMask', 0, '!'];
|
|
case 2: // alliance
|
|
return ['AND', [['reqRaceMask', RACE_MASK_HORDE, '&'], 0], ['reqRaceMask', RACE_MASK_ALLIANCE, '&']];
|
|
case 3: // horde
|
|
return ['AND', [['reqRaceMask', RACE_MASK_ALLIANCE, '&'], 0], ['reqRaceMask', RACE_MASK_HORDE, '&']];
|
|
case 4: // both
|
|
return ['AND', ['reqRaceMask', RACE_MASK_ALLIANCE, '&'], ['reqRaceMask', RACE_MASK_HORDE, '&']];
|
|
case 5: // no
|
|
return ['reqRaceMask', 0];
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected function cbEquippedWeapon($cr, $mask, $useInvType)
|
|
{
|
|
if (!$this->int2Bool($cr[1]))
|
|
return false;
|
|
|
|
$field = $useInvType ? 'equippedItemInventoryTypeMask' : 'equippedItemSubClassMask';
|
|
|
|
if ($cr[1])
|
|
return ['AND', ['equippedItemClass', ITEM_CLASS_WEAPON], [$field, $mask, '&']];
|
|
else
|
|
return ['OR', ['equippedItemClass', ITEM_CLASS_WEAPON, '!'], [[$field, $mask, '&'], 0]];
|
|
}
|
|
|
|
protected function cbUsableInArena($cr)
|
|
{
|
|
if (!$this->int2Bool($cr[1]))
|
|
return false;
|
|
|
|
if ($cr[1])
|
|
return ['AND',
|
|
[['attributes4', SPELL_ATTR4_NOT_USABLE_IN_ARENA, '&'], 0],
|
|
['OR', ['recoveryTime', 10 * MINUTE * 1000, '<='], ['attributes4', SPELL_ATTR4_USABLE_IN_ARENA, '&']]
|
|
];
|
|
else
|
|
return ['OR',
|
|
['attributes4', SPELL_ATTR4_NOT_USABLE_IN_ARENA, '&'],
|
|
['AND', ['recoveryTime', 10 * MINUTE * 1000, '>'], [['attributes4', SPELL_ATTR4_USABLE_IN_ARENA, '&'], 0]]
|
|
];
|
|
}
|
|
|
|
protected function cbBandageSpell($cr)
|
|
{
|
|
if (!$this->int2Bool($cr[1]))
|
|
return false;
|
|
|
|
if ($cr[1]) // match exact, not as flag
|
|
return ['AND', ['attributes1', SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2 | SPELL_ATTR1_CHANNEL_TRACK_TARGET], ['effect1ImplicitTargetA', 21]];
|
|
else
|
|
return ['OR', ['attributes1', SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2 | SPELL_ATTR1_CHANNEL_TRACK_TARGET, '!'], ['effect1ImplicitTargetA', 21, '!']];
|
|
}
|
|
}
|
|
|
|
?>
|