From 77f81c1bdef6e75450e8a2fdffc31ce9f7e35a51 Mon Sep 17 00:00:00 2001 From: Sarjuuk Date: Sat, 20 May 2023 03:18:36 +0200 Subject: [PATCH] Setup/Sources * rewrote SetupSrcipt * implemented 'zone' parameter * implemented 'bossdrop' parameter * implemented 'dungeondifficulty' parameter * implemented item filter relying on zone information (dropsInX) * fixed world random drops showing a single loot source * extended Source column of spells to the same functionality as items ToDo: * apply new 'commondrop' parameter on loot listviews * gather difficuly versions of gameobjects and apply the same logic as for creatures * implement fake spawns so npcs can get linked to a zone --- includes/basetype.class.php | 53 + includes/defines.php | 43 +- includes/types/creature.class.php | 5 +- includes/types/gameobject.class.php | 2 - includes/types/item.class.php | 136 ++- includes/types/spell.class.php | 15 +- includes/types/title.class.php | 46 +- includes/utilities.php | 31 + localization/lang.class.php | 2 +- localization/locale_dede.php | 4 +- localization/locale_enus.php | 4 +- localization/locale_eses.php | 4 +- localization/locale_frfr.php | 4 +- localization/locale_ruru.php | 4 +- localization/locale_zhcn.php | 4 +- setup/db_structure.sql | 3 +- setup/tools/sqlgen/source.func.php | 1585 +++++++++++++-------------- setup/updates/1684620409_01.sql | 2 + setup/updates/1684620409_02.sql | 1 + static/js/global.js | 109 +- 20 files changed, 1143 insertions(+), 914 deletions(-) create mode 100644 setup/updates/1684620409_01.sql create mode 100644 setup/updates/1684620409_02.sql diff --git a/includes/basetype.class.php b/includes/basetype.class.php index ceb0b281..33000704 100644 --- a/includes/basetype.class.php +++ b/includes/basetype.class.php @@ -842,6 +842,59 @@ trait profilerHelper } } +trait sourceHelper +{ + protected $sources = []; + protected $sourceMore = null; + + public function getSources(?array &$s, ?array &$sm) : bool + { + $s = $sm = null; + if (empty($this->sources[$this->id])) + return false; + + if ($this->sourceMore === null) + { + $buff = []; + $this->sourceMore = []; + + foreach ($this->iterate() as $_curTpl) + if ($_curTpl['moreType'] && $_curTpl['moreTypeId']) + $buff[$_curTpl['moreType']][] = $_curTpl['moreTypeId']; + + foreach ($buff as $type => $ids) + $this->sourceMore[$type] = (Type::newList($type, [CFG_SQL_LIMIT_NONE, ['id', $ids]]))->getSourceData(); + } + + $s = array_keys($this->sources[$this->id]); + if ($this->curTpl['moreType'] && $this->curTpl['moreTypeId'] && !empty($this->sourceMore[$this->curTpl['moreType']][$this->curTpl['moreTypeId']])) + $sm = $this->sourceMore[$this->curTpl['moreType']][$this->curTpl['moreTypeId']]; + else if (!empty($this->sources[$this->id][SRC_PVP])) + $sm['p'] = $this->sources[$this->id][SRC_PVP][0]; + + if ($z = $this->curTpl['moreZoneId']) + $sm['z'] = $z; + + if ($this->curTpl['moreMask'] & SRC_FLAG_BOSSDROP) + $sm['bd'] = 1; + + if (isset($this->sources[$this->id][SRC_DROP][0])) + { + $dd = $this->sources[$this->id][SRC_DROP][0]; + if ($this->curTpl['moreMask'] & SRC_FLAG_RAID_DROP) + $sm['dd'] = (1 << ($dd - 1)); + else if ($this->curTpl['moreMask'] & SRC_FLAG_DUNGEON_DROP) + $sm['dd'] = (1 << ($dd - 1)) * -1; + } + + if ($sm) + $sm = [$sm]; + + return true; + } +} + + abstract class Filter { private static $wCards = ['*' => '%', '?' => '_']; diff --git a/includes/defines.php b/includes/defines.php index bf9a26f1..6027bd4f 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -1127,18 +1127,22 @@ define('SPELL_ATTR7_CLIENT_INDICATOR', 0x80000000); // Client in // (some) Skill ids +define('SKILL_FIRST_AID', 129); define('SKILL_BLACKSMITHING', 164); define('SKILL_LEATHERWORKING', 165); define('SKILL_ALCHEMY', 171); define('SKILL_HERBALISM', 182); +define('SKILL_COOKING', 185); define('SKILL_MINING', 186); define('SKILL_TAILORING', 197); define('SKILL_ENGINEERING', 202); define('SKILL_ENCHANTING', 333); +define('SKILL_FISHING', 356); define('SKILL_SKINNING', 393); -define('SKILL_JEWELCRAFTING', 755); -define('SKILL_INSCRIPTION', 773); define('SKILL_LOCKPICKING', 633); +define('SKILL_JEWELCRAFTING', 755); +define('SKILL_RIDING', 762); +define('SKILL_INSCRIPTION', 773); // AchievementCriteriaCondition @@ -1671,4 +1675,39 @@ define('AT_TYPE_OBJECTIVE', 3); define('AT_TYPE_SMART', 4); define('AT_TYPE_SCRIPT', 5); +// Drop Sources +define('SRC_CRAFTED', 1); +define('SRC_DROP', 2); +define('SRC_PVP', 3); +define('SRC_QUEST', 4); +define('SRC_VENDOR', 5); +define('SRC_TRAINER', 6); +define('SRC_DISCOVERY', 7); +define('SRC_REDEMPTION', 8); // unused +define('SRC_TALENT', 9); +define('SRC_STARTER', 10); +define('SRC_EVENT', 11); // unused +define('SRC_ACHIEVEMENT', 12); +define('SRC_CUSTOM_STRING', 13); +// define('SRC_BLACK_MARKET', 14); // not in 3.3.5 +define('SRC_DISENCHANTMENT', 15); +define('SRC_FISHING', 16); +define('SRC_GATHERING', 17); +define('SRC_MILLING', 18); +define('SRC_MINING', 19); +define('SRC_PROSPECTING', 20); +define('SRC_PICKPOCKETING', 21); +define('SRC_SALVAGING', 22); +define('SRC_SKINNING', 23); +// define('SRC_INGAME_STORE', 24); // not in 3.3.5 + +define('SRC_SUB_PVP_ARENA', 1); +define('SRC_SUB_PVP_BG', 2); +define('SRC_SUB_PVP_WORLD', 4); + +define('SRC_FLAG_BOSSDROP', 0x01); +define('SRC_FLAG_COMMON', 0x02); +define('SRC_FLAG_DUNGEON_DROP', 0x10); +define('SRC_FLAG_RAID_DROP', 0x20); + ?> diff --git a/includes/types/creature.class.php b/includes/types/creature.class.php index 673ed8e1..9b3d3406 100644 --- a/includes/types/creature.class.php +++ b/includes/types/creature.class.php @@ -266,10 +266,7 @@ class CreatureList extends BaseType $data[$this->id] = array( 'n' => $this->getField('parentId') ? $this->getField('parent', true) : $this->getField('name', true), 't' => Type::NPC, - 'ti' => $this->getField('parentId') ?: $this->id, - // 'bd' => (int)($this->curTpl['cuFlags'] & NPC_CU_INSTANCE_BOSS || ($this->curTpl['typeFlags'] & 0x4 && $this->curTpl['rank'])) - // 'z' where am i spawned - // 'dd' DungeonDifficulty requires 'z' + 'ti' => $this->getField('parentId') ?: $this->id ); } diff --git a/includes/types/gameobject.class.php b/includes/types/gameobject.class.php index f835b723..cffc44d8 100644 --- a/includes/types/gameobject.class.php +++ b/includes/types/gameobject.class.php @@ -130,8 +130,6 @@ class GameObjectList extends BaseType 'n' => $this->getField('name', true), 't' => Type::OBJECT, 'ti' => $this->id - // 'bd' => bossdrop - // 'dd' => dungeondifficulty ); } diff --git a/includes/types/item.class.php b/includes/types/item.class.php index 9d3f3a89..697156a9 100644 --- a/includes/types/item.class.php +++ b/includes/types/item.class.php @@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION')) class ItemList extends BaseType { - use ListviewHelper; + use ListviewHelper, sourceHelper; public static $type = Type::ITEM; public static $brickFile = 'item'; @@ -14,12 +14,10 @@ class ItemList extends BaseType public $json = []; public $itemMods = []; - public $sources = []; public $rndEnchIds = []; public $subItems = []; - private $sourceMore = null; private $ssd = []; private $vendors = []; private $jsGlobals = []; // getExtendedCost creates some and has no access to template @@ -31,7 +29,7 @@ class ItemList extends BaseType 'is' => ['j' => ['?_item_stats `is` ON `is`.`type` = 3 AND `is`.`typeId` = `i`.`id`', true], 's' => ', `is`.*'], 's' => ['j' => ['?_spell `s` ON `s`.`effect1CreateItemId` = `i`.`id`', true], 'g' => 'i.id'], 'e' => ['j' => ['?_events `e` ON `e`.`id` = `i`.`eventId`', true], 's' => ', e.holidayId'], - 'src' => ['j' => ['?_source `src` ON `src`.`type` = 3 AND `src`.`typeId` = `i`.`id`', true], 's' => ', moreType, moreTypeId, src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24'] + 'src' => ['j' => ['?_source `src` ON `src`.`type` = 3 AND `src`.`typeId` = `i`.`id`', true], 's' => ', moreType, moreTypeId, moreZoneId, moreMask, src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24'] ); public function __construct($conditions = [], $miscData = null) @@ -1424,34 +1422,6 @@ class ItemList extends BaseType return round(($dps - 54.8) * 14, 0); } - public function getSources(&$s, &$sm) - { - $s = $sm = null; - if (empty($this->sources[$this->id])) - return false; - - if ($this->sourceMore === null) - { - $buff = []; - $this->sourceMore = []; - - foreach ($this->iterate() as $_curTpl) - if ($_curTpl['moreType'] && $_curTpl['moreTypeId']) - $buff[$_curTpl['moreType']][] = $_curTpl['moreTypeId']; - - foreach ($buff as $type => $ids) - $this->sourceMore[$type] = (Type::newList($type, [CFG_SQL_LIMIT_NONE, ['id', $ids]]))?->getSourceData(); - } - - $s = array_keys($this->sources[$this->id]); - if ($this->curTpl['moreType'] && $this->curTpl['moreTypeId'] && !empty($this->sourceMore[$this->curTpl['moreType']][$this->curTpl['moreTypeId']])) - $sm = [$this->sourceMore[$this->curTpl['moreType']][$this->curTpl['moreTypeId']]]; - else if (!empty($this->sources[$this->id][3])) - $sm = [['p' => $this->sources[$this->id][3][0]]]; - - return true; - } - private function parseRating($type, $value, $interactive = false, &$scaling = false) { // clamp level range @@ -1770,8 +1740,15 @@ class ItemListFilter extends Filter public $extraOpts = []; // score for statWeights public $wtCnd = []; protected $enums = array( - 99 => array( // profession | recycled for 86, 87 - null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773 + 16 => array( // drops in zone + 4494, 36, 2597, 3358, 45, 331, 3790, 4277, 16, 3524, 3, 3959, 719, 1584, 25, 1583, 2677, 3702, 3522, 4, 3525, 3537, 46, 1941, + 2918, 3905, 4024, 2817, 4395, 4378, 148, 393, 1657, 41, 2257, 405, 2557, 65, 4196, 1, 14, 10, 15, 139, 12, 3430, 3820, 361, + 357, 3433, 721, 394, 3923, 4416, 2917, 4272, 4820, 4264, 3483, 3562, 267, 495, 4742, 3606, 210, 4812, 1537, 4710, 4080, 3457, 38, 4131, + 3836, 3792, 2100, 2717, 493, 215, 3518, 3698, 3456, 3523, 2367, 2159, 1637, 4813, 4298, 2437, 722, 491, 44, 3429, 3968, 796, 2057, 51, + 3607, 3791, 3789, 209, 3520, 3703, 3711, 1377, 3487, 130, 3679, 406, 1519, 4384, 33, 2017, 1477, 4075, 8, 440, 141, 3428, 3519, 3848, + 17, 2366, 3840, 3713, 3847, 3775, 4100, 1581, 3557, 3845, 4500, 4809, 47, 3849, 4265, 4493, 4228, 3698, 4406, 3714, 3717, 3715, 717, 67, + 3716, 457, 4415, 400, 1638, 1216, 85, 4723, 4722, 1337, 4273, 490, 1497, 206, 1196, 4603, 718, 3277, 28, 40, 11, 4197, 618, 3521, + 3805, 66, 1176, 1977 ), 66 => array( // profession specialization 1 => -1, @@ -1790,15 +1767,16 @@ class ItemListFilter extends Filter 14 => -1, 15 => -1 ), - 152 => array( // class-specific - null, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, 11, true, false + 99 => array( // profession | recycled for 86, 87 + null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773 ), - 153 => array( // race-specific - null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false + 105 => array( // drops in nh dungeon + 4494, 3790, 4277, 4196, 4416, 4272, 4820, 4264, 3562, 4131, 3792, 2367, 4813, 3791, 3789, 3848, 2366, 3713, 3847, 4100, + 4809, 3849, 4265, 4228, 3714, 3717, 3715, 3716, 4415, 4723, 206, 1196 ), - 158 => array( // currency - 32572, 32569, 29736, 44128, 20560, 20559, 29434, 37829, 23247, 44990, 24368, 52027, 52030, 43016, 41596, 34052, 45624, 49426, 40752, 47241, 40753, 29024, - 24245, 26045, 26044, 38425, 29735, 24579, 24581, 32897, 22484, 52026, 52029, 4291, 28558, 43228, 34664, 47242, 52025, 52028, 37836, 20558, 34597, 43589 + 106 => array( // drops in hc dungeon + 4494, 3790, 4277, 4196, 4416, 4272, 4820, 4264, 3562, 4131, 3792, 2367, 4813, 3791, 3789, 3848, 2366, 3713, 3847, 4100, + 4809, 3849, 4265, 4228, 3714, 3717, 3715, 3716, 4415, 4723, 206, 1196 ), 118 => array( // tokens 34853, 34854, 34855, 34856, 34857, 34858, 34848, 34851, 34852, 40625, 40626, 40627, 45632, 45633, 45634, 34169, 34186, 29754, 29753, 29755, 31089, 31091, 31090, @@ -1809,6 +1787,16 @@ class ItemListFilter extends Filter 34170, 34192, 29763, 29764, 29762, 31101, 31103, 31102, 30248, 30249, 30250, 47557, 47558, 47559, 34233, 34234, 34202, 34195, 34209, 40622, 40623, 40624, 34193, 45659, 45660, 45661, 34212, 34351, 34215 ), + 126 => array( // Zones + 4494, 36, 2597, 3358, 45, 331, 3790, 4277, 16, 3524, 3, 3959, 719, 1584, 25, 1583, 2677, 3702, 3522, 4, 3525, 3537, 46, 1941, + 2918, 3905, 4024, 2817, 4395, 4378, 148, 393, 1657, 41, 2257, 405, 2557, 65, 4196, 1, 14, 10, 15, 139, 12, 3430, 3820, 361, + 357, 3433, 721, 394, 3923, 4416, 2917, 4272, 4820, 4264, 3483, 3562, 267, 495, 4742, 3606, 210, 4812, 1537, 4710, 4080, 3457, 38, 4131, + 3836, 3792, 2100, 2717, 493, 215, 3518, 3698, 3456, 3523, 2367, 2159, 1637, 4813, 4298, 2437, 722, 491, 44, 3429, 3968, 796, 2057, 51, + 3607, 3791, 3789, 209, 3520, 3703, 3711, 1377, 3487, 130, 3679, 406, 1519, 4384, 33, 2017, 1477, 4075, 8, 440, 141, 3428, 3519, 3848, + 17, 2366, 3840, 3713, 3847, 3775, 4100, 1581, 3557, 3845, 4500, 4809, 47, 3849, 4265, 4493, 4228, 3698, 4406, 3714, 3717, 3715, 717, 67, + 3716, 457, 4415, 400, 1638, 1216, 85, 4723, 4722, 1337, 4273, 490, 1497, 206, 1196, 4603, 718, 3277, 28, 40, 11, 4197, 618, 3521, + 3805, 66, 1176, 1977 + ), 128 => array( // source 1 => true, // Any 2 => false, // None @@ -1821,15 +1809,27 @@ class ItemListFilter extends Filter 10 => 11, // Event 11 => 12 // Achievement ), - 126 => array( // Zones - 4494, 36, 2597, 3358, 45, 331, 3790, 4277, 16, 3524, 3, 3959, 719, 1584, 25, 1583, 2677, 3702, 3522, 4, 3525, 3537, 46, 1941, - 2918, 3905, 4024, 2817, 4395, 4378, 148, 393, 1657, 41, 2257, 405, 2557, 65, 4196, 1, 14, 10, 15, 139, 12, 3430, 3820, 361, - 357, 3433, 721, 394, 3923, 4416, 2917, 4272, 4820, 4264, 3483, 3562, 267, 495, 4742, 3606, 210, 4812, 1537, 4710, 4080, 3457, 38, 4131, - 3836, 3792, 2100, 2717, 493, 215, 3518, 3698, 3456, 3523, 2367, 2159, 1637, 4813, 4298, 2437, 722, 491, 44, 3429, 3968, 796, 2057, 51, - 3607, 3791, 3789, 209, 3520, 3703, 3711, 1377, 3487, 130, 3679, 406, 1519, 4384, 33, 2017, 1477, 4075, 8, 440, 141, 3428, 3519, 3848, - 17, 2366, 3840, 3713, 3847, 3775, 4100, 1581, 3557, 3845, 4500, 4809, 47, 3849, 4265, 4493, 4228, 3698, 4406, 3714, 3717, 3715, 717, 67, - 3716, 457, 4415, 400, 1638, 1216, 85, 4723, 4722, 1337, 4273, 490, 1497, 206, 1196, 4603, 718, 3277, 28, 40, 11, 4197, 618, 3521, - 3805, 66, 1176, 1977 + 147 => array( // drops in nh raid 10 + 4812, 3456, 2159, 4500, 4493, 4722, 4273, 4603, 4987 + ), + 148 => array( // drops in nh raid 25 + 4812, 3456, 2159, 4500, 4493, 4722, 4273, 4603, 4987 + ), + 149 => array( // drops in hc raid 10 + 4987, 4812, 4722 + ), + 150 => array( // drops in hc raid 25 + 4987, 4812, 4722 + ), + 152 => array( // class-specific + null, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, 11, true, false + ), + 153 => array( // race-specific + null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false + ), + 158 => array( // currency + 32572, 32569, 29736, 44128, 20560, 20559, 29434, 37829, 23247, 44990, 24368, 52027, 52030, 43016, 41596, 34052, 45624, 49426, 40752, 47241, 40753, 29024, + 24245, 26045, 26044, 38425, 29735, 24579, 24581, 32897, 22484, 52026, 52029, 4291, 28558, 43228, 34664, 47242, 52025, 52028, 37836, 20558, 34597, 43589 ), 163 => array( // enchantment mats 34057, 22445, 11176, 34052, 11082, 34055, 16203, 10939, 11135, 11175, 22446, 16204, 34054, 14344, 11084, 11139, 22449, 11178, @@ -1853,7 +1853,7 @@ class ItemListFilter extends Filter 13 => [FILTER_CR_BOOLEAN, 'randomEnchant' ], // randomlyenchanted 14 => [FILTER_CR_BOOLEAN, 'pageTextId' ], // readable 15 => [FILTER_CR_CALLBACK, 'cbFieldHasVal', 'maxCount', 1 ], // unique [yn] - 16 => [FILTER_CR_NYI_PH, null, 1, ], // dropsin [zone] + 16 => [FILTER_CR_CALLBACK, 'cbDropsInZone', null, null ], // dropsin [zone] 17 => [FILTER_CR_ENUM, 'requiredFaction' ], // requiresrepwith 18 => [FILTER_CR_CALLBACK, 'cbFactionQuestReward', null, null ], // rewardedbyfactionquest [side] 20 => [FILTER_CR_NUMERIC, 'is.str', NUM_CAST_INT, true ], // str @@ -1936,8 +1936,8 @@ class ItemListFilter extends Filter 102 => [FILTER_CR_NUMERIC, 'is.splhastertng', NUM_CAST_INT, true ], // splhastertng 103 => [FILTER_CR_NUMERIC, 'is.hastertng', NUM_CAST_INT, true ], // hastertng 104 => [FILTER_CR_STRING, 'description', STR_LOCALIZED ], // flavortext - 105 => [FILTER_CR_NYI_PH, null, 1, ], // dropsinnormal [heroicdungeon-any] - 106 => [FILTER_CR_NYI_PH, null, 1, ], // dropsinheroic [heroicdungeon-any] + 105 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_DUNGEON_DROP, 1 ], // dropsinnormal [heroicdungeon-any] + 106 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_DUNGEON_DROP, 2 ], // dropsinheroic [heroicdungeon-any] 107 => [FILTER_CR_NYI_PH, null, 1, ], // effecttext [str] not yet parsed ['effectsParsed_loc'.User::$localeId, $cr[2]] 109 => [FILTER_CR_CALLBACK, 'cbArmorBonus', null, null ], // armorbonus [op] [int] 111 => [FILTER_CR_NUMERIC, 'requiredSkillRank', NUM_CAST_INT, true ], // reqskillrank @@ -1970,10 +1970,10 @@ class ItemListFilter extends Filter 144 => [FILTER_CR_CALLBACK, 'cbPvpPurchasable', 'reqHonorPoints', null ], // purchasablewithhonor [yn] 145 => [FILTER_CR_CALLBACK, 'cbPvpPurchasable', 'reqHonorPoints', null ], // purchasablewitharena [yn] 146 => [FILTER_CR_FLAG, 'flags', ITEM_FLAG_HEROIC ], // heroic - 147 => [FILTER_CR_NYI_PH, null, 1, ], // dropsinnormal10 [multimoderaid-any] - 148 => [FILTER_CR_NYI_PH, null, 1, ], // dropsinnormal25 [multimoderaid-any] - 149 => [FILTER_CR_NYI_PH, null, 1, ], // dropsinheroic10 [heroicraid-any] - 150 => [FILTER_CR_NYI_PH, null, 1, ], // dropsinheroic25 [heroicraid-any] + 147 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_RAID_DROP, 1, ], // dropsinnormal10 [multimoderaid-any] + 148 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_RAID_DROP, 2, ], // dropsinnormal25 [multimoderaid-any] + 149 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_RAID_DROP, 4, ], // dropsinheroic10 [heroicraid-any] + 150 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_RAID_DROP, 8, ], // dropsinheroic25 [heroicraid-any] 151 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true ], // id 152 => [FILTER_CR_CALLBACK, 'cbClassRaceSpec', 'requiredClass', CLASS_MASK_ALL], // classspecific [enum] 153 => [FILTER_CR_CALLBACK, 'cbClassRaceSpec', 'requiredRace', RACE_MASK_ALL ], // racespecific [enum] @@ -2173,7 +2173,7 @@ class ItemListFilter extends Filter switch ($_v['si']) { case 3: - $parts[] = $notEx; + $parts[] = ['OR', [['flagsExtra', 0x3, '&'], [0, 3]], ['requiredRace', RACE_MASK_ALL], ['requiredRace', 0]]; break; case 2: $parts[] = ['AND', [['flagsExtra', 0x3, '&'], [0, 1]], ['OR', $notEx, ['requiredRace', RACE_MASK_HORDE, '&']]]; @@ -2384,6 +2384,26 @@ class ItemListFilter extends Filter return false; } + protected function cbDropsInZone($cr) + { + if (in_array($cr[1], $this->enums[$cr[0]])) + return ['AND', ['src.src2', null, '!'], ['src.moreZoneId', $cr[1]]]; + else if ($cr[1] == FILTER_ENUM_ANY) + return ['src.src2', null, '!']; // well, this seems a bit redundant.. + + return false; + } + + protected function cbDropsInInstance($cr, $moreFlag, $modeBit) + { + if (in_array($cr[1], $this->enums[$cr[0]])) + return ['AND', ['src.src2', $modeBit, '&'], ['src.moreMask', $moreFlag, '&'], ['src.moreZoneId', $cr[1]]]; + else if ($cr[1] == FILTER_ENUM_ANY) + return ['AND', ['src.src2', $modeBit, '&'], ['src.moreMask', $moreFlag, '&']]; + + return false; + } + protected function cbPurchasableWith($cr) { if (in_array($cr[1], $this->enums[$cr[0]])) diff --git a/includes/types/spell.class.php b/includes/types/spell.class.php index 993fe787..aa52761a 100644 --- a/includes/types/spell.class.php +++ b/includes/types/spell.class.php @@ -6,11 +6,10 @@ if (!defined('AOWOW_REVISION')) class SpellList extends BaseType { - use listviewHelper; + use listviewHelper, sourceHelper; public $ranks = []; public $relItems = null; - public $sources = []; public static $type = Type::SPELL; public static $brickFile = 'spell'; @@ -60,7 +59,7 @@ class SpellList extends BaseType 'ic' => ['j' => ['?_icons ic ON ic.id = s.iconId', true], 's' => ', ic.name AS iconString'], 'ica' => ['j' => ['?_icons ica ON ica.id = s.iconIdAlt', true], 's' => ', ica.name AS iconStringAlt'], 'sr' => ['j' => ['?_spellrange sr ON sr.id = s.rangeId'], 's' => ', sr.rangeMinHostile, sr.rangeMinFriend, sr.rangeMaxHostile, sr.rangeMaxFriend, sr.name_loc0 AS rangeText_loc0, sr.name_loc2 AS rangeText_loc2, sr.name_loc3 AS rangeText_loc3, sr.name_loc4 AS rangeText_loc4, sr.name_loc6 AS rangeText_loc6, sr.name_loc8 AS rangeText_loc8'], - 'src' => ['j' => ['?_source src ON type = 6 AND typeId = s.id', true], 's' => ', src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24'] + 'src' => ['j' => ['?_source src ON type = 6 AND typeId = s.id', true], 's' => ', moreType, moreTypeId, moreZoneId, moreMask, src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24'] ); public function __construct($conditions = [], $miscData = []) @@ -2150,15 +2149,15 @@ class SpellList extends BaseType 'skill' => count($this->curTpl['skillLines']) > 4 ? array_merge(array_splice($this->curTpl['skillLines'], 0, 4), [-1]): $this->curTpl['skillLines'], // display max 4 skillLines (fills max three lines in listview) 'reagents' => array_values($this->getReagentsForCurrent()), 'source' => [] - // 'talentspec' => $this->curTpl['skillLines'][0] not used: g_chr_specs has the wrong structure for it; also setting .cat and .type does the same + // 'talentspec' => $this->curTpl['skillLines'][0] not used: g_chr_specs has the wrong structure for it; also setting .cat and .type does the same ); // Sources - if (!empty($this->sources[$this->id])) + if ($this->getSources($s, $sm)) { - $data[$this->id]['source'] = array_keys($this->sources[$this->id]); - if (!empty($this->sources[$this->id][3])) - $data[$this->id]['sourcemore'] = [['p' => $this->sources[$this->id][3][0]]]; + $data[$this->id]['source'] = $s; + if ($sm) + $data[$this->id]['sourcemore'] = $sm; } // Proficiencies diff --git a/includes/types/title.class.php b/includes/types/title.class.php index 77bb80ee..6aaba73d 100644 --- a/includes/types/title.class.php +++ b/includes/types/title.class.php @@ -29,15 +29,15 @@ class TitleList extends BaseType { // preparse sources - notice: under this system titles can't have more than one source (or two for achivements), which is enough for standard TC cases but may break custom cases if ($_curTpl['moreType'] == Type::ACHIEVEMENT) - $this->sources[$this->id][12][] = $_curTpl['moreTypeId']; + $this->sources[$this->id][SRC_ACHIEVEMENT][] = $_curTpl['moreTypeId']; else if ($_curTpl['moreType'] == Type::QUEST) - $this->sources[$this->id][4][] = $_curTpl['moreTypeId']; + $this->sources[$this->id][SRC_QUEST][] = $_curTpl['moreTypeId']; else if ($_curTpl['src13']) - $this->sources[$this->id][13][] = $_curTpl['src13']; + $this->sources[$this->id][SRC_CUSTOM_STRING][] = $_curTpl['src13']; // titles display up to two achievements at once if ($_curTpl['src12Ext']) - $this->sources[$this->id][12][] = $_curTpl['src12Ext']; + $this->sources[$this->id][SRC_ACHIEVEMENT][] = $_curTpl['src12Ext']; unset($_curTpl['src12Ext']); unset($_curTpl['moreType']); @@ -93,9 +93,9 @@ class TitleList extends BaseType private function createSource() { $sources = array( - 4 => [], // Quest - 12 => [], // Achievements - 13 => [] // simple text + SRC_QUEST => [], + SRC_ACHIEVEMENT => [], + SRC_CUSTOM_STRING => [] ); foreach ($this->iterate() as $__) @@ -109,43 +109,43 @@ class TitleList extends BaseType } // fill in the details - if (!empty($sources[4])) - $sources[4] = (new QuestList(array(['id', $sources[4]])))->getSourceData(); + if (!empty($sources[SRC_QUEST])) + $sources[SRC_QUEST] = (new QuestList(array(['id', $sources[SRC_QUEST]])))->getSourceData(); - if (!empty($sources[12])) - $sources[12] = (new AchievementList(array(['id', $sources[12]])))->getSourceData(); + if (!empty($sources[SRC_ACHIEVEMENT])) + $sources[SRC_ACHIEVEMENT] = (new AchievementList(array(['id', $sources[SRC_ACHIEVEMENT]])))->getSourceData(); foreach ($this->sources as $Id => $src) { $tmp = []; // Quest-source - if (isset($src[4])) + if (isset($src[SRC_QUEST])) { - foreach ($src[4] as $s) + foreach ($src[SRC_QUEST] as $s) { - if (isset($sources[4][$s]['s'])) - $this->faction2Side($sources[4][$s]['s']); + if (isset($sources[SRC_QUEST][$s]['s'])) + $this->faction2Side($sources[SRC_QUEST][$s]['s']); - $tmp[4][] = $sources[4][$s]; + $tmp[SRC_QUEST][] = $sources[SRC_QUEST][$s]; } } // Achievement-source - if (isset($src[12])) + if (isset($src[SRC_ACHIEVEMENT])) { - foreach ($src[12] as $s) + foreach ($src[SRC_ACHIEVEMENT] as $s) { - if (isset($sources[12][$s]['s'])) - $this->faction2Side($sources[12][$s]['s']); + if (isset($sources[SRC_ACHIEVEMENT][$s]['s'])) + $this->faction2Side($sources[SRC_ACHIEVEMENT][$s]['s']); - $tmp[12][] = $sources[12][$s]; + $tmp[SRC_ACHIEVEMENT][] = $sources[SRC_ACHIEVEMENT][$s]; } } // other source (only one item possible, so no iteration needed) - if (isset($src[13])) - $tmp[13] = [Lang::game('pvpSources', $this->sources[$Id][13][0])]; + if (isset($src[SRC_CUSTOM_STRING])) + $tmp[SRC_CUSTOM_STRING] = [Lang::game('pvpSources', $Id)]; $this->templates[$Id]['source'] = $tmp; } diff --git a/includes/utilities.php b/includes/utilities.php index 862e2b8d..b72e1c38 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -423,6 +423,37 @@ abstract class CLI } +class Timer +{ + private $t_cur = 0; + private $t_new = 0; + private $intv = 0; + + public function __construct(int $intervall) + { + $this->intv = $intervall / 1000; // in msec + $this->t_cur = microtime(true); + } + + public function update() : bool + { + $this->t_new = microtime(true); + if ($this->t_new > $this->t_cur + $this->intv) + { + $this->t_cur = $this->t_cur + $this->intv; + return true; + } + + return false; + } + + public function reset() : void + { + $this->t_cur = 0; + } +} + + abstract class Util { const FILE_ACCESS = 0777; diff --git a/localization/lang.class.php b/localization/lang.class.php index 2277c630..fc58cfa5 100644 --- a/localization/lang.class.php +++ b/localization/lang.class.php @@ -82,7 +82,7 @@ class Lang unset($args[$i]); } - if ($x = self::exist($prop, ...$args)) + if (($x = self::exist($prop, ...$args)) !== null) return self::vspf($x, $vspfArgs); $dbt = debug_backtrace()[0]; diff --git a/localization/locale_dede.php b/localization/locale_dede.php index 70b7bd8d..25b8a460 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -375,8 +375,8 @@ $lang = array( "In-Game-Store" ), 'pvpSources' => array( - null, "Arenasaison 1", "Arenasaison 2", "Arenasaison 3", "Arenasaison 4", - "Arenasaison 5", "Arenasaison 6", "Arenasaison 7", "Arenasaison 8", "2009 Arena-Turnier" + 42 => "Arenasaison 1", 52 => "Arenasaison 2", 71 => "Arenasaison 3", 80 => "Arenasaison 4", 157 => "Arenasaison 5", + 163 => "Arenasaison 6", 167 => "Arenasaison 7", 169 => "Arenasaison 8", 177 => "2009 Arena-Turnier" ), 'languages' => array( 1 => "Orcisch", 2 => "Darnassisch", 3 => "Taurisch", 6 => "Zwergisch", 7 => "Gemeinsprache", 8 => "Dämonisch", diff --git a/localization/locale_enus.php b/localization/locale_enus.php index 6f90be9f..43d7654f 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -375,8 +375,8 @@ $lang = array( "In-Game Store" ), 'pvpSources' => array( - null, "Arena Season 1", "Arena Season 2", "Arena Season 3", "Arena Season 4", - "Arena Season 5", "Arena Season 6", "Arena Season 7", "Arena Season 8", "2009 Arena Tournament" + 42 => "Arena Season 1", 52 => "Arena Season 2", 71 => "Arena Season 3", 80 => "Arena Season 4", 157 => "Arena Season 5", + 163 => "Arena Season 6", 167 => "Arena Season 7", 169 => "Arena Season 8", 177 => "2009 Arena Tournament" ), 'languages' => array( // Languages.dbc 1 => "Orcish", 2 => "Darnassian", 3 => "Taurahe", 6 => "Dwarvish", 7 => "Common", 8 => "Demonic", diff --git a/localization/locale_eses.php b/localization/locale_eses.php index 7000be32..f60e1ac9 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -375,8 +375,8 @@ $lang = array( "Tienda del juego" ), 'pvpSources' => array( - null, "Temporada de arena 1", "Temporada de arena 2", "Temporada de arena 3", "Temporada de arena 4", - "Temporada de arena 5", "Temporada de arena 6", "Temporada de arena 7", "Temporada de arena 8", "Torneo de arena 2009" + 42 => "Temporada de arena 1", 52 => "Temporada de arena 2", 71 => "Temporada de arena 3", 80 => "Temporada de arena 4", 157 => "Temporada de arena 5", + 163 => "Temporada de arena 6", 167 => "Temporada de arena 7", 169 => "Temporada de arena 8", 177 => "Torneo de arena 2009" ), 'languages' => array( 1 => "Orco", 2 => "Darnassiano", 3 => "Taurahe", 6 => "Enánico", 7 => "Lengua común", 8 => "Demoníaco", diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index abbcb497..bb038851 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -375,8 +375,8 @@ $lang = array( "Boutique en jeu" ), 'pvpSources' => array( - null, "Saison 1 des combats d'arène", "Saison 2 des combats d'arène", "Saison 3 des combats d'arène", "Saison 4 des combats d'arène", - "Saison 5 des combats d'arène", "Saison 6 des combats d'arène", "Saison 7 des combats d'arène", "Saison 8 des combats d'arène", "Tournoi 2009 des combats d'arène" + 42 => "Saison 1 des combats d'arène", 52 => "Saison 2 des combats d'arène", 71 => "Saison 3 des combats d'arène", 80 => "Saison 4 des combats d'arène", 157 => "Saison 5 des combats d'arène", + 163 => "Saison 6 des combats d'arène", 167 => "Saison 7 des combats d'arène", 169 => "Saison 8 des combats d'arène", 177 => "Tournoi 2009 des combats d'arène" ), 'languages' => array( 1 => "Orc", 2 => "Darnassien", 3 => "Taurahe", 6 => "Nain", 7 => "Commun", 8 => "Démoniaque", diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index da012177..d8c13941 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -375,8 +375,8 @@ $lang = array( "Внутриигровой магазин" ), 'pvpSources' => array( - null, "Сезон арены 1", "Сезон арены 2", "Сезон арены 3", "Сезон арены 4", - "Сезон арены 5", "Сезон арены 6", "Сезон арены 7", "Сезон арены 8", "Турнир арены 2009" + 42 => "Сезон арены 1", 52 => "Сезон арены 2", 71 => "Сезон арены 3", 80 => "Сезон арены 4", 157 => "Сезон арены 5", + 163 => "Сезон арены 6", 167 => "Сезон арены 7", 169 => "Сезон арены 8", 177 => "Турнир арены 2009" ), 'languages' => array( 1 => "орочий", 2 => "дарнасский", 3 => "таурахэ", 6 => "дворфийский", 7 => "всеобщий", 8 => "язык демонов", diff --git a/localization/locale_zhcn.php b/localization/locale_zhcn.php index c3cd4ee9..9cd19f77 100644 --- a/localization/locale_zhcn.php +++ b/localization/locale_zhcn.php @@ -374,8 +374,8 @@ $lang = array( "已加工", "失窃", "废弃", "已剥皮", "游戏商店" ), 'pvpSources' => array( - null, "Arena Season 1", "Arena Season 2", "Arena Season 3", "Arena Season 4", - "Arena Season 5", "Arena Season 6", "Arena Season 7", "Arena Season 8", "2009 Arena Tournament" + 42 => "Arena Season 1", 52 => "Arena Season 2", 71 => "Arena Season 3", 80 => "Arena Season 4", 157 => "Arena Season 5", + 163 => "Arena Season 6", 167 => "Arena Season 7", 169 => "Arena Season 8", 177 => "2009 Arena Tournament" ), 'languages' => array( 1 => "兽人语", 2 => "达纳苏斯语", 3 => "牛头人语", 6 => "矮人语", 7 => "通用语", 8 => "恶魔语", diff --git a/setup/db_structure.sql b/setup/db_structure.sql index 071f1904..03ae99a0 100644 --- a/setup/db_structure.sql +++ b/setup/db_structure.sql @@ -2477,6 +2477,7 @@ CREATE TABLE `aowow_source` ( `moreType` tinyint(4) unsigned DEFAULT NULL, `moreTypeId` mediumint(9) unsigned DEFAULT NULL, `moreZoneId` mediumint(9) unsigned DEFAULT NULL, + `moreMask` mediumint(9) unsigned DEFAULT NULL, `src1` tinyint(1) unsigned DEFAULT NULL COMMENT 'Crafted', `src2` tinyint(3) unsigned DEFAULT NULL COMMENT 'Drop (npc / object / item) (modeMask)', `src3` tinyint(3) unsigned DEFAULT NULL COMMENT 'PvP (g_sources_pvp)', @@ -3185,7 +3186,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_dbversion` WRITE; /*!40000 ALTER TABLE `aowow_dbversion` DISABLE KEYS */; -INSERT INTO `aowow_dbversion` VALUES (1682012751,0,NULL,NULL); +INSERT INTO `aowow_dbversion` VALUES (1684620410,0,NULL,NULL); /*!40000 ALTER TABLE `aowow_dbversion` ENABLE KEYS */; UNLOCK TABLES; diff --git a/setup/tools/sqlgen/source.func.php b/setup/tools/sqlgen/source.func.php index 7e8c9cfd..86575029 100644 --- a/setup/tools/sqlgen/source.func.php +++ b/setup/tools/sqlgen/source.func.php @@ -11,40 +11,185 @@ SqlGen::register(new class extends SetupScript { protected $command = 'source'; - protected $tblDependencyAowow = ['spell', 'achievement']; - protected $tblDependencyTC = ['playercreateinfo_skills', 'playercreateinfo_item', 'skill_discovery_template', 'achievement_reward', 'skill_perfect_item_template', 'item_template', 'gameobject_template', 'quest_template', 'quest_template_addon', 'creature_template', 'creature', 'trainer_spell', 'npc_vendor', 'game_event_npc_vendor', 'reference_loot_template', 'item_loot_template', 'creature_loot_template', 'gameobject_loot_template', 'mail_loot_template', 'disenchant_loot_template', 'fishing_loot_template', 'skinning_loot_template', 'milling_loot_template', 'prospecting_loot_template', 'pickpocketing_loot_template']; + protected $tblDependancyAowow = ['spell', 'achievement', 'items', 'spawns', 'creature', 'zones', 'titles']; + protected $tblDependancyTC = ['playercreateinfo_skills', 'playercreateinfo_item', 'skill_discovery_template', 'achievement_reward', 'skill_perfect_item_template', 'item_template', 'gameobject_template', 'quest_template', 'quest_template_addon', 'creature_template', 'creature', 'creature_default_trainer', 'npc_vendor', 'game_event_npc_vendor', 'reference_loot_template', 'item_loot_template', 'creature_loot_template', 'gameobject_loot_template', 'mail_loot_template', 'disenchant_loot_template', 'fishing_loot_template', 'skinning_loot_template', 'milling_loot_template', 'prospecting_loot_template', 'pickpocketing_loot_template']; protected $dbcSourceFiles = ['charstartoutfit', 'talent', 'spell', 'skilllineability', 'itemextendedcost', 'lock']; - private function queryfy(array $data, string $query) : string + private $srcBuffer = []; + private $refLoot = []; + private $dummyNPCs = []; + + private const PVP_MONEY = [26045, 24581, 24579, 43589, 37836]; // Nagrand, Hellfire Pen. H, Hellfire Pen. A, Wintergrasp, Grizzly Hills + private const COMMON_THRESHOLD = 100; + + public function generate(array $ids = []) : bool { - $buff = []; + /*********************************/ + /* resolve difficulty dummy NPCS */ + /*********************************/ - array_walk_recursive($data, function(&$item) { - if ($item === null) - $item = 'NULL'; - }); + $this->dummyNPCs = DB::Aowow()->select( + 'SELECT difficultyEntry1 AS ARRAY_KEY, 2 AS "0", `id` AS "1" FROM ?_creature WHERE difficultyEntry1 > 0 UNION + SELECT difficultyEntry2 AS ARRAY_KEY, 4 AS "0", `id` AS "1" FROM ?_creature WHERE difficultyEntry2 > 0 UNION + SELECT difficultyEntry3 AS ARRAY_KEY, 8 AS "0", `id` AS "1" FROM ?_creature WHERE difficultyEntry3 > 0' + ); + // todo: do the same for GOs - foreach ($data as $d) - $buff[] = '('.implode(', ', $d ?: 'NULL').')'; + CLI::write(' - resolving ref-loot tree', CLI::LOG_BLANK, true, true); + $this->refLoot = DB::World()->select( + 'SELECT rlt.Entry AS ARRAY_KEY, IF(Reference, -Reference, Item) AS ARRAY_KEY2, it.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2 + FROM reference_loot_template rlt + LEFT JOIN item_template it ON rlt.Reference = 0 AND rlt.Item = it.entry + GROUP BY ARRAY_KEY, ARRAY_KEY2' + ); - return str_replace('[V]', implode(', ', $buff), $query); + $hasChanged = true; + while ($hasChanged) + { + $hasChanged = false; + foreach ($this->refLoot as $entry => $refData) + { + foreach ($refData as $itemOrRef => $data) + { + if ($itemOrRef > 0) + continue; + + if (empty($this->refLoot[-$itemOrRef])) + continue; + + foreach ($this->refLoot[-$itemOrRef] AS $key => $data) + $this->refLoot[$entry][$key] = $data; + + unset($this->refLoot[$entry][$itemOrRef]); + $hasChanged = true; + } + } + } + + DB::Aowow()->query('TRUNCATE ?_source'); + + /***************************/ + /* Item & inherited Spells */ + /***************************/ + + CLI::write(' - Items & Spells [inherited]'); + # also everything from items that teach spells, is src of spell + + $this->itemCrafted(); # 1: Crafted # + $this->itemDrop(); # 2: Drop # + $this->itemPvP(); # 3: PvP # (Vendors w/ xCost Arena/Honor) + $this->itemQuest(); # 4: Quest # + $this->itemVendor(); # 5: Vendor # (w/o xCost Arena/Honor) + $this->itemStarter(); # 10: Starter # + $this->itemAchievement(); # 12: Achievement # + $this->itemDisenchantment(); # 15: Disenchanted # + $this->itemFishing(); # 16: Fished # + $this->itemGathering(); # 17: Gathered # + $this->itemMilling(); # 18: Milled # + $this->itemMining(); # 19: Mined # + $this->itemProspecting(); # 20: Prospected # + $this->itemPickpocketing(); # 21: Pickpocket # + $this->itemSalvaging(); # 22: Salvaged # + $this->itemSkinning(); # 23: Skinned # + + // flagging aowow_items for source (note: this is not exact! creatures dropping items may not be spawnd, quests granting items may be disabled) + DB::Aowow()->query('UPDATE ?_items SET cuFlags = cuFlags & ?d', ~CUSTOM_UNAVAILABLE); + DB::Aowow()->query('UPDATE ?_items i LEFT JOIN ?_source s ON s.typeId = i.id AND s.type = ?d SET i.cuFlags = i.cuFlags | ?d WHERE s.typeId IS NULL', Type::ITEM, CUSTOM_UNAVAILABLE); + + /*********/ + /* Spell */ + /*********/ + + CLI::write(' - Spells [original]'); + + $this->spellQuest(); # 4: Quest # + $this->spellTrainer(); # 6: Trainer # + $this->spellDiscovery(); # 7: Discovery # + $this->spellTalent(); # 9: Talent # + $this->spellStarter(); # 10: Starter # + + /**********/ + /* Titles */ + /**********/ + + CLI::write(' - Titles'); + + $this->titleQuest(); # 4: Quest # + $this->titleAchievement(); # 12: Achievement # + $this->titleCustomString(); # 13: Source-String # + + + $t = new Timer(500); + foreach ($this->srcBuffer as $type => $data) + { + $j = 0; + $rows = []; + $sum = count($data); + foreach ($data as $d) + { + $rows[++$j] = array_slice($d, 0, 6); + for ($i = 1; $i < 25; $i++) + $rows[$j][] = $d[6][$i] ?? 0; + + if ($d[7] > self::COMMON_THRESHOLD) + $rows[$j][5] |= SRC_FLAG_COMMON; + + if ($t->update()) + CLI::write(' - Inserting... (['.$type.'] '.$j.' / '.$sum.')', CLI::LOG_BLANK, true, true); + + if (!($j % 300)) + $this->insert($rows); + } + + if ($rows) // insert leftovers + $this->insert($rows); + } + + CLI::write(' - Inserting... (done)'); + + return true; } - private function pushBuffer(array &$buff, int $type, int $typeId, int $mType = 0, int $mTypeId = 0, int $src = 1) : void + private function pushBuffer(int $type, int $typeId, int $srcId, int $srcBit = 1, int $mType = 0, int $mTypeId = 0, int $mZoneId = 0, int $mMask = 0x0, int $qty = 1) : void { - $b = &$buff[$typeId]; + if (!isset($this->srcBuffer[$type])) + $this->srcBuffer[$type] = []; - if (isset($b) && $b[3] === null) - return; - else if (isset($b) && $b[1] != $typeId) + if (!isset($this->srcBuffer[$type][$typeId])) { - $b[3] = $b[4] = null; - $b[2] |= $src; // only quests atm + $this->srcBuffer[$type][$typeId] = [$type, $typeId, $mType ?: null, $mTypeId && $mType ? $mTypeId : null, $mZoneId, $mMask, [$srcId => $srcBit], $qty]; + return; } - else if ($mType && $mTypeId) - $b = [$type, $typeId, $src, $mType, $mTypeId]; - else - $b = [$type, $typeId, $src, null, null]; + + $b = &$this->srcBuffer[$type][$typeId]; + + if ($mType != $b[2] || $mTypeId != $b[3]) + $b[2] = $b[3] = null; + + if ($mZoneId && !$b[4]) + $b[4] = $mZoneId; + else if ($mZoneId && $b[4] && $mZoneId != $b[4]) + $b[4] = -1; + + $b[5] = ($b[5] ?? 0) & $mMask; // only bossdrop for now .. remove flag if regular source is available + $b[6][$srcId] = ($b[6][$srcId] ?? 0) | $srcBit; // SIDE_X for quests, modeMask for drops, subSrc for pvp, else: 1 + $b[7] += $qty; + } + + private function insert(array &$rows) : void + { + if (!$rows) + return; + + $str = ''; + foreach ($rows as &$r) + { + array_walk($r, function(&$x) {$x = (int)$x < 1 ? 'NULL' : $x; }); + $str .= '(' . implode(', ', $r) . '),'; + } + + $rows = []; + DB::Aowow()->query('INSERT INTO ?_source VALUES '. substr($str, 0, -1)); } private function taughtSpell(array $item) : int @@ -66,1032 +211,863 @@ SqlGen::register(new class extends SetupScript return 0; } - public function generate(array $ids = []) : bool + private function itemCrafted() : void { - $insBasic = 'INSERT INTO ?_source (`type`, typeId, src?d) VALUES [V] ON DUPLICATE KEY UPDATE moreType = NULL, moreTypeId = NULL, src?d = VALUES(src?d)'; - $insMore = 'INSERT INTO ?_source (`type`, typeId, src?d, moreType, moreTypeId) VALUES [V] ON DUPLICATE KEY UPDATE moreType = NULL, moreTypeId = NULL, src?d = VALUES(src?d)'; - $insSub = 'INSERT INTO ?_source (`type`, typeId, src?d, moreType, moreTypeId) ?s ON DUPLICATE KEY UPDATE moreType = NULL, moreTypeId = NULL, src?d = VALUES(src?d)'; - - // cant update existing rows - if ($ids) - DB::Aowow()->query('DELETE FROM ?_source WHERE `type` = ?d AND typeId IN (?a)', $well, $wellll); - else - DB::Aowow()->query('TRUNCATE TABLE ?_source'); - - - /***************************/ - /* Item & inherited Spells */ - /***************************/ - - CLI::write(' - Items & Spells [inherited]'); - # also everything from items that teach spells, is src of spell - # todo: check if items have learn-spells (effect: 36) - - CLI::write(' * resolve ref-loot tree', CLI::LOG_BLANK, true, true); - $refLoot = DB::World()->select(' - SELECT - rlt.Entry AS ARRAY_KEY, - IF(Reference, -Reference, Item) AS ARRAY_KEY2, - it.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - COUNT(1) AS qty - FROM - reference_loot_template rlt - LEFT JOIN - item_template it ON rlt.Reference = 0 AND rlt.Item = it.entry - GROUP BY - ARRAY_KEY, ARRAY_KEY2 - '); - - $hasChanged = true; - while ($hasChanged) - { - $hasChanged = false; - foreach ($refLoot as $entry => &$refData) - { - foreach ($refData as $itemOrRef => $data) - { - if ($itemOrRef > 0) - continue; - - if (!empty($refLoot[-$itemOrRef])) - { - foreach ($refLoot[-$itemOrRef] AS $key => $data) - { - if (!empty($refData[$key])) - $refData[$key]['qty'] += $data['qty']; - else - $refLoot[-$itemOrRef][$key] = $data; - } - } - - unset($refData[$itemOrRef]); - $hasChanged = true; - } - } - } - - - ############### - # 1: Crafted # - ############### CLI::write(' * #1 Crafted', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $itemSpells = DB::Aowow()->selectCol(' - SELECT - effect1CreateItemId AS ARRAY_KEY, s.id AS spell - FROM - dbc_spell s JOIN dbc_skilllineability sla ON s.id = sla.spellId - WHERE - effect1CreateItemId > 0 AND sla.skillLineId IN (?a) - GROUP BY - ARRAY_KEY', - [164, 165, 171, 182, 186, 197, 202, 333, 393, 755, 773, 129, 185, 356, 762] + $itemSpells = DB::Aowow()->selectCol( + 'SELECT effect1CreateItemId AS ARRAY_KEY, s.id + FROM dbc_spell s + JOIN dbc_skilllineability sla ON s.id = sla.spellId + WHERE effect1CreateItemId > 0 AND sla.skillLineId IN (?a) + GROUP BY ARRAY_KEY', + [SKILL_BLACKSMITHING, SKILL_LEATHERWORKING, SKILL_ALCHEMY, SKILL_HERBALISM, SKILL_MINING, SKILL_TAILORING, SKILL_ENGINEERING, SKILL_ENCHANTING, SKILL_SKINNING, SKILL_JEWELCRAFTING, SKILL_INSCRIPTION, SKILL_FIRST_AID, SKILL_COOKING, SKILL_FISHING] ); - // assume unique craft spells per item - if ($perfItems = DB::World()->selectCol('SELECT perfectItemType AS ARRAY_KEY, spellId AS spell FROM skill_perfect_item_template')) - foreach ($perfItems AS $item => $spell) - $itemSpells[$item] = $spell; + $perfectItems = DB::World()->selectCol('SELECT perfectItemType AS ARRAY_KEY, spellId AS spell FROM skill_perfect_item_template'); + foreach ($perfectItems AS $item => $spell) + $itemSpells[$item] = $spell; $spellItems = DB::World()->select('SELECT entry AS ARRAY_KEY, class, subclass, spellid_1, spelltrigger_1, spellid_2, spelltrigger_2 FROM item_template WHERE entry IN (?a)', array_keys($itemSpells)); - foreach ($spellItems as $iId => $si) { if ($_ = $this->taughtSpell($si)) - $spellBuff[$_] = [Type::SPELL, $_, 1, Type::SPELL, $itemSpells[$iId]]; + $this->pushBuffer(Type::SPELL, $_, SRC_CRAFTED, 1, Type::SPELL, $itemSpells[$iId]); - $itemBuff[$iId] = [Type::ITEM, $iId, 1, Type::SPELL, $itemSpells[$iId]]; + $this->pushBuffer(Type::ITEM, $iId, SRC_CRAFTED, 1, Type::SPELL, $itemSpells[$iId]); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 1, 1, 1); + private function itemDrop() : void + { + $objectOT = []; + $itemOT = []; - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 1, 1, 1); + CLI::write(' * #2 Drop [NPC]', CLI::LOG_BLANK, true, true); - - ############ - # 2: Drop # - ############ - CLI::write(' * #2 Drop', CLI::LOG_BLANK, true, true); - - $spellBuff = []; - $itemBuff = []; - $creatureLoot = DB::World()->select(' - SELECT - IF(clt.Reference > 0, -clt.Reference, clt.Item) AS ARRAY_KEY, - ct.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - count(1) AS qty - FROM - creature_loot_template clt - JOIN - creature_template ct ON clt.entry = ct.lootid - LEFT JOIN - item_template it ON it.entry = clt.Item AND clt.Reference <= 0 - WHERE - ct.lootid > 0 - GROUP BY - ARRAY_KEY - '); - - foreach ($creatureLoot as $roi => $l) - { - if ($roi < 0 && !empty($refLoot[-$roi])) - { - foreach ($refLoot[-$roi] as $iId => $r) - { - if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry'] /*, $lootmode */); - - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry'] /*, $lootmode */); - } - - continue; - } - - if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry'] /*, $lootmode */); - - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry'] /*, $lootmode */); - } - - $objectOT = []; - $exclLocks = DB::Aowow()->selectCol('SELECT id FROM dbc_lock WHERE properties1 IN (2, 3)'); - $objectLoot = DB::World()->select(' - SELECT - IF(glt.Reference > 0, -glt.Reference, glt.Item) AS ARRAY_KEY, - gt.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - count(1) AS qty - FROM - gameobject_loot_template glt - JOIN - gameobject_template gt ON glt.entry = gt.Data1 - LEFT JOIN - item_template it ON it.entry = glt.Item AND glt.Reference <= 0 - WHERE - `type` = 3 AND gt.Data1 > 0 AND gt.Data0 NOT IN (?a) - GROUP BY - ARRAY_KEY', - $exclLocks + $creatureLoot = DB::World()->select( + 'SELECT IF(clt.Reference > 0, -clt.Reference, clt.Item) AS refOrItem, ct.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty + FROM creature_loot_template clt + JOIN creature_template ct ON clt.entry = ct.lootid + LEFT JOIN item_template it ON it.entry = clt.Item AND clt.Reference <= 0 + WHERE ct.lootid > 0 + GROUP BY refOrItem, ct.entry' ); - foreach ($objectLoot as $roi => $l) + $spawns = DB::Aowow()->select('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT s.areaId) > 1, 0, s.areaId) AS areaId, z.type FROM ?_spawns s JOIN ?_zones z ON z.id = s.areaId WHERE s.`type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_filter(array_column($creatureLoot, 'entry'))); + $bosses = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, IF(cuFlags & ?d, 1, IF(typeFlags & 0x4 AND rank > 0, 1, 0)) FROM ?_creature WHERE id IN (?a)', NPC_CU_INSTANCE_BOSS, array_filter(array_column($creatureLoot, 'entry'))); + + foreach ($creatureLoot as $l) { - if ($roi < 0 && !empty($refLoot[-$roi])) + $roi = $l['refOrItem']; + $entry = $l['entry']; + $mode = 1; + $zoneId = 0; + $mMask = 0x0; + if (isset($this->dummyNPCs[$l['entry']])) + [$mode, $entry] = $this->dummyNPCs[$l['entry']]; + + if (isset($bosses[$entry]) && $bosses[$entry]) // can be empty...? + $mMask |= SRC_FLAG_BOSSDROP; + + if (isset($spawns[$entry])) { - foreach ($refLoot[-$roi] as $iId => $r) + switch ($spawns[$entry]['type']) + { + case 5: // heroic dungeon + $mMask |= SRC_FLAG_DUNGEON_DROP; break; + case 7: // multi mode raid + case 8: // heroic multi mode raid + $mMask |= SRC_FLAG_RAID_DROP; break; + } + + $zoneId = $spawns[$entry]['areaId']; + } + + if ($roi < 0 && !empty($this->refLoot[-$roi])) + { + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, $mMask, $l['qty']); + + $this->pushBuffer(Type::ITEM, $iId, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, $mMask, $l['qty']); + } + + continue; + } + else if ($roi < 0) + { + CLI::write(' - unresolved ref-loot ref: ' . $roi, CLI::LOG_WARN); + continue; + } + + if ($_ = $this->taughtSpell($l)) + $this->pushBuffer(Type::SPELL, $_, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, $mMask, $l['qty']); + + $this->pushBuffer(Type::ITEM, $roi, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, $mMask, $l['qty']); + } + + CLI::write(' * #2 Drop [Object]', CLI::LOG_BLANK, true, true); + + $objectLoot = DB::World()->select( + 'SELECT IF(glt.Reference > 0, -glt.Reference, glt.Item) AS refOrItem, gt.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty + FROM gameobject_loot_template glt + JOIN gameobject_template gt ON glt.entry = gt.Data1 + LEFT JOIN item_template it ON it.entry = glt.Item AND glt.Reference <= 0 + WHERE `type` = 3 AND gt.Data1 > 0 AND gt.Data0 NOT IN (?a) + GROUP BY refOrItem, gt.entry', + DB::Aowow()->selectCol('SELECT id FROM dbc_lock WHERE properties1 IN (2, 3)') + ); + + $spawns = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::OBJECT, array_column($objectLoot, 'entry')); + + // todo: difficulty entrys for boss chests + foreach ($objectLoot as $l) + { + $roi = $l['refOrItem']; + $zoneId = $spawns[$l['entry']] ?? 0; + $mMask = 0x0; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) + { + foreach ($this->refLoot[-$roi] as $iId => $r) + { + if ($_ = $this->taughtSpell($r)) + $this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']); $objectOT[] = $iId; - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']); $objectOT[] = $roi; - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']); } - $itemOT = []; - $itemLoot = DB::World()->select(' - SELECT - IF(ilt.Reference > 0, -ilt.Reference, ilt.Item) AS ARRAY_KEY, - itA.entry, - itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, - count(1) AS qty - FROM - item_loot_template ilt - JOIN - item_template itA ON ilt.entry = itA.entry - LEFT JOIN - item_template itB ON itB.entry = ilt.Item AND ilt.Reference <= 0 - WHERE - itA.flags & 0x4 - GROUP BY - ARRAY_KEY - '); + CLI::write(' * #2 Drop [Item]', CLI::LOG_BLANK, true, true); + + $itemLoot = DB::World()->select( + 'SELECT IF(ilt.Reference > 0, -ilt.Reference, ilt.Item) AS ARRAY_KEY, itA.entry, itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, COUNT(1) AS qty + FROM item_loot_template ilt + JOIN item_template itA ON ilt.entry = itA.entry + LEFT JOIN item_template itB ON itB.entry = ilt.Item AND ilt.Reference <= 0 + WHERE itA.flags & 0x4 + GROUP BY ARRAY_KEY' + ); foreach ($itemLoot as $roi => $l) { - if ($roi < 0 && !empty($refLoot[-$roi])) + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::ITEM, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']); $itemOT[] = $iId; - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::ITEM, $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']); $itemOT[] = $roi; - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']); } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 2, 2, 2); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 2, 2, 2); - DB::Aowow()->query('UPDATE ?_items SET cuFLags = cuFlags | ?d WHERE id IN (?a)', ITEM_CU_OT_ITEMLOOT, $itemOT); DB::Aowow()->query('UPDATE ?_items SET cuFLags = cuFlags | ?d WHERE id IN (?a)', ITEM_CU_OT_OBJECTLOOT, $objectOT); + } - - ########### - # 3: PvP # (Vendors w/ xCost Arena/Honor) - ########### + private function itemPvP() : void + { CLI::write(' * #3 PvP', CLI::LOG_BLANK, true, true); - // var g_sources_pvp = { - // 1: 'Arena', - // 2: 'Battleground', - // 4: 'World' basicly the tokens you get for openPvP .. manual data - // }; + $subSrcByXCost = array( + SRC_SUB_PVP_BG => DB::Aowow()->selectCol('SELECT id FROM dbc_itemextendedcost WHERE reqArenaPoints = 0 AND reqHonorPoints > 0'), + SRC_SUB_PVP_ARENA => DB::Aowow()->selectCol('SELECT id FROM dbc_itemextendedcost WHERE reqArenaPoints > 0'), + SRC_SUB_PVP_WORLD => DB::Aowow()->selectCol('SELECT id FROM dbc_itemextendedcost WHERE reqItemId1 IN (?a) OR reqItemId2 IN (?a) OR reqItemId3 IN (?a) OR reqItemId4 IN (?a) OR reqItemId5 IN (?a)', self::PVP_MONEY, self::PVP_MONEY, self::PVP_MONEY, self::PVP_MONEY, self::PVP_MONEY) + ); + $vendorQuery = + 'SELECT n.item, SUM(n.qty) AS qty, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2 + FROM (SELECT item, COUNT(1) AS qty FROM npc_vendor WHERE ExtendedCost IN (?a) GROUP BY item UNION + SELECT item, COUNT(1) AS qty FROM game_event_npc_vendor genv JOIN creature c ON c.guid = genv.guid WHERE ExtendedCost IN (?a) GROUP BY item) n + JOIN item_template it ON it.entry = n.item + GROUP BY item'; - $spellBuff = []; - $itemBuff = []; - $xCostH = DB::Aowow()->selectCol('SELECT id FROM dbc_itemextendedcost WHERE reqHonorPoints > 0 AND reqArenaPoints = 0'); - $xCostA = DB::Aowow()->selectCol('SELECT id FROM dbc_itemextendedcost WHERE reqArenaPoints > 0'); - $vendorQuery = 'SELECT n.item AS ARRAY_KEY, SUM(n.qty) AS qty, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2 FROM ( - SELECT item, COUNT(1) AS qty FROM npc_vendor WHERE ExtendedCost IN (?a) GROUP BY item - UNION - SELECT item, COUNT(1) AS qty FROM game_event_npc_vendor genv JOIN creature c ON c.guid = genv.guid WHERE ExtendedCost IN (?a) GROUP BY item - ) n JOIN item_template it ON it.entry = n.item - GROUP BY item'; - - foreach (DB::World()->select($vendorQuery, $xCostA, $xCostA) as $iId => $v) + foreach ($subSrcByXCost as $subSrc => $xCost) { - if ($_ = $this->taughtSpell($v)) - $spellBuff[$_] = [Type::SPELL, $_, 1]; + foreach (DB::World()->select($vendorQuery, $xCost, $xCost) as $v) + { + if ($_ = $this->taughtSpell($v)) + $this->pushBuffer(Type::SPELL, $_, SRC_PVP, $subSrc); - $itemBuff[$iId] = [Type::ITEM, $iId, 1]; + $this->pushBuffer(Type::ITEM, $v['item'], SRC_PVP, $subSrc); + } } + } - foreach (DB::World()->select($vendorQuery, $xCostH, $xCostH) as $iId => $v) - { - if ($_ = $this->taughtSpell($v)) - $spellBuff[$_] = [Type::SPELL, $_, 2]; - - $itemBuff[$iId] = [Type::ITEM, $iId, 2]; - } - - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insBasic), 3, 3, 3); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insBasic), 3, 3, 3); - - - ############# - # 4: Quest # - ############# + private function itemQuest() : void + { CLI::write(' * #4 Quest', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $quests = DB::World()->select( - 'SELECT n.item AS ARRAY_KEY, n.ID AS quest, SUM(n.qty) AS qty, BIT_OR(n.side) AS side, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2 FROM ( - SELECT RewardChoiceItemID1 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardChoiceItemID1 > 0 GROUP BY item UNION - SELECT RewardChoiceItemID2 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardChoiceItemID2 > 0 GROUP BY item UNION - SELECT RewardChoiceItemID3 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardChoiceItemID3 > 0 GROUP BY item UNION - SELECT RewardChoiceItemID4 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardChoiceItemID4 > 0 GROUP BY item UNION - SELECT RewardChoiceItemID5 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardChoiceItemID5 > 0 GROUP BY item UNION - SELECT RewardChoiceItemID6 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardChoiceItemID6 > 0 GROUP BY item UNION - SELECT RewardItem1 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardItem1 > 0 GROUP BY item UNION - SELECT RewardItem2 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardItem2 > 0 GROUP BY item UNION - SELECT RewardItem3 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardItem3 > 0 GROUP BY item UNION - SELECT RewardItem4 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE RewardItem4 > 0 GROUP BY item UNION - SELECT StartItem AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE StartItem > 0 GROUP BY item - ) n JOIN item_template it ON it.entry = n.item + $quests = DB::World()->select( + 'SELECT n.item AS ARRAY_KEY, n.ID AS quest, SUM(n.qty) AS qty, BIT_OR(n.side) AS side, IF(COUNT(DISTINCT `zone`) > 1, 0, `zone`) AS "zone", it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2 + FROM (SELECT RewardChoiceItemID1 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardChoiceItemID1 > 0 GROUP BY item UNION + SELECT RewardChoiceItemID2 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardChoiceItemID2 > 0 GROUP BY item UNION + SELECT RewardChoiceItemID3 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardChoiceItemID3 > 0 GROUP BY item UNION + SELECT RewardChoiceItemID4 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardChoiceItemID4 > 0 GROUP BY item UNION + SELECT RewardChoiceItemID5 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardChoiceItemID5 > 0 GROUP BY item UNION + SELECT RewardChoiceItemID6 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardChoiceItemID6 > 0 GROUP BY item UNION + SELECT RewardItem1 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardItem1 > 0 GROUP BY item UNION + SELECT RewardItem2 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardItem2 > 0 GROUP BY item UNION + SELECT RewardItem3 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardItem3 > 0 GROUP BY item UNION + SELECT RewardItem4 AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE RewardItem4 > 0 GROUP BY item UNION + SELECT StartItem AS item, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone" FROM quest_template WHERE StartItem > 0 GROUP BY item) n + JOIN item_template it ON it.entry = n.item GROUP BY item' ); + + $areaParent = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, parentArea FROM ?_zones WHERE id IN (?a) AND parentArea > 0', array_filter(array_column($quests, 'zone'))); + foreach ($quests as $iId => $q) { - if ($_ = $this->taughtSpell($q)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $q['qty'] > 1 ? 0 : Type::QUEST, $q['quest'], $q['side']); + if ($q['zone'] < 0) // remove questSort + $q['zone'] = 0; // todo: do not use questSort for zoneId, but .. questender? (starter can be item) - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $q['qty'] > 1 ? 0 : Type::QUEST, $q['quest'], $q['side']); + if ($_ = $this->taughtSpell($q)) + $this->pushBuffer(Type::SPELL, $_, SRC_QUEST, $q['side'], $q['qty'] > 1 ? 0 : Type::QUEST, $q['quest'], $areaParent[$q['zone']] ?? $q['zone']); + + $this->pushBuffer(Type::ITEM, $iId, SRC_QUEST, $q['side'], $q['qty'] > 1 ? 0 : Type::QUEST, $q['quest'], $areaParent[$q['zone']] ?? $q['zone']); } - $mailLoot = DB::World()->select(' - SELECT - IF(mlt.Reference > 0, -mlt.Reference, mlt.Item) AS ARRAY_KEY, - qt.ID AS entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - count(1) AS qty, - BIT_OR(IF(qt.AllowableRaces & 0x2B2 AND !(qt.AllowableRaces & 0x44D), 2, IF(qt.AllowableRaces & 0x44D AND !(qt.AllowableRaces & 0x2B2), 1, 3))) AS side - FROM - mail_loot_template mlt - JOIN - quest_template_addon qta ON qta.RewardMailTemplateId = mlt.entry - JOIN - quest_template qt ON qt.ID = qta.ID - LEFT JOIN - item_template it ON it.entry = mlt.Item AND mlt.Reference <= 0 - WHERE - qta.RewardMailTemplateId > 0 - GROUP BY - ARRAY_KEY + $mailLoot = DB::World()->select( + 'SELECT IF(mlt.Reference > 0, -mlt.Reference, mlt.Item) AS ARRAY_KEY, qt.ID AS entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty, IF(COUNT(DISTINCT QuestSortID) > 1, 0, GREATEST(QuestSortID, 0)) AS "zone", BIT_OR(IF(qt.AllowableRaces & 0x2B2 AND !(qt.AllowableRaces & 0x44D), 2, IF(qt.AllowableRaces & 0x44D AND !(qt.AllowableRaces & 0x2B2), 1, 3))) AS side + FROM mail_loot_template mlt + JOIN quest_template_addon qta ON qta.RewardMailTemplateId = mlt.entry + JOIN quest_template qt ON qt.ID = qta.ID + LEFT JOIN item_template it ON it.entry = mlt.Item AND mlt.Reference <= 0 + WHERE qta.RewardMailTemplateId > 0 + GROUP BY ARRAY_KEY '); + $areaParent = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, parentArea FROM ?_zones WHERE id IN (?a) AND parentArea > 0', array_filter(array_column($mailLoot, 'zone'))); + foreach ($mailLoot as $roi => $l) { - if ($roi < 0 && !empty($refLoot[-$roi])) + if ($l['zone'] < 0) // remove questSort + $l['zone'] = 0; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $l['side']); + $this->pushBuffer(Type::SPELL, $_, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? $l['zone']); $itemOT[] = $iId; - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $l['side']); + $this->pushBuffer(Type::ITEM, $iId, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? $l['zone']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $l['side']); + $this->pushBuffer(Type::SPELL, $_, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? $l['zone']); $itemOT[] = $roi; - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $l['side']); + $this->pushBuffer(Type::ITEM, $roi, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? $l['zone']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 4, 4, 4); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 4, 4, 4); - - - ############## - # 5: Vendor # (w/o xCost Arena/Honor) - ############## + private function itemVendor() : void + { CLI::write(' * #5 Vendor', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $xCostIds = DB::Aowow()->selectCol('SELECT id FROM dbc_itemextendedcost WHERE reqHonorPoints <> 0 OR reqArenaPoints <> 0'); - $vendors = DB::World()->select( - 'SELECT n.item AS ARRAY_KEY, n.npc, SUM(n.qty) AS qty, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2 FROM ( - SELECT item, entry AS npc, COUNT(1) AS qty FROM npc_vendor WHERE ExtendedCost NOT IN (?a) GROUP BY item - UNION - SELECT item, c.id AS npc, COUNT(1) AS qty FROM game_event_npc_vendor genv JOIN creature c ON c.guid = genv.guid WHERE ExtendedCost NOT IN (?a) GROUP BY item - ) n JOIN item_template it ON it.entry = n.item - GROUP BY item', - $xCostIds, - $xCostIds + $xCostIds = DB::Aowow()->selectCol('SELECT id FROM dbc_itemextendedcost WHERE reqHonorPoints <> 0 OR reqArenaPoints <> 0 OR reqItemId1 IN (?a) OR reqItemId2 IN (?a) OR reqItemId3 IN (?a) OR reqItemId4 IN (?a) OR reqItemId5 IN (?a)', self::PVP_MONEY, self::PVP_MONEY, self::PVP_MONEY, self::PVP_MONEY, self::PVP_MONEY); + $vendors = DB::World()->select( + 'SELECT n.item, n.npc, SUM(n.qty) AS qty, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2 + FROM (SELECT item, entry AS npc, COUNT(1) AS qty FROM npc_vendor WHERE ExtendedCost NOT IN (?a) GROUP BY item, npc UNION + SELECT item, c.id AS npc, COUNT(1) AS qty FROM game_event_npc_vendor genv JOIN creature c ON c.guid = genv.guid WHERE ExtendedCost NOT IN (?a) GROUP BY item, npc) n + JOIN item_template it ON it.entry = n.item + GROUP BY item, npc', + $xCostIds, $xCostIds ); - foreach ($vendors as $iId => $v) + $spawns = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_column($vendors, 'npc')); + + foreach ($vendors as $v) { if ($_ = $this->taughtSpell($v)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $v['qty'] > 1 ? 0 : Type::NPC, $v['npc']); + $this->pushBuffer(Type::SPELL, $_, SRC_VENDOR, 1, $v['qty'] > 1 ? 0 : Type::NPC, $v['npc'], $spawns[$v['npc']] ?? 0, 0, $v['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $v['qty'] > 1 ? 0 : Type::NPC, $v['npc']); + $this->pushBuffer(Type::ITEM, $v['item'], SRC_VENDOR, 1, $v['qty'] > 1 ? 0 : Type::NPC, $v['npc'], $spawns[$v['npc']] ?? 0, 0, $v['qty']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 5, 5, 5); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 5, 5, 5); - - - ############### - # 10: Starter # - ############### + private function itemStarter() : void + { CLI::write(' * #10 Starter', CLI::LOG_BLANK, true, true); - if ($pcii = DB::World()->select('SELECT ?d, itemid, 1 FROM playercreateinfo_item', Type::ITEM)) - DB::Aowow()->query($this->queryfy($pcii, $insBasic), 10, 10, 10); + if ($pcii = DB::World()->selectCol('SELECT DISTINCT itemid FROM playercreateinfo_item')) + foreach ($pcii as $item) + $this->pushBuffer(Type::ITEM, $item, SRC_STARTER); for ($i = 1; $i < 21; $i++) - DB::Aowow()->query($insSub, 10, DB::Aowow()->subquery('SELECT ?d, item?d, 1, NULL AS m, NULL AS mt FROM dbc_charstartoutfit WHERE item?d > 0', Type::ITEM, $i, $i), 10, 10); + if ($cso = DB::Aowow()->selectCol('SELECT item?d FROM dbc_charstartoutfit WHERE item?d > 0', $i, $i)) + foreach ($cso as $item) + $this->pushBuffer(Type::ITEM, $item, SRC_STARTER); + } - - ################### - # 12: Achievement # - ################### + private function itemAchievement() : void + { CLI::write(' * #12 Achievement', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $xItems = DB::Aowow()->select('SELECT id AS entry, itemExtra AS ARRAY_KEY, COUNT(1) AS qty FROM ?_achievement WHERE itemExtra > 0 GROUP BY itemExtra'); - $rewItems = DB::World()->select(' - SELECT - src.item AS ARRAY_KEY, - src.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - COUNT(1) AS qty - FROM ( - SELECT IFNULL(IF(mlt.Reference > 0, -mlt.Reference, mlt.Item), ar.ItemID) AS item, ar.ID AS entry - FROM achievement_reward ar LEFT JOIN mail_loot_template mlt ON mlt.entry = ar.MailTemplateID - WHERE ar.MailTemplateID > 0 OR ar.ItemID > 0 - ) src - LEFT JOIN - item_template it ON src.item = it.entry - GROUP BY - ARRAY_KEY - '); + $xItems = DB::Aowow()->select('SELECT id AS entry, itemExtra AS ARRAY_KEY, COUNT(1) AS qty FROM ?_achievement WHERE itemExtra > 0 GROUP BY itemExtra'); - $extraItems = DB::World()->select('SELECT entry AS ARRAY_KEY, class, subclass, spellid_1, spelltrigger_1, spellid_2, spelltrigger_2 FROM item_template WHERE entry IN (?a)', array_keys($xItems)); - foreach ($extraItems as $iId => $l) + $rewItems = DB::World()->select( + 'SELECT src.item AS ARRAY_KEY, src.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty + FROM (SELECT IFNULL(IF(mlt.Reference > 0, -mlt.Reference, mlt.Item), ar.ItemID) AS item, ar.ID AS entry + FROM achievement_reward ar + LEFT JOIN mail_loot_template mlt ON mlt.entry = ar.MailTemplateID + WHERE ar.MailTemplateID > 0 OR ar.ItemID > 0) src + LEFT JOIN item_template it ON src.item = it.entry + GROUP BY ARRAY_KEY' + ); + + if (empty($xItems)) + CLI::write(' SourceGen: itemAchievement() - Reward items are unexpectedly empty.', CLI::LOG_WARN); + else { - if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $xItems[$iId]['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $xItems[$iId]['entry']); + $extraItems = DB::World()->select('SELECT entry AS ARRAY_KEY, class, subclass, spellid_1, spelltrigger_1, spellid_2, spelltrigger_2 FROM item_template WHERE entry IN (?a)', array_keys($xItems)); + foreach ($extraItems as $iId => $l) + { + if ($_ = $this->taughtSpell($l)) + $this->pushBuffer(Type::SPELL, $_, SRC_ACHIEVEMENT, 1, $xItems[$iId]['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $xItems[$iId]['entry']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $xItems[$iId]['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $xItems[$iId]['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_ACHIEVEMENT, 1, $xItems[$iId]['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $xItems[$iId]['entry']); + } } foreach ($rewItems as $iId => $l) { - if ($roi < 0 && !empty($refLoot[-$roi])) - { - foreach ($refLoot[-$roi] as $iId => $r) - { - if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $l['entry']); - - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $l['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $l['entry']); - } - - continue; - } - if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_ACHIEVEMENT, 1, $l['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $l['entry']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $l['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_ACHIEVEMENT, 1, $l['qty'] > 1 ? 0 : Type::ACHIEVEMENT, $l['entry']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 12, 12, 12); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 12, 12, 12); - - - #################### - # 15: Disenchanted # - #################### + private function itemDisenchantment() : void + { CLI::write(' * #15 Disenchanted', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $deLoot = DB::World()->select(' - SELECT - IF(dlt.Reference > 0, -dlt.Reference, dlt.Item) AS ARRAY_KEY, - itA.entry, - itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, - count(1) AS qty - FROM - disenchant_loot_template dlt - JOIN - item_template itA ON dlt.entry = itA.DisenchantId - LEFT JOIN - item_template itB ON itB.entry = dlt.Item AND dlt.Reference <= 0 - WHERE - itA.DisenchantId > 0 - GROUP BY - ARRAY_KEY - '); + $deLoot = DB::World()->select( + 'SELECT IF(dlt.Reference > 0, -dlt.Reference, dlt.Item) AS refOrItem, itA.entry, itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, COUNT(1) AS qty + FROM disenchant_loot_template dlt + JOIN item_template itA ON dlt.entry = itA.DisenchantId + LEFT JOIN item_template itB ON itB.entry = dlt.Item AND dlt.Reference <= 0 + WHERE itA.DisenchantId > 0 + GROUP BY refOrItem, itA.entry' + ); - foreach ($deLoot as $roi => $l) + foreach ($deLoot as $l) { - if ($roi < 0 && !empty($refLoot[-$roi])) + $roi = $l['refOrItem']; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_); + $this->pushBuffer(Type::SPELL, $_, SRC_DISENCHANTMENT); - $this->pushBuffer($itemBuff, Type::ITEM, $iId); + $this->pushBuffer(Type::ITEM, $iId, SRC_DISENCHANTMENT); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_); + $this->pushBuffer(Type::SPELL, $_, SRC_DISENCHANTMENT); - $this->pushBuffer($itemBuff, Type::ITEM, $roi); + $this->pushBuffer(Type::ITEM, $roi, SRC_DISENCHANTMENT); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 15, 15, 15); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 15, 15, 15); - - - ############## - # 16: Fished # - ############## + private function itemFishing() : void + { CLI::write(' * #16 Fished', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $fishLoot = DB::World()->select(' - SELECT - src.itemOrRef AS ARRAY_KEY, - src.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - count(1) AS qty - FROM ( - SELECT 0 AS entry, IF(flt.Reference > 0, -flt.Reference, flt.Item) itemOrRef FROM fishing_loot_template flt UNION - SELECT gt.entry, IF(glt.Reference > 0, -glt.Reference, glt.Item) itemOrRef FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.entry = gt.Data1 WHERE `type` = 25 AND gt.Data1 > 0 - ) src - LEFT JOIN - item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry - GROUP BY - ARRAY_KEY - '); + $fishLoot = DB::World()->select( + 'SELECT src.itemOrRef AS refOrItem, src.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty, IF(COUNT(DISTINCT `zone`) > 2, 0, MAX(`zone`)) AS "zone" + FROM (SELECT 0 AS entry, IF(flt.Reference > 0, -flt.Reference, flt.Item) AS itemOrRef, entry AS "zone" FROM fishing_loot_template flt UNION + SELECT gt.entry, IF(glt.Reference > 0, -glt.Reference, glt.Item) AS itemOrRef, 0 AS "zone" FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.entry = gt.Data1 WHERE `type` = 25 AND gt.Data1 > 0) src + LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry + GROUP BY refOrItem, src.entry' + ); - foreach ($fishLoot as $roi => $l) + if (!$fishLoot) { - if ($roi < 0 && !empty($refLoot[-$roi])) + CLI::write(' SourceGen: itemFishing() - fishing_loot_template empty and gameobject_template contained no fishing nodes (type:25).', CLI::LOG_WARN); + return; + } + + $goSpawns = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::OBJECT, array_filter(array_column($fishLoot, 'entry'))); + $areaParent = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, parentArea FROM ?_zones WHERE id IN (?a) AND parentArea > 0', array_filter(array_column($fishLoot, 'zone'))); + + foreach ($fishLoot as $l) + { + $roi = $l['refOrItem']; + $l['zone'] = $areaParent[$l['zone']] ?? $l['zone']; + $zoneId = $goSpawns[$l['entry']] ?? 0; + if ($l['zone'] != $zoneId) + $zoneId = -1; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_FISHING, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_FISHING, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, 0, $l['qty']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_FISHING, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_FISHING, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, 0, $l['qty']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 16, 16, 16); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 16, 16, 16); - - - ################ - # 17: Gathered # - ################ + private function itemGathering() : void + { CLI::write(' * #17 Gathered', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $herbLocks = DB::Aowow()->selectCol('SELECT id FROM dbc_lock WHERE properties1 = 2'); - $herbLoot = DB::World()->select(' - SELECT - src.itemOrRef AS ARRAY_KEY, - src.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - count(1) AS qty, - src.srcType - FROM ( - SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef, ?d AS srcType FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) AND ct.skinloot > 0 UNION - SELECT gt.entry, IF(glt.Reference > 0, -glt.Reference, glt.Item) itemOrRef, ?d AS srcType FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.entry = gt.Data1 WHERE gt.`type` = 3 AND gt.Data1 > 0 AND Data0 IN (?a) - ) src - LEFT JOIN - item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry - GROUP BY - ARRAY_KEY', + $herbLoot = DB::World()->select( + 'SELECT src.itemOrRef AS refOrItem, src.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty, src.srcType + FROM (SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef, ?d AS srcType FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) AND ct.skinloot > 0 UNION + SELECT gt.entry, IF(glt.Reference > 0, -glt.Reference, glt.Item) itemOrRef, ?d AS srcType FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.entry = gt.Data1 WHERE gt.`type` = 3 AND gt.Data1 > 0 AND gt.Data0 IN (?a)) src + LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry + GROUP BY refOrItem, src.entry', Type::NPC, NPC_TYPEFLAG_HERBLOOT, - Type::OBJECT, $herbLocks + Type::OBJECT, DB::Aowow()->selectCol('SELECT id FROM dbc_lock WHERE properties1 = 2') ); - foreach ($herbLoot as $roi => $l) + if (!$herbLoot) { - if ($roi < 0 && !empty($refLoot[-$roi])) + CLI::write(' SourceGen: itemGathering() - skinning_loot_template/creature_template contained no loot/herb-lootable creature and gameobject_template contained no herbs (type:3).', CLI::LOG_WARN); + return; + } + + $spawns[Type::OBJECT] = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`', Type::OBJECT, array_column(array_filter($herbLoot, function($x) { return $x['srcType'] == Type::OBJECT; }), 'entry')); + $spawns[Type::NPC] = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`', Type::NPC, array_column(array_filter($herbLoot, function($x) { return $x['srcType'] == Type::NPC; }), 'entry')); + + foreach ($herbLoot as $l) + { + $roi = $l['refOrItem']; + $entry = $l['entry']; + $mode = 1; + if (isset($this->dummyNPCs[$l['entry']]) && $l['srcType'] == Type::NPC) + [$mode, $entry] = $this->dummyNPCs[$l['entry']]; + + $zoneId = $spawns[$l['srcType']][$l['entry']] ?? 0; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_GATHERING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_GATHERING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_GATHERING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_GATHERING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 17, 17, 17); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 17, 17, 17); - - - ############## - # 18: Milled # - ############## + private function itemMilling() : void + { CLI::write(' * #18 Milled', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $millLoot = DB::World()->select(' - SELECT - IF(mlt.Reference > 0, -mlt.Reference, mlt.Item) AS ARRAY_KEY, - itA.entry, - itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, - count(1) AS qty - FROM - milling_loot_template mlt - JOIN - item_template itA ON mlt.entry = itA.entry - LEFT JOIN - item_template itB ON itB.entry = mlt.Item AND mlt.Reference <= 0 - GROUP BY - ARRAY_KEY - '); - - foreach ($millLoot as $roi => $l) - { - if ($roi < 0 && !empty($refLoot[-$roi])) - { - foreach ($refLoot[-$roi] as $iId => $r) - { - if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_); - - $this->pushBuffer($itemBuff, Type::ITEM, $iId); - } - - continue; - } - - if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_); - - $this->pushBuffer($itemBuff, Type::ITEM, $roi); - } - - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 18, 18, 18); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 18, 18, 18); - - - ############# - # 19: Mined # - ############# - CLI::write(' * #19 Mined', CLI::LOG_BLANK, true, true); - - $spellBuff = []; - $itemBuff = []; - $mineLocks = DB::Aowow()->selectCol('SELECT id FROM dbc_lock WHERE properties1 = 3'); - $mineLoot = DB::World()->select(' - SELECT - src.itemOrRef AS ARRAY_KEY, - src.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - count(1) AS qty, - src.srcType - FROM ( - SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef, ?d AS srcType FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) AND ct.skinloot > 0 UNION - SELECT gt.entry, IF(glt.Reference > 0, -glt.Reference, glt.Item) itemOrRef, ?d AS srcType FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.entry = gt.Data1 WHERE gt.`type` = 3 AND gt.Data1 > 0 AND Data0 IN (?a) - ) src - LEFT JOIN - item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry - GROUP BY - ARRAY_KEY', - Type::NPC, NPC_TYPEFLAG_MININGLOOT, - Type::OBJECT, $mineLocks + $millLoot = DB::World()->select( + 'SELECT IF(mlt.Reference > 0, -mlt.Reference, mlt.Item) AS refOrItem, itA.entry, itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, COUNT(1) AS qty + FROM milling_loot_template mlt + JOIN item_template itA ON mlt.entry = itA.entry + LEFT JOIN item_template itB ON itB.entry = mlt.Item AND mlt.Reference <= 0 + GROUP BY refOrItem, itA.entry' ); - foreach ($mineLoot as $roi => $l) + foreach ($millLoot as $l) { - if ($roi < 0 && !empty($refLoot[-$roi])) + $roi = $l['refOrItem']; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_MILLING); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_MILLING); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_MILLING); - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : $l['srcType'], $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_MILLING); + } + } + + private function itemMining() : void + { + CLI::write(' * #19 Mined', CLI::LOG_BLANK, true, true); + + $mineLoot = DB::World()->select( + 'SELECT src.itemOrRef AS refOrItem, src.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty, src.srcType + FROM (SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef, ?d AS srcType FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) AND ct.skinloot > 0 UNION + SELECT gt.entry, IF(glt.Reference > 0, -glt.Reference, glt.Item) itemOrRef, ?d AS srcType FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.entry = gt.Data1 WHERE gt.`type` = 3 AND gt.Data1 > 0 AND gt.Data0 IN (?a)) src + LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry + GROUP BY refOrItem, src.entry', + Type::NPC, NPC_TYPEFLAG_MININGLOOT, + Type::OBJECT, DB::Aowow()->selectCol('SELECT id FROM dbc_lock WHERE properties1 = 3') + ); + + if (!$mineLoot) + { + CLI::write(' SourceGen: itemMining() - skinning_loot_template/creature_template contained no loot/mining-lootable creature and gameobject_template contained no nodes (type:3).', CLI::LOG_WARN); + return; } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 19, 19, 19); + $spawns[Type::OBJECT] = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::OBJECT, array_column(array_filter($mineLoot, function($x) { return $x['srcType'] == Type::OBJECT; }), 'entry')); + $spawns[Type::NPC] = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_column(array_filter($mineLoot, function($x) { return $x['srcType'] == Type::NPC; }), 'entry')); - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 19, 19, 19); + foreach ($mineLoot as $l) + { + $roi = $l['refOrItem']; + $entry = $l['entry']; + $mode = 1; + if (isset($this->dummyNPCs[$l['entry']]) && $l['srcType'] == Type::NPC) + [$mode, $entry] = $this->dummyNPCs[$l['entry']]; + $zoneId = $spawns[$l['srcType']][$l['entry']] ?? 0; - ################## - # 20: Prospected # - ################## + if ($roi < 0 && !empty($this->refLoot[-$roi])) + { + foreach ($this->refLoot[-$roi] as $iId => $r) + { + if ($_ = $this->taughtSpell($r)) + $this->pushBuffer(Type::SPELL, $_, SRC_MINING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); + + $this->pushBuffer(Type::ITEM, $iId, SRC_MINING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); + } + + continue; + } + + if ($_ = $this->taughtSpell($l)) + $this->pushBuffer(Type::SPELL, $_, SRC_MINING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); + + $this->pushBuffer(Type::ITEM, $roi, SRC_MINING, $mode, $l['qty'] > 1 ? 0 : $l['srcType'], $entry, $zoneId, 0, $l['qty']); + } + } + + private function itemProspecting() : void + { CLI::write(' * #20 Prospected', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $prospectLoot = DB::World()->select(' - SELECT - IF(plt.Reference > 0, -plt.Reference, plt.Item) AS ARRAY_KEY, - itA.entry, - itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, - count(1) AS qty - FROM - prospecting_loot_template plt - JOIN - item_template itA ON plt.entry = itA.entry - LEFT JOIN - item_template itB ON itB.entry = plt.Item AND plt.Reference <= 0 - GROUP BY - ARRAY_KEY - '); + $prospectLoot = DB::World()->select( + 'SELECT IF(plt.Reference > 0, -plt.Reference, plt.Item) AS refOrItem, itA.entry, itB.class, itB.subclass, itB.spellid_1, itB.spelltrigger_1, itB.spellid_2, itB.spelltrigger_2, COUNT(1) AS qty + FROM prospecting_loot_template plt + JOIN item_template itA ON plt.entry = itA.entry + LEFT JOIN item_template itB ON itB.entry = plt.Item AND plt.Reference <= 0 + GROUP BY refOrItem, itA.entry' + ); foreach ($prospectLoot as $roi => $l) { - if ($roi < 0 && !empty($refLoot[-$roi])) + $roi = $l['refOrItem']; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_); + $this->pushBuffer(Type::SPELL, $_, SRC_PROSPECTING); - $this->pushBuffer($itemBuff, Type::ITEM, $iId); + $this->pushBuffer(Type::ITEM, $iId, SRC_PROSPECTING); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_); + $this->pushBuffer(Type::SPELL, $_, SRC_PROSPECTING); - $this->pushBuffer($itemBuff, Type::ITEM, $roi); + $this->pushBuffer(Type::ITEM, $roi, SRC_PROSPECTING); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 20, 20, 20); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 20, 20, 20); - - - ################## - # 21: Pickpocket # - ################## + private function itemPickpocketing() : void + { CLI::write(' * #21 Pickpocket', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $theftLoot = DB::World()->select(' - SELECT - src.itemOrRef AS ARRAY_KEY, - src.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - COUNT(1) AS qty - FROM ( - SELECT ct.entry, IF(plt.Reference > 0, -plt.Reference, plt.Item) itemOrRef FROM creature_template ct JOIN pickpocketing_loot_template plt ON plt.entry = ct.pickpocketloot WHERE ct.pickpocketloot > 0 - ) src - LEFT JOIN - item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry - GROUP BY - ARRAY_KEY - '); + $theftLoot = DB::World()->select( + 'SELECT src.itemOrRef AS refOrItem, src.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty + FROM (SELECT ct.entry, IF(plt.Reference > 0, -plt.Reference, plt.Item) itemOrRef FROM creature_template ct JOIN pickpocketing_loot_template plt ON plt.entry = ct.pickpocketloot WHERE ct.pickpocketloot > 0) src + LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry + GROUP BY refOrItem, src.entry' + ); - foreach ($theftLoot as $roi => $l) + if (!$theftLoot) { - if ($roi < 0 && !empty($refLoot[-$roi])) + CLI::write(' SourceGen: itemPickpocketing() - pickpocketing_loot_template/creature_template contained no loot/generous creature.', CLI::LOG_WARN); + return; + } + + $spawns = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_filter(array_column($theftLoot, 'entry'))); + + foreach ($theftLoot as $l) + { + $roi = $l['refOrItem']; + $entry = $l['entry']; + $mode = 1; + if (isset($this->dummyNPCs[$l['entry']])) + [$mode, $entry] = $this->dummyNPCs[$l['entry']]; + + $zoneId = $spawns[$l['entry']] ?? 0; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_PICKPOCKETING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_PICKPOCKETING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_PICKPOCKETING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_PICKPOCKETING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 21, 21, 21); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 21, 21, 21); - - - ################ - # 22: Salvaged # - ################ + private function itemSalvaging() : void + { CLI::write(' * #22 Salvaged', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $salvageLoot = DB::World()->select(' - SELECT - src.itemOrRef AS ARRAY_KEY, - src.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - COUNT(1) AS qty - FROM ( - SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) AND ct.skinloot > 0 - ) src - LEFT JOIN - item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry - GROUP BY - ARRAY_KEY', + $salvageLoot = DB::World()->select( + 'SELECT src.itemOrRef AS refOrItem, src.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty + FROM (SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) AND ct.skinloot > 0) src + LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry + GROUP BY refOrItem, src.entry', NPC_TYPEFLAG_ENGINEERLOOT ); - foreach ($salvageLoot as $roi => $l) + if (!$salvageLoot) { - if ($roi < 0 && !empty($refLoot[-$roi])) + CLI::write(' SourceGen: itemSalvaging() - skinning_loot_template/creature_template contained no loot/salvageable creature.', CLI::LOG_WARN); + return; + } + + $spawns = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_filter(array_column($salvageLoot, 'entry'))); + + foreach ($salvageLoot as $l) + { + $roi = $l['refOrItem']; + $entry = $l['entry']; + $mode = 1; + if (isset($this->dummyNPCs[$l['entry']])) + [$mode, $entry] = $this->dummyNPCs[$l['entry']]; + + $zoneId = $spawns[$l['entry']] ?? 0; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_SALVAGING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_SALVAGING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_SALVAGING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_SALVAGING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 22, 22, 22); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 22, 22, 22); - - - ############### - # 23: Skinned # - ############### + private function itemSkinning() : void + { CLI::write(' * #23 Skinned', CLI::LOG_BLANK, true, true); - $spellBuff = []; - $itemBuff = []; - $skinLoot = DB::World()->select(' - SELECT - src.itemOrRef AS ARRAY_KEY, - src.entry, - it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, - COUNT(1) AS qty - FROM ( - SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) = 0 AND ct.skinloot > 0 - ) src - LEFT JOIN - item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry - GROUP BY - ARRAY_KEY', + $skinLoot = DB::World()->select( + 'SELECT src.itemOrRef AS refOrItem, src.entry, it.class, it.subclass, it.spellid_1, it.spelltrigger_1, it.spellid_2, it.spelltrigger_2, COUNT(1) AS qty + FROM (SELECT ct.entry, IF(slt.Reference > 0, -slt.Reference, slt.Item) itemOrRef FROM creature_template ct JOIN skinning_loot_template slt ON slt.entry = ct.skinloot WHERE (type_flags & ?d) = 0 AND ct.skinloot > 0 AND ct.type <> 13) src + LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.itemOrRef = it.entry + GROUP BY refOrItem, src.entry', (NPC_TYPEFLAG_HERBLOOT | NPC_TYPEFLAG_MININGLOOT | NPC_TYPEFLAG_ENGINEERLOOT) ); - foreach ($skinLoot as $roi => $l) + if (!$skinLoot) { - if ($roi < 0 && !empty($refLoot[-$roi])) + CLI::write(' SourceGen: itemSkinning() - skinning_loot_template/creature_template contained no loot/skinable beast.', CLI::LOG_WARN); + return; + } + + $spawns = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(COUNT(DISTINCT areaId) > 1, 0, areaId) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_filter(array_column($skinLoot, 'entry'))); + + foreach ($skinLoot as $l) + { + $roi = $l['refOrItem']; + $entry = $l['entry']; + $mode = 1; + if (isset($this->dummyNPCs[$l['entry']])) + [$mode, $entry] = $this->dummyNPCs[$l['entry']]; + + $zoneId = $spawns[$l['entry']] ?? 0; + + if ($roi < 0 && !empty($this->refLoot[-$roi])) { - foreach ($refLoot[-$roi] as $iId => $r) + foreach ($this->refLoot[-$roi] as $iId => $r) { if ($_ = $this->taughtSpell($r)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_SKINNING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $iId, $r['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::ITEM, $iId, SRC_SKINNING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); } continue; } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer($spellBuff, Type::SPELL, $_, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::SPELL, $_, SRC_SKINNING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); - $this->pushBuffer($itemBuff, Type::ITEM, $roi, $l['qty'] > 1 ? 0 : Type::NPC, $l['entry']); + $this->pushBuffer(Type::ITEM, $roi, SRC_SKINNING, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, 0, $l['qty']); } + } - if ($itemBuff) - DB::Aowow()->query($this->queryfy($itemBuff, $insMore), 23, 23, 23); - - if ($spellBuff) - DB::Aowow()->query($this->queryfy($spellBuff, $insMore), 23, 23, 23); - - - // flagging aowow_items for source (note: this is not exact! creatures dropping items may not be spawnd, quests granting items may be disabled) - DB::Aowow()->query('UPDATE ?_items SET cuFlags = cuFlags & ?d', ~CUSTOM_UNAVAILABLE); - DB::Aowow()->query('UPDATE ?_items i LEFT JOIN ?_source s ON s.typeId = i.id AND s.type = ?d SET i.cuFlags = i.cuFlags | ?d WHERE s.typeId IS NULL', Type::ITEM, CUSTOM_UNAVAILABLE); - - /*********/ - /* Spell */ - /*********/ - - CLI::write(' - Spells [original]'); - - # 4: Quest + private function spellQuest() : void + { CLI::write(' * #4 Quest', CLI::LOG_BLANK, true, true); - $quests = DB::World()->select(' - SELECT spell AS ARRAY_KEY, id, SUM(qty) AS qty, BIT_OR(side) AS side FROM ( - SELECT IF(RewardSpell = 0, RewardDisplaySpell, RewardSpell) AS spell, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template WHERE IF(RewardSpell = 0, RewardDisplaySpell, RewardSpell) > 0 GROUP BY spell - UNION - SELECT qta.SourceSpellId AS spell, qt.ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side FROM quest_template qt JOIN quest_template_addon qta ON qta.ID = qt.ID WHERE qta.SourceSpellId > 0 GROUP BY spell - ) t GROUP BY spell'); - if ($quests) + $quests = DB::World()->select( + 'SELECT spell AS ARRAY_KEY, id, SUM(qty) AS qty, BIT_OR(side) AS side, IF(COUNT(DISTINCT `zone`) > 1, 0, `zone`) AS "zone" + FROM (SELECT IF(RewardSpell = 0, RewardDisplaySpell, RewardSpell) AS spell, ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, GREATEST(QuestSortID, 0) AS "zone" FROM quest_template WHERE IF(RewardSpell = 0, RewardDisplaySpell, RewardSpell) > 0 GROUP BY spell UNION + SELECT qta.SourceSpellId AS spell, qt.ID, COUNT(1) AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, GREATEST(QuestSortID, 0) AS "zone" FROM quest_template qt JOIN quest_template_addon qta ON qta.ID = qt.ID WHERE qta.SourceSpellId > 0 GROUP BY spell) t + GROUP BY spell' + ); + + if (!$quests) { - $qSpells = DB::Aowow()->select('SELECT id AS ARRAY_KEY, effect1Id, effect2Id, effect3Id, effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell FROM dbc_spell WHERE id IN (?a) AND (effect1Id = 36 OR effect2Id = 36 OR effect3Id = 36)', array_keys($quests)); - $buff = []; - - foreach ($qSpells as $sId => $spell) - for ($i = 1; $i <= 3; $i++) - if ($spell['effect'.$i.'Id'] == 36) // effect: learnSpell - $this->pushBuffer($buff, Type::SPELL, $spell['effect'.$i.'TriggerSpell'], $quests[$sId]['qty'] > 1 ? 0 : Type::QUEST, $quests[$sId]['qty'] > 1 ? 0 : $quests[$sId]['id'], $quests[$sId]['side']); - - DB::Aowow()->query($this->queryfy($buff, $insMore), 4, 4, 4); + CLI::write(' SourceGen: spellQuest() - quest_template contained no spell rewards or grants.', CLI::LOG_WARN); + return; } - # 6: Trainer - CLI::write(' * #6 Trainer', CLI::LOG_BLANK, true, true); - if ($tNpcs = DB::World()->select('SELECT SpellID AS ARRAY_KEY, cdt.CreatureId AS entry, COUNT(1) AS qty FROM `trainer_spell` ts JOIN `creature_default_trainer` cdt ON cdt.TrainerId = ts.TrainerId GROUP BY ARRAY_KEY')) - { - $tSpells = DB::Aowow()->select('SELECT id AS ARRAY_KEY, effect1Id, effect2Id, effect3Id, effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell FROM dbc_spell WHERE id IN (?a)', array_keys($tNpcs)); - $buff = []; + $areaParent = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, parentArea FROM ?_zones WHERE id IN (?a) AND parentArea > 0', array_filter(array_column($quests, 'zone'))); + $qSpells = DB::Aowow()->select('SELECT id AS ARRAY_KEY, effect1Id, effect2Id, effect3Id, effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell FROM dbc_spell WHERE id IN (?a) AND (effect1Id = 36 OR effect2Id = 36 OR effect3Id = 36)', array_keys($quests)); - // todo (med): this skips some spells (e.g. riding) - foreach ($tNpcs as $spellId => $npc) + foreach ($qSpells as $sId => $spell) + for ($i = 1; $i <= 3; $i++) + if ($spell['effect'.$i.'Id'] == 36) // effect: learnSpell + $this->pushBuffer(Type::SPELL, $spell['effect'.$i.'TriggerSpell'], SRC_QUEST, $quests[$sId]['side'], $quests[$sId]['qty'] > 1 ? 0 : Type::QUEST, $quests[$sId]['id'], $areaParent[$quests[$sId]['zone']] ?? $quests[$sId]['zone']); + } + + private function spellTrainer() : void + { + CLI::write(' * #6 Trainer', CLI::LOG_BLANK, true, true); + + $tNpcs = DB::World()->select('SELECT SpellID AS ARRAY_KEY, cdt.CreatureId AS entry, COUNT(1) AS qty FROM `trainer_spell` ts JOIN `creature_default_trainer` cdt ON cdt.TrainerId = ts.TrainerId GROUP BY ARRAY_KEY'); + + if (!$tNpcs) + { + CLI::write(' SourceGen: spelltrainer() - creature_default_trainer contained no spell.', CLI::LOG_WARN); + return; + } + + // note: for consistency you could check for boss dummys and get the zone where the trainer resides, but seriously. Whats wrong with you‽ + + $tSpells = DB::Aowow()->select('SELECT id AS ARRAY_KEY, effect1Id, effect2Id, effect3Id, effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell FROM dbc_spell WHERE id IN (?a)', array_keys($tNpcs)); + + // todo (med): this skips some spells (e.g. riding) + foreach ($tNpcs as $spellId => $npc) + { + if (!isset($tSpells[$spellId])) + continue; + + $effects = $tSpells[$spellId]; + $trainerId = $npc['qty'] > 1 ? 0 : $npc['entry']; + + $triggered = false; + for ($i = 1; $i <= 3; $i++) { - if (!isset($tSpells[$spellId])) + if ($effects['effect'.$i.'Id'] != 36) // effect: learnSpell continue; - $effects = $tSpells[$spellId]; - $trainerId = $npc['qty'] > 1 ? 0 : $npc['entry']; - - for ($i = 1; $i <= 3; $i++) - if ($effects['effect'.$i.'Id'] == 36) // effect: learnSpell - $this->pushBuffer($buff, Type::SPELL, $effects['effect'.$i.'TriggerSpell'], $trainerId ? Type::NPC : 0, $trainerId); - - $this->pushBuffer($buff, Type::SPELL, $spellId, $trainerId ? Type::NPC : 0, $trainerId); + $triggered = true; + $this->pushBuffer(Type::SPELL, $effects['effect'.$i.'TriggerSpell'], SRC_TRAINER, 1, $trainerId ? Type::NPC : 0, $trainerId); } - DB::Aowow()->query($this->queryfy($buff, $insMore), 6, 6, 6); + if (!$triggered) + $this->pushBuffer(Type::SPELL, $spellId, SRC_TRAINER, 1, $trainerId ? Type::NPC : 0, $trainerId); } + } - # 7: Discovery + private function spellDiscovery() : void + { CLI::write(' * #7 Discovery', CLI::LOG_BLANK, true, true); - // 61756: Northrend Inscription Research (FAST QA VERSION); - if ($disco = DB::World()->select('SELECT ?d, spellId, 1 FROM skill_discovery_template WHERE reqSpell <> ?d', Type::SPELL, 61756)) - DB::Aowow()->query($this->queryfy($disco, $insBasic), 7, 7, 7); - # 9: Talent + // 61756: Northrend Inscription Research (FAST QA VERSION); + if ($disco = DB::World()->selectCol('SELECT spellId FROM skill_discovery_template WHERE reqSpell <> ?d', 61756)) + foreach ($disco as $d) + $this->pushBuffer(Type::SPELL, $d, SRC_DISCOVERY); + } + + private function spellTalent() : void + { CLI::write(' * #9 Talent', CLI::LOG_BLANK, true, true); - $tSpells = DB::Aowow()->select(' - SELECT s.id AS ARRAY_KEY, s.effect1Id, s.effect2Id, s.effect3Id, s.effect1TriggerSpell, s.effect2TriggerSpell, s.effect3TriggerSpell + + $tSpells = DB::Aowow()->select( + 'SELECT s.id AS ARRAY_KEY, s.effect1Id, s.effect2Id, s.effect3Id, s.effect1TriggerSpell, s.effect2TriggerSpell, s.effect3TriggerSpell FROM dbc_talent t JOIN dbc_spell s ON s.id = t.rank1 - WHERE t.rank2 < 1 AND (t.talentSpell = 1 OR (s.effect1Id = 36 OR s.effect2Id = 36 OR s.effect3Id = 36)) - '); + WHERE t.rank2 < 1 AND (t.talentSpell = 1 OR (s.effect1Id = 36 OR s.effect2Id = 36 OR s.effect3Id = 36))' + ); - $n = 0; - $buff = []; + $n = 0; while ($tSpells) { CLI::write(' - '.++$n.'. pass', CLI::LOG_BLANK, true, true); @@ -1108,64 +1084,77 @@ SqlGen::register(new class extends SetupScript } foreach ($tSpells as $tId => $__) - $buff[$tId] = [Type::SPELL, $tId, 1]; + $this->pushBuffer(Type::SPELL, $tId, SRC_TALENT); if (!$recurse) break; $tSpells = DB::Aowow()->select('SELECT id AS ARRAY_KEY, effect1Id, effect2Id, effect3Id, effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell FROM dbc_spell WHERE id IN (?a)', array_keys($recurse)); } + } - DB::Aowow()->query($this->queryfy($buff, $insBasic), 9, 9, 9); - - # 10: Starter - CLI::write(' * #10 Starter', CLI::LOG_BLANK, true, true); + private function spellStarter() : void + { /* acquireMethod ABILITY_LEARNED_ON_GET_PROFESSION_SKILL = 1, learnedAt = 1 && source10 = 1 ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL = 2 */ + CLI::write(' * #10 Starter', CLI::LOG_BLANK, true, true); $pcis = DB::World()->selectCol('SELECT DISTINCT skill FROM playercreateinfo_skills'); - $subSkills = DB::Aowow()->subquery('SELECT ?d, spellId, 1, NULL AS m, NULL AS mt FROM dbc_skilllineability WHERE {(skillLineId IN (?a) AND acquireMethod = 2) OR} (acquireMethod = 1 AND (reqSkillLevel = 1 OR skillLineId = 129)) GROUP BY spellId', Type::SPELL, $pcis ?: DBSIMPLE_SKIP); - DB::Aowow()->query($insSub, 10, $subSkills, 10, 10); + $subSkills = DB::Aowow()->selectCol('SELECT spellId FROM dbc_skilllineability WHERE {(skillLineId IN (?a) AND acquireMethod = 2) OR} (acquireMethod = 1 AND (reqSkillLevel = 1 OR skillLineId = 129)) GROUP BY spellId', $pcis ?: DBSIMPLE_SKIP); + foreach ($subSkills as $s) + $this->pushBuffer(Type::SPELL, $s, SRC_STARTER); + } - - /**********/ - /* Titles */ - /**********/ - - CLI::write(' - Titles'); - - # 4: Quest + private function titleQuest() : void + { CLI::write(' * #4 Quest', CLI::LOG_BLANK, true, true); - if ($quests = DB::World()->select('SELECT ?d, RewardTitle, 1, ?d, ID FROM quest_template WHERE RewardTitle > 0', Type::TITLE, Type::QUEST)) - DB::Aowow()->query($this->queryfy($quests, $insMore), 4, 4, 4); - # 12: Achievement - CLI::write(' * #12 Achievement', CLI::LOG_BLANK, true, true); - $sets = DB::World()->select(' - SELECT titleId AS ARRAY_KEY, MIN(ID) AS srcId, NULLIF(MAX(ID), MIN(ID)) AS altSrcId FROM ( - SELECT TitleA AS `titleId`, ID FROM achievement_reward WHERE TitleA <> 0 - UNION - SELECT TitleH AS `titleId`, ID FROM achievement_reward WHERE TitleH <> 0 - ) AS x GROUP BY titleId' + $quests = DB::World()->select( + 'SELECT RewardTitle AS ARRAY_KEY, id, SUM(qty) AS qty, BIT_OR(side) AS side, IF(COUNT(DISTINCT `zone`) > 1, 0, `zone`) AS "zone" + FROM (SELECT RewardTitle, ID, 1 AS qty, IF(AllowableRaces & 0x2B2 AND !(AllowableRaces & 0x44D), 2, IF(AllowableRaces & 0x44D AND !(AllowableRaces & 0x2B2), 1, 3)) AS side, GREATEST(QuestSortID, 0) AS "zone" FROM quest_template WHERE RewardTitle > 0) q + GROUP BY RewardTitle' ); + + if (!$quests) + { + CLI::write(' SourceGen: titleQuest() - quest_template contained no title rewards.', CLI::LOG_WARN); + return; + } + + $areaParent = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, parentArea FROM ?_zones WHERE id IN (?a) AND parentArea > 0', array_filter(array_column($quests, 'zone'))); + + foreach ($quests as $titleId => $q) + $this->pushBuffer(Type::TITLE, $titleId, SRC_QUEST, $q['side'], $q['qty'] > 1 ? 0 : Type::QUEST, $q['id'], $areaParent[$q['zone']] ?? $q['zone']); + } + + private function titleAchievement() : void + { + CLI::write(' * #12 Achievement', CLI::LOG_BLANK, true, true); + + $sets = DB::World()->select( + 'SELECT titleId AS ARRAY_KEY, MIN(ID) AS srcId, NULLIF(MAX(ID), MIN(ID)) AS altSrcId + FROM (SELECT TitleA as `titleId`, ID FROM achievement_reward WHERE TitleA <> 0 UNION + SELECT TitleH AS `titleId`, ID FROM achievement_reward WHERE TitleH <> 0) AS x + GROUP BY titleId' + ); + foreach ($sets as $tId => $set) { - DB::Aowow()->query($this->queryfy([[Type::TITLE, $tId, 1, Type::ACHIEVEMENT, $set['srcId']]], $insMore), 12, 12, 12); + $this->pushBuffer(Type::TITLE, $tId, SRC_ACHIEVEMENT, 1, Type::ACHIEVEMENT, $set['srcId']); if ($set['altSrcId']) DB::Aowow()->query('UPDATE ?_titles SET src12Ext = ?d WHERE id = ?d', $set['altSrcId'], $tId); } + } - # 13: Source-String + private function titleCustomString() : void + { CLI::write(' * #13 cuStrings', CLI::LOG_BLANK, true, true); - $src13 = [null, 42, 52, 71, 80, 157, 163, 167, 169, 177]; - foreach ($src13 as $src => $tId) - if ($tId) - DB::Aowow()->query($this->queryfy([[Type::TITLE, $tId, $src]], $insBasic), 13, 13, 13); - return true; + foreach (Lang::game('pvpSources') as $src => $__) + $this->pushBuffer(Type::TITLE, $src, SRC_CUSTOM_STRING, $src); } }); diff --git a/setup/updates/1684620409_01.sql b/setup/updates/1684620409_01.sql new file mode 100644 index 00000000..0eb594dd --- /dev/null +++ b/setup/updates/1684620409_01.sql @@ -0,0 +1,2 @@ +ALTER TABLE `aowow_source` + ADD COLUMN `moreMask` mediumint(9) unsigned DEFAULT NULL AFTER `moreZoneId`; diff --git a/setup/updates/1684620409_02.sql b/setup/updates/1684620409_02.sql new file mode 100644 index 00000000..bef0e1a9 --- /dev/null +++ b/setup/updates/1684620409_02.sql @@ -0,0 +1 @@ +UPDATE `aowow_dbversion` SET `sql` = CONCAT(IFNULL(`sql`, ''), ' source'); diff --git a/static/js/global.js b/static/js/global.js index 9fe9e618..90a9b064 100644 --- a/static/js/global.js +++ b/static/js/global.js @@ -12935,16 +12935,115 @@ Listview.templates = { hidden: true, compute: function(spell, td) { if (spell.source != null) { - var buff = ''; - for (var i = 0, len = spell.source.length; i < len; ++i) { - if (i > 0) { - buff += LANG.comma; + if (spell.source.length == 1) { + $WH.nw(td); + + var sm = (spell.sourcemore ? spell.sourcemore[0] : {}); + var type = 0; + + if (sm.t) { + type = sm.t; + + var a = $WH.ce('a'); + if (sm.q != null) { + a.className = 'q' + sm.q; + } + else { + a.className = 'q1'; + } + a.href = '?' + g_types[sm.t] + '=' + sm.ti; + a.style.whiteSpace = 'nowrap'; + + if (sm.icon) { + a.className += ' icontiny tinyspecial'; + a.style.backgroundImage = 'url("' + g_staticUrl + '/images/wow/icons/tiny/' + sm.icon.toLowerCase() + '.gif")'; + } + + $WH.ae(a, $WH.ct(sm.n)); + $WH.ae(td, a); + } + else { + $WH.ae(td, $WH.ct(Listview.funcBox.getUpperSource(spell.source[0], sm))); + } + + var ls = Listview.funcBox.getLowerSource(spell.source[0], sm, type); + + if (this.iconSize != 0 && ls != null) { + var div = $WH.ce('div'); + div.className = 'small2'; + + if (ls.pretext) { + $WH.ae(div, $WH.ct(ls.pretext)); + } + + if (ls.url) { + var a = $WH.ce('a'); + a.className = 'q1'; + a.href = ls.url; + $WH.ae(a, $WH.ct(ls.text)); + $WH.ae(div, a); + } + else { + $WH.ae(div, $WH.ct(ls.text)); + } + + if (ls.posttext) { + $WH.ae(div, $WH.ct(ls.posttext)); + } + + $WH.ae(td, div); } - buff += g_sources[spell.source[i]]; } + else { + var buff = ''; + for (var i = 0, len = spell.source.length; i < len; ++i) { + if (i > 0) { + buff += LANG.comma; + } + buff += g_sources[spell.source[i]]; + } + } + return buff; } }, + getVisibleText: function(spell) { + if (spell.source != null) { + if (spell.source.length == 1) { + var buff = ''; + + var sm = (spell.sourcemore ? spell.sourcemore[0] : {}); + var type = 0; + + if (sm.t) { + type = sm.t; + buff += ' ' + sm.n; + } + else { + buff += ' ' + Listview.funcBox.getUpperSource(spell.source[0], sm); + } + + var ls = Listview.funcBox.getLowerSource(spell.source[0], sm, type); + + if (ls != null) { + if (ls.pretext) { + buff += ' ' + ls.pretext; + } + + buff += ' ' + ls.text; + + if (ls.posttext) { + buff += ' ' + ls.posttext; + } + } + + return buff; + } + else { + return Listview.funcBox.arrayText(spell.source, g_sources); + } + } + }, sortFunc: function(a, b, col) { return Listview.funcBox.assocArrCmp(a.source, b.source, g_sources); }