error) return; // post processing $itemIcons = []; // if the spell creates an item use the itemIcon instead while ($this->iterate()) if ($this->curTpl['effect1CreateItemId']) $itemIcons[(int)$this->curTpl['effect1CreateItemId']] = $this->id; if ($itemIcons) { $itemList = new ItemList(array(['i.entry', array_keys($itemIcons)])); while ($itemList->iterate()) $this->templates[$itemIcons[$itemList->id]]['createItemString'] = $itemList->getField('icon'); } $this->reset(); // restore 'iterator' } // use if you JUST need the name public static function getName($id) { $n = DB::Aowow()->SelectRow('SELECT * FROM ?_spell WHERE id = ?d', $id ); return Util::localizedString($n, 'name'); } // end static use // required for itemSet-bonuses and socket-bonuses public function getStatGain() { $data = []; while ($this->iterate()) { $stats = []; for ($i = 1; $i <= 3; $i++) { if (!in_array($this->curTpl["effect".$i."AuraId"], [13, 22, 29, 34, 35, 83, 84, 85, 99, 124, 135, 143, 158, 161, 189, 230, 235, 240, 250])) continue; $mv = $this->curTpl["effect".$i."MiscValue"]; $bp = $this->curTpl["effect".$i."BasePoints"] + 1; switch ($this->curTpl["effect".$i."AuraId"]) { case 29: // ModStat MiscVal:type { if ($mv < 0) // all stats { for ($j = 0; $j < 5; $j++) @$stats[ITEM_MOD_AGILITY + $j] += $bp; } else // one stat @$stats[ITEM_MOD_AGILITY + $mv] += $bp; break; } case 34: // Increase Health case 230: case 250: { @$stats[ITEM_MOD_HEALTH] += $bp; break; } case 13: // damage splpwr + physical (dmg & any) { if ($mv == 1) // + weapon damage { @$stats[ITEM_MOD_WEAPON_DMG] += $bp; break; } if ($mv == 0x7E) // full magic mask, also counts towards healing { @$stats[ITEM_MOD_SPELL_POWER] += $bp; @$stats[ITEM_MOD_SPELL_DAMAGE_DONE] += $bp; } else { if ($mv & (1 << 1)) // HolySpellpower (deprecated; still used in randomproperties) @$stats[ITEM_MOD_HOLY_POWER] += $bp; if ($mv & (1 << 2)) // FireSpellpower (deprecated; still used in randomproperties) @$stats[ITEM_MOD_FIRE_POWER] += $bp; if ($mv & (1 << 3)) // NatureSpellpower (deprecated; still used in randomproperties) @$stats[ITEM_MOD_NATURE_POWER] += $bp; if ($mv & (1 << 4)) // FrostSpellpower (deprecated; still used in randomproperties) @$stats[ITEM_MOD_FROST_POWER] += $bp; if ($mv & (1 << 5)) // ShadowSpellpower (deprecated; still used in randomproperties) @$stats[ITEM_MOD_SHADOW_POWER] += $bp; if ($mv & (1 << 6)) // ArcaneSpellpower (deprecated; still used in randomproperties) @$stats[ITEM_MOD_ARCANE_POWER] += $bp; } break; } case 135: // healing splpwr (healing & any) .. not as a mask.. { @$stats[ITEM_MOD_SPELL_POWER] += $bp; @$stats[ITEM_MOD_SPELL_HEALING_DONE] += $bp; break; } case 35: // ModPower - MiscVal:type see defined Powers only energy/mana in use { if ($mv == -2) @$stats[ITEM_MOD_HEALTH] += $bp; if ($mv == 3) @$stats[ITEM_MOD_ENERGY] += $bp; else if ($mv == 0) @$stats[ITEM_MOD_MANA] += $bp; else if ($mv == 6) @$stats[ITEM_MOD_RUNIC_POWER] += $bp; break; } case 189: // CombatRating MiscVal:ratingMask // special case: resilience - consists of 3 ratings strung together. MOD_CRIT_TAKEN_MELEE|RANGED|SPELL (14,15,16) if (($mv & 0x1C000) == 0x1C000) @$stats[ITEM_MOD_RESILIENCE_RATING] += $bp; for ($j = 0; $j < count(Util::$combatRatingToItemMod); $j++) { if (!Util::$combatRatingToItemMod[$j]) continue; if (($mv & (1 << $j)) == 0) continue; @$stats[Util::$combatRatingToItemMod[$j]] += $bp; } break; case 143: // Resistance MiscVal:school case 83: case 22: if ($mv == 1) // Armor only if explixitly specified { @$stats[ITEM_MOD_ARMOR] += $bp; break; } if ($mv == 2) // holy-resistance ONLY if explicitly specified (shouldn't even exist...) { @$stats[ITEM_MOD_HOLY_RESISTANCE] += $bp; break; } for ($j = 0; $j < 7; $j++) { if (($mv & (1 << $j)) == 0) continue; switch ($j) { case 2: @$stats[ITEM_MOD_FIRE_RESISTANCE] += $bp; break; case 3: @$stats[ITEM_MOD_NATURE_RESISTANCE] += $bp; break; case 4: @$stats[ITEM_MOD_FROST_RESISTANCE] += $bp; break; case 5: @$stats[ITEM_MOD_SHADOW_RESISTANCE] += $bp; break; case 6: @$stats[ITEM_MOD_ARCANE_RESISTANCE] += $bp; break; } } break; case 84: // hp5 case 161: @$stats[ITEM_MOD_HEALTH_REGEN] += $bp; break; case 85: // mp5 @$stats[ITEM_MOD_MANA_REGENERATION] += $bp; break; case 99: // atkpwr @$stats[ITEM_MOD_ATTACK_POWER] += $bp; break; // ?carries over to rngatkpwr? case 124: // rngatkpwr @$stats[ITEM_MOD_RANGED_ATTACK_POWER] += $bp; break; case 158: // blockvalue @$stats[ITEM_MOD_BLOCK_VALUE] += $bp; break; case 240: // ModExpertise @$stats[ITEM_MOD_EXPERTISE_RATING] += $bp; break; } } $data[$this->id] = $stats; } return $data; } // description-, buff-parsing component private function resolveEvaluation($formula) { // todo: define unresolvable texts like AP, MHW, ect $pl = $PL = $this->charLevel; $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); }; if (preg_match_all('/\$[a-z]+\b/i', $formula, $vars)) { 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; return eval('return '.$formula.';'); } // there should not be any letters without a leading $ return eval('return '.$formula.';'); } // description-, buff-parsing component private function resolveVariableString($variable) { $signs = ['+', '-', '/', '*', '%', '^']; $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) return; if (!$effIdx) // if EffectIdx is omitted, assume EffectIdx: 1 $effIdx = 1; // cache at least some lookups.. should be moved to single spellList :/ if ($lookup && !isset($this->refSpells[$lookup])) $this->refSpells[$lookup] = new SpellList(array(['s.id', $lookup])); switch ($var) { case 'a': // EffectRadiusMin case 'A': // EffectRadiusMax (ToDo) if ($lookup) $base = $this->refSpells[$lookup]->getField('effect'.$effIdx.'RadiusMax'); else $base = $this->getField('effect'.$effIdx.'RadiusMax'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'b': // PointsPerComboPoint case 'B': if ($lookup) $base = $this->refSpells[$lookup]->getField('effect'.$effIdx.'PointsPerComboPoint'); else $base = $this->getField('effect'.$effIdx.'PointsPerComboPoint'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'd': // SpellDuration case 'D': // todo: min/max? if ($lookup) $base = $this->refSpells[$lookup]->getField('duration'); else $base = $this->getField('duration'); if ($base < 0) return Lang::$spell['untilCanceled']; if ($op && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return explode(' ', Util::formatTime(abs($base), true)); case 'e': // EffectValueMultiplier case 'E': if ($lookup) $base = $this->refSpells[$lookup]->getField('effect'.$effIdx.'ValueMultiplier'); else $base = $this->getField('effect'.$effIdx.'ValueMultiplier'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'f': // EffectDamageMultiplier case 'F': if ($lookup) $base = $this->refSpells[$lookup]->getField('effect'.$effIdx.'DamageMultiplier'); else $base = $this->getField('effect'.$effIdx.'DamageMultiplier'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $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]->getField('procChance'); else $base = $this->getField('procChance'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'i': // MaxAffectedTargets case 'I': if ($lookup) $base = $this->refSpells[$lookup]->getField('targets'); else $base = $this->getField('targets'); 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]->getField('effect'.$effIdx.'BasePoints'); $add = $this->refSpells[$lookup]->getField('effect'.$effIdx.'DieSides'); $mv = $this->refSpells[$lookup]->getField('effect'.$effIdx.'MiscValue'); $aura = $this->refSpells[$lookup]->getField('effect'.$effIdx.'AuraId'); } else { $base = $this->getField('effect'.$effIdx.'BasePoints'); $add = $this->getField('effect'.$effIdx.'DieSides'); $mv = $this->getField('effect'.$effIdx.'MiscValue'); $aura = $this->getField('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++) { if (!Util::$combatRatingToItemMod[$j]) continue; if (($mv & (1 << $j)) == 0) continue; $rType = Util::$combatRatingToItemMod[$j]; break; } } // Aura end if ($rType && $this->interactive) return ''.abs($base)." (".Util::setRatingLevel($this->charLevel, $rType, abs($base)).")"; else return $base; case 'n': // ProcCharges case 'N': if ($lookup) $base = $this->refSpells[$lookup]->getField('procCharges'); else $base = $this->getField('procCharges'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'o': // TotalAmount for periodic auras (with variance) case 'O': if ($lookup) { $base = $this->refSpells[$lookup]->getField('effect'.$effIdx.'BasePoints'); $add = $this->refSpells[$lookup]->getField('effect'.$effIdx.'DieSides'); $periode = $this->refSpells[$lookup]->getField('effect'.$effIdx.'Periode'); $duration = $this->refSpells[$lookup]->getField('duration'); } else { $base = $this->getField('effect'.$effIdx.'BasePoints'); $add = $this->getField('effect'.$effIdx.'DieSides'); $periode = $this->getField('effect'.$effIdx.'Periode'); $duration = $this->getField('duration'); } if (!$periode) $periode = 3000; $tick = $duration / $periode; $min = abs($base + 1) * $tick; $max = abs($base + $add) * $tick; $equal = $min == $max; if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) if ($equal) eval("\$min = $min $op $oparg;"); return $min . (!$equal ? Lang::$game['valueDelim'] . $max : null); case 'q': // EffectMiscValue case 'Q': if ($lookup) $base = $this->refSpells[$lookup]->getField('effect'.$effIdx.'MiscValue'); else $base = $this->getField('effect'.$effIdx.'MiscValue'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'r': // SpellRange case 'R': if ($lookup) $base = $this->refSpells[$lookup]->getField('rangeMaxHostile'); else $base = $this->getField('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]->getField('effect'.$effIdx.'BasePoints'); $add = $this->refSpells[$lookup]->getField('effect'.$effIdx.'DieSides'); $mv = $this->refSpells[$lookup]->getField('effect'.$effIdx.'MiscValue'); $aura = $this->refSpells[$lookup]->getField('effect'.$effIdx.'AuraId'); } else { $base = $this->getField('effect'.$effIdx.'BasePoints'); $add = $this->getField('effect'.$effIdx.'DieSides'); $mv = $this->getField('effect'.$effIdx.'MiscValue'); $aura = $this->getField('effect'.$effIdx.'AuraId'); } $min = abs($base + 1); $max = abs($base + $add); $equal = $min == $max; if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) if ($equal) eval("\$min = $min $op $oparg;"); // Aura giving combat ratings $rType = 0; if ($aura == 189) { 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 && $equal && $this->interactive) return ''.$min." (".Util::setRatingLevel($this->charLevel, $rType, $min).")"; else return $min . (!$equal ? Lang::$game['valueDelim'] . $max : null); case 't': // Periode case 'T': if ($lookup) $base = $this->refSpells[$lookup]->getField('effect'.$effIdx.'Periode') / 1000; else $base = $this->getField('effect'.$effIdx.'Periode') / 1000; if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'u': // StackCount case 'U': if ($lookup) $base = $this->refSpells[$lookup]->getField('stackAmount'); else $base = $this->getField('stackAmount'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'v': // MaxTargetLevel case 'V': if ($lookup) $base = $this->refSpells[$lookup]->getField('MaxTargetLevel'); else $base = $this->getField('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]->getField('effect'.$effIdx.'ChainTarget'); else $base = $this->getField('effect'.$effIdx.'ChainTarget'); if (in_array($op, $signs) && is_numeric($oparg) && is_numeric($base)) eval("\$base = $base $op $oparg;"); return $base; case 'z': // HomeZone return Lang::$spell['home']; } } // description-, buff-parsing component private function resolveFormulaString($formula, $precision = 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 (@$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) $formula = substr_replace($formula, $formOutStr, $formStartPos, ($formCurPos - $formStartPos)); } // step 2: resolve variables $pos = 0; // continue strpos-search from this offset $str = ''; $suffix = ''; 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]); $var = $this->resolveVariableString($result); if (is_array($var)) { $str .= $var[0]; $suffix = ' '.$var[1]; } else $str .= $var; } $str .= substr($formula, $pos); $str = str_replace('#', '$', $str); // reset marks // step 3: try to evaluate result $evaled = $this->resolveEvaluation($str); $return = is_numeric($evaled) ? number_format($evaled, $precision) : $evaled; return $return.$suffix; } // 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, $interactive = false) { // 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 $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 */ $this->interactive = $interactive; $this->charLevel = $level; // step 0: get text $data = Util::localizedString($this->curTpl, $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->curTpl['spellDescriptionVariableId'] > 0) { $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: 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 $condOutStr = ''; 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; } $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; } 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++; } $formCurPos++; // check for precision-modifiers if ($formCurPos + 1 < strlen($data) && $data[$formCurPos] == '.' && is_numeric($data[$formCurPos + 1])) { $formPrecision = $data[$formCurPos + 1]; $formCurPos += 2; } $formOutStr = $this->resolveFormulaString($formOutStr, $formPrecision); $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]); $var = $this->resolveVariableString($result); $resolved = is_array($var) ? $var[0] : $var; $str .= is_numeric($resolved) ? abs($resolved) : $resolved; if (is_array($var)) $str .= ' '.$var[1]; } $str .= substr($data, $pos); $str = str_replace('#', '$', $str); // reset marks // step 5: variable-depentant variable-text // special case $lONE:ELSE; // todo: russian uses THREE (wtf?! oO) cases ($l[singular]:[plural1]:[plural2]) .. explode() chooses always the first plural option :/ while (preg_match('/([\d\.]+)([^\d]*)(\$l:*)([^:]*):([^;]*);/i', $str, $m)) $str = str_ireplace($m[1].$m[2].$m[3].$m[4].':'.$m[5].';', $m[1].$m[2].($m[1] == 1 ? $m[4] : explode(':', $m[5])[0]), $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; } public function renderBuff($Id = 0) { while ($this->iterate()) { if ($Id && $this->id != $Id) continue; // doesn't have a buff if (!Util::localizedString($this->curTpl, 'buff')) return ''; $x = ''; // spellName $x .= ''; // dispelType (if applicable) if ($dispel = Lang::$game['di'][$this->curTpl['dispelType']]) $x .= ''; $x .= '
'.Util::localizedString($this->curTpl, 'name').''.$dispel.'
'; $x .= '
'; // parse Buff-Text $x .= $this->parseText('buff').'
'; // duration if ($this->curTpl['duration'] > 0) $x .= ''.sprintf(Lang::$spell['remaining'], Util::formatTime($this->curTpl['duration'])).''; $x .= '
'; $this->buffs[$this->id] = $x; } return $Id ? $this->buffs[$Id] : true; } public function renderTooltip($Id = 0) { while ($this->iterate()) { if ($Id && $this->id != $Id) continue; // get reagents $reagents = []; for ($j = 1; $j <= 8; $j++) { if($this->curTpl['reagent'.$j] <= 0) continue; $reagents[] = array( 'id' => $this->curTpl['reagent'.$j], 'name' => ItemList::getName($this->curTpl['reagent'.$j]), 'count' => $this->curTpl['reagentCount'.$j] // if < 0 : require, but don't use ); } $reagents = array_reverse($reagents); // get tools $tools = []; for ($i = 1; $i <= 2; $i++) { // Tools if ($this->curTpl['tool'.$i]) $tools[$i-1] = array('itemId' => $this->curTpl['tool'.$i], 'name' => ItemList::getName($this->curTpl['tool'.$i])); // TotemCategory if ($this->curTpl['toolCategory'.$i]) { $tc = DB::Aowow()->selectRow('SELECT * FROM aowow_totemcategory WHERE id = ?d', $this->curTpl['toolCategory'.$i]); $tools[$i+1] = array('categoryMask' => $tc['categoryMask'], 'name' => Util::localizedString($tc, 'name')); } } $tools = array_reverse($tools); // get description $desc = $this->parseText('description'); $reqWrapper = $this->curTpl['rangeMaxHostile'] && ($this->curTpl['powerCost'] > 0 || $this->curTpl['powerCostPercent'] > 0 || ($this->curTpl['powerCostRunes'] & 0x333)); $reqWrapper2 = $reagents ||$tools || $desc; $x = ''; $x .= '
'; $rankText = Util::localizedString($this->curTpl, 'rank'); if (!empty($rankText)) $x .= '
'; // name $x .= ''.$this->getField('name', true).''; // rank if (!empty($rankText)) $x .= '
'.$rankText.'
'; else $x .= '
'; if ($reqWrapper) $x .= '
'; // check for custom PowerDisplay $pt = $this->curTpl['powerDisplayString'] ? $this->curTpl['powerDisplayString'] : $this->curTpl['powerType']; // power cost: pct over static if ($this->curTpl['powerCostPercent'] > 0) $x .= $this->curTpl['powerCostPercent']."% ".sprintf(Lang::$spell['pctCostOf'], strtolower(Lang::$spell['powerTypes'][$pt])); else if ($this->curTpl['powerCost'] > 0 || $this->curTpl['powerPerSecond'] > 0 || $this->curTpl['powerCostPerLevel'] > 0) $x .= ($pt == 1 || $pt == 6 ? $this->curTpl['powerCost'] / 10 : $this->curTpl['powerCost']).' '.ucFirst(Lang::$spell['powerTypes'][$pt]); else if ($rCost = ($this->curTpl['powerCostRunes'] & 0x333)) { // Blood 2|1 - Unholy 2|1 - Frost 2|1 $runes = []; if ($_ = (($rCost & 0x300) >> 8)) $runes[] = $_." ".Lang::$spell['powerRunes'][2]; if ($_ = (($rCost & 0x030) >> 4)) $runes[] = $_." ".Lang::$spell['powerRunes'][1]; if ($_ = ($rCost & 0x003)) $runes[] = $_." ".Lang::$spell['powerRunes'][0]; $x .= implode(', ', $runes); } // append periodic cost if ($this->curTpl['powerPerSecond'] > 0) $x .= sprintf(Lang::$spell['costPerSec'], $this->curTpl['powerPerSecond']); // append level cost if ($this->curTpl['powerCostPerLevel'] > 0) $x .= sprintf(Lang::$spell['costPerLevel'], $this->curTpl['powerCostPerLevel']); if ($reqWrapper) $x .= ''; // ranges if ($this->curTpl['rangeMaxHostile']) { // minRange exists; show as range if ($this->curTpl['rangeMinHostile']) $x .= sprintf(Lang::$spell['range'], $this->curTpl['rangeMinHostile'].' - '.$this->curTpl['rangeMaxHostile']).'
'; // friend and hostile differ; do color else if ($this->curTpl['rangeMaxHostile'] != $this->curTpl['rangeMaxFriend']) $x .= sprintf(Lang::$spell['range'], ''.$this->curTpl['rangeMaxHostile'].' - '.$this->curTpl['rangeMaxHostile']. '').'
'; // hardcode: "melee range" else if ($this->curTpl['rangeMaxHostile'] == 5) $x .= Lang::$spell['meleeRange'].'
'; // regular case else $x .= sprintf(Lang::$spell['range'], $this->curTpl['rangeMaxHostile']).'
'; } if ($reqWrapper) $x .= '
'; $x .= ''; // cooldown or categorycooldown if ($this->curTpl['recoveryTime']) $x.= ''; else if ($this->curTpl['recoveryCategory']) $x.= ''; $x .= ''; // SPELL_ATTR2_NOT_NEED_SHAPESHIFT if ($this->curTpl['stanceMask'] && ($this->curTpl['attributes2'] & 0x80000) == 0) $x.= ''; $x .= '
'; // cast times if ($this->curTpl['interruptFlagsChannel']) $x .= Lang::$spell['channeled']; else if ($this->curTpl['castTime'] > 0) $x .= sprintf(Lang::$spell['castIn'], $this->curTpl['castTime'] / 1000); else if ($this->curTpl['attributes0'] & 0x10) // SPELL_ATTR0_ABILITY instant ability.. yeah, wording thing only $x .= Lang::$spell['instantPhys']; else // instant cast $x .= Lang::$spell['instantMagic']; $x .= ''.sprintf(Lang::$game['cooldown'], Util::formatTime($this->curTpl['recoveryTime'], true)).''.sprintf(Lang::$game['cooldown'], Util::formatTime($this->curTpl['recoveryCategory'], true)).'
'.Lang::$game['requires'].' '.Lang::getStances($this->curTpl['stanceMask']).'
'; $x .= '
'; if ($reqWrapper2) $x .= ''; if ($tools) { $x .= ''; } if ($reagents) { $x .= ''; } if($desc && $desc <> '_empty_') $x .= ''; if ($reqWrapper2) $x .= "
'; $x .= Lang::$spell['tools'].':
'; while ($tool = array_pop($tools)) { if (isset($tool['itemId'])) $x .= ''.$tool['name'].''; else if (isset($tool['totemCategory'])) $x .= ''.$tool['name'].''; else $x .= $tool['name']; if (!empty($tools)) $x .= ', '; else $x .= '
'; } $x .= '
'; $x .= Lang::$spell['reagents'].':
'; while ($reagent = array_pop($reagents)) { $x .= ''.$reagent['name'].''; if ($reagent['count'] > 1) $x .= ' ('.$reagent['count'].')'; if(!empty($reagents)) $x .= ', '; else $x .= '
'; } $x .= '
'.$desc.'
"; // append created items (may need improvement) for ($i = 1; $i <= 3; $i++) { if ($cId = $this->curTpl['effect'.$i.'CreateItemId']) { $x .= '
'.(new ItemList(array(['i.entry', (int)$cId])))->renderTooltip(); break; } } $this->tooltips[$this->id] = $x; } return $Id ? $this->tooltips[$Id] : true; } public function getTalentHeadForCurrent() { // upper: cost :: range // lower: time :: cooldown $x = ''; // power cost: pct over static $cost = ''; if ($this->curTpl['powerCostPercent'] > 0) $cost .= $this->curTpl['powerCostPercent']."% ".sprintf(Lang::$spell['pctCostOf'], strtolower(Lang::$spell['powerTypes'][$this->curTpl['powerType']])); else if ($this->curTpl['powerCost'] > 0 || $this->curTpl['powerPerSecond'] > 0 || $this->curTpl['powerCostPerLevel'] > 0) $cost .= ($this->curTpl['powerType'] == 1 ? $this->curTpl['powerCost'] / 10 : $this->curTpl['powerCost']).' '.ucFirst(Lang::$spell['powerTypes'][$this->curTpl['powerType']]); // append periodic cost if ($this->curTpl['powerPerSecond'] > 0) $cost .= sprintf(Lang::$spell['costPerSec'], $this->curTpl['powerPerSecond']); // append level cost if ($this->curTpl['powerCostPerLevel'] > 0) $cost .= sprintf(Lang::$spell['costPerLevel'], $this->curTpl['powerCostPerLevel']); // ranges $range = ''; if ($this->curTpl['rangeMaxHostile']) { // minRange exists; show as range if ($this->curTpl['rangeMinHostile']) $range .= sprintf(Lang::$spell['range'], $this->curTpl['rangeMinHostile'].' - '.$this->curTpl['rangeMaxHostile']); // friend and hostile differ; do color else if ($this->curTpl['rangeMaxHostile'] != $this->curTpl['rangeMaxFriend']) $range .= sprintf(Lang::$spell['range'], ''.$this->curTpl['rangeMaxHostile'].' - '.$this->curTpl['rangeMaxHostile']. ''); // hardcode: "melee range" else if ($this->curTpl['rangeMaxHostile'] == 5) $range .= Lang::$spell['meleeRange']; // regular case else $range .= sprintf(Lang::$spell['range'], $this->curTpl['rangeMaxHostile']); } // cast times $time = ''; if ($this->curTpl['interruptFlagsChannel']) $time .= Lang::$spell['channeled']; else if ($this->curTpl['castTime']) $time .= sprintf(Lang::$spell['castIn'], $this->curTpl['castTime'] / 1000); else if ($this->curTpl['attributes0'] & 0x10) // SPELL_ATTR0_ABILITY instant ability.. yeah, wording thing only $time .= Lang::$spell['instantPhys']; else // instant cast $time .= Lang::$spell['instantMagic']; // cooldown or categorycooldown $cool = ''; if ($this->curTpl['recoveryTime']) $cool.= sprintf(Lang::$game['cooldown'], Util::formatTime($this->curTpl['recoveryTime'], true)).''; else if ($this->curTpl['recoveryCategory']) $cool.= sprintf(Lang::$game['cooldown'], Util::formatTime($this->curTpl['recoveryCategory'], true)).''; // assemble parts // upper if ($cost && $range) $x .= '
'.$cost.''.$range.'
'; else if ($cost) $x .= $cost; else if ($range) $x .= $range; if (($cost xor $range) && ($time xor $cool)) $x .= '
'; // lower if ($time && $cool) $x .= '
'.$time.''.$cool.'
'; else if ($time) $x .= $time; else if ($cool) $x .= $cool; return $x; } public function getListviewData() { // this is going to be .. ""fun"" $data = []; while ($this->iterate()) { $data[$this->id] = array( 'name' => $this->getField('name', true), 'icon' => $this->curTpl['iconString'], 'level' => $this->curTpl['baseLevel'], ); } return $data; } public function addGlobalsToJscript(&$refs) { if (!isset($refs['gSpells'])) $refs['gSpells'] = []; while ($this->iterate()) { $iconString = isset($this->curTpl['createItemString']) ? 'createItemString' : 'iconString'; $refs['gSpells'][$this->id] = array( 'icon' => $this->curTpl[$iconString], 'name' => $this->getField('name', true), ); } } public function addRewardsToJScript(&$refs) { } } ?>