typeId = intVal($id); $this->contribute = Type::getClassAttrib($this->type, 'contribute') ?? CONTRIBUTE_NONE; } protected function generate() : void { $this->subject = new SpellList(array(['id', $this->typeId])); if ($this->subject->error) $this->generateNotFound(Lang::game('spell'), Lang::spell('notFound')); if ($jsg = $this->subject->getJSGlobals(GLOBALINFO_ANY, $extra)) $this->extendGlobalData($jsg, $extra); $this->modelInfo = $this->subject->getModelInfo($this->typeId); $this->difficulties = DB::Aowow()->selectRow( // has difficulty versions of itself 'SELECT `normal10` AS "0", `normal25` AS "1", `heroic10` AS "2", `heroic25` AS "3" FROM ?_spelldifficulty WHERE `normal10` = ?d OR `normal25` = ?d OR `heroic10` = ?d OR `heroic25` = ?d', $this->typeId, $this->typeId, $this->typeId, $this->typeId ); // returns self or firstRank if ($fr = DB::World()->selectCell('SELECT `first_spell_id` FROM spell_ranks WHERE `spell_id` = ?d', $this->typeId)) $this->firstRank = $fr; else $this->firstRank = DB::Aowow()->selectCell( 'SELECT IF(s1.`RankNo` <> 1 AND s2.`id`, s2.`id`, s1.`id`) FROM ?_spell s1 LEFT JOIN ?_spell s2 ON s1.`SpellFamilyId` = s2.`SpelLFamilyId` AND s1.`SpellFamilyFlags1` = s2.`SpelLFamilyFlags1` AND s1.`SpellFamilyFlags2` = s2.`SpellFamilyFlags2` AND s1.`SpellFamilyFlags3` = s2.`SpellFamilyFlags3` AND s1.`name_loc0` = s2.`name_loc0` AND s2.`RankNo` = 1 WHERE s1.`id` = ?d', $this->typeId ); $this->h1 = Util::htmlEscape($this->subject->getField('name', true)); $this->gPageInfo += array( 'type' => $this->type, 'typeId' => $this->typeId, 'name' => $this->subject->getField('name', true) ); /*************/ /* Menu Path */ /*************/ $this->generatePath(); /**************/ /* Page Title */ /**************/ array_unshift($this->title, $this->subject->getField('name', true), Util::ucFirst(Lang::game('spell'))); /***********/ /* Infobox */ /***********/ $this->createInfobox(); /***************/ /* Red Buttons */ /***************/ $this->redButtons = array( BUTTON_VIEW3D => false, BUTTON_WOWHEAD => true, BUTTON_LINKS => array( 'linkColor' => 'ff71d5ff', 'linkId' => Type::getFileString(Type::SPELL).':'.$this->typeId, 'linkName' => $this->subject->getField('name', true), 'type' => $this->type, 'typeId' => $this->typeId ) ); // could have multiple models set, one per effect foreach ($this->modelInfo as $mI) { $this->redButtons[BUTTON_VIEW3D] = ['type' => $mI['type'], 'displayId' => $mI['displayId']]; if (isset($mI['humanoid'])) { $this->redButtons[BUTTON_VIEW3D]['typeId'] = $mI['typeId']; $this->redButtons[BUTTON_VIEW3D]['humanoid'] = 1; } break; } /*******************/ /* Reagent Listing */ /*******************/ $this->createReagentList(); /**********************/ /* Spell Scaling Info */ /**********************/ $this->createScalingData(); /******************/ /* Required Items */ /******************/ $this->createRequiredItems(); /*************************/ /* Required Tools/Totems */ /*************************/ // prepare Tools foreach ($this->subject->getToolsForCurrent() as $tool) $this->tools[] = new IconElement( Type::ITEM, $tool['itemId'] ?? 0, $tool['name'], quality: ITEM_QUALITY_NORMAL, size: IconElement::SIZE_SMALL, url: !isset($tool['itemId']) ? '?items&filter=cr=91;crs='.$tool['id'].';crv=0' : '', element: 'iconlist-icon' ); /**********************/ /* Spell Effect Block */ /**********************/ $this->createEffects(); /**************************************************/ /* Spell Attributes listing and SpellFilter links */ /**************************************************/ $this->createAttributesList(); /*************************/ /* Factionchange pendant */ /*************************/ // factionchange-equivalent if ($pendant = DB::World()->selectCell('SELECT IF(`horde_id` = ?d, `alliance_id`, -`horde_id`) FROM player_factionchange_spells WHERE `alliance_id` = ?d OR `horde_id` = ?d', $this->typeId, $this->typeId, $this->typeId)) { $altSpell = new SpellList(array(['id', abs($pendant)])); if (!$altSpell->error) { $this->transfer = Lang::spell('_transfer', array( $altSpell->id, ITEM_QUALITY_NORMAL, $altSpell->getField('iconString'), $altSpell->getField('name', true), $pendant > 0 ? 'alliance' : 'horde', $pendant > 0 ? Lang::game('si', SIDE_ALLIANCE) : Lang::game('si', SIDE_HORDE) )); } } /****************/ /* Main Content */ /****************/ $this->powerCost = $this->subject->createPowerCostForCurrent(); $this->castTime = $this->subject->createCastTimeForCurrent(false, false); $this->level = $this->subject->getField('spellLevel'); $this->rangeName = $this->subject->getField('rangeText', true); $this->gcd = Util::formatTime($this->subject->getField('startRecoveryTime')); $this->school = $this->fmtStaffTip(Lang::getMagicSchools($this->subject->getField('schoolMask')), Util::asHex($this->subject->getField('schoolMask'))); $this->dispel = $this->subject->getField('dispelType') ? Lang::game('dt', $this->subject->getField('dispelType')) : null; $this->mechanic = $this->subject->getField('mechanic') ? Lang::game('me', $this->subject->getField('mechanic')) : null; $this->tooltip = array( $this->subject->getField('iconString'), $this->subject->getField('stackAmount') ?: ($this->subject->getField('procCharges') > 1 ? $this->subject->getField('procCharges') : 0), $this->subject->getField('buff', true, true) ? 1 : 0 ); $this->gcdCat = match((int)$this->subject->getField('startRecoveryCategory')) { 133 => Lang::spell('normal'), 330, // Mounts 1156, // Heart of the Phoenix 1159, // Ignis Grab and Slag Pot 1164, // Kessel Run Elek 1173, // Birmingham Test Spells 1178, // Stealth (Druid Cat, Rogue, Hunter Cat Pets) + Charge (Warrior) 1244 => Lang::spell('special'), // Argent Tournament Vehcile Jousting Abilities default => '' // n/a }; $this->range = $this->subject->getField('rangeMaxHostile'); if ($_ = $this->subject->getField('rangeMinHostile')) $this->range = $_.' - '.$this->range; if (!($this->subject->getField('attributes2') & SPELL_ATTR2_NOT_NEED_SHAPESHIFT)) $this->stances = Lang::getStances($this->subject->getField('stanceMask')); if (($_ = $this->subject->getField('recoveryTime')) && $_ > 0) $this->cooldown = Util::formatTime($_); else if (($_ = $this->subject->getField('recoveryCategory')) && $_ > 0) $this->cooldown = Util::formatTime($_); if (($_ = $this->subject->getField('duration')) && $_ > 0) $this->duration = Util::formatTime($_); /**************/ /* Extra Tabs */ /**************/ $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], 'tabsRelated', true); $ubSAI = SmartAI::getOwnerOfSpellCast($this->typeId); // tab: abilities [of shapeshift form] $formSpells = []; for ($i = 1; $i < 4; $i++) if ($this->subject->getField('effect'.$i.'AuraId') == SPELL_AURA_MOD_SHAPESHIFT) if ($_ = DB::Aowow()->selectRow('SELECT `spellId1`, `spellId2`, `spellId3`, `spellId4`, `spellId5`, `spellId6`, `spellId7`, `spellId8` FROM ?_shapeshiftforms WHERE `id` = ?d', $this->subject->getField('effect'.$i.'MiscValue'))) $formSpells = array_merge($formSpells, $_); if ($formSpells) { $abilities = new SpellList(array(['id', $formSpells])); if (!$abilities->error) { $tabData = array( 'data' => $abilities->getListviewData(), 'id' => 'controlledabilities', 'name' => '$LANG.tab_controlledabilities', 'visibleCols' => ['level'], ); if (!$abilities->hasSetFields('skillLines')) $tabData['hiddenCols'] = ['skill']; $this->lvTabs->addListviewTab(new Listview($tabData, SpellList::$brickFile)); $this->extendGlobalData($abilities->getJSGlobals(GLOBALINFO_SELF)); } } // tab: [$this] modifies $sub = []; $conditions = [ ['s.typeCat', [-9], '!'], // GM (-9); also include uncategorized (0), NPC-Spell (-8)?; NPC includes totems, lightwell and others :/ ['s.spellFamilyId', $this->subject->getField('spellFamilyId')], &$sub ]; $modifiesData = []; $hideSkillCol = true; for ($i = 1; $i < 4; $i++) { if (!in_array($this->subject->getField('effect'.$i.'AuraId'), self::MOD_AURAS)) continue; $m1 = $this->subject->getField('effect'.$i.'SpellClassMaskA'); $m2 = $this->subject->getField('effect'.$i.'SpellClassMaskB'); $m3 = $this->subject->getField('effect'.$i.'SpellClassMaskC'); if (!$m1 && !$m2 && !$m3) continue; $classSpells = $miscSpells = []; $this->effects[$i]['modifies'] = [&$classSpells, &$miscSpells]; $sub = ['OR', ['s.spellFamilyFlags1', $m1, '&'], ['s.spellFamilyFlags2', $m2, '&'], ['s.spellFamilyFlags3', $m3, '&']]; $modSpells = new SpellList($conditions); if (!$modSpells->error) { foreach ($modSpells->iterate() as $id => $__) { if (in_array($modSpells->getField('typeCat'), [-2, 7])) $classSpells[$id] = [new IconElement(Type::SPELL, $id, $modSpells->getField('name', true), size: IconElement::SIZE_SMALL), []]; else $miscSpells[$id] = [new IconElement(Type::SPELL, $id, $modSpells->getField('name', true), size: IconElement::SIZE_SMALL), []]; } if ($classSpells) { foreach (DB::World()->select('SELECT `spell_id` AS ARRAY_KEY, `first_spell_id` AS "0", `rank` AS "1" FROM spell_ranks WHERE `spell_id` IN (?a)', array_keys($classSpells)) as $spellId => [$firstSpellId, $rank]) { $classSpells[$firstSpellId][1][0] = min($classSpells[$firstSpellId][1][0] ?? $rank, $rank); $classSpells[$firstSpellId][1][1] = max($classSpells[$firstSpellId][1][1] ?? $rank, $rank); if ($spellId != $firstSpellId) unset($classSpells[$spellId]); } array_walk($classSpells, function(&$x) { if ($x[1] && $x[1][0] == $x[1][1]) // only one rank => unset $x[1] = null; }); } $modifiesData += $modSpells->getListviewData(); if ($modSpells->hasSetFields('skillLines')) $hideSkillCol = false; $this->extendGlobalData($modSpells->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } $classSpells = array_values($classSpells); $miscSpells = array_values($miscSpells); unset($classSpells, $miscSpells); } if ($modifiesData) { $tabData = array( 'data' => $modifiesData, 'id' => 'modifies', 'name' => '$LANG.tab_modifies', 'visibleCols' => ['level'], ); if ($hideSkillCol) $tabData['hiddenCols'] = ['skill']; $this->lvTabs->addListviewTab(new Listview($tabData, SpellList::$brickFile)); } // tab: [$this is] modified by $sub = ['OR']; $conditions = [ ['s.typeCat', [-9], '!'], // GM (-9); also include uncategorized (0), NPC-Spell (-8)?; NPC includes totems, lightwell and others :/ ['s.spellFamilyId', $this->subject->getField('spellFamilyId')], &$sub ]; for ($i = 1; $i < 4; $i++) { $m1 = $this->subject->getField('spellFamilyFlags1'); $m2 = $this->subject->getField('spellFamilyFlags2'); $m3 = $this->subject->getField('spellFamilyFlags3'); if (!$m1 && !$m2 && !$m3) continue; $sub[] = array( 'AND', ['s.effect'.$i.'AuraId', self::MOD_AURAS], [ 'OR', ['s.effect'.$i.'SpellClassMaskA', $m1, '&'], ['s.effect'.$i.'SpellClassMaskB', $m2, '&'], ['s.effect'.$i.'SpellClassMaskC', $m3, '&'] ] ); } if (count($sub) > 1) { $modsSpell = new SpellList($conditions); if (!$modsSpell->error) { $tabData = array( 'data' => $modsSpell->getListviewData(), 'id' => 'modified-by', 'name' => '$LANG.tab_modifiedby', 'visibleCols' => ['level'], ); if (!$modsSpell->hasSetFields('skillLines')) $tabData['hiddenCols'] = ['skill']; $this->lvTabs->addListviewTab(new Listview($tabData, SpellList::$brickFile)); $this->extendGlobalData($modsSpell->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } } // tab: see also $conditions = array( ['s.schoolMask', $this->subject->getField('schoolMask')], ['s.effect1Id', $this->subject->getField('effect1Id')], ['s.effect2Id', $this->subject->getField('effect2Id')], ['s.effect3Id', $this->subject->getField('effect3Id')], ['s.id', $this->subject->id, '!'], ['s.name_loc'.Lang::getLocale()->value, $this->subject->getField('name', true)] ); $saSpells = new SpellList($conditions); if (!$saSpells->error) { $data = $saSpells->getListviewData(); if ($this->difficulties) // needs a way to distinguish between dungeon and raid :x; creature using this -> map -> areaType? { $saE = ['$Listview.extraCols.mode']; foreach ($data as $id => &$d) { $d['modes'] = ['mode' => 0]; if ($this->difficulties[0] == $id) // b0001000 { if (!$this->difficulties[2] && !$this->difficulties[3]) $d['modes']['mode'] |= 0x2; else $d['modes']['mode'] |= 0x8; } if ($this->difficulties[1] == $id) // b0010000 { if (!$this->difficulties[2] && !$this->difficulties[3]) $d['modes']['mode'] |= 0x1; else $d['modes']['mode'] |= 0x10; } if ($this->difficulties[2] == $id) // b0100000 $d['modes']['mode'] |= 0x20; if ($this->difficulties[3] == $id) // b1000000 $d['modes']['mode'] |= 0x40; } } $tabData = array( 'data' => $data, 'id' => 'see-also', 'name' => '$LANG.tab_seealso', 'visibleCols' => ['level'], ); if (!$saSpells->hasSetFields('skillLines')) $tabData['hiddenCols'] = ['skill']; if (isset($saE)) $tabData['extraCols'] = $saE; $this->lvTabs->addListviewTab(new Listview($tabData, SpellList::$brickFile)); $this->extendGlobalData($saSpells->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } // tab: shared cooldown if ($this->subject->getField('recoveryCategory')) { $conditions = array( ['id', $this->typeId, '!'], ['category', $this->subject->getField('category')], ['recoveryCategory', 0, '>'], ); // limit shared cooldowns to same player class for regulat users if (!User::isInGroup(U_GROUP_STAFF) && $this->subject->getField('spellFamilyId')) $conditions[] = ['spellFamilyId', $this->subject->getField('spellFamilyId')]; $cdSpells = new SpellList($conditions); if (!$cdSpells->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $cdSpells->getListviewData(), 'name' => '$LANG.tab_sharedcooldown', 'id' => 'shared-cooldown' ), SpellList::$brickFile)); $this->extendGlobalData($cdSpells->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } } // tab: used by - spell if ($so = DB::Aowow()->selectCell('SELECT `id` FROM ?_spelloverride WHERE `spellId1` = ?d OR `spellId2` = ?d OR `spellId3` = ?d OR `spellId4` = ?d OR `spellId5` = ?d', $this->subject->id, $this->subject->id, $this->subject->id, $this->subject->id, $this->subject->id)) { $conditions = array( 'OR', ['AND', ['effect1AuraId', SPELL_AURA_OVERRIDE_SPELLS], ['effect1MiscValue', $so]], ['AND', ['effect2AuraId', SPELL_AURA_OVERRIDE_SPELLS], ['effect2MiscValue', $so]], ['AND', ['effect3AuraId', SPELL_AURA_OVERRIDE_SPELLS], ['effect3MiscValue', $so]] ); $ubSpells = new SpellList($conditions); if (!$ubSpells->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $ubSpells->getListviewData(), 'id' => 'used-by-spell', 'name' => '$LANG.tab_usedby' ), SpellList::$brickFile)); $this->extendGlobalData($ubSpells->getJSGlobals(GLOBALINFO_SELF)); } } // tab: used by - itemset $conditions = array( 'OR', ['spell1', $this->subject->id], ['spell2', $this->subject->id], ['spell3', $this->subject->id], ['spell4', $this->subject->id], ['spell5', $this->subject->id], ['spell6', $this->subject->id], ['spell7', $this->subject->id], ['spell8', $this->subject->id] ); $ubSets = new ItemsetList($conditions); if (!$ubSets->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $ubSets->getListviewData(), 'id' => 'used-by-itemset', 'name' => '$LANG.tab_usedby' ), ItemsetList::$brickFile)); $this->extendGlobalData($ubSets->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } // tab: used by - item $conditions = array( 'OR', ['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN, '!'], ['spellId1', $this->subject->id]], ['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN, '!'], ['spellId2', $this->subject->id]], ['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN, '!'], ['spellId3', $this->subject->id]], ['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN, '!'], ['spellId4', $this->subject->id]], ['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN, '!'], ['spellId5', $this->subject->id]] ); $ubItems = new ItemList($conditions); if (!$ubItems->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $ubItems->getListviewData(), 'id' => 'used-by-item', 'name' => '$LANG.tab_usedby' ), ItemList::$brickFile)); $this->extendGlobalData($ubItems->getJSGlobals(GLOBALINFO_SELF)); } // tab: used by - object $conditions = array( 'OR', ['onUseSpell', $this->subject->id], ['onSuccessSpell', $this->subject->id], ['auraSpell', $this->subject->id], ['triggeredSpell', $this->subject->id] ); if (!empty($ubSAI[Type::OBJECT])) $conditions[] = ['id', $ubSAI[Type::OBJECT]]; $ubObjects = new GameObjectList($conditions); if (!$ubObjects->error) { $this->addDataLoader('zones'); $this->lvTabs->addListviewTab(new Listview(array( 'data' => $ubObjects->getListviewData(), 'id' => 'used-by-object', 'name' => '$LANG.tab_usedby' ), GameObjectList::$brickFile)); $this->extendGlobalData($ubObjects->getJSGlobals()); } // tab: used by - areatrigger if (User::isInGroup(U_GROUP_EMPLOYEE)) { if (!empty($ubSAI[Type::AREATRIGGER])) { $ubTriggers = new AreaTriggerList(array(['id', $ubSAI[Type::AREATRIGGER]])); if (!$ubTriggers->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $ubTriggers->getListviewData(), 'id' => 'used-by-areatrigger', 'name' => '$LANG.tab_usedby' ), AreaTriggerList::$brickFile, 'areatrigger')); } } } // tab: criteria of $conditions = array( ['ac.type', [ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2, ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL] ], ['ac.value1', $this->typeId] ); $coAchievemnts = new AchievementList($conditions); if (!$coAchievemnts->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $coAchievemnts->getListviewData(), 'id' => 'criteria-of', 'name' => '$LANG.tab_criteriaof' ), AchievementList::$brickFile)); $this->extendGlobalData($coAchievemnts->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } // tab: contains // spell_loot_template & skill_extra_item_template $extraItem = DB::World()->selectRow('SELECT * FROM skill_extra_item_template WHERE `spellid` = ?d', $this->subject->id); $spellLoot = new Loot(); if ($spellLoot->getByContainer(LOOT_SPELL, $this->subject->id) || $extraItem) { $this->extendGlobalData($spellLoot->jsGlobals); $lv = $spellLoot->getResult(); $extraCols = $spellLoot->extraCols; $extraCols[] = '$Listview.extraCols.percent'; $lvName = '$LANG.tab_contains'; if ($extraItem && $this->subject->canCreateItem()) { $foo = $this->subject->relItems->getListviewData(); for ($i = 1; $i < 4; $i++) { if (($bar = $this->subject->getField('effect'.$i.'CreateItemId')) && isset($foo[$bar])) { $lvName = '$LANG.tab_bonusloot'; $lv[$bar] = $foo[$bar]; $lv[$bar]['percent'] = $extraItem['additionalCreateChance']; $lv[$bar]['pctstack'] = $this->buildPctStack($extraItem['additionalCreateChance'] / 100, $extraItem['additionalMaxNum']); if ($max = ($extraItem['additionalMaxNum'] - 1)) $lv[$bar]['stack'] = [1, $max]; if (Conditions::extendListviewRow($lv[$bar], Conditions::SRC_NONE, $this->typeId, [Conditions::SPELL, $extraItem['requiredSpecialization']])) { $this->extendGlobalIds(Type::SPELL, $extraItem['requiredSpecialization']); $extraCols[] = '$Listview.extraCols.condition'; } break; // skill_extra_item_template can only contain 1 item } } } $this->lvTabs->addListviewTab(new Listview(array( 'data' => $lv, 'name' => $lvName, 'id' => 'contains', 'hiddenCols' => ['side', 'slot', 'source', 'reqlevel'], 'extraCols' => array_unique($extraCols) ), ItemList::$brickFile)); } // tab: exclusive with if ($this->firstRank && DB::World()->selectCell('SELECT 1 FROM spell_group WHERE `spell_id` = ?d', $this->firstRank)) { $groups = DB::World()->selectCol('SELECT `id` AS ARRAY_KEY, `spell_id` AS ARRAY_KEY2, `spell_id` FROM spell_group'); // unpack recursion foreach ($groups as $i => $group) { foreach ($group as $j => $g) { if ($g > 0) continue; foreach ($groups[-$g] ?? [] as $new) $groups[$i][] = $new; unset($group[$j]); } } // find ourselves if ($filtered = array_filter($groups, fn($x) => in_array($this->firstRank, $x))) { // get rule set $rules = DB::World()->selectCol('SELECT `group_id` AS ARRAY_KEY, `stack_rule` FROM spell_group_stack_rules WHERE `group_id` IN (?a)', array_keys($filtered)); // only use groups that have rules set if ($filtered = array_intersect_key($filtered, $rules)) { $cnd = ['OR']; foreach ($filtered as $gr) $cnd[] = ['s.id', $gr]; $stacks = new SpellList($cnd); if (!$stacks->error) { $lvData = $stacks->getListviewData(); $this->extendGlobalData($stacks->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); if (!$stacks->hasSetFields('skillLines')) $sH = ['skill']; foreach ($filtered as $gId => $spellIds) { $data = []; foreach ($spellIds as $id) if (isset($lvData[$id]) && $id != $this->firstRank) $data[] = array_merge($lvData[$id], ['stackRule' => $rules[$gId]]); if (!$data) continue; $tabData = array( 'data' => $data, 'id' => 'spell-group-stack-'.$rules[$gId], 'name' => Lang::spell('stackGroup'), 'visibleCols' => ['stackRules'] ); if (isset($sH)) $tabData['hiddenCols'] = $sH; $this->lvTabs->addListviewTab(new Listview($tabData, SpellList::$brickFile)); } } } } } // tab: linked with $rows = DB::World()->select( 'SELECT `spell_trigger` AS "trigger", `spell_effect` AS "effect", `type`, IF(ABS(`spell_effect`) = ?d, ABS(`spell_trigger`), ABS(`spell_effect`)) AS "related" FROM spell_linked_spell WHERE ABS(`spell_effect`) = ?d OR ABS(`spell_trigger`) = ?d', $this->typeId, $this->typeId, $this->typeId ); $related = []; foreach ($rows as $row) $related[] = $row['related']; if ($related) $linked = new SpellList(array(['s.id', $related])); if (isset($linked) && !$linked->error) { $lv = $linked->getListviewData(); $data = []; foreach ($rows as $r) { foreach ($lv as $dk => $d) { if ($r['related'] != $dk) continue; $lv[$dk]['linked'] = [$r['trigger'], $r['effect'], $r['type']]; $data[] = $lv[$dk]; break; } } $this->lvTabs->addListviewTab(new Listview(array( 'data' => $data, 'id' => 'spell-link', 'name' => Lang::spell('linkedWith'), 'hiddenCols' => ['skill', 'name'], 'visibleCols' => ['linkedTrigger', 'linkedEffect'] ), SpellList::$brickFile)); $this->extendGlobalData($linked->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } // tab: triggered by $conditions = array( 'OR', ['AND', ['OR', ['effect1Id', SpellList::EFFECTS_TRIGGER], ['effect1AuraId', SpellList::AURAS_TRIGGER]], ['effect1TriggerSpell', $this->subject->id]], ['AND', ['OR', ['effect2Id', SpellList::EFFECTS_TRIGGER], ['effect2AuraId', SpellList::AURAS_TRIGGER]], ['effect2TriggerSpell', $this->subject->id]], ['AND', ['OR', ['effect3Id', SpellList::EFFECTS_TRIGGER], ['effect3AuraId', SpellList::AURAS_TRIGGER]], ['effect3TriggerSpell', $this->subject->id]], ); $trigger = new SpellList($conditions); if (!$trigger->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $trigger->getListviewData(), 'id' => 'triggered-by', 'name' => '$LANG.tab_triggeredby' ), SpellList::$brickFile)); $this->extendGlobalData($trigger->getJSGlobals(GLOBALINFO_SELF)); } // tab: used by - creature $conditions = array( 'OR', ['spell1', $this->typeId], ['spell2', $this->typeId], ['spell3', $this->typeId], ['spell4', $this->typeId], ['spell5', $this->typeId], ['spell6', $this->typeId], ['spell7', $this->typeId], ['spell8', $this->typeId] ); if (!empty($ubSAI[Type::NPC])) $conditions[] = ['id', $ubSAI[Type::NPC]]; if ($auras = DB::World()->selectCol('SELECT `entry` FROM creature_template_addon WHERE `auras` REGEXP ?', '\\b'.$this->typeId.'\\b')) $conditions[] = ['id', $auras]; $ubCreature = new CreatureList($conditions); if (!$ubCreature->error) { $this->addDataLoader('zones'); $this->lvTabs->addListviewTab(new Listview(array( 'data' => $ubCreature->getListviewData(), 'id' => 'used-by-npc', 'name' => '$LANG.tab_usedby' ), CreatureList::$brickFile)); $this->extendGlobalData($ubCreature->getJSGlobals(GLOBALINFO_SELF)); } // tab: zone if ($areaSpells = DB::World()->select('SELECT `area` AS ARRAY_KEY, `aura_spell` AS "0", `quest_start` AS "1", `quest_end` AS "2", `quest_start_status` AS "3", `quest_end_status` AS "4", `racemask` AS "5", `gender` AS "6" FROM spell_area WHERE `spell` = ?d', $this->typeId)) { $zones = new ZoneList(array(['id', array_keys($areaSpells)])); if (!$zones->error) { $lvZones = $zones->getListviewData(); $this->extendGlobalData($zones->getJSGlobals()); $resultLv = []; $parents = []; $extraCols = []; foreach ($areaSpells as $areaId => $condition) { if (empty($lvZones[$areaId])) continue; $row = $lvZones[$areaId]; // attach to lv row and evaluate after merging $row['__condition'] = $condition; // merge subzones, into one row, if: spell_area data is identical && parentZone is shared if ($p = $zones->getEntry($areaId)['parentArea']) { $parents[] = $p; $row['__parent'] = $p; $row['subzones'] = [$areaId]; } else $row['__parent'] = 0; $set = false; foreach ($resultLv as &$v) { if ($v['__parent'] != $row['__parent'] && $v['id'] != $row['__parent']) continue; if ($v['__condition'] != $row['__condition']) continue; if (!$row['__parent'] && $v['id'] != $row['__parent']) continue; $set = true; $v['subzones'][] = $row['id']; break; } // add self as potential subzone; IF we are a parentZone without added children, we get filtered in JScript if (!$set) { $row['subzones'] = [$row['id']]; $resultLv[] = $row; } } // overwrite lvData with parent-lvData (condition and subzones are kept) if ($parents) { $parents = (new ZoneList(array(['id', $parents])))->getListviewData(); foreach ($resultLv as &$_) if (isset($parents[$_['__parent']])) $_ = array_merge($_, $parents[$_['__parent']]); } $cnd = new Conditions(); foreach ($resultLv as $idx => $lv) { [$auraSpell, $questStart, $questEnd, $questStartState, $questEndState, $raceMask, $gender] = $lv['__condition']; if ($auraSpell) $cnd->addExternalCondition(Conditions::SRC_NONE, $lv['id'], [$auraSpell > 0 ? Conditions::AURA : -Conditions::AURA, abs($auraSpell)]); if ($questStart) $cnd->addExternalCondition(Conditions::SRC_NONE, $lv['id'], [Conditions::QUESTSTATE, $questStart, $questStartState]); if ($questEnd && $questEnd != $questStart) $cnd->addExternalCondition(Conditions::SRC_NONE, $lv['id'], [Conditions::QUESTSTATE, $questEnd, $questEndState]); if ($raceMask) $cnd->addExternalCondition(Conditions::SRC_NONE, $lv['id'], [Conditions::CHR_RACE, $raceMask]); if ($gender != 2) // 2: both $cnd->addExternalCondition(Conditions::SRC_NONE, $lv['id'], [Conditions::GENDER, $gender]); // remove temp storage from result unset($resultLv[$idx]['__condition']); unset($resultLv[$idx]['__parent']); } if ($cnd->toListviewColumn($resultLv, $extraCols)) $this->extendGlobalData($cnd->getJsGlobals()); $tabData = ['data' => $resultLv]; if ($extraCols) { $tabData['extraCols'] = $extraCols; $tabData['hiddenCols'] = ['instancetype']; } $this->lvTabs->addListviewTab(new Listview($tabData, ZoneList::$brickFile)); } } // tab: teaches if ($ids = Game::getTaughtSpells($this->subject)) { $teaches = new SpellList(array(['id', $ids])); if (!$teaches->error) { $this->extendGlobalData($teaches->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); $vis = ['level', 'schools']; foreach ($teaches->iterate() as $__) { if (!$teaches->canCreateItem()) continue; $vis[] = 'reagents'; break; } $tabData = array( 'data' => $teaches->getListviewData(), 'id' => 'teaches-spell', 'name' => '$LANG.tab_teaches', 'visibleCols' => $vis, ); if (!$teaches->hasSetFields('skillLines')) $tabData['hiddenCols'] = ['skill']; $this->lvTabs->addListviewTab(new Listview($tabData, SpellList::$brickFile)); } } // tab: taught by npc if ($this->subject->getSources($s) && in_array(SRC_TRAINER, $s)) { $trainers = DB::World()->select( 'SELECT cdt.`CreatureId` AS ARRAY_KEY, ts.`ReqSkillLine` AS "reqSkillId", ts.`ReqSkillRank` AS "reqSkillValue", ts.`ReqLevel` AS "reqLevel", ts.`ReqAbility1` AS "reqSpellId1", ts.`reqAbility2` AS "reqSpellId2" FROM creature_default_trainer cdt JOIN trainer_spell ts ON ts.`TrainerId` = cdt.`TrainerId` WHERE ts.`SpellId` = ?d', $this->typeId ); if ($trainers) { $tbTrainer = new CreatureList(array(Cfg::get('SQL_LIMIT_NONE'), ['ct.id', array_keys($trainers)], ['s.guid', null, '!'], ['ct.npcflag', NPC_FLAG_TRAINER, '&'])); if (!$tbTrainer->error) { $this->extendGlobalData($tbTrainer->getJSGlobals()); $cnd = new Conditions(); $skill = $this->subject->getField('skillLines'); foreach ($trainers as $tId => $train) { if ($_ = $train['reqLevel']) $cnd->addExternalCondition(Conditions::SRC_NONE, $tId, [Conditions::LEVEL, $_, Conditions::OP_GT_E]); if ($_ = $train['reqSkillId']) if (count($skill) == 1 && $_ != $skill[0]) $cnd->addExternalCondition(Conditions::SRC_NONE, $tId, [Conditions::SKILL, $_, $train['reqSkillValue']]); for ($i = 1; $i < 3; $i++) if ($_ = $train['reqSpellId'.$i]) $cnd->addExternalCondition(Conditions::SRC_NONE, $tId, [Conditions::SPELL, $_]); } $lvData = $tbTrainer->getListviewData(); $extraCols = []; if ($cnd->toListviewColumn($lvData, $extraCols)) $this->extendGlobalData($cnd->getJsGlobals()); $tabData = array( 'data' => $lvData, 'id' => 'taught-by-npc', 'name' => '$LANG.tab_taughtby', ); if ($extraCols) $tabData['extraCols'] = $extraCols; $this->addDataLoader('zones'); $this->lvTabs->addListviewTab(new Listview($tabData, CreatureList::$brickFile)); } } } // tab: taught by spell $conditions = array( 'OR', ['AND', ['effect1Id', SpellList::EFFECTS_TEACH], ['effect1TriggerSpell', $this->subject->id]], ['AND', ['effect2Id', SpellList::EFFECTS_TEACH], ['effect2TriggerSpell', $this->subject->id]], ['AND', ['effect3Id', SpellList::EFFECTS_TEACH], ['effect3TriggerSpell', $this->subject->id]], ); $tbSpell = new SpellList($conditions); $tbsData = []; if (!$tbSpell->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $tbSpell->getListviewData(), 'id' => 'taught-by-spell', 'name' => '$LANG.tab_taughtby' ), SpellList::$brickFile)); $this->extendGlobalData($tbSpell->getJSGlobals(GLOBALINFO_SELF)); } // tab: taught by quest $conditions = ['OR', ['sourceSpellId', $this->typeId], ['rewardSpell', $this->typeId]]; if ($tbsData) { $conditions[] = ['rewardSpell', array_keys($tbsData)]; if (User::isInGroup(U_GROUP_EMPLOYEE)) $conditions[] = ['rewardSpellCast', array_keys($tbsData)]; } if (User::isInGroup(U_GROUP_EMPLOYEE)) $conditions[] = ['rewardSpellCast', $this->typeId]; $tbQuest = new QuestList($conditions); if (!$tbQuest->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $tbQuest->getListviewData(), 'id' => 'reward-from-quest', 'name' => '$LANG.tab_rewardfrom' ), QuestList::$brickFile)); $this->extendGlobalData($tbQuest->getJSGlobals()); } // tab: taught by item (i'd like to precheck $this->subject->sources, but there is no source:item only complicated crap like "drop" and "vendor") $conditions = array( 'OR', ['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN], ['spellId1', $this->subject->id]], ['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN], ['spellId2', $this->subject->id]], ['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN], ['spellId3', $this->subject->id]], ['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN], ['spellId4', $this->subject->id]], ['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN], ['spellId5', $this->subject->id]], ); $tbItem = new ItemList($conditions); if (!$tbItem->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $tbItem->getListviewData(), 'id' => 'taught-by-item', 'name' => '$LANG.tab_taughtby' ), ItemList::$brickFile)); $this->extendGlobalData($tbItem->getJSGlobals(GLOBALINFO_SELF)); } // tab: enchantments $conditions = array( 'OR', ['AND', ['type1', [ENCHANTMENT_TYPE_COMBAT_SPELL, ENCHANTMENT_TYPE_EQUIP_SPELL, ENCHANTMENT_TYPE_USE_SPELL]], ['object1', $this->typeId]], ['AND', ['type2', [ENCHANTMENT_TYPE_COMBAT_SPELL, ENCHANTMENT_TYPE_EQUIP_SPELL, ENCHANTMENT_TYPE_USE_SPELL]], ['object2', $this->typeId]], ['AND', ['type3', [ENCHANTMENT_TYPE_COMBAT_SPELL, ENCHANTMENT_TYPE_EQUIP_SPELL, ENCHANTMENT_TYPE_USE_SPELL]], ['object3', $this->typeId]] ); $enchList = new EnchantmentList($conditions); if (!$enchList->error) { $this->lvTabs->addListviewTab(new Listview(array( 'data' => $enchList->getListviewData(), 'name' => Util::ucFirst(Lang::game('enchantments')) ), EnchantmentList::$brickFile, 'enchantment')); $this->extendGlobalData($enchList->getJSGlobals()); } // tab: sounds $data = []; $seSounds = []; for ($i = 1; $i < 4; $i++) // sounds from screen effect if ($this->subject->getField('effect'.$i.'AuraId') == SPELL_AURA_SCREEN_EFFECT) $seSounds = DB::Aowow()->selectRow('SELECT `ambienceDay`, `ambienceNight`, `musicDay`, `musicNight` FROM ?_screeneffect_sounds WHERE `id` = ?d', $this->subject->getField('effect'.$i.'MiscValue')); $activitySounds = DB::Aowow()->selectRow('SELECT * FROM ?_spell_sounds WHERE `id` = ?d', $this->subject->getField('spellVisualId')); array_shift($activitySounds); // remove id-column if ($soundIDs = $activitySounds + $seSounds) { $sounds = new SoundList(array(['id', $soundIDs])); if (!$sounds->error) { $data = $sounds->getListviewData(); foreach ($activitySounds as $activity => $id) if (isset($data[$id])) $data[$id]['activity'] = $activity; // no index, js wants a string :( $tabData = ['data' => $data]; if ($activitySounds) $tabData['visibleCols'] = ['activity']; $this->extendGlobalData($sounds->getJSGlobals(GLOBALINFO_SELF)); $this->lvTabs->addListviewTab(new Listview($tabData, SoundList::$brickFile)); } } // find associated NPC, Item and merge results // taughtbypets (unused..?) // taughtbyquest (usually the spell casted as quest reward teaches something; exclude those seplls from taughtBySpell) // taughtbytrainers // taughtbyitem // tab: conditions $cnd = new Conditions(); $cnd->getBySourceEntry($this->typeId, Conditions::SRC_SPELL_IMPLICIT_TARGET, Conditions::SRC_SPELL, Conditions::SRC_SPELL_CLICK_EVENT, Conditions::SRC_VEHICLE_SPELL, Conditions::SRC_SPELL_PROC) ->getByCondition(Type::SPELL, $this->typeId) ->prepare(); if ($tab = $cnd->toListviewTab()) { $this->extendGlobalData($cnd->getJsGlobals()); $this->lvTabs->addDataTab(...$tab); } parent::generate(); } /******************************************/ /* SpellLoot recursive dropchance builder */ /******************************************/ private function buildPctStack(float $baseChance, int $maxStack) : string { // note: pctStack does not contain absolute values but chances relative to the overall drop chance // e.g.: dropChance is 17% then [1 => 50, 2 => 25, 3 => 25] displays > Stack of 1: 8%; Stack of 2: 4%; Stack of 3: 4% $maxStack = $maxStack ?: 1; $pctStack = []; for ($i = 1; $i <= $maxStack; $i++) { $pctStack[$i] = (($baseChance ** $i) * 100) / $baseChance; // remove chance from previous stacks if ($i > 1) $pctStack[$i-1] -= $pctStack[$i]; } // cleanup rounding errors $pctStack = array_map(fn($x) => round($x, 3), $pctStack); // cleanup tiny fractions $pctStack = array_filter($pctStack, fn($x) => ($x * $baseChance) >= 0.01); return json_encode($pctStack, JSON_NUMERIC_CHECK); // do not replace with Util::toJSON ! } /**********************************/ /* recursive reagent list builder */ /**********************************/ private function appendReagentItem(array &$reagentResult, int $itemId, int $qty, int $mult, int $level, string $path, array $alreadyUsed, int $fromSpell = 0) : bool { if (in_array($itemId, $alreadyUsed)) return false; $item = DB::Aowow()->selectRow( 'SELECT `name_loc0`, `name_loc2`, `name_loc3`, `name_loc4`, `name_loc6`, `name_loc8`, i.`id`, ic.`name` AS `iconString`, `quality`, `spellId1`, `spellCharges1` FROM ?_items i LEFT JOIN ?_icons ic ON ic.`id` = i.`iconId` WHERE i.`id` = ?d', $itemId ); if (!$item) return false; $this->extendGlobalIds(Type::ITEM, $item['id']); // the spell calling this is also on the item and triggering it destroys the item // so effectively we need one more. (see elemental particles and enchantment essences) if ($fromSpell && $fromSpell == $item['spellId1'] && $item['spellCharges1'] == -1) $qty++; $level++; $data = array( 'path' => $path.'.'.Type::ITEM.'-'.$item['id'], 'level' => $level, 'final' => false, 'typeStr' => Type::getFileString(Type::ITEM), 'icon' => new IconElement(Type::ITEM, $item['id'], Util::localizedString($item, 'name'), $qty * $mult, '', $item['quality'], IconElement::SIZE_SMALL, align: 'right', element: 'iconlist-icon') ); $idx = count($reagentResult); $reagentResult[] = $data; $alreadyUsed[] = $item['id']; if (!$this->appendReagentSpell($reagentResult, $item['id'], $qty * $mult, $data['level'], $data['path'], $alreadyUsed)) $reagentResult[$idx]['final'] = true; return true; } private function appendReagentSpell(array &$reagentResult, int $itemId, int $qty, int $level, string $path, array $alreadyUsed) : bool { $level++; // assume that tradeSpells only use the first index to create items, so this runs somewhat efficiently >.< $spells = DB::Aowow()->select( 'SELECT `reagent1`, `reagent2`, `reagent3`, `reagent4`, `reagent5`, `reagent6`, `reagent7`, `reagent8`, `reagentCount1`, `reagentCount2`, `reagentCount3`, `reagentCount4`, `reagentCount5`, `reagentCount6`, `reagentCount7`, `reagentCount8`, `name_loc0`, `name_loc2`, `name_loc3`, `name_loc4`, `name_loc6`, `name_loc8`, `iconIdBak`, s.`id` AS ARRAY_KEY, ic.`name` AS `iconString` FROM ?_spell s JOIN ?_icons ic ON s.`iconId` = ic.`id` WHERE (`effect1CreateItemId` = ?d AND `effect1Id` = ?d)',// OR // (`effect2CreateItemId` = ?d AND `effect2Id` = ?d) OR // (`effect3CreateItemId` = ?d AND `effect3Id` = ?d)', $itemId, SPELL_EFFECT_CREATE_ITEM //, $itemId, SPELL_EFFECT_CREATE_ITEM, $itemId, SPELL_EFFECT_CREATE_ITEM ); if (!$spells) return false; $didAppendSomething = false; foreach ($spells as $sId => $row) { if (in_array(-$sId, $alreadyUsed)) continue; $this->extendGlobalIds(Type::SPELL, $sId); $data = array( 'path' => $path.'.'.Type::SPELL.'-'.$sId, 'level' => $level, 'final' => false, 'typeStr' => Type::getFileString(Type::SPELL), 'icon' => new IconElement(Type::SPELL, $sId, Util::localizedString($row, 'name'), $qty, size: IconElement::SIZE_SMALL, align: 'right', element: 'iconlist-icon') ); $reagentResult[] = $data; $_aU = $alreadyUsed; $_aU[] = -$sId; $hasUnusedReagents = false; for ($i = 1; $i < 9; $i++) { if ($row['reagent'.$i] <= 0 || $row['reagentCount'.$i] <= 0) continue; if ($this->appendReagentItem($reagentResult, $row['reagent'.$i], $row['reagentCount'.$i], $qty, $data['level'], $data['path'], $_aU, $sId)) { $hasUnusedReagents = true; $didAppendSomething = true; } } if (!$hasUnusedReagents) // no reagents were added, remove spell from result set array_pop($reagentResult); } return $didAppendSomething; } private function createReagentList() : void { $reagentResult = []; $enhanced = false; $reagents = $this->subject->getReagentsForCurrent(); if (!$reagents) return; foreach ($this->subject->relItems->iterate() as $iId => $__) { if (!in_array($iId, array_keys($reagents))) continue; $data = array( 'path' => Type::ITEM.'-'.$iId, // id of the html-element 'level' => 0, // depths in array, used for indentation 'final' => false, 'typeStr' => Type::getFileString(Type::ITEM), 'icon' => new IconElement( Type::ITEM, $iId, $this->subject->relItems->getField('name', true), $reagents[$iId][1], quality: $this->subject->relItems->getField('quality'), size: IconElement::SIZE_SMALL, align: 'right', element: 'iconlist-icon' ) ); $idx = count($reagentResult); $reagentResult[] = $data; // start with self and current original item in usedEntries (spell < 0; item > 0) if ($this->appendReagentSpell($reagentResult, $iId, $reagents[$iId][1], 0, $data['path'], [-$this->typeId, $iId])) $enhanced = true; else $reagentResult[$idx]['final'] = true; } // increment all indizes (by prepending null and removing it again) array_unshift($reagentResult, null); unset($reagentResult[0]); $this->reagents = [$enhanced, $reagentResult]; } private function createScalingData() : void // calculation mostly like seen in TC { $scaling = ['directSP' => 0, 'dotSP' => 0, 'directAP' => 0, 'dotAP' => 0]; $pMask = $this->subject->periodicEffectsMask(); $allDoTs = true; for ($i = 1; $i < 4; $i++) { if (!$this->subject->getField('effect'.$i.'Id')) continue; if ($pMask & 1 << ($i - 1)) { $scaling['dotSP'] = $this->subject->getField('effect'.$i.'BonusMultiplier'); continue; } else $scaling['directSP'] = $this->subject->getField('effect'.$i.'BonusMultiplier'); $allDoTs = false; } if ($s = DB::World()->selectRow('SELECT `direct_bonus` AS "directSP", `dot_bonus` AS "dotSP", `ap_bonus` AS "directAP", `ap_dot_bonus` AS "dotAP" FROM spell_bonus_data WHERE `entry` = ?d', $this->firstRank)) $scaling = $s; if ((!$this->subject->isDamagingSpell() && !$this->subject->isHealingSpell()) || !in_array($this->subject->getField('typeCat'), [-2, -3, -7, 7]) || $this->subject->getField('damageClass') == SPELL_DAMAGE_CLASS_NONE) { $this->scaling = array_filter($scaling, fn($x) => $x > 0); return; } foreach ($scaling as $k => $v) { // recalculate if spell_bonus_data says so if ($v != -1) continue; // no known calculation for physical abilities if ($k == 'directAP' || $k == 'dotAP') continue; // dont use spellPower to scale physical Abilities if ($this->subject->getField('schoolMask') == (1 << SPELL_SCHOOL_NORMAL) && ($k == 'directSP' || $k == 'dotSP')) continue; $isDOT = false; if ($k == 'dotSP' || $k == 'dotAP') { if ($pMask) $isDOT = true; else continue; } else if ($allDoTs) // if all used effects are periodic, dont calculate direct component continue; // damage over time spells bonus calculation $dotFactor = 1.0; if ($isDOT) { $dotDuration = $this->subject->getField('duration'); // 200% limit if ($dotDuration > 0) { if ($dotDuration > 30000) $dotDuration = 30000; if (!$this->subject->isChanneledSpell()) $dotFactor = $dotDuration / 15000; } } // distribute damage over multiple effects, reduce by AoE $castingTime = $this->subject->getCastingTimeForBonus($isDOT); // 50% for damage and healing spells for leech spells from damage bonus and 0% from healing for ($j = 1; $j < 4; ++$j) { if ($this->subject->getField('effectId'.$j) == SPELL_EFFECT_HEALTH_LEECH || $this->subject->getField('effect'.$j.'AuraId') == SPELL_AURA_PERIODIC_LEECH) { $castingTime /= 2; break; } } if ($this->subject->isHealingSpell()) $castingTime *= 1.88; // SPELL_SCHOOL_MASK_NORMAL if ($this->subject->getField('schoolMask') != (1 << SPELL_SCHOOL_NORMAL)) $scaling[$k] = ($castingTime / 3500.0) * $dotFactor; else $scaling[$k] = 0; // would be 1 ($dotFactor), but we dont want it to be displayed } $this->scaling = array_filter($scaling, fn($x) => $x > 0); } private function createRequiredItems() : void { // parse itemClass & itemSubClassMask $class = $this->subject->getField('equippedItemClass'); $subClass = $this->subject->getField('equippedItemSubClassMask'); $invType = $this->subject->getField('equippedItemInventoryTypeMask'); if ($class <= 0) return; $tip = 'Class: '.$class.'
SubClass: '.Util::asHex($subClass); $text = Lang::getRequiredItems($class, $subClass, false); if ($invType) { // remap some duplicated strings 'Off Hand' and 'Shield' are never used simultaneously if ($invType & (1 << INVTYPE_ROBE)) // Robe => Chest { $invType &= ~(1 << INVTYPE_ROBE); $invType |= (1 << INVTYPE_CHEST); } if ($invType & (1 << INVTYPE_RANGEDRIGHT)) // Ranged2 => Ranged { $invType &= ~(1 << INVTYPE_RANGEDRIGHT); $invType |= (1 << INVTYPE_RANGED); } $_ = []; $strs = Lang::item('inventoryType'); foreach ($strs as $k => $str) if ($invType & 1 << $k && $str) $_[] = $str; $tip .= '
'.Lang::item('slot').Lang::main('colon').Util::asHex($invType); $text .= ' '.Lang::spell('_inSlot').implode(', ', $_); } $this->items = $this->fmtStaffTip($text, $tip); } private function createEffects() : void { // proc data .. maybe use more information..? $procData = array( 'chance' => $this->subject->getField('procChance'), 'cooldown' => 0 ); if ($sp = DB::World()->selectRow('SELECT IF(`ProcsPerMinute` > 0, -`ProcsPerMinute`, `Chance`) AS "chance", `Cooldown` AS "cooldown" FROM `spell_proc` WHERE ABS(`SpellId`) = ?d', $this->firstRank)) { $procData['chance'] = $sp['chance'] ?: $procData['chance']; $procData['cooldown'] = $sp['cooldown'] ?: $procData['cooldown']; } $effects = []; $spellIdx = array_unique(array_merge($this->subject->canTriggerSpell(), $this->subject->canTeachSpell())); $itemIdx = $this->subject->canCreateItem(); $perfItem = DB::World()->selectRow('SELECT `perfectItemType` AS "itemId", `requiredSpecialization` AS "reqSpellId", `perfectCreateChance` AS "chance" FROM skill_perfect_item_template WHERE `spellId` = ?d', $this->typeId); // Iterate through all effects: for ($i = 1; $i < 4; $i++) { if ($this->subject->getField('effect'.$i.'Id') <= 0) continue; $effId = $this->subject->getField('effect'.$i.'Id'); $effMV = $this->subject->getField('effect'.$i.'MiscValue'); $effMVB = $this->subject->getField('effect'.$i.'MiscValueB'); $effBP = $this->subject->getField('effect'.$i.'BasePoints'); $effDS = $this->subject->getField('effect'.$i.'DieSides'); $effRPPL = $this->subject->getField('effect'.$i.'RealPointsPerLevel'); $effPPCP = $this->subject->getField('effect'.$i.'PointsPerComboPoint'); $effAura = $this->subject->getField('effect'.$i.'AuraId'); /* Effect Format * * EffectName<: AuraName>< (effMV)>< [effMVB]> * Value: A<, plus y per level> * Radius: Byd * Interval: Cms * Mechanic: D * Proc Chance: E% || (E procs per minute) * Cooldown: Fs * < formated markup > * < iconlist > * < icon > */ $_nameEffect = $_nameAura = $_nameMV = $_nameMVB = $_markup = ''; $_icon = $_perfItem = $_footer = []; $_footer['value'] = [0, 0]; $valueFmt = '%s'; // Icons: // .. from item if (in_array($i, $itemIdx)) { if ($itemId = $this->subject->getField('effect'.$i.'CreateItemId')) { $itemEntry = $this->subject->relItems->getEntry($itemId); $_icon = new IconElement( Type::ITEM, $itemId, $itemEntry ? $this->subject->relItems->getField('name', true) : Util::ucFirst(Lang::game('item')).' #'.$itemId, ($effBP + 1) . ($effDS > 1 ? '-' . ($effBP + $effDS) : ''), quality: $itemEntry ? $this->subject->relItems->getField('quality') : '', link: !empty($itemEntry) ); } // perfect Items if ($perfItem && $this->subject->relItems->getEntry($perfItem['itemId'])) { $cndSpell = new SpellList(array(['id', $perfItem['reqSpellId']])); if (!$cndSpell->error) { $_perfItem = array( 'spellId' => $perfItem['reqSpellId'], 'spellName' => $cndSpell->getField('name', true), 'icon' => $cndSpell->getField('iconString'), 'chance' => $perfItem['chance'], 'item' => new IconElement( Type::ITEM, $perfItem['itemId'], $this->subject->relItems->getField('name', true), quality: $this->subject->relItems->getField('quality') ) ); } } } // .. from spell else if (in_array($i, $spellIdx) || $effId == SPELL_EFFECT_UNLEARN_SPECIALIZATION) { if ($effId == SPELL_EFFECT_TITAN_GRIP) $triggeredSpell = $effMV; else $triggeredSpell = $this->subject->getField('effect'.$i.'TriggerSpell'); if ($triggeredSpell > 0) // Dummy Auras are probably scripted { $trig = new SpellList(array(['s.id', (int)$triggeredSpell])); $_icon = new IconElement( Type::SPELL, $triggeredSpell, $trig->error ? Util::ucFirst(Lang::game('spell')).' #'.$triggeredSpell : $trig->getField('name', true), link: !$trig->error ); $this->extendGlobalData($trig->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } } // small text under effect name and resolved links .. order of adding to _footer determines order of output. // cases where we dont want 'Value' to be displayed [min, max, staffTT] if (!in_array($i, $itemIdx) && !in_array($effAura, [SPELL_AURA_MOD_TAUNT, SPELL_AURA_MOD_STUN, SPELL_AURA_MOD_SHAPESHIFT, SPELL_AURA_MECHANIC_IMMUNITY]) && !in_array($effId, [SPELL_EFFECT_PLAY_MUSIC]) && ($effBP + $effDS) != 0) $_footer['value'] = [$effBP + ($effDS ? 1 : 0), $effBP + $effDS]; if ($this->subject->getField('effect'.$i.'RadiusMax') > 0) $_footer['radius'] = Lang::spell('_radius').$this->subject->getField('effect'.$i.'RadiusMax').' '.Lang::spell('_distUnit'); if ($this->subject->getField('effect'.$i.'Periode') > 0) $_footer['interval'] = Lang::spell('_interval').Util::formatTime($this->subject->getField('effect'.$i.'Periode')); if ($_ = $this->subject->getField('effect'.$i.'Mechanic')) $_footer['mechanic'] = Lang::game('mechanic').Lang::main('colon').Lang::game('me', $_); if (in_array($i, $this->subject->canTriggerSpell()) && $procData['chance'] && $procData['chance'] < 100) { $_footer['proc'] = $procData['chance'] < 0 ? Lang::spell('ppm', [Lang::nf(-$procData['chance'], 1)]) : Lang::spell('procChance') . $procData['chance'] . '%'; if ($procData['cooldown']) $_footer['procCD'] = Lang::game('cooldown', [Util::formatTime($procData['cooldown'], true)]); } // Effect Name if ($_ = Lang::spell('effects', $effId)) $_nameEffect = ''.$this->fmtStaffTip($_, 'EffectId: '.$effId).''; else $_nameEffect = Lang::spell('unkEffect', [$effId]); // parse masks and indizes switch ($effId) { case SPELL_EFFECT_ENERGIZE_PCT: $valueFmt = '%s%%'; case SPELL_EFFECT_POWER_DRAIN: case SPELL_EFFECT_ENERGIZE: case SPELL_EFFECT_POWER_BURN: if ($_ = Lang::spell('powerTypes', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); if ($effMV == POWER_RAGE || $effMV == POWER_RUNIC_POWER) array_walk($_footer['value'], fn(&$x) => $x /= 10); break; case SPELL_EFFECT_BIND: if ($effMV <= 0) $_nameMV = $this->fmtStaffTip(Lang::spell('currentArea'), 'MiscValue: '.$effMV); else if ($a = ZoneList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('zone')).' #'.$effMV; break; case SPELL_EFFECT_QUEST_COMPLETE: case SPELL_EFFECT_CLEAR_QUEST: case SPELL_EFFECT_QUEST_FAIL: if ($a = QuestList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('quest')).' #'.$effMV; break; case SPELL_EFFECT_SUMMON_PET: $effMVB = 67; // TC uses hardcoded summon property 67 // DO NOT BREAK ! case SPELL_EFFECT_SUMMON: if (($sp = DB::Aowow()->selectRow('SELECT `control`, `slot` FROM ?_summonproperties WHERE `id` = ?d', $effMVB))) $_nameMVB = $this->fmtStaffTip(Lang::spell('summonControl', $sp['control']).' – '.Lang::spell('summonSlot', $sp['slot']) , 'SummonProperty: '.$effMVB); // DO NOT BREAK ! case SPELL_EFFECT_SUMMON_DEMON: case SPELL_EFFECT_KILL_CREDIT: case SPELL_EFFECT_KILL_CREDIT2: if ($a = CreatureList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('npc')).' #'.$effMV; break; case SPELL_EFFECT_OPEN_LOCK: if ($effMV && ($_ = Lang::spell('lockType', $effMV))) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_EFFECT_ENCHANT_ITEM: case SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY: case SPELL_EFFECT_ENCHANT_HELD_ITEM: case SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC: if ($a = EnchantmentList::makeLink($effMV, cssClass: 'q2')) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('enchantment')).' #'.$effMV; break; case SPELL_EFFECT_DISPEL: case SPELL_EFFECT_STEAL_BENEFICIAL_BUFF: if ($effMV && ($_ = Lang::game('dt', $effMV))) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_EFFECT_LANGUAGE: if ($effMV && ($_ = Lang::game('languages', $effMV))) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_EFFECT_TRANS_DOOR: case SPELL_EFFECT_SUMMON_OBJECT_WILD: case SPELL_EFFECT_SUMMON_OBJECT_SLOT1: case SPELL_EFFECT_SUMMON_OBJECT_SLOT2: case SPELL_EFFECT_SUMMON_OBJECT_SLOT3: case SPELL_EFFECT_SUMMON_OBJECT_SLOT4: if ($a = GameobjectList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('object')).' #'.$effMV; break; case SPELL_EFFECT_ACTIVATE_OBJECT: if ($_ = Lang::gameObject('actions', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_EFFECT_APPLY_GLYPH: if ($_ = DB::Aowow()->selectCell('SELECT `spellId` FROM ?_glyphproperties WHERE `id` = ?d', $effMV)) { if ($a = SpellList::makeLink($_)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('spell')).' #'.$_; } break; case SPELL_EFFECT_SKINNING: $_ = match ($effMV) { 0 => Lang::game('ct', 1).', '.Lang::game('ct', 2), // Skinning > Beast, Dragonkin 1, 2 => Lang::game('ct', 4), // Gathering, Mining > Elemental 3 => Lang::game('ct', 9), // Dismantling > Mechanic default => '' }; if ($_) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_EFFECT_DISPEL_MECHANIC: if ($_ = Lang::game('me', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_EFFECT_SKILL_STEP: case SPELL_EFFECT_SKILL: if ($a = SkillList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('skill')).' #'.$effMV; break; case SPELL_EFFECT_ACTIVATE_RUNE: if ($_ = Lang::spell('powerRunes', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_EFFECT_PLAY_SOUND: case SPELL_EFFECT_PLAY_MUSIC: if (DB::Aowow()->selectCell('SELECT 1 FROM ?_sounds WHERE `id` = ?d', $effMV)) { $_markup = '[sound='.$effMV.']'; $effMV = 0; // prevent default display } else $_nameMV = Util::ucFirst(Lang::game('sound')).' #'.$effMV; break; case SPELL_EFFECT_REPUTATION: if ($a = FactionList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('faction')).' #'.$effMV; // apply custom reward rated if ($cuRate = DB::World()->selectCell('SELECT `spell_rate` FROM reputation_reward_rate WHERE `spell_rate` <> 1 AND `faction` = ?d', $effMV)) $_footer['value'][2] = sprintf(Util::$dfnString, Lang::faction('customRewRate'), ' ('.(($cuRate < 1 ? '-' : '+').intVal(($cuRate - 1) * $_footer['value'][0])).')'); break; case SPELL_EFFECT_SEND_TAXI: $_ = DB::Aowow()->selectRow( 'SELECT tn1.`name_loc0` AS `start_loc0`, tn1.name_loc?d AS start_loc?d, tn2.`name_loc0` AS `end_loc0`, tn2.name_loc?d AS end_loc?d FROM ?_taxipath tp JOIN ?_taxinodes tn1 ON tp.`startNodeId` = tn1.`id` JOIN ?_taxinodes tn2 ON tp.`endNodeId` = tn2.`id` WHERE tp.`id` = ?d', Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, $effMV ); if ($_) $_nameMV = $this->fmtStaffTip(''.Util::localizedString($_, 'start').''.Util::localizedString($_, 'end'), 'MiscValue: '.$effMV); break; case SPELL_EFFECT_TITAN_GRIP: $effMV = 0; // effMV is trigger spell and was handled earlier break; case SPELL_EFFECT_CAST_BUTTON: $_nameMV = $effMV; // has a valid 0 value break; // Aura case SPELL_EFFECT_APPLY_AURA: case SPELL_EFFECT_PERSISTENT_AREA_AURA: case SPELL_EFFECT_APPLY_AREA_AURA_PARTY: case SPELL_EFFECT_APPLY_AREA_AURA_RAID: case SPELL_EFFECT_APPLY_AREA_AURA_PET: case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND: case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY: case SPELL_EFFECT_APPLY_AREA_AURA_OWNER: { if ($effAura > 0 && ($_ = Lang::spell('auras', $effAura))) $_nameAura = ''.$this->fmtStaffTip($_, 'AuraId: '.$effAura).''; else if ($effAura > 0) $_nameAura = Lang::spell('unkAura', [$effAura]); switch ($effAura) { case SPELL_AURA_MOD_STEALTH_DETECT: if ($_ = Lang::spell('stealthType', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MOD_INVISIBILITY: case SPELL_AURA_MOD_INVISIBILITY_DETECT: if ($_ = Lang::spell('invisibilityType', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MOD_POWER_REGEN_PERCENT: case SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT: case SPELL_AURA_OBS_MOD_POWER: $valueFmt = '%s%%'; case SPELL_AURA_PERIODIC_ENERGIZE: case SPELL_AURA_MOD_INCREASE_ENERGY: case SPELL_AURA_MOD_POWER_REGEN: if ($_ = Lang::spell('powerTypes', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); if ($effMV == POWER_RAGE || $effMV == POWER_RUNIC_POWER) array_walk($_footer['value'], fn(&$x) => $x /= 10); break; case SPELL_AURA_MOD_PERCENT_STAT: case SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE: case SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT: case SPELL_AURA_MOD_RANGED_ATTACK_POWER_OF_STAT_PERCENT: case SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT: case SPELL_AURA_MOD_MANA_REGEN_FROM_STAT: $valueFmt = '%s%%'; case SPELL_AURA_MOD_STAT: if ($effMV < 0) $_nameMV = $this->fmtStaffTip(Lang::main('all'), 'MiscValue: '.$effMV); else if ($_ = Lang::game('stats', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MOD_SHAPESHIFT: if ($_ = Lang::game('st', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_EFFECT_IMMUNITY: if ($_ = Lang::spell('effects', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_STATE_IMMUNITY: if ($_ = Lang::spell('auras', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MOD_DEBUFF_RESISTANCE: case SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL: case SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL_NOT_STACK: $valueFmt = '%s%%'; case SPELL_AURA_DISPEL_IMMUNITY: if ($_ = Lang::game('dt', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_TRACK_CREATURES: if ($_ = Lang::game('ct', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_TRACK_RESOURCES: if ($_ = Lang::spell('lockType', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MOD_LANGUAGE: if ($_ = Lang::game('languages', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT: case SPELL_AURA_MOD_MECHANIC_RESISTANCE: case SPELL_AURA_MECHANIC_DURATION_MOD: case SPELL_AURA_MECHANIC_DURATION_MOD_NOT_STACK: case SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC: $valueFmt = '%s%%'; case SPELL_AURA_MECHANIC_IMMUNITY: if ($_ = Lang::game('me', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MECHANIC_IMMUNITY_MASK: $_ = []; foreach (Lang::game('me') as $k => $str) if ($k && ($effMV & (1 << $k - 1))) $_[] = $str; if ($_ = implode(', ', $_)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.Util::asHex($effMV)); break; case SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT: case SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT: if ($_ = Lang::game('stats', $effMVB)) $_nameMVB = $this->fmtStaffTip($_, 'MiscValueB: '.$effMVB); // ! DO NOT BREAK ! case SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT: case SPELL_AURA_SPLIT_DAMAGE_PCT: case SPELL_AURA_MOD_RESISTANCE_PCT: case SPELL_AURA_MOD_HEALING_PCT: case SPELL_AURA_MOD_BASE_RESISTANCE_PCT: case SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT: case SPELL_AURA_MOD_HOT_PCT: case SPELL_AURA_SHARE_DAMAGE_PCT: case SPELL_AURA_MOD_THREAT: case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE: case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN: case SPELL_AURA_MOD_HEALING_DONE_PERCENT: case SPELL_AURA_MOD_WEAPON_CRIT_PERCENT: case SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT: case SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL: case SPELL_AURA_REFLECT_SPELLS_SCHOOL: case SPELL_AURA_REDUCE_PUSHBACK: case SPELL_AURA_MOD_CRIT_DAMAGE_BONUS: case SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE: case SPELL_AURA_MOD_TARGET_ABSORB_SCHOOL: case SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL: case SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE: case SPELL_AURA_MOD_DAMAGE_FROM_CASTER: case SPELL_AURA_MOD_CREATURE_AOE_DAMAGE_AVOIDANCE: case SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER: case SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER: case SPELL_AURA_MOD_SPELL_DAMAGE_FROM_HEALING: // ? Mod Spell & Healing Power by % of Int $valueFmt = '%s%%'; case SPELL_AURA_SCHOOL_ABSORB: case SPELL_AURA_MOD_DAMAGE_DONE: case SPELL_AURA_MOD_DAMAGE_TAKEN: case SPELL_AURA_MOD_RESISTANCE: case SPELL_AURA_SCHOOL_IMMUNITY: case SPELL_AURA_DAMAGE_IMMUNITY: case SPELL_AURA_MOD_POWER_COST_SCHOOL: case SPELL_AURA_MOD_BASE_RESISTANCE: case SPELL_AURA_MANA_SHIELD: case SPELL_AURA_MOD_HEALING: case SPELL_AURA_MOD_TARGET_RESISTANCE: case SPELL_AURA_MOD_HEALING_DONE: case SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE: case SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL: // ? Cancel Aura Buffer at % of Caster Health case SPELL_AURA_MOD_IGNORE_TARGET_RESIST: case SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR: // ? Mod Attack Power by School Resistance case SPELL_AURA_SCHOOL_HEAL_ABSORB: if ($_ = Lang::getMagicSchools($effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.Util::asHex($effMV)); break; case SPELL_AURA_MOD_SKILL: case SPELL_AURA_MOD_SKILL_TALENT: if ($a = SkillList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('skill')).' #'.$effMV; break; case SPELL_AURA_ADD_PCT_MODIFIER: $valueFmt = '%s%%'; case SPELL_AURA_ADD_FLAT_MODIFIER: if ($_ = Lang::spell('spellModOp', $effMV)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV); break; case SPELL_AURA_MOD_RATING_FROM_STAT: $valueFmt = '%s%%'; if ($_ = Lang::game('stats', $effMVB)) $_nameMVB = $this->fmtStaffTip($_, 'MiscValueB: '.$effMVB); // DO NOT BREAK ! case SPELL_AURA_MOD_RATING: foreach (Lang::spell('combatRatingMask') as $m => $str) { if ($effMV != $m) continue; $_nameMV = $this->fmtStaffTip($str, 'MiscValue: '.Util::asHex($effMV)); break 2; } $_ = []; foreach (Lang::spell('combatRating') as $k => $str) if ((1 << $k) & $effMV) $_[] = $str; if ($_ = implode(', ', $_)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.Util::asHex($effMV)); break; case SPELL_AURA_MOD_DAMAGE_DONE_VERSUS: $valueFmt = '%s%%'; case SPELL_AURA_MOD_DAMAGE_DONE_CREATURE: case SPELL_AURA_MOD_MELEE_ATTACK_POWER_VERSUS: case SPELL_AURA_MOD_RANGED_ATTACK_POWER_VERSUS: case SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS: $_ = []; foreach (Lang::game('ct') as $k => $str) if ($k && ($effMV & (1 << $k - 1))) $_[] = $str; if ($_ = implode(', ', $_)) $_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.Util::asHex($effMV)); break; case SPELL_AURA_CONVERT_RUNE: $from = $effMV; if ($_ = Lang::spell('powerRunes', $effMV)) $from = $_; $to = $effMVB; if ($_ = Lang::spell('powerRunes', $effMVB)) $to = $_; $effMVB = 0; // prevent default display $_nameMV = $this->fmtStaffTip(''.$from.'', 'MiscValue: '.$effMV).$this->fmtStaffTip($to, 'MiscValueB: '.$effMVB); break; case SPELL_AURA_MOUNTED: case SPELL_AURA_TRANSFORM: case SPELL_AURA_CHANGE_MODEL_FOR_ALL_HUMANOIDS: case SPELL_AURA_X_RAY: case SPELL_AURA_MOD_FAKE_INEBRIATE: if ($effMV && $a = CreatureList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('npc')).' #'.$effMV; break; case SPELL_AURA_FORCE_REACTION: $_footer['value'][1] = $this->fmtStaffTip(Lang::game('rep', $_footer['value'][1]), $_footer['value'][1]); $_footer['value'][0] = null; // disable range here as the string replacement will fail the comparison at the end // DO NOT BREAK ! case SPELL_AURA_MOD_FACTION_REPUTATION_GAIN: if ($effAura == SPELL_AURA_MOD_FACTION_REPUTATION_GAIN) $valueFmt = '%s%%'; if ($a = FactionList::makeLink($effMV)) $_nameMV = $a; else $_nameMV = Util::ucFirst(Lang::game('faction')).' #'.$effMV; break; // also breaks for SPELL_AURA_FORCE_REACTION case SPELL_AURA_OVERRIDE_SPELLS: if ($so = DB::Aowow()->selectRow('SELECT `spellId1`, `spellId2`, `spellId3`, `spellId4`, `spellId5` FROM ?_spelloverride WHERE `id` = ?d', $effMV)) { if ($so = array_filter($so)) { $this->extendGlobalData([Type::SPELL => $so]); $_markup = '[spell='.implode('], [spell=', $so).']'; $effMV = 0; // prevent default display } } break; case SPELL_AURA_MOD_COMBAT_RESULT_CHANCE: $valueFmt = '%s%%'; case SPELL_AURA_IGNORE_COMBAT_RESULT: $what = match ($effMV) { 2 => Lang::spell('combatRating', 2), // Dodged 3 => Lang::spell('combatRating', 4), // Blocked 4 => Lang::spell('combatRating', 3), // Parried default => '' // Evaded(0) Missed(1) Glanced(5) Crited'ed..ed(6) Crushed(7) Regular(8) }; if ($what) $_nameMV = $this->fmtStaffTip($what, 'MiscValue: '.$effMV); else trigger_error('unused case #'.$effMV.' found for aura #'.$effAura); break; case SPELL_AURA_SCREEN_EFFECT: if ($ses = DB::Aowow()->selectRow('SELECT `name`, `ambienceDay` AS "0", IF(`ambienceNight` <> `ambienceDay`, `ambienceNight`, 0) AS "1", `musicDay` AS "2", IF(`musicNight` <> `musicDay`, `musicNight`, 0) AS "3" FROM ?_screeneffect_sounds WHERE `id` = ?d', $effMV)) { $_nameMV = $this->fmtStaffTip($ses['name'], 'MiscValue: '.$effMV); for ($j = 0; $j < 4; $j++) if ($ses[$j]) $_markup .= '[sound='.$ses[$j].']'; } break; case SPELL_AURA_MOD_HEALTH_REGEN_PERCENT: case SPELL_AURA_OBS_MOD_HEALTH: case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT: case SPELL_AURA_MOD_ARMOR_PENETRATION_PCT: case SPELL_AURA_MOD_SCALE: case SPELL_AURA_MOD_SCALE_2: case SPELL_AURA_MOD_SPEED_ALWAYS: case SPELL_AURA_MOD_SPEED_SLOW_ALL: case SPELL_AURA_MOD_SPEED_NOT_STACK: case SPELL_AURA_MOD_INCREASE_SPEED: case SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED: case SPELL_AURA_MOD_DECREASE_SPEED: case SPELL_AURA_MOD_INCREASE_SWIM_SPEED: case SPELL_AURA_MOD_PARRY_PERCENT: case SPELL_AURA_MOD_DODGE_PERCENT: case SPELL_AURA_MOD_BLOCK_PERCENT: case SPELL_AURA_MOD_BLOCK_CRIT_CHANCE: case SPELL_AURA_MOD_HIT_CHANCE: case SPELL_AURA_MOD_CRIT_PCT: case SPELL_AURA_MOD_SPELL_HIT_CHANCE: case SPELL_AURA_MOD_SPELL_CRIT_CHANCE: case SPELL_AURA_MOD_MELEE_RANGED_HASTE: case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: $valueFmt = '%s%%'; break; } break; } case SPELL_EFFECT_RESURRECT: case SPELL_EFFECT_SPIRIT_HEAL: case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE: case SPELL_EFFECT_DURABILITY_DAMAGE_PCT: case SPELL_EFFECT_MODIFY_THREAT_PERCENT: case SPELL_EFFECT_REDIRECT_THREAT: case SPELL_EFFECT_HEAL_PCT: $valueFmt = '%s%%'; break; } if ($_footer['value'][1]) { $buffer = Lang::spell('_value').Lang::main('colon').sprintf($valueFmt, $_footer['value'][0]); if ($_footer['value'][0] != $_footer['value'][1]) $buffer .= Lang::game('valueDelim').sprintf($valueFmt, $_footer['value'][1]); if ($effRPPL != 0) $buffer .= Lang::spell('costPerLevel', [sprintf($valueFmt, $effRPPL)]); if ($effPPCP != 0) $buffer .= Lang::spell('pointsPerCP', [sprintf($valueFmt, $effPPCP)]); if (isset($_footer['value'][2])) $buffer .= $_footer['value'][2]; $_footer['value'] = $buffer; } else unset($_footer['value']); $_header = $_nameEffect; if ($_nameAura) $_header .= Lang::main('colon').$_nameAura; if (strlen($_nameMV)) $_header .= ' ('.$_nameMV.')'; else if ($effMV) $_header .= ' ('.$effMV.')'; if (strlen($_nameMVB)) $_header .= ' ['.$_nameMVB.']'; else if ($effMVB) $_header .= ' ['.$effMVB.']'; $effects[$i] = array( 'icon' => $_icon, 'perfectItem' => $_perfItem, 'name' => $_header, 'footer' => $_footer, 'markup' => $_markup, 'modifies' => [] // may be set later ); } $this->effects = $effects; } private function createAttributesList() : void { $list = []; for ($i = 0; $i < 8; $i++) { $attributes = $this->subject->getField('attributes'.$i); for ($j = 1; $j <= (1 << 31); $j <<= 1) { if (!($attributes & $j)) continue; $listItem = Lang::spell('attributes'.$i, $j); if (!$listItem && User::isInGroup(U_GROUP_STAFF)) $listItem = 'Unknown SpellAttribute'.$i.''; else if (!$listItem) continue; if ($crId = (SpellListFilter::$attributesFilter[$i][$j] ?? 0)) $listItem = sprintf('%1$s', $listItem, abs($crId), $crId > 0 ? 1 : 2); $list[] = $this->fmtStaffTip($listItem, 'Attributes'.$i.': '.Util::asHex($j)); } } $this->attributes = $list; } private function generatePath() { $cat = $this->subject->getField('typeCat'); $cf = $this->subject->getField('cuFlags'); $this->breadcrumb[] = $cat; // reconstruct path switch ($cat) { case -2: case 7: case -13: if ($cl = $this->subject->getField('reqClassMask')) $this->breadcrumb[] = ChrClass::fromMask($cl)[0]; else if ($sf = $this->subject->getField('spellFamilyId')) foreach (ChrClass::cases() as $cl) if ($cl->spellFamily() == $sf) { $this->breadcrumb[] = $cl->value; break; } if ($cat == -13) $this->breadcrumb[] = ($cf & (SPELL_CU_GLYPH_MAJOR | SPELL_CU_GLYPH_MINOR)) >> 6; else $this->breadcrumb[] = $this->subject->getField('skillLines')[0]; break; case 9: case -3: case 11: $this->breadcrumb[] = $this->subject->getField('skillLines')[0]; if ($cat == 11) if ($_ = $this->subject->getField('reqSpellId')) $this->breadcrumb[] = $_; break; case -11: foreach (SpellList::$skillLines as $line => $skills) if (in_array($this->subject->getField('skillLines')[0], $skills)) $this->breadcrumb[] = $line; break; case -7: // only spells unique in skillLineAbility will always point to the right skillLine :/ if ($cf & SPELL_CU_PET_TALENT_TYPE0) $this->breadcrumb[] = 411; // Ferocity else if ($cf & SPELL_CU_PET_TALENT_TYPE1) $this->breadcrumb[] = 409; // Tenacity else if ($cf & SPELL_CU_PET_TALENT_TYPE2) $this->breadcrumb[] = 410; // Cunning break; case -5: if ($this->subject->getField('effect2AuraId') == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED || $this->subject->getField('effect3AuraId') == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) $this->breadcrumb[] = 2; // flying (also contains SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED, so checked first) else if ($this->subject->getField('effect2AuraId') == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED || $this->subject->getField('effect3AuraId') == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) $this->breadcrumb[] = 1; // ground else $this->breadcrumb[] = 3; // misc } } private function createInfobox() : void { $infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags')); $typeCat = $this->subject->getField('typeCat'); // level if (!in_array($typeCat, [-5, -6])) // not mount or vanity pet { if ($_ = $this->subject->getField('talentLevel')) $infobox[] = (in_array($typeCat, [-2, 7, -13]) ? Lang::game('reqLevel', [$_]) : Lang::game('level').Lang::main('colon').$_); else if ($_ = $this->subject->getField('spellLevel')) $infobox[] = (in_array($typeCat, [-2, 7, -13]) ? Lang::game('reqLevel', [$_]) : Lang::game('level').Lang::main('colon').$_); } $jsg = []; // races if ($_ = Lang::getRaceString($this->subject->getField('reqRaceMask'), $jsg, Lang::FMT_MARKUP)) { $this->extendGlobalIds(Type::CHR_RACE, ...$jsg); $t = count($jsg) == 1 ? Lang::game('race') : Lang::game('races'); $infobox[] = Util::ucFirst($t).Lang::main('colon').$_; } // classes if ($_ = Lang::getClassString($this->subject->getField('reqClassMask'), $jsg, Lang::FMT_MARKUP)) { $this->extendGlobalIds(Type::CHR_CLASS, ...$jsg); $t = count($jsg) == 1 ? Lang::game('class') : Lang::game('classes'); $infobox[] = Util::ucFirst($t).Lang::main('colon').$_; } // spell focus if ($_ = $this->subject->getField('spellFocusObject')) { if ($sfObj = DB::Aowow()->selectRow('SELECT * FROM ?_spellfocusobject WHERE `id` = ?d', $_)) { $n = Util::localizedString($sfObj, 'name'); if ($objId = DB::Aowow()->selectCell('SELECT `id` FROM ?_objects WHERE `spellFocusId` = ?d', $_)) $n = '[url=?object='.$objId.']'.$n.'[/url]'; $infobox[] = Lang::game('requires2').' '.$n; } } // primary & secondary trades if (in_array($typeCat, [9, 11])) { // skill if ($_ = $this->subject->getField('skillLines')[0]) { $this->extendGlobalIds(Type::SKILL, $_); $bar = Lang::game('requires', [' [skill='.$_.']']); if ($_ = $this->subject->getField('learnedAt')) $bar .= ' ('.$_.')'; $infobox[] = $bar; } // specialization if ($_ = $this->subject->getField('reqSpellId')) { $this->extendGlobalIds(Type::SPELL, $_); $infobox[] = Lang::game('requires2').' [spell='.$_.']'; } // difficulty if ($_ = $this->subject->getColorsForCurrent()) $infobox[] = Lang::formatSkillBreakpoints($_); } // accquisition.. 10: starter spell; 7: discovery if ($this->subject->getSources($s)) { if (in_array(SRC_STARTER, $s)) $infobox[] = Lang::spell('starter'); else if (in_array(SRC_DISCOVERY, $s)) $infobox[] = Lang::spell('discovered'); } // training cost if ($cost = $this->subject->getField('trainingCost')) $infobox[] = Lang::spell('trainingCost').'[money='.$cost.']'; // icon if ($_ = $this->subject->getField('iconId')) { $this->extendGlobalIds(Type::ICON, $_); $infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]'; } // used in mode foreach ($this->difficulties as $n => $id) if ($id == $this->typeId) $infobox[] = Lang::game('mode').Lang::game('modes', $n); // Creature Type from Aura: Shapeshift foreach ($this->modelInfo as $mI) { if (!isset($mI['creatureType'])) continue; if ($mI['creatureType'] > 0) $infobox[] = Lang::game('type').Lang::game('ct', $mI['creatureType']); break; } // spell script if (User::isInGroup(U_GROUP_STAFF)) if ($_ = DB::World()->selectCell('SELECT `ScriptName` FROM spell_script_names WHERE ABS(`spell_id`) = ?d', $this->firstRank)) $infobox[] = 'Script'.Lang::main('colon').$_; $this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0'); // append glyph symbol if available $glyphId = 0; for ($i = 1; $i < 4; $i++) if ($this->subject->getField('effect'.$i.'Id') == SPELL_EFFECT_APPLY_GLYPH) $glyphId = $this->subject->getField('effect'.$i.'MiscValue'); if ($_ = DB::Aowow()->selectCell('SELECT ic.`name` FROM ?_glyphproperties gp JOIN ?_icons ic ON gp.`iconId` = ic.`id` WHERE gp.`spellId` = ?d { OR gp.`id` = ?d }', $this->typeId, $glyphId ?: DBSIMPLE_SKIP)) if (file_exists('static/images/wow/Interface/Spellbook/'.$_.'.png')) $this->infobox->append('[img src='.Cfg::get('STATIC_URL').'/images/wow/Interface/Spellbook/'.$_.'.png border=0 float=center margin=15]'); } } ?>