diff --git a/includes/class.spell.php b/includes/class.spell.php index ffab7e28..75fdc01a 100644 --- a/includes/class.spell.php +++ b/includes/class.spell.php @@ -8,6 +8,9 @@ class Spell extends BaseType public $tooltip = ''; public $buff = ''; + private $spellVars = []; + private $refSpells = []; + protected $setupQuery = 'SELECT * FROM ?_spell WHERE Id = ?d'; // use if you JUST need the name @@ -188,433 +191,717 @@ class Spell extends BaseType return $stats; } - // TODO: optimize, complete, not go stark raving mad - public function parseText($type = 'description', $level = MAX_LEVEL) + // description-, buff-parsing component + private function resolveEvaluation($formula, $level) { - $lastduration = array('durationBase' => $this->template['duration']); + // todo: define unresolvable texts like AP, MHW, ect - $signs = array('+', '-', '/', '*', '%', '^'); + $pl = $PL = $level; + $PlayerName = Lang::$main['name']; + $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; }; + $floor = $FLOOR = function($a) { return floor($a); }; - $data = Util::localizedString($this->template, $type); - if (empty($data) || $data =="[]") // empty tooltip shouldn't be displayed anyway - return null; - - // line endings - $data = strtr($data, array("\r" => '', "\n" => '
')); - - // colors - $data = preg_replace('/\|cff([a-f0-9]{6})(.+?)\|r/i', '$2', $data); - - $pos = 0; - $str = ''; - while(false!==($npos=strpos($data, '$', $pos))) + if (preg_match_all('/\$[a-z]+\b/i', $formula, $vars)) { - if($npos!=$pos) - $str .= substr($data, $pos, $npos-$pos); - $pos = $npos+1; + 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 >.< + if (@eval('return getType('.$var.');') != 'object') + return $formula; - if('$' == substr($data, $pos, 1)) - { - $str .= '$'; - $pos++; - continue; - } + return eval('return '.$formula.';'); + } - if(!preg_match('/^((([+\-\/*])(\d+);)?(\d*)(?:([lg].*?:.*?);|(\w\d*)))/', substr($data, $pos), $result)) - continue; + // there should not be any letters without a leading $ + return eval('return '.$formula.';'); + } - if(empty($exprData[0])) - $exprData[0] = 1; + // description-, buff-parsing component + private function resolveVariableString($variable, $level) + { + $signs = ['+', '-', '/', '*', '%', '^']; - $op = $result[3]; - $oparg = $result[4]; - $lookup = $result[5]; - $var = $result[6] ? $result[6] : $result[7]; - $pos += strlen($result[0]); + $op = $variable[2]; + $oparg = $variable[3]; + $lookup = (int)$variable[4]; + $var = $variable[6] ? $variable[6] : $variable[8]; + $effIdx = $variable[6] ? null : $variable[9]; + $switch = $variable[7] ? explode(':', $variable[7]) : null; - if(!$var) - continue; + if (!$var) + return; - $exprType = strtolower(substr($var, 0, 1)); - $exprData = explode(':', substr($var, 1)); - switch($exprType) - { - case 'r': - // if(!IsSet($this->template['rangeMaxHostile'])) - // $this->template = array_merge($this->template, DB::Aowow()->selectRow('SELECT * FROM ?_spellrange WHERE rangeID=? LIMIT 1', $this->template['rangeID'])); + if (!$effIdx) // if EffectIdx is omitted, assume EffectIdx: 1 + $effIdx = 1; - $base = $this->template['rangeMaxHostile']; + // cache at least some lookups.. should be moved to single spellList :/ + if ($lookup && !isset($this->refSpells[$lookup])) + $this->refSpells[$lookup] = new Spell($lookup); - if($op && is_numeric($oparg) && is_numeric($base)) + switch ($var) + { + case 'a': // EffectRadiusMin + case 'A': // EffectRadiusMax (ToDo) + if ($lookup) + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'RadiusMax']; + else + $base = $this->template['effect'.$effIdx.'RadiusMax']; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return abs($base); + case 'b': // PointsPerComboPoint + case 'B': + if ($lookup) + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'PointsPerComboPoint']; + else + $base = $this->template['effect'.$effIdx.'PointsPerComboPoint']; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return abs($base); +/* where the heck is this var...? + case 'c': // BasePoints (raw) + if ($lookup > 0 && $exprData[0]) + $spell = DB::Aowow()->selectRow('SELECT effect'.$exprData[0].'BasePoints, effect'.$exprData[0].'AuraId, effect'.$exprData[0].'MiscValue FROM ?_spell WHERE id=? LIMIT 1', $lookup); + else + $spell = $this->template; + + $base = $spell['effect'.$exprData[0].'BasePoints'] + 1; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + { + $equation = $base.$op.$oparg; + eval("\$base = $equation;"); + } + + // Aura giving combat ratings (click-interactive) + $rType = 0; + if ($spell['effect'.$exprData[0].'AuraId'] == 189) + { + $mv = $spell['effect'.$exprData[0].'MiscValue']; + for ($j = 0; $j < count(Util::$combatRatingToItemMod); $j++) { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); + if (!Util::$combatRatingToItemMod[$j]) + continue; + + if (($mv & (1 << $j)) == 0) + continue; + + $rType = Util::$combatRatingToItemMod[$j]; + break; } + } + // Aura end + + if ($rType) + $str .= ''.$base." (".Util::setRatingLevel($level, $rType, $base).")"; + else $str .= $base; - break; - case 'z': - $str .= htmlspecialchars(''); - break; - case 'c': ### - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect'.$exprData[0].'BasePoints, effect'.$exprData[0].'AuraId, effect'.$exprData[0].'MiscValue FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - $base = $spell['effect'.$exprData[0].'BasePoints']+1; + $lastvalue = $base; + break; +*/ + case 'd': // SpellDuration + case 'D': // todo: min/max? + if ($lookup) + $base = $this->refSpells[$lookup]->template['duration']; + else + $base = $this->template['duration']; - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } + if ($base < 0) + return Lang::$spell['untilCanceled']; - // Aura giving combat ratings - $rType = 0; - if ($spell['effect'.$exprData[0].'AuraId'] == 189) - { - $mv = $spell['effect'.$exprData[0].'MiscValue']; - for ($j = 0; $j < count(Util::$combatRatingToItemMod); $j++) - { - if (!Util::$combatRatingToItemMod[$j]) - continue; + if ($op && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); - if (($mv & (1 << $j)) == 0) - continue; + return Util::formatTime($base, true); + case 'e': // EffectValueMultiplier + case 'E': + if ($lookup) + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'ValueMultiplier']; + else + $base = $this->template['effect'.$effIdx.'ValueMultiplier']; - $rType = Util::$combatRatingToItemMod[$j]; - break; - } - } - // Aura end + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); - if ($rType) - $str .= ''.$base." (".Util::setRatingLevel($level, $rType, $base).")"; - else - $str .= $base; + return $base; + case 'f': // EffectDamageMultiplier + case 'F': + if ($lookup) + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'DamageMultiplier']; + else + $base = $this->template['effect'.$effIdx.'DamageMultiplier']; - $lastvalue = $base; - break; - case 's': ### - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect'.$exprData[0].'BasePoints, effect'.$exprData[0].'AuraId, effect'.$exprData[0].'MiscValue, effect'.$exprData[0].'DieSides FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); - if(!$exprData[0]) $exprData[0]=1; - @$base = $spell['effect'.$exprData[0].'BasePoints']+1; + return abs($base); + case 'g': // boolean choice with casters gender as condition $gX:Y; + case 'G': + return '>'.$switch[0].'/'.$switch[1].'<'; + case 'h': // ProcChance + case 'H': + if ($lookup) + $base = $this->refSpells[$lookup]->template['procChance']; + else + $base = $this->template['procChance']; - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); - // Aura giving combat ratings - $rType = 0; - if (@$spell['effect'.$exprData[0].'AuraId'] == 189) - { - $mv = $spell['effect'.$exprData[0].'MiscValue']; - for ($j = 0; $j < count(Util::$combatRatingToItemMod); $j++) - { - if (!Util::$combatRatingToItemMod[$j]) - continue; - - if (($mv & (1 << $j)) == 0) - continue; - - $rType = Util::$combatRatingToItemMod[$j]; - break; - } - } - // Aura end - - if ($rType && $spell['effect'.$exprData[0].'DieSides'] <= 1) - $str .= ''.abs($base)." (".Util::setRatingLevel($level, $rType, abs($base)).")"; - else - @$str .= abs($base).($spell['effect'.$exprData[0].'DieSides'] > 1 ? Lang::$game['valueDelim'].abs(($base+$spell['effect'.$exprData[0].'DieSides'])) : ''); - - $lastvalue = $base; - break; - case 'o': - if($lookup > 0 && $exprData[0]) - { - $spell = DB::Aowow()->selectRow('SELECT duration, effect'.$exprData[0].'BasePoints, effect'.$exprData[0].'Periode, effect'.$exprData[0].'DieSides FROM ?_spell WHERE id=? LIMIT 1', $lookup); - // $lastduration = DB::Aowow()->selectRow('SELECT * FROM ?_spellduration WHERE durationID=? LIMIT 1', $spell['durationID']); - } - else - $spell = $this->template; - - if(!$exprData[0]) $exprData[0] = 1; - $base = $spell['effect'.$exprData[0].'BasePoints']+1; - - if($spell['effect'.$exprData[0].'Periode'] <= 0) $spell['effect'.$exprData[0].'Periode'] = 5000; - - $str .= (($spell['duration'] / $spell['effect'.$exprData[0].'Periode']) * abs($base).($spell['effect'.$exprData[0].'DieSides'] > 1 ? '-'.abs(($base+$spell['effect'.$exprData[0].'DieSides'])) : '')); - break; - case 't': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT * FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - if(!$exprData[0]) $exprData[0]=1; - $base = $spell['effect'.$exprData[0].'Periode']/1000; - - // TODO!! - if($base==0) $base=1; - // !!TODO - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - $lastvalue = $base; - break; - case 'm': ### - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect'.$exprData[0].'BasePoints, effect'.$exprData[0].'AuraId, effect'.$exprData[0].'MiscValue FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - // TODO!! - if(!$exprData[0]) $exprData[0] = 1; - - $base = $spell['effect'.$exprData[0].'BasePoints']+1; - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - - // Aura giving combat ratings - $rType = 0; - if ($spell['effect'.$exprData[0].'AuraId'] == 189) - { - $mv = $spell['effect'.$exprData[0].'MiscValue']; - for ($j = 0; $j < count(Util::$combatRatingToItemMod); $j++) - { - if (!Util::$combatRatingToItemMod[$j]) - continue; - - if (($mv & (1 << $j)) == 0) - continue; - - $rType = Util::$combatRatingToItemMod[$j]; - break; - } - } - // Aura end - - if ($rType) - $str .= ''.abs($base)." (".Util::setRatingLevel($level, $rType, abs($base)).")"; - else - $str .= abs($base); - - $lastvalue = $base; - break; - case 'x': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect'.$exprData[0].'ChainTarget FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - $base = $spell['effect'.$exprData[0].'ChainTarget']; - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - $lastvalue = $base; - break; - case 'q': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect'.$exprData[0].'MiscValue FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - if(!($exprData[0])) - $exprData[0]=1; - $base = $spell['effect'.$exprData[0].'MiscValue']; - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - $lastvalue = $base; - break; - case 'a': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect1RadiusMax, effect2RadiusMax, effect3RadiusMax FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - $exprData[0] = 1; // TODO - $radius = $this->template['effect'.$exprData[0].'RadiusMax']; - $base = $radius; - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - break; - case 'h': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT procChance FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - $base = $spell['procChance']; - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - break; - case 'f': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT dmg_multiplier'.$exprData[0].' FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - $base = $spell['dmg_multiplier'.$exprData[0]]; - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - break; - case 'n': - if($lookup > 0) - $spell = DB::Aowow()->selectRow('SELECT procCharges FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; - - $base = $spell['procCharges']; - - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - break; - case 'd': - if($lookup > 0) - { - $spell = DB::Aowow()->selectRow('SELECT duration as durationBase FROM ?_spell WHERE id=? LIMIT 1', $lookup); - @$base = ($spell['durationBase'] > 0 ? $spell['durationBase'] + 1 : 0); - } - else - $base = ($lastduration['durationBase'] > 0 ? $lastduration['durationBase'] : 0); - - if($op && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= Util::formatTime($base, true); - break; - case 'i': - // $base = $this->template['spellTargets']; + return abs($base); + case 'i': // MaxAffectedTargets + case 'I': + if ($lookup) + $base = $this->refSpells[$lookup]->template['targets']; + else $base = $this->template['targets']; - if($op && is_numeric($oparg) && is_numeric($base)) + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return $base; + case 'l': // boolean choice with last value as condition $lX:Y; + case 'L': + return '$l'.$switch[0].':'.$switch[1]; // resolve later by backtracking + case 'm': // BasePoints (minValue) + case 'M': // BasePoints (maxValue) + if ($lookup) + { + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'BasePoints']; + $add = $this->refSpells[$lookup]->template['effect'.$effIdx.'DieSides']; + $mv = $this->refSpells[$lookup]->template['effect'.$effIdx.'MiscValue']; + $aura = $this->refSpells[$lookup]->template['effect'.$effIdx.'AuraId']; + + } + else + { + $base = $this->template['effect'.$effIdx.'BasePoints']; + $add = $this->template['effect'.$effIdx.'DieSides']; + $mv = $this->template['effect'.$effIdx.'MiscValue']; + $aura = $this->template['effect'.$effIdx.'AuraId']; + } + + if (ctype_lower($var)) + $add = 1; + + $base += $add; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + // Aura giving combat ratings + $rType = 0; + if ($aura == 189) + { + for ($j = 0; $j < count(Util::$combatRatingToItemMod); $j++) { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); + if (!Util::$combatRatingToItemMod[$j]) + continue; + + if (($mv & (1 << $j)) == 0) + continue; + + $rType = Util::$combatRatingToItemMod[$j]; + break; } - $str .= $base; - break; - case 'e': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect_'.$exprData[0].'_proc_value FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; + } + // Aura end - $base = $spell['effect_'.$exprData[0].'_proc_value']; + if ($rType) + return ''.abs($base)." (".Util::setRatingLevel($level, $rType, abs($base)).")"; + else + return abs($base); + case 'n': // ProcCharges + case 'N': + if ($lookup) + $base = $this->refSpells[$lookup]->template['procCharges']; + else + $base = $this->template['procCharges']; - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return abs($base); + case 'o': // TotalAmount for periodic auras (with variance) + case 'O': + if ($lookup) + { + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'BasePoints']; + $add = $this->refSpells[$lookup]->template['effect'.$effIdx.'DieSides']; + $periode = $this->refSpells[$lookup]->template['effect'.$effIdx.'Periode']; + $duration = $this->refSpells[$lookup]->template['duration']; + } + else + { + $base = $this->template['effect'.$effIdx.'BasePoints']; + $add = $this->template['effect'.$effIdx.'DieSides']; + $periode = $this->template['effect'.$effIdx.'Periode']; + $duration = $this->template['duration']; + } + + if (!$periode) + $periode = 3000; + + $tick = $duration / $periode; + + return (abs($base + 1) * $tick) . ($add > 1 ? Lang::$game['valueDelim'] . (abs($base + $add) * $tick) : null); + case 'q': // EffectMiscValue + case 'Q': + if ($lookup) + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'MiscValue']; + else + $base = $this->template['effect'.$effIdx.'MiscValue']; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return abs($base); + case 'r': // SpellRange + case 'R': + if ($lookup) + $base = $this->refSpells[$lookup]->template['rangeMaxHostile']; + else + $base = $this->template['rangeMaxHostile']; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return $base; + case 's': // BasePoints (with variance) + case 'S': + if ($lookup) + { + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'BasePoints']; + $add = $this->refSpells[$lookup]->template['effect'.$effIdx.'DieSides']; + $mv = $this->refSpells[$lookup]->template['effect'.$effIdx.'MiscValue']; + $aura = $this->refSpells[$lookup]->template['effect'.$effIdx.'AuraId']; + } + else + { + $base = $this->template['effect'.$effIdx.'BasePoints']; + $add = $this->template['effect'.$effIdx.'DieSides']; + $mv = $this->template['effect'.$effIdx.'MiscValue']; + $aura = $this->template['effect'.$effIdx.'AuraId']; + } + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + // Aura giving combat ratings + $rType = 0; + if ($aura == 189) + { + for ($j = 0; $j < count(Util::$combatRatingToItemMod); $j++) { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); + if (!Util::$combatRatingToItemMod[$j]) + continue; + + if (($mv & (1 << $j)) == 0) + continue; + + $rType = Util::$combatRatingToItemMod[$j]; + break; } + } + // Aura end - $str .= $base; - $lastvalue = $base; + if ($rType && $add <= 1) + return ''.abs($base + 1)." (".Util::setRatingLevel($level, $rType, abs($base + 1)).")"; + else + return abs($base + 1) . ($add > 1 ? Lang::$game['valueDelim'] . abs($base + $add) : null); + case 't': // Periode + case 'T': + if ($lookup) + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'Periode'] / 1000; + else + $base = $this->template['effect'.$effIdx.'Periode'] / 1000; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return abs($base); + case 'u': // StackCount + case 'U': + if ($lookup) + $base = $this->refSpells[$lookup]->template['stackAmount']; + else + $base = $this->template['stackAmount']; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return abs($base); + case 'v': // MaxTargetLevel + case 'V': + if ($lookup) + $base = $this->refSpells[$lookup]->template['MaxTargetLevel']; + else + $base = $this->template['MaxTargetLevel']; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return $base; + case 'x': // ChainTargetCount + case 'X': + if ($lookup) + $base = $this->refSpells[$lookup]->template['effect'.$effIdx.'ChainTarget']; + else + $base = $this->template['effect'.$effIdx.'ChainTarget']; + + if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) + eval("\$base = $base.$op.$oparg;"); + + return abs($base); + case 'z': // HomeZone + return Lang::$spell['home']; + } + } + + // description-, buff-parsing component + private function resolveFormulaString($formula, $precision = 0, $level) + { + // 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; - case 'v': - $base = $spell['affected_target_level']; - if($op && $oparg > 0 && $base > 0) + $formCurPos++; + } + + if (@$formula[++$formCurPos] == '.') + { + $formPrecision = (int)$formula[++$formCurPos]; + ++$formCurPos; // for some odd reason the precision decimal survives if wo dont increment further.. + } + + $formOutStr = $this->resolveFormulaString($formOutStr, $formPrecision, $level); + + $formula = substr_replace($formula, $formOutStr, $formStartPos, ($formCurPos - $formStartPos)); + } + + // 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++; + + if (!preg_match('/^(([\+\-\*\/])(\d+);)?(\d*)(([g])([\w\s]*:[\w\s]*);|([a-z])([123]?)\b)/i', substr($formula, $pos), $result)) + { + $str .= '#'; // mark as done, reset below + continue; + } + + $pos += strlen($result[0]); + $str .= $this->resolveVariableString($result, $level); + } + $str .= substr($formula, $pos); + $str = str_replace('#', '$', $str); // reset marks + + // step 3: try to evaluate result + $evaled = $this->resolveEvaluation($str, $level); + + return (int)$evaled ? round($evaled, $precision) : $evaled; + } + + // 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) + { + /* 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 + $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 + $c - todo: not found but in use below.. wtf ?! + $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 condiition $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 + + deviations from standard procedures + division - example: $/10;2687s1 => $2687s1/10 + + 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 + */ + + + // step 0: get text + $data = Util::localizedString($this->template, $type); + if (empty($data) || $data == "[]") // empty tooltip shouldn't be displayed anyway + return null; + + // step 1: if the text is supplemented with text-variables, get and replace them + if (empty($this->spellVars[$this->Id]) && $this->template['spellDescriptionVariableId'] > 0) + { + $spellVars = DB::Aowow()->SelectCell('SELECT vars FROM ?_spellVariables WHERE id = ?d', $this->template['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)) { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); + $this->spellVars[$this->Id][$k] = str_replace('$<'.$matches[1].'>', '${'.$this->spellVars[$this->Id][$matches[1]].'}', $sv); + $reset = true; } - $str .= $base; - break; - case 'u': - if($lookup > 0 && $exprData[0]) - $spell = DB::Aowow()->selectRow('SELECT effect'.$exprData[0].'MiscValue FROM ?_spell WHERE id=?d LIMIT 1', $lookup); - else - $spell = $this->template; + } + } - // $base = $spell['effect_'.$exprData[0].'_misc_value']; - if(isset($spell['effect'.$exprData[0].'MiscValue'])) - $base = $spell['effect'.$exprData[0].'MiscValue']; + // finally, replace SpellDescVars + foreach ($this->spellVars[$this->Id] as $k => $sv) + $data = str_replace('$<'.$k.'>', $sv, $data); + } - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - $lastvalue = $base; - break; - case 'b': // only used at one spell (14179) should be 20, column 110/111/112?) - if($lookup > 0) - $spell = DB::Aowow()->selectRow('SELECT effect_'.$exprData[0].'_proc_chance FROM ?_spell WHERE id=? LIMIT 1', $lookup); - else - $spell = $this->template; + // step 2: resolving conditions + // aura- or spell-conditions cant be resolved for our purposes, so force them to false for now (todo: strg+f "know" in aowowPower.js ^.^) + // \1: full pattern match; \2: any sequence, that may include an aura/spell-ref; \3: any other sequence, between "?$" and "[" + while (preg_match('/\$\?(([\W\D]*[as]\d+)|([^\[]*))/i', $data, $matches)) + { + $condBrktCnt = 0; + $targetPart = 3; // we usually want the second pair of brackets + $curPart = 0; // parts: $? 0 [ 1 ] 2 [ 3 ] 4 - $base = $spell['effect'.$exprData[0].'PointsPerComboPoint']; + $condOutStr = ''; - if(in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) - { - $equation = $base.$op.$oparg; - eval("\$base = $equation;"); - } - $str .= abs($base); - $lastvalue = $base; + if (!empty($matches[3])) // we can do this! -> eval + { + $cnd = eval('return ('.$matches[3].');'); + if (is_numeric($cnd) && $cnd) // only case, deviating from normal; positive result -> use [true] + $targetPart = 1; + + $condStartPos = strpos($data, $matches[3]) - 2; + $condCurPos = $condStartPos; + + } + else if (!empty($matches[2])) // aura/spell-condition .. use false; TODO (low priority) catch cases and port "know"-param for tooltips from 5.0 + { // tooltip_enus: Charge to an enemy, stunning it for 1 sec.; spells_enus: {"58377": [["", "and 2 additional nearby targets "]], "103828": [["1 sec", "3 sec"]]}; + $condStartPos = strpos($data, $matches[2]) - 2; + $condCurPos = $condStartPos; + } + else // empty too? WTF?! GTFO! + die('what a terrible failure'); + + while ($condCurPos <= strlen($data)) // only hard-exit condition, we'll hit those breaks eventually^^ + { + // we're through with this condition. replace with found result and continue + if ($curPart == 4 || $condCurPos == strlen($data)) + { + $data = substr_replace($data, $condOutStr, $condStartPos, ($condCurPos - $condStartPos)); break; - case 'l': - if($lastvalue > 1) - $str .= $exprData[1]; - else - $str .= $exprData[0]; + } + + $char = $data[$condCurPos]; + + // advance position + $condCurPos++; + + if ($char == '[') + { + if (!$condBrktCnt) + $curPart++; + + $condBrktCnt++; + + if ($condBrktCnt == 1) + continue; + } + else if ($char == ']') + { + if ($condBrktCnt == 1) + $curPart++; + + $condBrktCnt--; + + if (!$condBrktCnt) + continue; + } + + // we got an elseif .. since they are self-containing we can just remove everything we've got up to here and restart the iteration + if ($curPart == 2 && $char == '?') + { + $replace = $targetPart == 1 ? $condOutStr.' $' : '$'; + $data = substr_replace($data, $replace, $condStartPos, ($condCurPos - $condStartPos) - 1); break; - case 'g': - $str .= $exprData[0]; - break; - default: - $str .= "[{$var} ($op::$oparg::$lookup::$exprData[0])]"; + } + + if ($curPart == $targetPart) + $condOutStr .= $char; + } } + + // step 3: unpack formulas ${ .. }.X + // 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++; + } + + // check for precision-modifiers .. yes, the precrements fullfill a role! + if ($formCurPos + 2 < strlen($data) && $data[++$formCurPos] == '.') + if ($prec = $data[++$formCurPos]) + { + $formPrecision = (int)$prec; + ++$formCurPos; // for some odd reason the precision decimal survives if wo dont increment further.. + } + + $formOutStr = $this->resolveFormulaString($formOutStr, $formPrecision, $level); + + $data = substr_replace($data, $formOutStr, $formStartPos, ($formCurPos - $formStartPos)); + } + + // step 4: find and eliminate regular variables + $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++; + + // ( (op) (oparg); )? (refSpell) ( ([g]ifText:elseText; | (var) (effIdx) ) + if (!preg_match('/^(([\+\-\*\/])(\d+);)?(\d*)(([g])([\w\s]*:[\w\s]*);|([a-z])([123]?)\b)/i', substr($data, $pos), $result)) + { + $str .= '#'; // mark as done, reset below + continue; + } + + $pos += strlen($result[0]); + $str .= $this->resolveVariableString($result, $level); + } $str .= substr($data, $pos); - $str = @preg_replace_callback("|\{([^\}]+)\}|", create_function('$matches', 'return eval("return abs(".$matches[1].");");'), $str); + $str = str_replace('#', '$', $str); // reset marks + + // step 5: variable-depentant variable-text + // special case $lONE:ELSE; + while (preg_match('/([\d\.]+) \$l([\w\s]*):([\w\s]*);/i', $str, $m)) + $str = str_replace($m[1].' $l'.$m[2].':'.$m[3].';', $m[1].' '.($m[1] == 1 ? $m[2] : $m[3]), $str); + + // step 6: HTMLize + // colors + $str = preg_replace('/\|cff([a-f0-9]{6})(.+?)\|r/i', '$2', $str); + + // line endings + $str = strtr($str, ["\r" => '', "\n" => '
']); return $str; } diff --git a/localization/locale_dede.php b/localization/locale_dede.php index f89bf644..15f3baf6 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -263,6 +263,7 @@ $lang = array( ), 'spell' => array( 'remaining' => "Noch %s", + 'untilCanceled' => "bis Abbruch", 'castIn' => "Wirken in %s Sek.", 'instantPhys' => "Sofort", 'instantMagic' => "Spontanzauber", @@ -271,6 +272,7 @@ $lang = array( 'meleeRange' => "Nahkampfreichweite", 'reagents' => "Reagenzien", 'tools' => "Extras", + 'home' => "%lt;Gasthaus>", 'pctCostOf' => "vom Grund%s", 'costPerSec' => ", plus %s pro Sekunde", 'costPerLevel' => ", plus %s pro Stufe", diff --git a/localization/locale_enus.php b/localization/locale_enus.php index 439052a9..9f603ff7 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -265,6 +265,7 @@ $lang = array( ), 'spell' => array( 'remaining' => "%s remaining", + 'untilCanceled' => "until canceled", 'castIn' => "%s sec cast", 'instantPhys' => "Instant", 'instantMagic' => "Instant cast", @@ -273,6 +274,7 @@ $lang = array( 'meleeRange' => "Melee Range", 'reagents' => "Reagents", 'tools' => "Tools", + 'home' => "<Inn>", 'pctCostOf' => "of base %s", 'costPerSec' => ", plus %s per second", 'costPerLevel' => ", plus %s per level", diff --git a/localization/locale_eses.php b/localization/locale_eses.php index 061cd7b1..9e923a79 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -160,6 +160,7 @@ $lang = array( ), 'spell' => array( 'remaining' => "%s restantes", + 'untilCanceled' => "hasta que se cancela", 'castIn' => "Hechizo de %s seg", 'instantPhys' => "Instante", 'instantMagic' => "Hechizo instantáneo", @@ -168,6 +169,7 @@ $lang = array( 'meleeRange' => "Alcance de ataques cuerpo a cuerpo", 'reagents' => "Componentes", 'tools' => "Herramientas", + 'home' => "<Posada>", 'pctCostOf' => "del %s base", 'costPerSec' => ", mas %s por segundo", 'costPerLevel' => ", mas %s por nivel", diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index d5aa13ce..fd3e7b57 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -164,6 +164,7 @@ $lang = array( ), 'spell' => array( 'remaining' => "%s restantes", + 'untilCanceled' => "jusqu’à annulation", 'castIn' => "%s s d'incantation", 'instantPhys' => "Incantation immédiate", 'instantMagic' => "Instantanée", @@ -172,6 +173,7 @@ $lang = array( 'meleeRange' => "Allonge", 'reagents' => "Composants", 'tools' => "Outils", + 'home' => "%lt;Auberge>", 'pctCostOf' => "de la %s de base", 'costPerSec' => ", plus %s par seconde", 'costPerLevel' => ", plus %s par niveau", diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index 35b69570..7e988502 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -164,6 +164,7 @@ $lang = array( ), 'spell' => array( 'remaining' => "Осталось: %s", + 'untilCanceled' => "до отмены", 'castIn' => "Применение: %s сек.", 'instantPhys' => "Мгновенное действие", 'instantMagic' => "Мгновенное действие", @@ -171,6 +172,7 @@ $lang = array( 'range' => "Радиус действия: %s м", 'meleeRange' => "Дистанция ближнего боя", 'reagents' => "Реагент", + 'home' => "%lt;Гостиница>", 'tools' => "Инструменты", 'pctCostOf' => "от базовой %s", 'costPerSec' => ", плюс %s в секунду",