From bf184e7555ed5ad5a2bb838b3fbf4376ec3a16c1 Mon Sep 17 00:00:00 2001 From: Sarjuuk Date: Thu, 20 Jun 2024 16:32:20 +0200 Subject: [PATCH] Core/Setup * rewritten to be able to dynamicly load it's components - CLISetup -> checks for UtilityScripts (config, setup, dbc reader, etc.) -> checks for SetupScripts (individual sql/file generators) - each step may now have a help prompt attached. If none are provided, the containing script may provide it's help. - all Scripts are self contained modules. No more editing of 3+ files if some component is added/removed * removed intermediaries FileGen & SqlGen * functional changes - allow providing CLI arguments to siteconfig and account UtilityScript and skip the interactive prompts - set slot for consumable enchantment items so they are filtrable - zones dataset is now localized and generated from GlobalStrings.lua and DungeonMap.dbc. Related data dumps removed. - 'aowow' and 'prQueue' executables now have shebangs WARNING - command line options have been renamed! --- aowow | 14 +- datasets/zones | 46 - includes/ajaxHandler/data.class.php | 2 +- includes/basetype.class.php | 2 +- includes/game.php | 7 - localization/locale_dede.php | 38 - localization/locale_enus.php | 38 - localization/locale_eses.php | 38 - localization/locale_frfr.php | 38 - localization/locale_ruru.php | 38 - localization/locale_zhcn.php | 38 - prQueue | 1 + setup/setup.php | 56 +- setup/tools/CLISetup.class.php | 540 ++++++-- setup/tools/clisetup/account.func.php | 73 -- setup/tools/clisetup/account.us.php | 130 ++ setup/tools/clisetup/build.func.php | 102 -- setup/tools/clisetup/datagen.us.php | 159 +++ .../clisetup/{dbc.func.php => dbc.us.php} | 73 +- setup/tools/clisetup/dbconfig.func.php | 207 ---- setup/tools/clisetup/dbconfig.us.php | 327 +++++ setup/tools/clisetup/filegen.us.php | 173 +++ setup/tools/clisetup/setup.func.php | 376 ------ setup/tools/clisetup/setup.us.php | 184 +++ setup/tools/clisetup/siteconfig.func.php | 315 ----- setup/tools/clisetup/siteconfig.us.php | 468 +++++++ setup/tools/clisetup/sql.func.php | 68 -- setup/tools/clisetup/sync.func.php | 59 - setup/tools/clisetup/sync.us.php | 104 ++ setup/tools/clisetup/update.func.php | 99 -- setup/tools/clisetup/update.us.php | 121 ++ setup/tools/fileGen.class.php | 243 ---- setup/tools/filegen/complexImg.func.php | 749 ------------ setup/tools/filegen/demo.ss.php | 22 + .../{enchants.func.php => enchants.ss.php} | 155 +-- setup/tools/filegen/gems.func.php | 104 -- setup/tools/filegen/gems.ss.php | 101 ++ .../{glyphs.func.php => glyphs.ss.php} | 54 +- setup/tools/filegen/img-artwork.ss.php | 130 ++ setup/tools/filegen/img-maps.ss.php | 1082 +++++++++++++++++ setup/tools/filegen/img-talentcalc.ss.php | 110 ++ setup/tools/filegen/itemScaling.func.php | 114 -- setup/tools/filegen/itemscaling.ss.php | 136 +++ .../{itemsets.func.php => itemsets.ss.php} | 70 +- .../{locales.func.php => locales.ss.php} | 20 +- setup/tools/filegen/markup.ss.php | 22 + setup/tools/filegen/pets.func.php | 97 -- setup/tools/filegen/pets.ss.php | 95 ++ setup/tools/filegen/profiler.func.php | 473 ------- setup/tools/filegen/profiler.ss.php | 447 +++++++ setup/tools/filegen/realmMenu.func.php | 72 -- setup/tools/filegen/realmmenu.ss.php | 85 ++ setup/tools/filegen/realms.func.php | 47 - setup/tools/filegen/realms.ss.php | 56 + setup/tools/filegen/searchbox.ss.php | 22 + setup/tools/filegen/searchplugin.ss.php | 23 + setup/tools/filegen/simpleImg.func.php | 480 -------- setup/tools/filegen/simpleimg.ss.php | 376 ++++++ ...{soundfiles.func.php => soundfiles.ss.php} | 39 +- setup/tools/filegen/statistics.func.php | 201 --- setup/tools/filegen/statistics.ss.php | 209 ++++ setup/tools/filegen/talentCalc.func.php | 211 ---- setup/tools/filegen/talentIcons.func.php | 102 -- setup/tools/filegen/talentcalc.ss.php | 192 +++ setup/tools/filegen/talenticons.ss.php | 107 ++ setup/tools/filegen/tooltips.ss.php | 28 + ...tPresets.func.php => weightpresets.ss.php} | 19 +- setup/tools/setupScript.class.php | 463 ++++++- setup/tools/sqlGen.class.php | 256 ---- setup/tools/sqlgen/achievement.func.php | 132 -- setup/tools/sqlgen/achievement.ss.php | 138 +++ .../tools/sqlgen/achievementcriteria.func.php | 18 - setup/tools/sqlgen/achievementcriteria.ss.php | 18 + ...reatrigger.func.php => areatrigger.ss.php} | 33 +- .../{classes.func.php => classes.ss.php} | 10 +- setup/tools/sqlgen/creature.func.php | 185 --- setup/tools/sqlgen/creature.ss.php | 146 +++ setup/tools/sqlgen/currencies.func.php | 84 -- setup/tools/sqlgen/currencies.ss.php | 73 ++ ...linedword.func.php => declinedword.ss.php} | 13 +- ...{dungeonmap.func.php => dungeonmap.ss.php} | 2 +- .../sqlgen/{emotes.func.php => emotes.ss.php} | 190 ++- setup/tools/sqlgen/events.func.php | 51 - setup/tools/sqlgen/events.ss.php | 48 + setup/tools/sqlgen/factions.func.php | 105 -- setup/tools/sqlgen/factions.ss.php | 94 ++ setup/tools/sqlgen/factiontemplate.func.php | 41 - setup/tools/sqlgen/glyphproperties.func.php | 27 - setup/tools/sqlgen/glyphproperties.ss.php | 30 + setup/tools/sqlgen/holidays.func.php | 39 - setup/tools/sqlgen/holidays.ss.php | 36 + .../sqlgen/{icons.func.php => icons.ss.php} | 26 +- setup/tools/sqlgen/itemenchantment.func.php | 43 - setup/tools/sqlgen/itemenchantment.ss.php | 42 + ...nc.php => itemenchantmentcondition.ss.php} | 2 +- ...dcost.func.php => itemextendedcost.ss.php} | 2 +- ...gory.func.php => itemlimitcategory.ss.php} | 2 +- ...hant.func.php => itemrandomenchant.ss.php} | 19 +- ...s.func.php => itemrandomproppoints.ss.php} | 2 +- setup/tools/sqlgen/items.func.php | 265 ---- setup/tools/sqlgen/items.ss.php | 266 ++++ .../{itemset.func.php => itemset.ss.php} | 19 +- .../{item_stats.func.php => itemstats.ss.php} | 22 +- .../sqlgen/{lock.func.php => lock.ss.php} | 2 +- setup/tools/sqlgen/mailtemplate.func.php | 68 -- setup/tools/sqlgen/mailtemplate.ss.php | 55 + setup/tools/sqlgen/objects.func.php | 123 -- setup/tools/sqlgen/objects.ss.php | 96 ++ setup/tools/sqlgen/pet.func.php | 128 -- setup/tools/sqlgen/pet.ss.php | 109 ++ setup/tools/sqlgen/quests.func.php | 226 ---- setup/tools/sqlgen/quests.ss.php | 198 +++ ...tartend.func.php => questsstartend.ss.php} | 26 +- .../sqlgen/{races.func.php => races.ss.php} | 25 +- ...unc.php => scalingstatdistribution.ss.php} | 2 +- ...lues.func.php => scalingstatvalues.ss.php} | 2 +- setup/tools/sqlgen/shapeshiftforms.func.php | 36 - setup/tools/sqlgen/shapeshiftforms.ss.php | 35 + .../{skillline.func.php => skillline.ss.php} | 52 +- ...ility.func.php => skilllineability.ss.php} | 2 +- ...ndemitter.func.php => soundemitter.ss.php} | 2 +- setup/tools/sqlgen/sounds.func.php | 453 ------- setup/tools/sqlgen/sounds.ss.php | 385 ++++++ .../sqlgen/{source.func.php => source.ss.php} | 442 +++---- .../sqlgen/{spawns.func.php => spawns.ss.php} | 101 +- setup/tools/sqlgen/spell.func.php | 756 ------------ setup/tools/sqlgen/spell.ss.php | 732 +++++++++++ ...iculty.func.php => spelldifficulty.ss.php} | 9 +- ...bject.func.php => spellfocusobject.ss.php} | 2 +- ...override.func.php => spelloverride.ss.php} | 2 +- ...{spellrange.func.php => spellrange.ss.php} | 2 +- ...riables.func.php => spellvariables.ss.php} | 2 +- setup/tools/sqlgen/talents.func.php | 44 - setup/tools/sqlgen/talents.ss.php | 44 + setup/tools/sqlgen/taxi.func.php | 175 --- setup/tools/sqlgen/taxi.ss.php | 147 +++ setup/tools/sqlgen/titles.func.php | 103 -- setup/tools/sqlgen/titles.ss.php | 100 ++ ...category.func.php => totemcategory.ss.php} | 2 +- ...ldmaparea.func.php => worldmaparea.ss.php} | 2 +- setup/tools/sqlgen/zones.func.php | 224 ---- setup/tools/sqlgen/zones.ss.php | 185 +++ setup/tools/utilityScript.class.php | 85 ++ static/js/locale_dede.js | 39 +- static/js/locale_enus.js | 39 +- static/js/locale_eses.js | 39 +- static/js/locale_frfr.js | 39 +- static/js/locale_ruru.js | 39 +- static/js/locale_zhcn.js | 39 +- 149 files changed, 9619 insertions(+), 9168 deletions(-) delete mode 100644 datasets/zones delete mode 100644 setup/tools/clisetup/account.func.php create mode 100644 setup/tools/clisetup/account.us.php delete mode 100644 setup/tools/clisetup/build.func.php create mode 100644 setup/tools/clisetup/datagen.us.php rename setup/tools/clisetup/{dbc.func.php => dbc.us.php} (56%) delete mode 100644 setup/tools/clisetup/dbconfig.func.php create mode 100644 setup/tools/clisetup/dbconfig.us.php create mode 100644 setup/tools/clisetup/filegen.us.php delete mode 100644 setup/tools/clisetup/setup.func.php create mode 100644 setup/tools/clisetup/setup.us.php delete mode 100644 setup/tools/clisetup/siteconfig.func.php create mode 100644 setup/tools/clisetup/siteconfig.us.php delete mode 100644 setup/tools/clisetup/sql.func.php delete mode 100644 setup/tools/clisetup/sync.func.php create mode 100644 setup/tools/clisetup/sync.us.php delete mode 100644 setup/tools/clisetup/update.func.php create mode 100644 setup/tools/clisetup/update.us.php delete mode 100644 setup/tools/fileGen.class.php delete mode 100644 setup/tools/filegen/complexImg.func.php create mode 100644 setup/tools/filegen/demo.ss.php rename setup/tools/filegen/{enchants.func.php => enchants.ss.php} (74%) delete mode 100644 setup/tools/filegen/gems.func.php create mode 100644 setup/tools/filegen/gems.ss.php rename setup/tools/filegen/{glyphs.func.php => glyphs.ss.php} (68%) create mode 100644 setup/tools/filegen/img-artwork.ss.php create mode 100644 setup/tools/filegen/img-maps.ss.php create mode 100644 setup/tools/filegen/img-talentcalc.ss.php delete mode 100644 setup/tools/filegen/itemScaling.func.php create mode 100644 setup/tools/filegen/itemscaling.ss.php rename setup/tools/filegen/{itemsets.func.php => itemsets.ss.php} (66%) rename setup/tools/filegen/{locales.func.php => locales.ss.php} (82%) create mode 100644 setup/tools/filegen/markup.ss.php delete mode 100644 setup/tools/filegen/pets.func.php create mode 100644 setup/tools/filegen/pets.ss.php delete mode 100644 setup/tools/filegen/profiler.func.php create mode 100644 setup/tools/filegen/profiler.ss.php delete mode 100644 setup/tools/filegen/realmMenu.func.php create mode 100644 setup/tools/filegen/realmmenu.ss.php delete mode 100644 setup/tools/filegen/realms.func.php create mode 100644 setup/tools/filegen/realms.ss.php create mode 100644 setup/tools/filegen/searchbox.ss.php create mode 100644 setup/tools/filegen/searchplugin.ss.php delete mode 100644 setup/tools/filegen/simpleImg.func.php create mode 100644 setup/tools/filegen/simpleimg.ss.php rename setup/tools/filegen/{soundfiles.func.php => soundfiles.ss.php} (57%) delete mode 100644 setup/tools/filegen/statistics.func.php create mode 100644 setup/tools/filegen/statistics.ss.php delete mode 100644 setup/tools/filegen/talentCalc.func.php delete mode 100644 setup/tools/filegen/talentIcons.func.php create mode 100644 setup/tools/filegen/talentcalc.ss.php create mode 100644 setup/tools/filegen/talenticons.ss.php create mode 100644 setup/tools/filegen/tooltips.ss.php rename setup/tools/filegen/{weightPresets.func.php => weightpresets.ss.php} (67%) delete mode 100644 setup/tools/sqlGen.class.php delete mode 100644 setup/tools/sqlgen/achievement.func.php create mode 100644 setup/tools/sqlgen/achievement.ss.php delete mode 100644 setup/tools/sqlgen/achievementcriteria.func.php create mode 100644 setup/tools/sqlgen/achievementcriteria.ss.php rename setup/tools/sqlgen/{areatrigger.func.php => areatrigger.ss.php} (60%) rename setup/tools/sqlgen/{classes.func.php => classes.ss.php} (86%) delete mode 100644 setup/tools/sqlgen/creature.func.php create mode 100644 setup/tools/sqlgen/creature.ss.php delete mode 100644 setup/tools/sqlgen/currencies.func.php create mode 100644 setup/tools/sqlgen/currencies.ss.php rename setup/tools/sqlgen/{declinedword.func.php => declinedword.ss.php} (54%) rename setup/tools/sqlgen/{dungeonmap.func.php => dungeonmap.ss.php} (79%) rename setup/tools/sqlgen/{emotes.func.php => emotes.ss.php} (81%) delete mode 100644 setup/tools/sqlgen/events.func.php create mode 100644 setup/tools/sqlgen/events.ss.php delete mode 100644 setup/tools/sqlgen/factions.func.php create mode 100644 setup/tools/sqlgen/factions.ss.php delete mode 100644 setup/tools/sqlgen/factiontemplate.func.php delete mode 100644 setup/tools/sqlgen/glyphproperties.func.php create mode 100644 setup/tools/sqlgen/glyphproperties.ss.php delete mode 100644 setup/tools/sqlgen/holidays.func.php create mode 100644 setup/tools/sqlgen/holidays.ss.php rename setup/tools/sqlgen/{icons.func.php => icons.ss.php} (50%) delete mode 100644 setup/tools/sqlgen/itemenchantment.func.php create mode 100644 setup/tools/sqlgen/itemenchantment.ss.php rename setup/tools/sqlgen/{itemenchantmentcondition.func.php => itemenchantmentcondition.ss.php} (81%) rename setup/tools/sqlgen/{itemextendedcost.func.php => itemextendedcost.ss.php} (80%) rename setup/tools/sqlgen/{itemlimitcategory.func.php => itemlimitcategory.ss.php} (80%) rename setup/tools/sqlgen/{itemrandomenchant.func.php => itemrandomenchant.ss.php} (56%) rename setup/tools/sqlgen/{itemrandomproppoints.func.php => itemrandomproppoints.ss.php} (80%) delete mode 100644 setup/tools/sqlgen/items.func.php create mode 100644 setup/tools/sqlgen/items.ss.php rename setup/tools/sqlgen/{itemset.func.php => itemset.ss.php} (94%) rename setup/tools/sqlgen/{item_stats.func.php => itemstats.ss.php} (83%) rename setup/tools/sqlgen/{lock.func.php => lock.ss.php} (78%) delete mode 100644 setup/tools/sqlgen/mailtemplate.func.php create mode 100644 setup/tools/sqlgen/mailtemplate.ss.php delete mode 100644 setup/tools/sqlgen/objects.func.php create mode 100644 setup/tools/sqlgen/objects.ss.php delete mode 100644 setup/tools/sqlgen/pet.func.php create mode 100644 setup/tools/sqlgen/pet.ss.php delete mode 100644 setup/tools/sqlgen/quests.func.php create mode 100644 setup/tools/sqlgen/quests.ss.php rename setup/tools/sqlgen/{quests_startend.func.php => questsstartend.ss.php} (70%) rename setup/tools/sqlgen/{races.func.php => races.ss.php} (59%) rename setup/tools/sqlgen/{scalingstatdistribution.func.php => scalingstatdistribution.ss.php} (81%) rename setup/tools/sqlgen/{scalingstatvalues.func.php => scalingstatvalues.ss.php} (80%) delete mode 100644 setup/tools/sqlgen/shapeshiftforms.func.php create mode 100644 setup/tools/sqlgen/shapeshiftforms.ss.php rename setup/tools/sqlgen/{skillline.func.php => skillline.ss.php} (59%) rename setup/tools/sqlgen/{skilllineability.func.php => skilllineability.ss.php} (79%) rename setup/tools/sqlgen/{soundemitter.func.php => soundemitter.ss.php} (79%) delete mode 100644 setup/tools/sqlgen/sounds.func.php create mode 100644 setup/tools/sqlgen/sounds.ss.php rename setup/tools/sqlgen/{source.func.php => source.ss.php} (50%) rename setup/tools/sqlgen/{spawns.func.php => spawns.ss.php} (55%) delete mode 100644 setup/tools/sqlgen/spell.func.php create mode 100644 setup/tools/sqlgen/spell.ss.php rename setup/tools/sqlgen/{spelldifficulty.func.php => spelldifficulty.ss.php} (73%) rename setup/tools/sqlgen/{spellfocusobject.func.php => spellfocusobject.ss.php} (80%) rename setup/tools/sqlgen/{spelloverride.func.php => spelloverride.ss.php} (80%) rename setup/tools/sqlgen/{spellrange.func.php => spellrange.ss.php} (79%) rename setup/tools/sqlgen/{spellvariables.func.php => spellvariables.ss.php} (80%) delete mode 100644 setup/tools/sqlgen/talents.func.php create mode 100644 setup/tools/sqlgen/talents.ss.php delete mode 100644 setup/tools/sqlgen/taxi.func.php create mode 100644 setup/tools/sqlgen/taxi.ss.php delete mode 100644 setup/tools/sqlgen/titles.func.php create mode 100644 setup/tools/sqlgen/titles.ss.php rename setup/tools/sqlgen/{totemcategory.func.php => totemcategory.ss.php} (79%) rename setup/tools/sqlgen/{worldmaparea.func.php => worldmaparea.ss.php} (79%) delete mode 100644 setup/tools/sqlgen/zones.func.php create mode 100644 setup/tools/sqlgen/zones.ss.php create mode 100644 setup/tools/utilityScript.class.php diff --git a/aowow b/aowow index 050ed6a2e..50715309c 100755 --- a/aowow +++ b/aowow @@ -1,12 +1,12 @@ +#!/usr/bin/env php diff --git a/datasets/zones b/datasets/zones deleted file mode 100644 index e6766d539..000000000 --- a/datasets/zones +++ /dev/null @@ -1,46 +0,0 @@ -Mapper.multiLevelZones = { - 206: ['206-1', '206-2', '206-3'], - 209: ['209-1', '209-2', '209-3', '209-4', '209-5', '209-6', '209-7'], - 616: ['616-1', '616_1', '616_2'], - 719: ['719-1', '719-2', '719-3'], - 721: ['721-1', '721-2', '721-3', '721-4'], - 796: ['796-1', '796-2', '796-3', '796-4'], - 1196: ['1196-1', '1196-2'], - 1337: ['1337-1', '1337-2'], - 1581: ['1581-1', '1581-2'], - 1583: ['1583-1', '1583-2', '1583-3', '1583-4', '1583-5', '1583-6', '1583-7'], - 1584: ['1584-1', '1584-2'], - 2017: ['2017-1', '2017-2'], - 2057: ['2057-1', '2057-2', '2057-3', '2057-4'], - 2100: ['2100-1', '2100-2'], - 2557: ['2557-1', '2557-2', '2557-3', '2557-4', '2557-5', '2557-6'], - 2677: ['2677-1', '2677-2', '2677-3', '2677-4'], - 3959: ['3959', '3959-1', '3959-2', '3959-3', '3959-4', '3959-5', '3959-6', '3959-7'], - 3428: ['3428-1', '3428-2', '3428-3'], - 3456: ['3456-1', '3456-2', '3456-3', '3456-4', '3456-5', '3456-6'], - 3457: ['3457-1', '3457-2', '3457-3', '3457-4', '3457-5', '3457-6', '3457-7', '3457-8', '3457-9', '3457-10', '3457-11', '3457-12', '3457-13', '3457-14', '3457-15', '3457-16', '3457-17'], - 3477: ['3477-1', '3477-2', '3477-3'], - 3715: ['3715-1', '3715-2'], - 3790: ['3790-1', '3790-2'], - 3791: ['3791-1', '3791-2'], - 3848: ['3848-1', '3848-2', '3848-3'], - 3849: ['3849-1', '3849-2'], - 4075: ['4075', '4075-1'], - 4100: ['4100-1', '4100-2'], - 4131: ['4131-1', '4131-2'], - 4196: ['4196-1', '4196-2'], - 4228: ['4228-1', '4228-2', '4228-3', '4228-4'], - 4272: ['4272-1', '4272-2'], - 4273: ['4273-0', '4273-1', '4273-2', '4273-3', '4273-4', '4273-5'], - 4277: ['4277-1', '4277-2', '4277-3'], - 4395: ['4395-1', '4395-2'], - 4494: ['4494-1', '4494-2'], - 4714: ['4714-1', '4714_1', '4714_2', '4714_3'], - 4722: ['4722-1', '4722-2'], - 4812: ['4812-1', '4812-2', '4812-3', '4812-4', '4812-5', '4812-6', '4812-7', '4812-8'], -}; - -/* -var g_zone_areas = {}; -in locale files -*/ \ No newline at end of file diff --git a/includes/ajaxHandler/data.class.php b/includes/ajaxHandler/data.class.php index 493484891..bc1f8adca 100644 --- a/includes/ajaxHandler/data.class.php +++ b/includes/ajaxHandler/data.class.php @@ -81,7 +81,6 @@ protected function handleData() : string break; // locale independant case 'quick-excludes': - case 'zones': case 'weight-presets': case 'item-scaling': case 'realms': @@ -102,6 +101,7 @@ protected function handleData() : string case 'enchants': case 'itemsets': case 'pets': + case 'zones': if (!Util::loadStaticFile($set, $result, true) && Cfg::get('DEBUG')) $result .= "alert('could not fetch static data: ".$set." for locale: ".User::$localeString."');"; diff --git a/includes/basetype.class.php b/includes/basetype.class.php index fde42231e..10a378147 100644 --- a/includes/basetype.class.php +++ b/includes/basetype.class.php @@ -671,7 +671,7 @@ private function createFullSpawns() // for display on map (o $floors = []; foreach ($points as $p) { - if (isset(Game::$areaFloors[$p['areaId']])) + if ($p['floor']) $floors[$p['areaId']][] = $p['floor']; if (isset($menu[$p['areaId']])) diff --git a/includes/game.php b/includes/game.php index eb2edc72b..2754a88fd 100644 --- a/includes/game.php +++ b/includes/game.php @@ -140,13 +140,6 @@ class Game null, 4, 10, 9, 8, 6, 15, 11, 3, 5, null, 7 ); - public static $areaFloors = array( - 206 => 3, 209 => 7, 719 => 3, 721 => 4, 796 => 4, 1196 => 2, 1337 => 2, 1581 => 2, 1583 => 7, 1584 => 2, - 2017 => 2, 2057 => 4, 2100 => 2, 2557 => 6, 2677 => 4, 3428 => 3, 3457 => 17, 3790 => 2, 3791 => 2, 3959 => 8, - 3456 => 6, 3715 => 2, 3848 => 3, 3849 => 2, 4075 => 2, 4100 => 2, 4131 => 2, 4196 => 2, 4228 => 4, 4272 => 2, - 4273 => 6, 4277 => 3, 4395 => 2, 4494 => 2, 4722 => 2, 4812 => 8 - ); - public static function sideByRaceMask($race) { // Any diff --git a/localization/locale_dede.php b/localization/locale_dede.php index 2abb9f164..cbb8bc7fa 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -1188,44 +1188,6 @@ 'cat' => array( "Östliche Königreiche", "Kalimdor", "Dungeons", "Schlachtzüge", "Unbenutzt", null, "Schlachtfelder", null, "Scherbenwelt", "Arenen", "Nordend" - ), - 'floors' => array( - 206 => ["Vorbereitung der Norndir", "Aufstieg der Drachenschinder", "Tyrs Terrasse"], - 209 => ["Der Hof", "Speisesaal", "Die Verwaiste Höhle", "Das Tiefere Observatorium", "Das Obere Observatorium", "Lord Godfreys Kammer", "Der Wehrgang"], - 719 => ["Der Teich von Ask'Ar", "Mondschreinsanktum", "Der Vergessene Teich"], - 721 => ["Die Halle der Zahnräder", "Der Schlafsaal", "Startrampe", "Tüftlerhof"], - 796 => ["Friedhof", "Bibliothek", "Waffenkammer", "Kathedrale"], - 1196 => ["Untere Spitze", "Obere Spitze"], - 1337 => ["Halle der Bewahrer", "Khaz'goroths Sitz"], - 1581 => ["Die Todesminen", "Eiserne Bucht"], - 1583 => ["Tazz'Alaor", "Listspinnertunnel", "Hordemar", "Schwarzfausthalle", "Drachenspitzhalle", "Der Krähenhorst", "Schwarzfelsstadion"], - 1584 => ["Gefängnisblock", "Die Schattenschmiede"], - 2017 => ["Kreuzzüglerplatz", "Der Spießrutenlauf"], - 2057 => ["Das Reliquiarium", "Kammer der Beschwörung", "Das Arbeitszimmer des Direktors", "Familiengruft der Barovs"], - 2100 => ["Höhlen von Maraudon", "Zaetars Grab"], - 2557 => ["Gordokhallen", "Hauptstadtgärten", "Hof der Hochgeborenen", "Das Gefängnis von Immol'thar", "Wucherborkenviertel", "Der Schrein von Eldretharr"], - 2677 => ["Garnison des Drachenmals", "Hallen des Zwists", "Die Blutroten Labore", "Nefarians Unterschlupf"], - 3428 => ["Untergrund des Schwarmbaus", "Die Tempeltore", "Höhle von C'Thun"], - 3457 => ["Bedienstetenunterkünfte", "Obere Nobelställe", "Der Bankettsaal", "Die Gästezimmer", "Balkon des Opernsaals", "Die Terrasse des Meisters", "Untere Eingestürzte Treppe", "Obere Eingestürzte Treppe", "Die Menagerie", "Bibliothek des Wächters", "Das Warenlager", "Obere Bibliothek", "Die Himmelswacht", "Halle der Spiele", "Medivhs Gemächer", "Die Energiekammer", "Netherraum"], - 3790 => ["Hallen des Jenseits", "Brücke der Seelen"], - 3791 => ["Sethekkversteck", "Hallen der Trauer"], - 3959 => ["Ausbildungsgelände der Illidari", "Kanäle von Karabor", "Zuflucht der Schatten", "Hallen der Pein", "Blutschattens Wacht", "Hof der Irdischen Gelüste", "Kommandoraum", "Tempelspitze"], - 3456 => ["Das Konstruktviertel", "Das Arachnidenviertel", "Das Militärviertel", "Das Seuchenviertel", "Übersicht", "Frostwyrmhort"], - 3715 => ["Die Dampfkammer", "Die Kühlteiche"], - 3848 => ["Stasisblock: Trion", "Stasisblock: Maximus", "Eindämmungskern"], - 3849 => ["Die Mechanar", "Berechnungskammer"], - 4075 => ["Sonnenbrunnenplateau", "Schrein der Finsternis"], - 4100 => ["Außerhalb von Stratholme", "Stratholme"], - 4131 => ["Zuflucht des Großmagisters", "Beobachtungsplatz"], - 4196 => ["Die Vorhallen von Drak'Tharon", "Aussichtspunkt von Drak'Tharon"], - 4228 => ["Band der Varianz", "Band der Akzeleration", "Band der Transmutation", "Band der Angleichung"], - 4272 => ["Die unnachgiebige Garnison", "Straße der Schöpfer"], - 4273 => ["Der große Vorstoß", "Die Vorkammer von Ulduar", "Das innere Sanktum von Ulduar", "Das Gefängnis von Yogg-Saron", "Der Funke der Imagination", "Das Gedankenauge"], - 4277 => ["Die Brutgrube", "Hadronox' Hort", "Das vergoldete Tor"], - 4395 => ["Dalaran", "Die Schattenseite"], - 4494 => ["Ahn'kahet", "2. Stockwerk"], - 4722 => ["Kolosseum der Kreuzfahrer", "Die eisigen Tiefen"], - 4812 => ["Die untere Zitadelle", "Das Schädelbollwerk", "Dom des Todbringers", "Hort der Frostkönigin", "Der obere Bereich", "Königliche Quartiere", "Der Frostthron", "Frostgram"] ) ), 'quest' => array( diff --git a/localization/locale_enus.php b/localization/locale_enus.php index 6c125e93d..832188af0 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -1189,44 +1189,6 @@ 'cat' => array( "Eastern Kingdoms", "Kalimdor", "Dungeons", "Raids", "Unused", null, "Battlegrounds", null, "Outland", "Arenas", "Northrend" - ), - 'floors' => array( - 206 => ["Norndir Preparation", "Dragonflayer Ascent", "Tyr's Terrace"], - 209 => ["The Courtyard", "Dining Hall", "The Vacant Den", "Lower Observatory", "Upper Observatory", "Lord Godfrey's Chamber", "The Wall Walk"], - 719 => ["The Pool of Ask'Ar", "Moonshrine Sanctum", "The Forgotten Pool"], - 721 => ["The Hall of Gears", "The Dormitory", "Launch Bay", "Tinkers' Court"], - 796 => ["Graveyard", "Library", "Armory", "Cathedral"], - 1196 => ["Lower Pinnacle", "Upper Pinnacle"], - 1337 => ["Hall of the Keepers", "Khaz'Goroth's Seat"], - 1581 => ["The Deadmines", "Ironclad Cove"], - 1583 => ["Tazz'Alaor", "Skitterweb Tunnels", "Hordemar City", "Hall of Blackhand", "Dragonspire Hall", "The Rookery", "Blackrock Stadium"], - 1584 => ["Detention Block", "Shadowforge City"], - 2017 => ["Crusader's Square", "The Gauntlet"], - 2057 => ["The Reliquary", "Chamber of Summoning", "The Headmaster's Study", "Barov Family Vault"], - 2100 => ["Caverns of Maraudon", "Zaetar's Grave"], - 2557 => ["Gordok Commons", "Capital Gardens", "Court of the Highborne", "Prison of Immol'Thar", "Warpwood Quarter", "The Shrine of Eldretharr"], - 2677 => ["Dragonmaw Garrison", "Halls of Strife", "Crimson Laboratories", "Nefarian's Lair"], - 3428 => ["The Hive Undergrounds", "The Temple Gates", "Vault of C'Thun"], - 3456 => ["The Construct Quarter", "The Arachnid Quarter", "The Military Quarter", "The Plague Quarter", "Overview", "Frostwyrm Lair"], - 3457 => ["Servant's Quarters", "Upper Livery Stables", "The Banquet Hall", "The Guest Chambers", "Opera Hall Balcony", "Master's Terrace", "Lower Broken Stair", "Upper Broken Stair", "The Menagerie", "Guardian's Library", "The Repository", "Upper Library", "The Celestial Watch", "Gamesman's Hall", "Medivh's Chambers", "The Power Station", "Netherspace"], - 3715 => ["The Steamvault", "The Cooling Pools"], - 3790 => ["Halls of the Hereafter", "Bridge of Souls"], - 3791 => ["Veil Sethekk", "Halls of Mourning"], - 3848 => ["Stasis Block: Trion", "Stasis Block: Maximus", "Containment Core"], - 3849 => ["The Mechanar", "Calculation Chamber"], - 3959 => ["Illidari Training Grounds", "Karabor Sewers", "Sanctuary of Shadows", "Halls of Anguish", "Gorefiend's Vigil", "Den of Mortal Delights", "Chamber of Command", "Temple Summit"], - 4075 => ["Sunwell Plateau", "Shrine of the Eclipse"], - 4100 => ["Outside Stratholme", "Stratholme City"], - 4131 => ["Grand Magister's Asylum", "Observation Grounds"], - 4196 => ["The Vestibules of Drak'Tharon", "Drak'Tharon Overlook"], - 4228 => ["Band of Variance", "Band of Acceleration", "Band of Transmutation", "Band of Alignment"], - 4272 => ["Unyielding Garrison", "Walk of the Makers"], - 4273 => ["The Grand Approach", "The Antechamber of Ulduar", "The Inner Sanctum of Ulduar", "The Prison of Yogg-Saron", "The Spark of Imagination", "The Mind's Eye"], - 4277 => ["The Brood Pit", "Hadronox's Lair", "The Gilded Gate"], - 4395 => ["Dalaran City", "The Underbelly"], - 4494 => ["Ahn'Kahet", "Level 2"], - 4722 => ["Crusaders' Coliseum", "The Icy Depths"], - 4812 => ["The Lower Citadel", "The Rampart of Skulls", "Deathbringer's Rise", "The Frost Queen's Lair", "The Upper Reaches", "Royal Quarters", "The Frozen Throne", "Frostmourne"] ) ), 'quest' => array( diff --git a/localization/locale_eses.php b/localization/locale_eses.php index 7cd357d7a..b09e3133c 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -1188,44 +1188,6 @@ 'cat' => array( "Reinos del Este", "Kalimdor", "Mazmorras", "Bandas", "No las uso", null, "Campos de batalla", null, "Terrallende", "Arenas", "Rasganorte" - ), - 'floors' => array( - 206 => ["Preparación Norndir", "Ascenso de los Desuelladragones", "Bancal de Tyr"], - 209 => ["El Patio", "Comedor", "El Cubil Vacío", "Observatorio inferior", "Observatorio superior", "Cámara de Lord Godfrey", "El Camino de la Muralla"], - 719 => ["La Alberca de Ask'ar", "Sagrario Lunar", "Las Charcas del Olvido"], - 721 => ["La Sala de Máquinas", "Los Dormitorios", "Aeropuerto", "Cámara Manitas"], - 796 => ["[Cementerio]", "[Biblioteca]", "[Armería]", "[Catedral]"], - 1196 => ["Pináculo inferior", "Pináculo superior"], - 1337 => ["Sala de los Guardianes", "Trono de Khaz'goroth"], - 1581 => ["Las Minas de la Muerte", "Cala del Acorazado"], - 1583 => ["Tazz'Alaor", "Túneles de Arácnidas", "Ciudad Hordemar", "Sala de Puño Negro", "Sala Dracopico", "El Grajero", "Estadio de Roca Negra"], - 1584 => ["Bloque de Detención", "Ciudad Forjatiniebla"], - 2017 => ["Plaza de los Cruzados", "El Guantelete"], - 2057 => ["El Relicario", "Cámara de la Invocación", "Sala Rectoral", "[Barov Family Vault]"], - 2100 => ["Cavernas de Maraudon", "Tumba de Zaetar"], - 2557 => ["Ágora de Gordok", "Jardines de la Capital", "Corte de los Altonato", "Prisión de Immol'thar", "Barrio Alabeo", "Santuario de Eldretharr"], - 2677 => ["Cuartel Faucedraco", "Salas de los Conflictos", "Laboratorios Carmesí", "Guarida de Nefarian"], - 3428 => ["El Subterráneo de la Colmena", "Las Puertas del Templo", "Cámara de C'Thun"], - 3456 => ["El arrabal de los ensamblajes", "El arrabal arácnido", "El arrabal militar", "El arrabal de la peste", "La Necrópolis inferior", "La Necrópolis superior"], - 3457 => ["Alcobas de los Sirvientes", "Caballerizas superiores", "La Sala de Banquetes", "Los Aposentos de los Invitados", "Balcón de la Sala de la Ópera", "El Bancal del Maestro", "La Escalera Quebrada inferior", "La Escalera Quebrada superior", "La Sala de las Fieras", "Biblioteca del Guardián", "El Repositorio", "La Biblioteca superior", "El Mirador Celestial", "Sala del Tablero", "Estancias de Medivh", "La Central Eléctrica", "Espacio Abisal"], - 3715 => ["La Cámara de Vapor", "Las Charcas Refrescantes"], - 3790 => ["Salas del Más Allá", "Puente de las Almas"], - 3791 => ["Velo Sethekk", "Salas del Luto"], - 3848 => ["Bloque de Estasis: Trion", "Bloque de Estasis: Maximus", "Pabellón de Aislamiento"], - 3849 => ["El Mechanar", "Estancias de Calculación"], - 3959 => ["Campo de entrenamiento Illidari", "Cloacas de Karabor", "Santuario de las Sombras", "Salas de Angustia", "Vigilia de Sanguino", "Guarida de los Placeres Mortales", "Cámara de Mando", "Cima del Templo"], - 4075 => ["Meseta de La Fuente del Sol", "Santuario del Eclipse"], - 4100 => ["El Camino a Stratholme", "Stratholme"], - 4131 => ["Asilo del Gran Magister", "Sector de Observación"], - 4196 => ["El vestíbulo de Drak'Tharon", "Centinela de Drak'Tharon"], - 4228 => ["Sortija de discrepancia", "Sortija de Aceleración", "Sortija de transmutación", "Sortija de alineación"], - 4272 => ["El Cuartel Implacable", "Camino de los Creadores"], - 4273 => ["El Gran Acceso", "La Antecámara de Ulduar", "El Sagrario Interior de Ulduar", "La Prisión de Yogg-Saron", "La Chispa de la Imaginación", "El Ojo de la Mente"], - 4277 => ["El Foso del Linaje", "Guarida de Hadronox", "La Puerta dorada"], - 4395 => ["Ciudad de Dalaran", "Los Bajos Fondos"], - 4494 => ["Ahn'kahet", "Nivel 2"], - 4722 => ["El Coliseo Argenta", "Las profundidades heladas"], - 4812 => ["La ciudadela inferior", "La Muralla de las Calaveras", "Ascenso del Libramorte", "La guarida de la Reina de Escarcha", "Los Confines superiores", "Cuarteles Reales", "El Trono Helado", "Agonía de Escarcha"] ) ), 'quest' => array( diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index 50333f030..d80619367 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -1188,44 +1188,6 @@ 'cat' => array( "Royaumes de l'est", "Kalimdor", "Donjons", "Raids", "Inutilisées", null, "Champs de bataille", null, "Outreterre", "Arènes", "Norfendre" - ), - 'floors' => array( - 206 => ["Préparation de Norndir", "Ascension d'Écorche-dragon", "Terrasse de Tyr"], - 209 => ["La cour", "Salle à manger", "Antre Vacant", "Observatoire Inférieur", "Observatoire Supérieur", "Chambre du seigneur Godfrey", "Le chemin de ronde"], - 719 => ["Le Bassin d'Ask'ar", "Sanctuaire d’Écrin-de-Lune", "Les bassins Oubliés"], - 721 => ["Le Hall des engrenages", "Le dortoir", "Baie de lancement", "Cour du Bricoleur"], - 796 => ["[Cimetière]", "[Bibliothèque]", "[Armurerie]", "[Cathédrale]"], - 1196 => ["Pinnacle inférieur", "Pinacle Supérieur"], - 1337 => ["Hall des Gardiens", "Siège de Khaz'goroth"], - 1581 => ["Les Mortemines", "Crique du Cuirassé"], - 1583 => ["Tazz'Alaor", "Tunnels de Toile-grouillante", "Cité d'Hordemar", "Hall de Main-noire", "Hall de la Flèche des dragons", "La colonie", "Stade Rochenoire"], - 1584 => ["Le mitard", "Ville des Ombreforges"], - 2017 => ["Place des Croisés", "Le Défi"], - 2057 => ["Le Reliquaire", "Chambre d'invocation", "Bureau du proviseur", "[Barov Family Vault]"], - 2100 => ["Cavernes de Maraudon", "Tombe de Zaetar"], - 2557 => ["Communs gordok", "Grands jardins", "Cours des Bien-nés", "Prison d'Immol'Thar", "Quartier de Crochebois", "Le sanctuaire d'Eldretharr"], - 2677 => ["Garnison des Gueules-de-dragon", "Halls des conflits", "Laboratoires Cramoisis", "Antre de Nefarian"], - 3428 => ["Les souterrains de la ruche", "Portes du Temple", "Caveau de C'Thun"], - 3456 => ["Le quartier des Assemblages", "Le Quartier des Arachnide", "Le Quartier Militaire", "Le quartier de la Peste", "La Nécropole Inférieure", "La Nécropole Supérieure"], - 3457 => ["Quartiers des serviteurs", "Écuries supérieures", "La salle de banquet", "Les Appartements des hôtes", "Balcon de l’Opéra", "Terrasse du maître", "Partie inférieure de l’Escalier brisé", "Partie supérieure de l’Escalier brisé", "La Ménagerie", "Bibliothèque du Gardien", "Le Dépôt", "Bibliothèque supérieure", "Le Guet céleste", "Hall du Flambeur", "Appartements de Medivh", "Centrale électrique", "Néantespace"], - 3715 => ["Le caveau de la Vapeur", "Les bassins de refroidissement"], - 3790 => ["Les salles de l’Après-vie", "Le pont des âmes"], - 3791 => ["Voile Sethekk", "Les salles du Deuil"], - 3848 => ["Bloc de stase : Trion", "Bloc de stase : Maximus", "Cœur de confinement"], - 3849 => ["Le Méchanar", "Chambre des Calculs"], - 3959 => ["Terrain d'entraînement Illidari", "Égouts de Karabor", "Sanctuaire des ombres", "Les salles de l’Angoisse", "Veillée de Fielsang", "Tanière des délices mortels", "Chambre de commandement", "Sommet du temple"], - 4075 => ["Plateau du Puits de soleil", "Sanctuaire de l’eclipse"], - 4100 => ["La Route de Stratholme", "Stratholme"], - 4131 => ["Asile du grand magistère", "Terrain d’observation"], - 4196 => ["Le Vestibule de Drak'Tharon", "Surplombe de Drak'Tharon"], - 4228 => ["Bande d'Écart", "Bande d'Accélération", "Bande de Transmutation", "Bande d'Alignement"], - 4272 => ["La garnison inflexible", "Promenade des Faiseurs"], - 4273 => ["Le Grand abord", "L'antichambre d'Ulduar", "Le sanctum intérieur d'Ulduar", "La prison de Yogg-Saron", "L'Étincelle d'imagination", "La Vue de l'esprit"], - 4277 => ["La Fosse des couvées", "Antre d'Hadronox", "La Porte de la Daurade"], - 4395 => ["Dalaran", "Les Entrailles"], - 4494 => ["Ahn'kahet", "Plancher 2"], - 4722 => ["L'colisée d'Argent", "Les Profondeurs Glacées"], - 4812 => ["La Citadelle Inférieure", "Le Rempart des Cranes", "Ascension de Porte-mort", "Le repaire de la reine du Givre", "Les étages supérieurs", "Quartiers Royaux", "Le Trône Gelé", "Deuillegivre"] ) ), 'quest' => array( diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index 19160aaf6..c76ad93c5 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -1188,44 +1188,6 @@ 'cat' => array( "Восточные королевства", "Калимдор", "Подземелья", "Рейды", "Неактивно", null, "Поля боя", null, "Запределье", "Арены", "Нордскол" - ), - 'floors' => array( - 206 => ["Подготовка Норндира", "Подъем Укротителей драконов", "Терраса Тира"], - 209 => ["Внутренний двор", "Обеденный зал", "Свободная берлога", "Нижняя обсерватория", "Верхняя обсерватория", "Палата лорда Годфри", "Крепостной вал"], - 719 => ["Пруд Аск'ара", "Алтарь святилища Луны", "Забытый пруд"], - 721 => ["Машинный зал", "Спальни", "Пусковая установка", "Двор Механиков"], - 796 => ["[Кладбище]", "[Библиотека]", "[Арсенал]", "[Собор]"], - 1196 => ["Подножие", "Вершина"], - 1337 => ["Зал Хранителей", "Трон Каз'горота"], - 1581 => ["Мертвые копи", "Потайная бухта"], - 1583 => ["Тазз'Алаор", "Паучий лабиринт", "Ордамар", "Зал Чернорука", "Зал Драконов", "Гнездовье", "Стадион Черной горы"], - 1584 => ["Тюремный блок", "Тенегорн"], - 2017 => ["Площадь рыцарей", "Улица Испытаний"], - 2057 => ["Хранилище реликвий", "Чертог Призыва", "Кабинет ректора", "[Barov Family Vault]"], - 2100 => ["Пещеры Мародона", "Могила Зейтара"], - 2557 => ["Палаты Гордока", "Центральный сад", "Двор высокорожденных", "Тюрьма Бессмер'тера", "Квартал Криводревов", "Святилище Элдретарра"], - 2677 => ["Гарнизон Драконьей Пасти", "Залы Раздора", "Багровые лаборатории", "Логово Нефариана"], - 3428 => ["Подземелье улья", "Ворота храма", "Обитель К'Туна"], - 3456 => ["Квартал Мерзости", "Паучий квартал", "Военный квартал", "Чумной квартал", "Нижний некрополь", "Верхний некрополь"], - 3457 => ["Комнаты cлуг", "Cтойла", "Пиршественный зал", "Гостевые комнаты", "Балкон в опере", "Терраса Мастера", "Низ разрушенной лестницы", "Верх разрушенной лестницы", "Галерея", "Библиотека Стража", "Хранилище", "Верхний ярус библиотеки", "Обсерватория", "Игровой зал", "Покои Медива", "Энергетический блок", "Пустомарь"], - 3715 => ["Паровое подземелье", "Охладительные резервуары"], - 3790 => ["Потусторонние залы", "Мост Душ"], - 3791 => ["Гнездовье Сетекк", "Залы Плача"], - 3848 => ["Изоляционная камера: Трион", "Изоляционная камера: Максимус", "Ядро Сдерживания"], - 3849 => ["Механар", "Комната Вычислений"], - 3959 => ["Campo de Treinamento Illidari", "Esgotos de Karabor", "Santuário das Sombras", "Salões da Angústia", "Vigia do Sanguinávido", "Covil dos Prazeres Mortais", "Câmara de Comando", "Ápice do Templo"], - 4075 => ["Плато Солнечного Колодца", "Святилище Затмения"], - 4100 => ["Дорога к Стратхольму", "Стратхольм"], - 4131 => ["Пристанище Великого Магистра", "Обзорная площадка"], - 4196 => ["Залы крепости Драк'Тарон", "Дозорное укрепление Драк'Тарона"], - 4228 => ["Кольцо отклонения", "Кольцо ускорения", "Кольцо трансмутации", "Кольцо управления"], - 4272 => ["Стойкий гарнизон", "Галерея Творцов"], - 4273 => ["Большой переход", "Вестибюль Ульдуара", "Внутреннее святилище Ульдуара", "Темница Йогг-Сарона", "Искра Воображения", "Око разума"], - 4277 => ["Родовая яма", "Логово Хадронокса", "Золоченые врата"], - 4395 => ["Даларан", "Клоака"], - 4494 => ["Ан'кахет", "Уровень 2"], - 4722 => ["Колизей Серебряного Авангарда", "Ледяные глубины"], - 4812 => ["Нижний ярус", "Черепной вал", "Подъем Смертоносного", "Логово Королевы Льда", "Верхний ярус", "Королевские палаты", "Ледяной Трон", "Ледяная Скорбь"] ) ), 'quest' => array( diff --git a/localization/locale_zhcn.php b/localization/locale_zhcn.php index 249f35c04..4d76f8ad0 100644 --- a/localization/locale_zhcn.php +++ b/localization/locale_zhcn.php @@ -1188,44 +1188,6 @@ 'cat' => array( "东部王国", "卡利姆多", "地下城", "团队副本", "未使用", null, "战场", null, "外域", "竞技场", "诺森德" - ), - 'floors' => array( - 206 => ["诺迪尔备战区", "掠龙氏族高台", "提尔之台"], - 209 => ["庭院", "饭厅", "空巢", "下层瞭望台", "上层瞭望台", "高弗雷勋爵的大厅", "城墙走道"], - 719 => ["阿斯卡之池", "月神圣地密室", "遗忘之池"], - 721 => ["齿轮大厅", "宿舍", "发射台", "工匠议会"], - 796 => ["墓地", "图书馆", "军械库", "大教堂"], - 1196 => ["尖塔下层", "尖塔上层"], - 1337 => ["守护者大厅", "卡兹格罗斯之座"], - 1581 => ["死亡矿井", "铁甲湾"], - 1583 => ["塔萨洛尔", "蛛网隧道", "霍德玛尔城", "黑手大厅", "龙塔大厅", "孵化间", "黑石竞技场"], - 1584 => ["禁闭室", "暗炉城"], - 2017 => ["十字军广场", "街巷"], - 2057 => ["遗骨之穴", "召唤大厅", "书房上层", "院长的书房"], - 2100 => ["玛拉顿的洞穴", "扎尔塔之墓"], - 2557 => ["戈多克议会", "中心花园", "上层精灵庭院", "伊莫塔尔的牢笼", "扭木广场", "艾德雷斯神殿"], - 2677 => ["龙喉兵营", "征战大厅", "血色实验室", "奈法利安的巢穴"], - 3428 => ["地下虫巢", "神殿大门", "克苏恩地穴"], - 3456 => ["构造区", "蜘蛛区", "军事区", "瘟疫区", "大墓地下层", "大墓地上层"], - 3457 => ["仆役宿舍", "上层马厩", "宴会厅", "会客间", "歌剧院楼座", "主宰的露台", "下层断阶", "上层断阶", "展览馆", "守护者的图书馆", "储藏室", "上层图书馆", "观星大厅", "象棋大厅", "麦迪文的房间", "能量站", "虚空异界"], - 3715 => ["蒸汽地窟", "冷却池"], - 3790 => ["转生大厅", "灵魂之桥"], - 3791 => ["塞泰克鸦巢", "哀悼大厅"], - 3848 => ["静止隔间:特雷奥", "静止隔间:玛克希姆", "密封核心"], - 3849 => ["能源舰", "计算密室"], - 3959 => ["伊利达雷训练场", "卡拉波下水道", "暗影圣殿", "苦痛大厅", "血魔之厅", "欢愉之园", "命令大厅", "神殿之巅"], - 4075 => ["太阳之井高地", "日蚀神殿"], - 4100 => ["斯坦索姆外围", "斯坦索姆城"], - 4131 => ["大魔导师的圣堂", "观测台"], - 4196 => ["达克萨隆前庭", "达克萨隆悬崖"], - 4228 => ["突变之环", "加速之环", "转化之环", "校准之环"], - 4272 => ["坚韧军营", "造物者步道"], - 4273 => ["壮阔大道", "奥杜尔的前厅", "奥杜尔的内部圣殿", "尤格-萨隆的监狱", "思想火花", "心灵之眼"], - 4277 => ["孵化深渊", "哈多诺克斯之巢", "镀金之门"], - 4395 => ["达拉然城", "达拉然下水道"], - 4494 => ["安卡赫特", '被亵渎的祭坛'], - 4722 => ["银色演武场", "寒冰深渊"], - 4812 => ["堡垒下层", "颅骨之墙", "死亡使者之台", "冰霜女王的巢穴", "上层区域", "皇家区", "冰封王座", "霜之哀伤"] ) ), 'quest' => array( diff --git a/prQueue b/prQueue index 874fdc195..9680aa89d 100755 --- a/prQueue +++ b/prQueue @@ -1,3 +1,4 @@ +#!/usr/bin/env php $str) - if ($i && $str[0] != '-') - $args[] = $str; +CLISetup::runInitial(); - dbc($args); - break; -} +die("\n"); ?> diff --git a/setup/tools/CLISetup.class.php b/setup/tools/CLISetup.class.php index b5bfc4011..7a5daad5c 100644 --- a/setup/tools/CLISetup.class.php +++ b/setup/tools/CLISetup.class.php @@ -20,123 +20,404 @@ class CLISetup 'frFR' => LOCALE_FR, 'deDE' => LOCALE_DE, 'zhCN' => LOCALE_CN, 'enCN' => LOCALE_CN, - 'esES' => LOCALE_ES, 'esMX' => LOCALE_ES, + 'esES' => LOCALE_ES, 'ruRU' => LOCALE_RU ); + public const SQL_BATCH = 1000; // max. n items per sql insert + public const LOCK_OFF = 0; public const LOCK_ON = 1; public const LOCK_RESTORE = 2; - private static $lock = 1; + private static $lock = self::LOCK_ON; + + public const ARGV_NONE = 0x00; + public const ARGV_REQUIRED = 0x01; + public const ARGV_OPTIONAL = 0x02; + public const ARGV_PARAM = 0x04; // parameter to another argument + public const ARGV_ARRAY = 0x10; // arg accepts list of values - private const ARGV_REQUIRED = 0x01; - private const ARGV_OPTIONAL = 0x02; - private const ARGV_ARRAY = 0x10; + public const OPT_GRP_SETUP = 0; + public const OPT_GRP_UTIL = 1; + public const OPT_GRP_MISC = 2; + + private const GLOBALSTRINGS_LUA = '%s%sinterface/framexml/globalstrings.lua'; private static $opts = []; - private static $optGroups = ['AoWoW Setup', 'Utility Functions', 'Additional Options', 'Additional arguments specific to --build=simpleImg', 'Additional arguments specific to --build=complexImg']; - private static $optDefs = array( // cmd => [groupId, aliasses[], flags, description, appendix] - 'setup' => [0, ['s', 'firstrun'], 0x00, 'Step by step initial setup. Resumes if interrupted.', '' ], - 'update' => [0, ['u'], 0x00, 'Apply new sql updates fetched from Github and run --sync as needed.', '' ], - 'dbconfig' => [1, [], 0x00, 'Set up DB connection.', '' ], - 'siteconfig' => [1, [], 0x00, 'Set up site variables.', '' ], - 'account' => [1, [], 0x00, 'Create an account with admin privileges.', '' ], - 'sql' => [1, [], 0x12, 'Generate DB content from your world tables.', '=' ], - 'build' => [1, [], 0x12, 'Compile image files and data dumps.', '=' ], - 'sync' => [1, [], 0x12, 'Regenerate tables/files that depend on given world DB table.', '='], - 'dbc' => [1, [], 0x11, 'Extract dbc files from mpqDataDir into sql table. Structure must be defined in setup/dbc.class.php.', '=' ], - 'delete' => [2, ['d'], 0x00, 'Delete dbc_* tables generated by this prompt when done.', '' ], - 'log' => [2, [], 0x01, 'Write CLI ouput to file.', '=logfile' ], - 'help' => [2, ['h'], 0x00, 'Display contextual help, if available.', '' ], - 'force' => [2, ['f'], 0x00, 'Force existing files to be overwritten.', '' ], - 'locales' => [2, [], 0x12, 'Limit setup to enUS, frFR, deDE, zhCN, esES and/or ruRU. (does not override config settings)', '=' ], - 'mpqDataDir' => [2, [], 0x02, 'Manually point to directory with extracted mpq files. This is limited to setup/ (default: setup/mpqData/)', '=path/' ], - 'icons' => [3, ['1'], 0x00, 'Generate icons for spells, items, classes, races, ect.', '' ], - 'glyphs' => [3, ['2'], 0x00, 'Generate decorative glyph symbols displayed on related item and spell pages.', '' ], - 'pagetexts' => [3, ['3'], 0x00, 'Generate images contained in text on readable items and gameobjects.', '' ], - 'loadingscreens' => [3, ['4'], 0x00, 'Generate loading screen images (not used on page; skipped by default)', '' ], - 'talentbgs' => [4, ['1'], 0x00, 'Generate backgrounds for the talent calculator.', '' ], - 'maps' => [4, ['2'], 0x00, 'Generate zone and continental maps.', '' ], - 'spawn-maps' => [4, ['3'], 0x00, 'Fallback to generate alpha masks for each zone to match creature and gameobject spawn points.', '' ], - 'artwork' => [4, ['4'], 0x00, 'Generate images from /glues/credits (not used on page; skipped by default))', '' ], - 'area-maps' => [4, ['5'], 0x00, 'Generate additional area maps with highlighting for subzones (optional; skipped by default)', '' ] + private static $optGroups = ['AoWoW Setup', 'Utility Functions', 'Additional Options']; + private static $optDefs = array( // cmd => [groupId, aliases[], argvFlags, description, appendix] + 'delete' => [self::OPT_GRP_MISC, ['d'], self::ARGV_NONE, 'Delete dbc_* tables generated by this prompt when done. (not recommended)', '' ], + 'log' => [self::OPT_GRP_MISC, [], self::ARGV_REQUIRED, 'Write CLI ouput to file.', '=logfile' ], + 'help' => [self::OPT_GRP_MISC, ['h'], self::ARGV_NONE, 'Display contextual help, if available.', '' ], + 'force' => [self::OPT_GRP_MISC, ['f'], self::ARGV_NONE, 'Force existing files to be overwritten.', '' ], + 'locales' => [self::OPT_GRP_MISC, [], self::ARGV_ARRAY | self::ARGV_OPTIONAL, 'Limit setup to enUS, frFR, deDE, zhCN, esES and/or ruRU. (does not override config settings)', '='], + 'datasrc' => [self::OPT_GRP_MISC, [], self::ARGV_OPTIONAL, 'Manually point to directory with extracted mpq files. This is limited to setup/ (default: setup/mpqdata/)', '=path/' ], ); + private static $utilScriptRefs = []; + private static $setupScriptRefs = []; + private static $tmpStore = []; + private static $gsFiles = []; - /**************************/ - /* command line arguments */ - /**************************/ + public static function registerUtility(UtilityScript $us) : void + { + if (isset(self::$optDefs[$us::COMMAND]) || isset(self::$utilScriptRefs[$us::COMMAND])) + { + CLI::write(' Utility function '.CLI::bold($us::COMMAND).' already defined.', CLI::LOG_ERROR); + return; + } + self::$optDefs[$us::COMMAND] = [$us->optGroup, $us->argvOpts, $us->argvFlags, $us::DESCRIPTION, $us::APPENDIX]; + self::$utilScriptRefs[$us::COMMAND] = $us; + } - public static function init() : void + public static function registerSetup(string $invoker, SetupScript $ss) : void { - $short = ''; - $long = []; - $alias = []; + if (isset(self::$optDefs[$invoker]) || isset(self::$utilScriptRefs[$invoker])) + { + CLI::write(' Utility function '.CLI::bold($invoker).' not defined. Can\'t attach Subscript '.CLI::bold($ss->getName()).', invoker is missing. Skipping...', CLI::LOG_ERROR); + return; + } - foreach (self::$optDefs as $opt => [$idx, $aliasses, $flags, , ]) + if (isset(self::$setupScriptRefs[$invoker][$ss->getName()])) { - if ($flags & self::ARGV_REQUIRED) - $opt .= ':'; - else if ($flags & self::ARGV_OPTIONAL) - $opt .= '::'; + CLI::write(' Subscript function '.CLI::bold($ss->getName()).' already defined for invoker '.CLI::bold($invoker).'. Skipping...', CLI::LOG_ERROR); + return; + } - $long[] = $opt; - foreach ($aliasses as $a) + if ($childArgs = $ss->getSubCommands()) + { + if ($duplicates = array_intersect(array_keys($childArgs), array_keys(self::$optDefs))) { - if ($flags & self::ARGV_REQUIRED) // neither should be set with shortOpts - $_a = $a.':'; - else if ($flags & self::ARGV_OPTIONAL) - $_a = $a.'::'; - else - $_a = $a; + CLI::write(' Subscript function '.CLI::bold($ss->getName()).'\'s child arguments --'.implode(', --', $duplicates).' are already defined. Skipping...', CLI::LOG_ERROR); + return; + } - $alias[$a] = $opt; - if (strlen($a) == 1) - $short .= $_a; - else - $long[] = $_a; + $newIdx = count(self::$optGroups); + self::$optGroups[] = '--' . $invoker . '=' . $ss->getName(); + + foreach ($childArgs as $cmd => [$aliases, $argFlags, $description]) + self::$optDefs[$cmd] = [$newIdx, $aliases, $argFlags, $description, '']; + } + + // checks done ... store SetupScript + if (self::checkDependencies($ss)) + { + self::$setupScriptRefs[] = [$invoker, $ss->getName(), $ss]; + + // recheck temp stored dependencies + foreach (self::$tmpStore as $idx => [$invoker, $ts]) + { + if (!self::checkDependencies($ts)) + continue; + + self::$setupScriptRefs[] = [$invoker, $ts->getName(), $ts]; + unset(self::$tmpStore[$idx]); } } + else // if dependencies haven't been stored yet, put aside for later use + self::$tmpStore[] = [$invoker, $ss]; + } - if ($opts = getopt($short, $long)) - foreach ($opts as $o => $v) - self::$opts[$alias[$o] ?? $o] = (self::$optDefs[$alias[$o] ?? $o][2] & self::ARGV_ARRAY) ? ($v ? explode(',', $v) : []) : ($v ?: true); + private static function checkDependencies(SetupScript &$ss) : bool + { + if ($ss->isOptional) // optional scripts should no depend on anything + return true; + + [$sDep, $bDep] = $ss->getSelfDependencies(); + + return ((!$sDep || $sDep == array_intersect($sDep, array_column(array_filter(self::$setupScriptRefs, function($x) { return $x[0] == 'sql'; }), 1))) && + (!$bDep || $bDep == array_intersect($bDep, array_column(array_filter(self::$setupScriptRefs, function($x) { return $x[0] == 'build'; }), 1)))); + } + + public static function loadScripts() : void + { + foreach (glob('setup/tools/clisetup/*.us.php') as $file) + include_once $file; + + if (self::$tmpStore) + { + CLI::write('Some SubScripts have unresolved dependencies and have not been loaded', CLI::LOG_ERROR); + CLI::write(); + $tbl = [['Name', '--sql dep.', '--build dep.']]; + foreach (self::$tmpStore as [$_, $ssRef]) + { + [$sDep, $bDep] = $ssRef->getSelfDependencies(); + + $missS = array_intersect($sDep, array_column(array_filter(self::$setupScriptRefs, function($x) { return $x[0] == 'sql'; }), 1)); + $missB = array_intersect($sDep, array_column(array_filter(self::$setupScriptRefs, function($x) { return $x[0] == 'build'; }), 1)); + + array_walk($sDep, function (&$x) use($missS) { $x = in_array($x, $missS) ? $x : CLI::red($x); }); + array_walk($bDep, function (&$x) use($missB) { $x = in_array($x, $missB) ? $x : CLI::red($x); }); + + $tbl[] = [$ssRef->getName(), implode(', ', $sDep), implode(', ', $bDep)]; + } + + CLI::writeTable($tbl); + } + + // link SubScipts back to UtilityScript after all UtilityScripts have been loaded + foreach (self::$utilScriptRefs as $name => $us) + if (in_array('TrSubScripts', class_uses($us))) + $us->assignGenerators($name); + + self::evalOpts(); + } + + public static function getSubScripts(string $invoker = '') : generator + { + foreach (self::$setupScriptRefs as [$src, $name, $ref]) + if (!$invoker || $src == $invoker) + yield $name => [$src, $ref]; + } + + public static function init() : void + { + self::evalOpts(); // optional logging if (isset(self::$opts['log'])) CLI::initLogFile(trim(self::$opts['log'])); // alternative data source (no quotes, use forward slash) - if (isset(self::$opts['mpqDataDir'])) - self::$srcDir = CLI::nicePath(self::$opts['mpqDataDir']); + if (isset(self::$opts['datasrc'])) + self::$srcDir = CLI::nicePath(self::$opts['datasrc']); // optional limit handled locales if (isset(self::$opts['locales'])) { // engb and enus are identical for all intents and purposes - $from = ['engb', 'esmx', 'encn']; - $to = ['enus', 'eses', 'zhcn']; - $opts['locales'] = str_ireplace($from, $to, strtolower($opts['locales'])); + $from = ['engb', 'encn']; + $to = ['enus', 'zhcn']; - self::$locales = array_intersect(Util::$localeStrings, explode(',', $opts['locales'])); + self::$opts['locales'] = str_ireplace($from, $to, self::$opts['locales']); + + self::$locales = array_intersect(Util::$localeStrings, array_map('strtolower', self::$opts['locales'])); } if (!self::$locales) self::$locales = array_filter(Util::$localeStrings); // restrict actual locales - foreach (self::$locales as $idx => $str) + foreach (self::$locales as $idx => $_) + { if (!($l = Cfg::get('LOCALES')) || ($l & (1 << $idx))) self::$localeIds[] = $idx; + else + unset(self::$locales[$idx]); + } + + if (!self::$localeIds) + CLI::write('No valid locale specified. Check your config or --locales parameter, if used', CLI::LOG_ERROR); // get site status if (DB::isConnected(DB_AOWOW)) - self::$lock = (int)Cfg::get('MAINTENANCE'); + self::$lock = Cfg::get('MAINTENANCE'); else self::$lock = self::LOCK_ON; } - public static function getOpt(...$args) + public static function writeCLIHelp(bool $full = false) : void + { + $cmd = self::getOpt(1 << self::OPT_GRP_SETUP | 1 << self::OPT_GRP_UTIL); + if (!$cmd || !self::$utilScriptRefs[$cmd[0]]->writeCLIHelp()) + { + $lines = []; + + foreach (self::$optGroups as $idx => $og) + { + if (!$full && $idx > self::OPT_GRP_SETUP) + continue; + + $lines[] = [$og, '']; + + foreach (self::$optDefs as $opt => [$group, $alias, , $desc, $app]) + { + if ($group != $idx) + continue; + + $cmd = ' --'.$opt; + foreach ($alias as $a) + $cmd .= ' | '.(strlen($a) == 1 ? '-'.$a : '--'.$a); + + $lines[] = [$cmd.$app, $desc]; + } + } + + CLI::writeTable($lines); + CLI::write(); + } + } + + // called from Setup + public static function runInitial() : void + { + global $argc, $argv; // todo .. find better way? argv, argc are effectivley already global + + // get arguments present in argGroup 1 or 2, if set. Pick first. + $cmd = self::getOpt(1 << self::OPT_GRP_SETUP | 1 << self::OPT_GRP_UTIL)[0]; + $us = &self::$utilScriptRefs[$cmd]; + $inOut = [null, null, null, null]; + $allOk = true; + + $i = 0; + if ($us::USE_CLI_ARGS) + foreach ($argv as $n => $arg) + { + if (!$n || ($arg && $arg[0] == '-')) // not parent; not handled by getOpt() + continue; + + $inOut[$i++] = $arg; + + if ($i > 3) + break; + } + + if ($dbError = array_filter($us::REQUIRED_DB, function ($x) { return !DB::isConnected($x); })) + { + CLI::write('Database on index '.implode(', ', $dbError).' not yet set up!', CLI::LOG_ERROR); + CLI::write('Please use '.CLI::bold('"php aowow --db"').' for setup', CLI::LOG_BLANK); + CLI::write(); + return; + } + + if ($us::LOCK_SITE != self::LOCK_OFF) + self::siteLock(self::LOCK_ON); + + if ($us::NOTE_START) + CLI::write($us::NOTE_START); + + if (!$us->run($inOut)) + $allOk = false; + + $error = []; + if ($allOk && !$us->test($error)) + { + if ($us::NOTE_ERROR) + CLI::write($us::NOTE_ERROR, CLI::LOG_ERROR); + + foreach ($error as $e) + CLI::write($e, CLI::LOG_BLANK); + + CLI::write(); + $allOk = false; + } + + if ($allOk) + if ($ff = $us->followupFn) + if (array_filter($inOut)) + self::run($ff, $inOut); + + self::siteLock($us::LOCK_SITE == self::LOCK_RESTORE ? self::LOCK_RESTORE : self::LOCK_OFF); + + // end + if ($us::NOTE_END_OK && $allOk) + CLI::write($us::NOTE_END_OK, CLI::LOG_OK); + else if($us::NOTE_END_FAIL && !$allOk) + CLI::write($us::NOTE_END_FAIL, CLI::LOG_ERROR); + } + + // consecutive calls + public static function run(string $cmd, &$args) : bool + { + if (!isset(self::$utilScriptRefs[$cmd])) + return false; + + $us = &self::$utilScriptRefs[$cmd]; + + if ($dbError = array_filter($us::REQUIRED_DB, function ($x) { return !DB::isConnected($x); })) + { + CLI::write('Database on index '.implode(', ', $dbError).' not yet set up!', CLI::LOG_ERROR); + CLI::write('Please use '.CLI::bold('"php aowow --db"').' for setup', CLI::LOG_BLANK); + CLI::write(); + return false; + } + + if ($us::PROMPT) + { + CLI::write($us::PROMPT, -1, false); + CLI::write(); + + if (!CLI::read(['x' => ['Press any key to continue', true, true]], $_)) // we don't actually care about the input + return false; + } + + $args = array_pad($args, 4, []); + + $success = $us->run($args); + + $error = []; + if ($us::NOTE_ERROR && $success && !$us->test($error)) + { + CLI::write($us::NOTE_ERROR, CLI::LOG_ERROR); + foreach ($error as $e) + CLI::write($e, CLI::LOG_BLANK); + + CLI::write(); + return false; + } + + if ($success) + if ($ff = $us->followupFn) + if (array_filter($args)) + if (!self::run($ff, $args)) + $success = false; + + return $success; + } + + + /**************************/ + /* command line arguments */ + /**************************/ + + public static function evalOpts() : void + { + $short = ''; + $long = []; + $alias = []; + + foreach (self::$optDefs as $opt => [, $aliases, $flags, , ]) + { + foreach ($aliases as $i => $a) + { + if (isset($alias[$a])) + $alias[$a][] = $opt; + else + $alias[$a] = [$opt]; + + if ($flags & self::ARGV_REQUIRED) + $a .= ':'; + else if ($flags & self::ARGV_OPTIONAL) + $a .= '::'; + + if (strlen($aliases[$i]) == 1) + $short .= $a; + else + $long[] = $a; + } + + if ($flags & self::ARGV_REQUIRED) + $opt .= ':'; + else if ($flags & self::ARGV_OPTIONAL) + $opt .= '::'; + + $long[] = $opt; + } + + if ($opts = getopt($short, $long)) + { + foreach ($opts as $o => $v) + { + if (!isset($alias[$o])) + self::$opts[$o] = (self::$optDefs[$o][2] & self::ARGV_ARRAY) ? ($v ? explode(',', $v) : []) : ($v ?: true); + else + foreach ($alias[$o] as $a) + self::$opts[$a] = (self::$optDefs[$a][2] & self::ARGV_ARRAY) ? ($v ? explode(',', $v) : []) : ($v ?: true); + } + } + } + + public static function getOpt(/* string|int */ ...$args) // : bool|array|string { if (!$args) return false; @@ -165,39 +446,12 @@ public static function getOpt(...$args) return $result; } - public static function optHelp(int $groupMask = 0x0) : void - { - $lines = []; - - foreach (self::$optGroups as $idx => $og) - { - if ($groupMask && !($groupMask & (1 << $idx))) - continue; - - $lines[] = [$og, '']; - - foreach (self::$optDefs as $opt => [$group, $alias, , $desc, $app]) - { - if ($group != $idx) - continue; - - $cmd = ' --'.$opt; - foreach ($alias as $a) - $cmd .= ' | '.(strlen($a) == 1 ? '-'.$a : '--'.$a); - - $lines[] = [$cmd.$app, $desc]; - } - } - - CLI::writeTable($lines); - } - /*******************/ /* web page access */ /*******************/ - public static function siteLock(int $mode = self::LOCK_RESTORE) : void + private static function siteLock(int $mode = self::LOCK_RESTORE) : void { if (DB::isConnected(DB_AOWOW)) Cfg::set('MAINTENANCE', $mode == self::LOCK_RESTORE ? self::$lock : $mode); @@ -216,8 +470,7 @@ public static function siteLock(int $mode = self::LOCK_RESTORE) : void */ private static function buildFileList() : bool { - CLI::write(); - CLI::write('indexing game data from '.self::$srcDir.' for first time use...'); + CLI::write('indexing game data from '.self::$srcDir.' for first time use...', CLI::LOG_INFO, true, true); $setupDirs = glob('setup/*'); foreach ($setupDirs as $sd) @@ -246,8 +499,7 @@ private static function buildFileList() : bool self::$mpqFiles[strtolower($_)] = $_; } - CLI::write('done'); - CLI::write(); + CLI::write('indexing game data from '.self::$srcDir.' for first time use... done!', CLI::LOG_INFO); } catch (UnexpectedValueException $e) { @@ -258,7 +510,7 @@ private static function buildFileList() : bool return true; } - public static function fileExists(&$file) + public static function fileExists(string &$file) : bool { // read mpq source file structure to tree if (!self::$mpqFiles) @@ -281,7 +533,7 @@ public static function fileExists(&$file) return false; } - public static function filesInPath($path, $useRegEx = false) + public static function filesInPath(string $path, bool $useRegEx = false) : array { $result = []; @@ -304,12 +556,82 @@ public static function filesInPath($path, $useRegEx = false) return $result; } + public static function filesInPathLocalized(string $pathPattern, ?bool &$status = true, bool $matchAll = true) : array + { + $result = []; + + foreach (self::$expectedPaths as $xp => $locId) + { + if (!in_array($locId, self::$localeIds)) + continue; + + if (isset($result[$locId])) + continue; + + if ($xp) // if in subDir add trailing slash + $xp .= '/'; + + $path = sprintf($pathPattern, $xp); + if (self::fileExists($path)) + { + $result[$locId] = $path; + continue; + } + } + + if (!$matchAll && !$result) + $status = false; + + if ($matchAll && array_diff(self::$localeIds, array_keys($result))) + $status = false; + + return $result; + } + + public static function loadGlobalStrings() : bool + { + CLI::write('loading required GlobalStrings', CLI::LOG_INFO); + + // try to load globalstrings for all selected locales + foreach (self::$expectedPaths as $xp => $lId) + { + if (isset(self::$gsFiles[$lId])) + continue; + + if ($xp) + $xp .= '/'; + + $gsFile = sprintf(self::GLOBALSTRINGS_LUA, self::$srcDir, $xp); + if (self::fileExists($gsFile)) + self::$gsFiles[$lId] = file($gsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + } + + if ($missing = array_diff(self::$localeIds, array_keys(self::$gsFiles))) + { + ClI::write('GlobalStrings.lua not found for locale '. Lang::concat($missing), CLI::LOG_WARN); + return false; + } + + return true; + } + + public static function searchGlobalStrings(string $pattern) : generator + { + if (!self::$gsFiles) + return; + + foreach (self::$gsFiles as $lId => $globalStrings) + foreach ($globalStrings as $gs) + if (preg_match($pattern, $gs, $result)) + yield $lId => $result; + } + /*****************/ /* file handling */ /*****************/ - public static function writeFile($file, $content) + public static function writeFile(string $file, string $content) : bool { if (Util::writeFile($file, $content)) { @@ -322,17 +644,23 @@ public static function writeFile($file, $content) return false; } - public static function writeDir($dir) + public static function writeDir(string $dir, bool &$exist = true) : bool { - if (Util::writeDir($dir)) + if (Util::writeDir($dir, $exist)) return true; CLI::write(error_get_last()['message'].' '.CLI::bold($dir), CLI::LOG_ERROR); return false; } - public static function loadDBC($name) + public static function loadDBC( string $name) : bool { + if (!DB::isConnected(DB_AOWOW)) + { + CLI::write('CLISetup::loadDBC() - not connected to DB. Cannot write results!', CLI::LOG_ERROR); + return false; + } + if (DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'dbc_'.$name) && DB::Aowow()->selectCell('SELECT count(1) FROM ?#', 'dbc_'.$name)) return true; diff --git a/setup/tools/clisetup/account.func.php b/setup/tools/clisetup/account.func.php deleted file mode 100644 index 9d48913ee..000000000 --- a/setup/tools/clisetup/account.func.php +++ /dev/null @@ -1,73 +0,0 @@ - ['Username', false], - 'pass1' => ['Enter Password', true ], - 'pass2' => ['Confirm Password', true ] - ); - - if (!DB::isConnected(DB_AOWOW)) - { - CLI::write('Database not yet set up!', CLI::LOG_WARN); - CLI::write('Please use '.CLI::bold('"php aowow --dbconfig"').' for setup', CLI::LOG_BLANK); - CLI::write(); - return; - } - - User::useLocale(LOCALE_EN); - Lang::load(LOCALE_EN); - - if (CLI::read($fields, $uiAccount)) - { - CLI::write(); - - if (!User::isValidName($uiAccount['name'], $e)) - CLI::write(Lang::account($e == 1 ? 'errNameLength' : 'errNameChars'), CLI::LOG_ERROR); - else if (!User::isValidPass($uiAccount['pass1'], $e)) - CLI::write(Lang::account($e == 1 ? 'errPassLength' : 'errPassChars'), CLI::LOG_ERROR); - else if ($uiAccount['pass1'] != $uiAccount['pass2']) - CLI::write(Lang::account('passMismatch'), CLI::LOG_ERROR); - else if ($_ = DB::Aowow()->SelectCell('SELECT 1 FROM ?_account WHERE user = ? AND (status <> ?d OR (status = ?d AND statusTimer > UNIX_TIMESTAMP()))', $uiAccount['name'], ACC_STATUS_NEW, ACC_STATUS_NEW)) - CLI::write(Lang::account('nameInUse'), CLI::LOG_ERROR); - else - { - // write to db - $ok = DB::Aowow()->query('REPLACE INTO ?_account (user, passHash, displayName, joindate, email, allowExpire, userGroups, userPerms) VALUES (?, ?, ?, UNIX_TIMESTAMP(), ?, 0, ?d, 1)', - $uiAccount['name'], - User::hashCrypt($uiAccount['pass1']), - Util::ucFirst($uiAccount['name']), - Cfg::get('CONTACT_EMAIL'), - U_GROUP_ADMIN - ); - if ($ok) - { - $newId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $uiAccount['name']); - Util::gainSiteReputation($newId, SITEREP_ACTION_REGISTER); - - CLI::write("account ".$uiAccount['name']." created successfully", CLI::LOG_OK); - } - else // something went wrong - CLI::write(Lang::main('intError'), CLI::LOG_ERROR); - } - } - else - { - CLI::write(); - CLI::write("account creation aborted", CLI::LOG_INFO); - } -} - -?> diff --git a/setup/tools/clisetup/account.us.php b/setup/tools/clisetup/account.us.php new file mode 100644 index 000000000..ed1be498e --- /dev/null +++ b/setup/tools/clisetup/account.us.php @@ -0,0 +1,130 @@ + ['Username', false], + 'pass1' => ['Enter Password', true ], + 'pass2' => ['Confirm Password', true ], + 'email' => ['Email (optional)', false] + ); + + // args: username, password, email, null // iiin + public function run(&$args) : bool + { + User::useLocale(LOCALE_EN); + Lang::load(LOCALE_EN); + + $name = $args[0] ?? ''; + $passw = $args[1] ?? ''; + $email = $args[2]; + + if ($name && User::isValidName($name)) + unset($this->fields['name']); + else + $name = ''; + + if ($passw && User::isValidPass($passw)) + { + unset($this->fields['pass1']); + unset($this->fields['pass2']); + } + else + $passw = ''; + + if (is_string($email) && (!strlen($email) || Util::isValidEmail($email))) + unset($this->fields['email']); + else + $email = ''; + + if ($this->fields && CLI::read($this->fields, $uiAccount)) + { + CLI::write(); + + if (!$name && !User::isValidName($uiAccount['name'], $e)) + CLI::write(Lang::account($e == 1 ? 'errNameLength' : 'errNameChars'), CLI::LOG_ERROR); + else if (!$name) + $name = $uiAccount['name']; + + if (!$passw && !User::isValidPass($uiAccount['pass1'], $e)) + CLI::write(Lang::account($e == 1 ? 'errPassLength' : 'errPassChars'), CLI::LOG_ERROR); + else if (!$passw && $uiAccount['pass1'] != $uiAccount['pass2']) + CLI::write(Lang::account('passMismatch'), CLI::LOG_ERROR); + else if (!$passw) + $passw = $uiAccount['pass1']; + + if (!$email && Util::isValidEmail($uiAccount['email'])) + $email = $uiAccount['email']; + else if (!$email && $uiAccount['email']) + CLI::write('[account] email invalid ... using default: ' . Cfg::get('CONTACT_EMAIL'), CLI::LOG_INFO); + } + else if ($this->fields) + { + CLI::write(); + CLI::write("[account] admin creation aborted", CLI::LOG_INFO); + CLI::write(); + return true; + } + + if (DB::Aowow()->SelectCell('SELECT 1 FROM ?_account WHERE user = ? AND (status <> ?d OR (status = ?d AND statusTimer > UNIX_TIMESTAMP()))', $name, ACC_STATUS_NEW, ACC_STATUS_NEW)) + { + CLI::write('[account] ' . Lang::account('nameInUse'), CLI::LOG_ERROR); + CLI::write(); + return false; + } + + if (!$name || !$passw) + return false; + + if (DB::Aowow()->query('REPLACE INTO ?_account (user, passHash, displayName, joindate, email, allowExpire, userGroups, userPerms) VALUES (?, ?, ?, UNIX_TIMESTAMP(), ?, 0, ?d, 1)', + $name, User::hashCrypt($passw), Util::ucFirst($name), $email ?: Cfg::get('CONTACT_EMAIL'), U_GROUP_ADMIN)) + { + $newId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $name); + Util::gainSiteReputation($newId, SITEREP_ACTION_REGISTER); + + CLI::write("[account] admin ".$name." created successfully", CLI::LOG_OK); + CLI::write(); + + return true; + } + + CLI::write('[account] ' . Lang::main('intError'), CLI::LOG_ERROR); + CLI::write(); + + return false; + } + + public function test(?array &$error = []) : bool + { + $error = []; + return !!DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE `userPerms` = 1'); + } +}); + +?> diff --git a/setup/tools/clisetup/build.func.php b/setup/tools/clisetup/build.func.php deleted file mode 100644 index dcfa78cb1..000000000 --- a/setup/tools/clisetup/build.func.php +++ /dev/null @@ -1,102 +0,0 @@ - [$file, $destPath, $deps]) - { - $reqDBC = []; - - if (!in_array($name, FileGen::$subScripts)) - continue; - - if (!file_exists(FileGen::$tplPath.$file.'.in')) - { - CLI::write(sprintf(ERR_MISSING_FILE, FileGen::$tplPath.$file.'.in'), CLI::LOG_ERROR); - $allOk = false; - continue; - } - - if (!CLISetup::writeDir($destPath)) - continue; - - $syncIds = []; // todo: fetch what exactly must be regenerated - - $ok = FileGen::generate($name, $syncIds); - if (!$ok) - $allOk = false; - else - $done[] = $name; - - CLI::write(' - subscript \''.$file.'\' returned '.($ok ? 'successfully' : 'with errors'), $ok ? CLI::LOG_OK : CLI::LOG_ERROR); - set_time_limit(FileGen::$defaultExecTime); // reset to default for the next script - } - - // files without template - foreach (FileGen::$datasets as $file => $deps) - { - if (!in_array($file, FileGen::$subScripts)) - continue; - - $syncIds = []; // todo: fetch what exactly must be regenerated - - $ok = FileGen::generate($file, $syncIds); - if (!$ok) - $allOk = false; - else - $done[] = $file; - - CLI::write(' - subscript \''.$file.'\' returned '.($ok ? 'successfully' : 'with errors'), $ok ? CLI::LOG_OK : CLI::LOG_ERROR); - set_time_limit(FileGen::$defaultExecTime); // reset to default for the next script - } - - // end - CLI::write(); - if ($allOk) - CLI::write('successfully finished file generation', CLI::LOG_OK); - else - CLI::write('finished file generation with errors', CLI::LOG_ERROR); - - CLISetup::siteLock(CLISetup::LOCK_RESTORE); - } - else if (FileGen::getMode() == FileGen::MODE_NORMAL) - CLI::write('no valid script names supplied', CLI::LOG_ERROR); - - return $done; -} - -?> diff --git a/setup/tools/clisetup/datagen.us.php b/setup/tools/clisetup/datagen.us.php new file mode 100644 index 000000000..d49bbc181 --- /dev/null +++ b/setup/tools/clisetup/datagen.us.php @@ -0,0 +1,159 @@ +'; + public const NOTE_START = '[sql] begin generation of:'; + public const NOTE_END_OK = 'successfully finished sql generation'; + public const NOTE_END_FAIL = 'finished sql generation with errors'; + + public const REQUIRED_DB = [DB_AOWOW, DB_WORLD]; + + public const LOCK_SITE = CLISetup::LOCK_RESTORE; + + public function __construct() + { + if ($this->inited) + return true; + + $this->defaultExecTime = ini_get('max_execution_time'); + + // register subscripts to CLISetup + foreach (glob('setup/tools/sqlgen/*.ss.php') as $file) + include_once $file; + + $this->inited = true; + return true; + } + + // args: scriptToDo, scriptSuccess, null, null // ionn + public function run(&$args) : bool + { + $todo = &$args['doSql']; + $done = &$args['doneSql']; + + if (!$this->inited) + return false; + + // check passed subscript names; limit to real scriptNames + if (($sqlArgs = CLISetup::getOpt('sql')) !== false) + { + if ($sqlArgs === []) // used --sql without arguments + $todo = array_keys($this->generators); // do everything + else if ($_ = array_intersect(array_keys($this->generators), $sqlArgs)) + $todo = $_; + else + { + CLI::write('[sql] no valid script names supplied', CLI::LOG_ERROR); + return false; + } + + // supplement self::NOTE_START + CLI::write(' - '.Lang::concat($todo), CLI::LOG_BLANK, false); + CLI::write(); + } + else if ($todo) + { + $todo = array_intersect(array_keys($this->generators), is_array($todo) ? $todo : [$todo]); + if (!$todo) + return false; + } + else + return false; + + $allOk = true; + + // start file generation + foreach ($todo as $cmd) + { + $syncIds = []; // todo: fetch what exactly must be regenerated + $success = false; + $scriptRef = &$this->generators[$cmd]; + + CLI::write('[sql] filling aowow_'.$cmd.' with data'); + + if ($scriptRef->fulfillRequirements()) + { + if ($scriptRef->generate($syncIds)) + { + if (method_exists($scriptRef, 'applyCustomData')) + $success = $scriptRef->applyCustomData(); + + $success = true; + } + } + + if (!$success) + $allOk = false; + else + $done[] = $cmd; + + CLI::write('[sql] subscript \''.$cmd.'\' returned '.($success ? 'successfully' : 'with errors'), $success ? CLI::LOG_OK : CLI::LOG_ERROR); + CLI::write(); + set_time_limit($this->defaultExecTime); // reset to default for the next script + } + + return $allOk; + } + + public function writeCLIHelp() : bool + { + if ($args = CLISetup::getOpt('sql')) + { + $anyHelp = false; + foreach ($args as $cmd) + if (isset($this->generators[$cmd]) && $this->generators[$cmd]->writeCLIHelp()) + $anyHelp = true; + + if ($anyHelp) + return true; + } + + CLI::write(' usage: php aowow --sql= [--mpqDataDir: --locales:]', -1, false); + CLI::write(); + CLI::write(' Regenerates DB table content for a given SetupScript. Dependencies are taken into account by the triggered calls of --sync and --update', -1, false); + CLI::write(); + + ksort($this->generators); + + $lines = [['Command', 'TC dependencies', 'AoWoW dependencies', 'Info']]; + foreach ($this->generators as $cmd => $ssRef) + { + $tcDeps = explode("\n", Lang::breakTextClean(implode(', ', $ssRef->getRemoteDependencies() ), 35, Lang::FMT_RAW)); + $aoDeps = explode("\n", Lang::breakTextClean(implode(', ', $ssRef->getSelfDependencies()[0]), 35, Lang::FMT_RAW)); + + for ($i = 0; $i < max(count($tcDeps), count($aoDeps)); $i++) + $lines[] = array( + $i ? '' : $cmd, + $tcDeps[$i] ?? '', + $aoDeps[$i] ?? '', + $i ? '' : $ssRef->getInfo() + ); + } + + CLI::writeTable($lines); + CLI::write(); + + return true; + } +}); + +?> diff --git a/setup/tools/clisetup/dbc.func.php b/setup/tools/clisetup/dbc.us.php similarity index 56% rename from setup/tools/clisetup/dbc.func.php rename to setup/tools/clisetup/dbc.us.php index ee1825513..84497cf7f 100644 --- a/setup/tools/clisetup/dbc.func.php +++ b/setup/tools/clisetup/dbc.us.php @@ -11,53 +11,54 @@ /* Create content from world tables / dbc files */ /************************************************/ -function dbc($args) : bool +CLISetup::registerUtility(new class extends UtilityScript { - if (!DB::isConnected(DB_AOWOW)) - { - CLI::write('Database not yet set up!', CLI::LOG_WARN); - CLI::write('Please use '.CLI::bold('"php aowow --dbconfig"').' for setup', CLI::LOG_BLANK); - CLI::write(); - return false; - } + public $argvFlags = CLISetup::ARGV_ARRAY | CLISetup::ARGV_REQUIRED; + public $optGroup = CLISetup::OPT_GRP_UTIL; + + public const COMMAND = 'dbc'; + public const DESCRIPTION = 'Extract dbc files from mpqDataDir into sql table. Structural ini file must be defined in setup/tools/dbc/.'; + public const APPENDIX = '= [tablename [wowbuild]]'; + + public const REQUIRED_DB = [DB_AOWOW]; - if (!CLISetup::getOpt('dbc')) - return writeCLIHelp(); + public const USE_CLI_ARGS = true; - foreach (CLISetup::getOpt('dbc') as $n) + // args: tblName, wowbuild, null, null // iinn + public function run(&$args) : bool { - $n = str_ireplace('.dbc', '', trim($n)); + foreach (CLISetup::getOpt('dbc') as $n) + { + $n = str_ireplace('.dbc', '', trim($n)); - if (empty($n)) - continue; + if (empty($n)) + continue; - $opts = ['temporary' => false]; - if ($args[0]) - $opts['tableName'] = $args[0]; + $opts = ['temporary' => false]; + if ($args[0]) + $opts['tableName'] = $args[0]; - $dbc = new DBC(strtolower($n), $opts, $args[1] ?: DBC::DEFAULT_WOW_BUILD); - if ($dbc->error) - { - CLI::write('[dbc] required DBC '.CLI::bold($n).'.dbc not found!', CLI::LOG_ERROR); - CLI::write(); - return false; - } + $dbc = new DBC(strtolower($n), $opts, $args[1] ?: DBC::DEFAULT_WOW_BUILD); + if ($dbc->error) + { + CLI::write('[dbc] required DBC '.CLI::bold($n).'.dbc not found!', CLI::LOG_ERROR); + return false; + } - if (!$dbc->readFile()) - { - CLI::write('[dbc] DBC '.CLI::bold($n).'.dbc could not be written to DB!', CLI::LOG_ERROR); - CLI::write(); - return false; + if (!$dbc->readFile()) + { + CLI::write('[dbc] DBC '.CLI::bold($n).'.dbc could not be written to DB!', CLI::LOG_ERROR); + return false; + } + + $self = DB::Aowow()->selectCell('SELECT DATABASE()'); + CLI::write('[dbc] DBC '.CLI::bold($n).'.dbc written to '.CLI::bold('`'.($self ?? 'NULL').'`.`'.$dbc->getTableName().'`').'!', CLI::LOG_OK); } - $self = DB::Aowow()->selectCell('SELECT DATABASE()'); - CLI::write('[dbc] DBC '.CLI::bold($n).'.dbc written to '.CLI::bold('`'.($self ?? 'NULL').'`.`'.$dbc->getTableName().'`').'!', CLI::LOG_OK); - CLI::write(); + return true; } - return true; - - function writeCLIHelp() : bool + public function writeCLIHelp() : bool { CLI::write(' usage: php aowow --dbc= [--locales=] [tablename [wowbuild]]', -1, false); CLI::write(); @@ -113,6 +114,6 @@ function writeCLIHelp() : bool return true; } -}; +}); ?> diff --git a/setup/tools/clisetup/dbconfig.func.php b/setup/tools/clisetup/dbconfig.func.php deleted file mode 100644 index d6eb006ed..000000000 --- a/setup/tools/clisetup/dbconfig.func.php +++ /dev/null @@ -1,207 +0,0 @@ - ['Hostname / IP', false], - 'user' => ['User', false], - 'pass' => ['Password', true ], - 'db' => ['Database Name', false], - 'prefix' => ['Table prefix', false] - ); - $testDB = function($idx, $name, $dbInfo) - { - $buff = ['['.CLI::bold($idx).']', $name]; - - if ($dbInfo['host']) - { - $result = CLI::green('OK'); - $note = ''; - - if (DB::test($dbInfo, $note)) - { - DB::load($idx, $dbInfo); - - $ok = false; - switch ($idx) - { - case DB_AOWOW: - if (DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'aowow_dbversion')) - { - if ($date = DB::Aowow()->selectCell('SELECT `date` FROM ?_dbversion')) - { - $note = 'AoWoW DB version @ ' . date(Util::$dateFormatInternal, $date); - $ok = true; - } - else - $note = CLI::yellow('AoWoW DB version empty! Import of DB dump failed?'); - } - else - $note = CLI::yellow('DB test failed to find dbversion table. setup/db_structure.sql not yet imported?'); - break; - case DB_WORLD: - if (DB::World()->selectCell('SHOW TABLES LIKE ?', 'version')) - { - [$vString, $vNo] = DB::World()->selectRow('SELECT `db_version` AS "0", `cache_id` AS "1" FROM `version`'); - if (strpos($vString, 'TDB') === 0) - { - if ($vNo < TDB_WORLD_MINIMUM_VER) - $note = CLI::yellow('DB test found TrinityDB version older than rev. ').CLI::bold(TDB_WORLD_MINIMUM_VER).CLI::yellow('. Please update to at least rev. ').CLI::bold(TDB_WORLD_MINIMUM_VER); - else if ($vNo > TDB_WORLD_EXPECTED_VER) - $note = CLI::yellow('DB test found TrinityDB version newer than rev. ').CLI::bold(TDB_WORLD_EXPECTED_VER).CLI::yellow('. Be advised! DB structure may diverge!'); - else - { - $note = 'TrinityDB version @ ' . $vString; - $ok = true; - } - } - else if (strpos($vString, 'ACDB') === 0) - $note = CLI::yellow('DB test found AzerothCore DB version. AzerothCore DB structure is not supported!'); - else - $note = CLI::yellow('DB test found unexpected vendor in expected version table. Uhh.. Good Luck..!?'); - } - else if (DB::World()->selectCell('SHOW TABLES LIKE ?', 'db_version')) - $note = CLI::yellow('DB test found MaNGOS styled version table. MaNGOS DB structure is not supported!'); - else - $note = CLI::yellow('DB test failed to find version table. TrinityDB world not yet imported?'); - break; - default: - $ok = true; // no tests right now - } - - if (!$ok) - $result = CLI::yellow('WARN'); - } - else - $result = CLI::red('ERR'); - - $buff[] = $result; - $buff[] = 'mysqli://'.$dbInfo['user'].':'.($dbInfo['pass'] ? '**********' : '').'@'.$dbInfo['host'].'/'.$dbInfo['db']; - $buff[] = $dbInfo['prefix'] ? 'pre.: '.$dbInfo['prefix'] : ''; - $buff[] = $note; - - } - else - $buff[] = CLI::bold(''); - - return $buff; - }; - - if (file_exists('config/config.php')) - require 'config/config.php'; - - foreach ($databases as $idx => $name) - if (empty($AoWoWconf[$name]) && $name != 'characters' ) - $AoWoWconf[$name] = array_combine(array_keys($dbFields), ['', '', '', '', '']); - - while (true) - { - CLI::write('select an index to use the corresponding entry', -1, false); - - $nCharDBs = 0; - $tblRows = []; - foreach ($databases as $idx => $name) - { - if ($idx != DB_CHARACTERS) - $tblRows[] = $testDB($idx, $name, $AoWoWconf[$name]); - else if (!empty($AoWoWconf[$name])) - foreach ($AoWoWconf[$name] as $charIdx => $dbInfo) - $tblRows[] = $testDB($idx + $nCharDBs++, $name.' ['.$charIdx.']', $AoWoWconf[$name][$charIdx]); - } - - $tblRows[] = ['['.CLI::bold('N').']', 'new characters DB']; - $tblRows[] = ['['.CLI::bold('R').']', 'retest / reload DBs']; - CLI::writeTable($tblRows, false, true); - - while (true) - { - if (CLI::read(['idx' => ['', true, true, '/\d|R|N/i']], $uiIndex) && $uiIndex) - { - if (strtoupper($uiIndex['idx']) == 'R') - continue 2; - else if (($uiIndex['idx'] >= DB_AOWOW && $uiIndex['idx'] < (DB_CHARACTERS + $nCharDBs)) || strtoupper($uiIndex['idx']) == 'N') - { - $curFields = $uiIndex['idx'] ? $dbFields : array_slice($dbFields, 0, 4); - - if (strtoupper($uiIndex['idx']) == 'N') // add new characters DB - $curFields['realmId'] = ['Realm Id', false, false, '/\d{1,3}/']; - - if (CLI::read($curFields, $uiRealm)) - { - if ($uiIndex['idx'] == DB_AOWOW && $uiRealm) - $uiRealm['prefix'] = 'aowow_'; - - if (strtoupper($uiIndex['idx']) == 'N') // new char DB - { - if ($uiRealm) - { - $_ = $uiRealm['realmId']; - unset($uiRealm['realmId']); - $AoWoWconf[$databases[DB_CHARACTERS]][$_] = $uiRealm; - } - } - else if ($uiIndex['idx'] < DB_CHARACTERS) // auth, world or aowow - $AoWoWconf[$databases[$uiIndex['idx']]] = $uiRealm ?: array_combine(array_keys($dbFields), ['', '', '', '', '']); - else // existing char DB - { - $i = 0; - foreach ($AoWoWconf[$databases[DB_CHARACTERS]] as $realmId => &$dbInfo) - { - if ($uiIndex['idx'] - DB_CHARACTERS != $i++) - continue; - - if ($uiRealm) - $dbInfo = $uiRealm; - else - unset($AoWoWconf[$databases[DB_CHARACTERS]][$realmId]); - } - } - - // write config file - $buff = " $charInfo) - $buff .= '$AoWoWconf[\''.$db.'\'][\''.$idx.'\'] = '.var_export($AoWoWconf[$db][$idx], true).";\n\n"; - } - $buff .= "?>\n"; - CLI::write(); - CLISetup::writeFile('config/config.php', $buff); - continue 2; - } - else - { - CLI::write("edit canceled! returning to list...", CLI::LOG_INFO); - CLI::write(); - sleep(1); - continue 2; - } - } - } - else - { - CLI::write("leaving db setup...", CLI::LOG_INFO); - CLI::write(); - break 2; - } - } - } -} - -?> diff --git a/setup/tools/clisetup/dbconfig.us.php b/setup/tools/clisetup/dbconfig.us.php new file mode 100644 index 000000000..c23fe6508 --- /dev/null +++ b/setup/tools/clisetup/dbconfig.us.php @@ -0,0 +1,327 @@ + ['Server Host', false], + 'user' => ['User', false], + 'pass' => ['Password', true ], + 'db' => ['Database Name', false], + 'prefix' => ['Table prefix', false] + ); + + private $icons = ['Normal[0]', 'PvP', null, null, 'Normal[4]', 'RP', null, 'RP-PvP']; + private $regions = array( + null, 'Development', 'United States', 'Oceanic', 'Latin America', 'Tournament (Americas)', 'Korea', 'Tournament (Korea)', 'English', 'German', 'French', 'Spanish', 'Russian', 'Tournament (EU)', 'Taiwan', 'Tournament (Taiwan)', 'China', + 25 => 'Tournament (China)', 26 => 'Test Server', 27 => 'Tournament (Test Server)', 28 => 'QA Server', 30 => 'Test Server 2' + ); + + // args: null, null, null, null // nnnn + public function run(&$args) : bool + { + if (!$this->config && file_exists(self::CONFIG_FILE)) + { + require self::CONFIG_FILE; + $this->config = $AoWoWconf; + unset($AoWoWconf); + } + + foreach ($this->databases as $idx => $name) + if (empty($this->config[$name]) && $name != 'characters' ) + $this->config[$name] = array_combine(array_keys($this->dbFields), ['', '', '', '', '']); + + while (true) + { + CLI::write("select an index to use the corresponding entry", -1, false); + + $nCharDBs = 0; + $tblRows = []; + foreach ($this->databases as $idx => $name) + { + if ($idx != DB_CHARACTERS) + $tblRows[] = $this->testDB($idx, $name, $this->config[$name]); + else if (!empty($this->config[$name])) + foreach ($this->config[$name] as $charIdx => $dbInfo) + $tblRows[] = $this->testDB($idx + $nCharDBs++, $name.' ['.$charIdx.']', $this->config[$name][$charIdx]); + } + + $tblRows[] = ['['.CLI::bold('N').']', 'new characters DB']; + $tblRows[] = ['['.CLI::bold('S').']', 'show available realms']; + $tblRows[] = ['['.CLI::bold('R').']', 'retest / reload DBs']; + CLI::writeTable($tblRows, false, true); + + while (true) + { + if (CLI::read(['idx' => ['', true, true, '/\d|R|N|S/i']], $uiIndex) && $uiIndex) + { + if (strtoupper($uiIndex['idx']) == 'R') + continue 2; + else if (strtoupper($uiIndex['idx']) == 'S') + { + CLI::write(); + if (!DB::isConnectable(DB_AUTH) || !$this->test()) + CLI::write('[db] auth server not yet set up.', CLI::LOG_ERROR); + else if ($realms = DB::Auth()->select('SELECT id AS "0", name AS "1", icon AS "2", timezone AS "3", isRestricted AS "4" FROM realmlist')) + { + $tbl = [['Realm Id', 'Name', 'Type', 'Region', 'GMLevel', 'Status']]; + foreach ($realms as [$id, $name, $icon, $region, $level]) + { + $status = []; + $hasRegion = false; + foreach (Profiler::REGIONS as $n => $valid) + if ($hasRegion = in_array($region, $valid)) + { + if ($n == 'dev') + $status[] = 'Restricted region (staff only)'; + break; + } + + if (!$hasRegion && !$status) + $status[] = 'Unsupported region'; + if ($level > 0) + $status[] = 'GM-Level locked'; + if (DB::isConnectable(DB_CHARACTERS . $id)) + $status[] = 'Already in use'; + + $tbl[] = [$id, $name, $this->icons[$icon] ?? '', $this->regions[$region] ?? '', $level, $status ? CLI::yellow(implode(', ', $status)) : CLI::green('Usable')]; + } + + CLI::writeTable($tbl); + } + else + CLI::write('[db] table `realmlist` is empty.', CLI::LOG_WARN); + + CLI::write(); + + continue 2; + } + else if (($uiIndex['idx'] >= DB_AOWOW && $uiIndex['idx'] < (DB_CHARACTERS + $nCharDBs)) || strtoupper($uiIndex['idx']) == 'N') + { + $curFields = $uiIndex['idx'] ? $this->dbFields : array_slice($this->dbFields, 0, 4); + + if (strtoupper($uiIndex['idx']) == 'N') // add new characters DB + $curFields['realmId'] = ['Realm Id', false, false, '/\d{1,3}/']; + + if (CLI::read($curFields, $uiRealm)) + { + if ($uiIndex['idx'] == DB_AOWOW && $uiRealm) + $uiRealm['prefix'] = 'aowow_'; + + if (strtoupper($uiIndex['idx']) == 'N') // new char DB + { + if ($uiRealm) + { + $_ = $uiRealm['realmId']; + unset($uiRealm['realmId']); + $this->config[$this->databases[DB_CHARACTERS]][$_] = $uiRealm; + } + } + else if ($uiIndex['idx'] < DB_CHARACTERS) // auth, world or aowow + $this->config[$this->databases[$uiIndex['idx']]] = $uiRealm ?: array_combine(array_keys($this->dbFields), ['', '', '', '', '']); + else // existing char DB + { + $i = 0; + foreach ($this->config[$this->databases[DB_CHARACTERS]] as $realmId => &$dbInfo) + { + if ($uiIndex['idx'] - DB_CHARACTERS != $i++) + continue; + + if ($uiRealm) + $dbInfo = $uiRealm; + else + unset($this->config[$this->databases[3]][$realmId]); + } + } + + // write config file + $buff = "databases as $db) + { + if ($db != 'characters') + $buff .= '$AoWoWconf[\''.$db.'\'] = '.var_export($this->config[$db], true).";\n\n"; + else if (isset($this->config[$db])) + foreach ($this->config[$db] as $idx => $charInfo) + $buff .= '$AoWoWconf[\''.$db.'\'][\''.$idx.'\'] = '.var_export($this->config[$db][$idx], true).";\n\n"; + } + $buff .= "?>\n"; + CLI::write(); + CLISetup::writeFile(self::CONFIG_FILE, $buff); + continue 2; + } + else + { + CLI::write("[db] edit canceled! returning to list...", CLI::LOG_INFO); + CLI::write(); + sleep(1); + continue 2; + } + } + } + else + { + CLI::write("[db] leaving db config...", CLI::LOG_INFO); + CLI::write(); + break 2; + } + } + } + + return true; + } + + public function test(?array &$error = []) : bool + { + if (!$this->config) + { + require self::CONFIG_FILE; + $this->config = $AoWoWconf; + unset($AoWoWconf); + } + + $error = []; + foreach (['aowow', 'world', 'auth'] as $idx => $what) + { + if ($what == 'auth' && (empty($this->config['auth']) || empty($this->config['auth']['host']))) + continue; + + // init proper access for further setup + if (DB::test($this->config[$what], $err)) + { + DB::load($idx, $this->config[$what]); + switch ($idx) + { + case DB_AOWOW: + if (DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'aowow_dbversion')) + Cfg::load(); // first time load after successful db setup + else + $error[] = ' * '.$what.': doesn\'t seem to contain aowow tables!'; + break; + case DB_WORLD: + if (!DB::World()->selectCell('SHOW TABLES LIKE ?', 'version')) + $error[] = ' * '.$what.': doesn\'t seem to contain TrinityCore world tables!'; + else if (DB::World()->selectCell('SELECT `cache_id` FROM `version`') < TDB_WORLD_MINIMUM_VER) + $error[] = ' * '.$what.': TDB world db is structurally outdated! (min rev.: '.CLI::bold(TDB_WORLD_MINIMUM_VER).')'; + break; + default: + // no further checks at this time + } + } + else + $error[] = ' * '.$what.': '.$err; + } + + return empty($error); + } + + private function testDB($idx, $name, $dbInfo) + { + $buff = ['['.CLI::bold($idx).']', $name]; + + if ($dbInfo['host']) + { + $result = CLI::green('OK'); + $note = ''; + + if (DB::test($dbInfo, $note)) + { + DB::load($idx, $dbInfo); + + $ok = false; + switch ($idx) + { + case DB_AOWOW: + if (DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'aowow_dbversion')) + { + if ($date = DB::Aowow()->selectCell('SELECT `date` FROM ?_dbversion')) + { + $note = 'AoWoW DB version @ ' . date(Util::$dateFormatInternal, $date); + $ok = true; + } + else + $note = CLI::yellow('AoWoW DB version empty! Import of DB dump failed?'); + } + else + $note = CLI::yellow('DB test failed to find dbversion table. setup/db_structure.sql not yet imported?'); + break; + case DB_WORLD: + if (DB::World()->selectCell('SHOW TABLES LIKE ?', 'version')) + { + [$vString, $vNo] = DB::World()->selectRow('SELECT `db_version` AS "0", `cache_id` AS "1" FROM `version`'); + if (strpos($vString, 'TDB') === 0) + { + if ($vNo < TDB_WORLD_MINIMUM_VER) + $note = CLI::yellow('DB test found TrinityDB version older than rev. ').CLI::bold(TDB_WORLD_MINIMUM_VER).CLI::yellow('. Please update to at least rev. ').CLI::bold(TDB_WORLD_MINIMUM_VER); + else if ($vNo > TDB_WORLD_EXPECTED_VER) + $note = CLI::yellow('DB test found TrinityDB version newer than rev. ').CLI::bold(TDB_WORLD_EXPECTED_VER).CLI::yellow('. Be advised! DB structure may diverge!'); + else + { + $note = 'TrinityDB version @ ' . $vString; + $ok = true; + } + } + else if (strpos($vString, 'ACDB') === 0) + $note = CLI::yellow('DB test found AzerothCore DB version. AzerothCore DB structure is not supported!'); + else + $note = CLI::yellow('DB test found unexpected vendor in expected version table. Uhh.. Good Luck..!?'); + } + else if (DB::World()->selectCell('SHOW TABLES LIKE ?', 'db_version')) + $note = CLI::yellow('DB test found MaNGOS styled version table. MaNGOS DB structure is not supported!'); + else + $note = CLI::yellow('DB test failed to find version table. TrinityDB world not yet imported?'); + break; + default: + $ok = true; // no tests right now + } + + if (!$ok) + $result = CLI::yellow('WARN'); + } + else + $result = CLI::red('ERR'); + + $buff[] = $result; + $buff[] = 'mysqli://'.$dbInfo['user'].':'.($dbInfo['pass'] ? '**********' : '').'@'.$dbInfo['host'].'/'.$dbInfo['db']; + $buff[] = $dbInfo['prefix'] ? 'pre.: '.$dbInfo['prefix'] : ''; + $buff[] = $note; + } + else + $buff[] = CLI::bold(''); + + return $buff; + } + + public function writeCLIHelp() : bool + { + CLI::write(' Remember to use the correct Realm Id from '.CLI::bold('`logon`.`realmlist`').' when connecting to your characters DB.', -1, false); + CLI::write(' To remove a db entry edit it and leave all fields empty.', -1, false); + CLI::write(); + CLI::write(); + + return true; + } +}); + +?> diff --git a/setup/tools/clisetup/filegen.us.php b/setup/tools/clisetup/filegen.us.php new file mode 100644 index 000000000..37630a0d7 --- /dev/null +++ b/setup/tools/clisetup/filegen.us.php @@ -0,0 +1,173 @@ +'; + public const NOTE_START = '[build] begin generation of:'; + public const NOTE_END_OK = 'successfully finished file generation'; + public const NOTE_END_FAIL = 'finished file generation with errors'; + + public const REQUIRED_DB = [DB_AOWOW, DB_WORLD]; + + public const LOCK_SITE = CLISetup::LOCK_RESTORE; + + private $uploadDirs = array( // stuff that should be writable by www-data and isn't directly created by setup steps + 'static/uploads/screenshots/normal/', + 'static/uploads/screenshots/pending/', + 'static/uploads/screenshots/resized/', + 'static/uploads/screenshots/temp/', + 'static/uploads/screenshots/thumb/', + 'static/uploads/temp/', + 'static/uploads/guide/images/', + ); + + public function __construct() + { + if ($this->inited) + return true; + + $this->defaultExecTime = ini_get('max_execution_time'); + + // register subscripts to CLISetup + foreach (glob('setup/tools/filegen/*.ss.php') as $file) + include_once $file; + + $this->inited = true; + return true; + } + + // args: scriptToDo, scriptSuccess, null, null // ionn + public function run(&$args) : bool + { + $todo = &$args['doBuild']; + $done = &$args['doneBuild']; + + if (!$this->inited) + return false; + + + // check passed subscript names; limit to real scriptNames + if (($buildArgs = CLISetup::getOpt('build')) !== false) + { + if ($buildArgs === []) // used --build without arguments + $todo = array_keys($this->generators); // do everything + else if ($_ = array_intersect(array_keys($this->generators), $buildArgs)) + $todo = $_; + else + { + CLI::write('[build] no valid script names supplied', CLI::LOG_ERROR); + return false; + } + + // supplement self::NOTE_START + CLI::write(' - '.Lang::concat($todo), CLI::LOG_BLANK, false); + CLI::write(); + } + else if ($todo) + { + $todo = array_intersect(array_keys($this->generators), is_array($todo) ? $todo : [$todo]); + if (!$todo) + return false; + } + else + return false; + + // create user upload dir structure + foreach ($this->uploadDirs as $ud) + { + if (CLISetup::writeDir($ud)) + continue; + + CLI::write('[build] could not create directory: '.CLI::bold($ud), CLI::LOG_ERROR); + return false; + } + + $done = []; + $allOk = true; + + // start file generation + foreach ($todo as $cmd) + { + $success = false; + $scriptRef = &$this->generators[$cmd]; + + CLI::write('[build] gathering data for '.$cmd); + + if ($scriptRef->fulfillRequirements()) + $success = $scriptRef->generate(); + + if (!$success) + $allOk = false; + else + $done[] = $cmd; + + CLI::write('[build] subscript \''.$cmd.'\' returned '.($success ? 'successfully' : 'with errors'), $success ? CLI::LOG_OK : CLI::LOG_ERROR); + CLI::write(); + + set_time_limit($this->defaultExecTime); // reset to default for the next script + } + + return $allOk; + } + + public function writeCLIHelp() : bool + { + if ($args = CLISetup::getOpt('build')) + { + $anyHelp = false; + foreach ($args as $cmd) + if (isset($this->generators[$cmd]) && $this->generators[$cmd]->writeCLIHelp()) + $anyHelp = true; + + if ($anyHelp) + return true; + } + + CLI::write(' usage: php aowow --build= [--mpqDataDir: --locales:]', -1, false); + CLI::write(); + CLI::write(' Compiles files for a given SetupScript. Existing files are kept by default. Dependencies are taken into account by the triggered calls of --sync --update', -1, false); + CLI::write(); + + ksort($this->generators); + + $lines = [['Command', 'TC dependencies', 'AoWoW dependencies', 'Info']]; + foreach ($this->generators as $cmd => $ssRef) + { + $tcDeps = explode("\n", Lang::breakTextClean(implode(', ', $ssRef->getRemoteDependencies() ), 35, Lang::FMT_RAW)); + $aoDeps = explode("\n", Lang::breakTextClean(implode(', ', $ssRef->getSelfDependencies()[0]), 35, Lang::FMT_RAW)); + + for ($i = 0; $i < max(count($tcDeps), count($aoDeps)); $i++) + $lines[] = array( + $i ? '' : $cmd, + $tcDeps[$i] ?? '', + $aoDeps[$i] ?? '', + $i ? '' : $ssRef->getInfo() + ); + } + + CLI::writeTable($lines); + CLI::write(); + + return true; + } +}); + +?> diff --git a/setup/tools/clisetup/setup.func.php b/setup/tools/clisetup/setup.func.php deleted file mode 100644 index 33ae61f20..000000000 --- a/setup/tools/clisetup/setup.func.php +++ /dev/null @@ -1,376 +0,0 @@ - $what) - { - if ($what == 'auth' && (empty($AoWoWconf['auth']) || empty($AoWoWconf['auth']['host']))) - continue; - - // init proper access for further setup - if (DB::test($AoWoWconf[$what], $err)) - { - DB::load($idx, $AoWoWconf[$what]); - switch ($idx) - { - case DB_AOWOW: - if (DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'aowow_dbversion')) - Cfg::load(); // first time load after successful db setup - else - $error[] = ' * '.$what.': doesn\'t seem to contain aowow tables!'; - break; - case DB_WORLD: - if (!DB::World()->selectCell('SHOW TABLES LIKE ?', 'version')) - $error[] = ' * '.$what.': doesn\'t seem to contain TrinityCore world tables!'; - else if (DB::World()->selectCell('SELECT `cache_id` FROM `version`') < TDB_WORLD_MINIMUM_VER) - $error[] = ' * '.$what.': TDB world db is structurally outdated! (min rev.: '.CLI::bold(TDB_WORLD_MINIMUM_VER).')'; - break; - default: - // no further checks at this time - } - } - else - $error[] = ' * '.$what.': '.$err; - } - - return empty($error); - } - - function testSelf(array &$error) : bool - { - $error = []; - $test = function(&$protocol, &$host, $testFile, &$rCode) - { - $res = get_headers($protocol.$host.$testFile, true); - - if (!preg_match("/HTTP\/[0-9\.]+\s+([0-9]+)/", $res[0], $m)) - return false; - - $rCode = $m[1]; - - if ($rCode == 200) - return true; - - if ($rCode == 301 || $rCode == 302) - { - if (!empty($res['Location']) && preg_match("/(https?:\/\/)(.*)".strtr($testFile, ['/' => '\/', '.' => '\.'])."/i", is_array($res['Location']) ? array_pop($res['Location']) : $res['Location'], $n)) - { - $protocol = $n[1]; - $host = $n[2]; - } - - return false; - } - - $rCode = 0; - return false; - }; - - if (!DB::isConnected(DB_AOWOW)) - { - $error[] = ' * not connected to DB'; - return false; - } - - $prot = Cfg::get('FORCE_SSL') ? 'https://' : 'http://'; - $cases = array( - 'site_host' => [$prot, Cfg::get('SITE_HOST'), '/README.md'], - 'static_host' => [$prot, Cfg::get('STATIC_HOST'), '/css/aowow.css'] - ); - - foreach ($cases as $conf => [$protocol, $host, $testFile]) - { - if ($host) - { - if (!$test($protocol, $host, $testFile, $resp)) - { - if ($resp == 301 || $resp == 302) - { - CLI::write('self test received status '.CLI::bold($resp).' (page moved) for '.$conf.', pointing to: '.$protocol.$host.$testFile, CLI::LOG_WARN); - if (!CLI::read(['x' => ['should '.CLI::bold($conf).' be set to '.CLI::bold($host).' and force_ssl be updated? (y/n)', true, true, '/y|n/i']], $uiYN) || !$uiYN || strtolower($uiYN['x']) == 'n') - $error[] = ' * '.$protocol.$host.$testFile.' ['.$resp.']'; - else - { - Cfg::set($conf, $host); - Cfg::set('FORCE_SSL', $protocol == 'https://'); - } - - CLI::write(); - } - else - $error[] = ' * '.$protocol.$host.$testFile.' ['.$resp.']'; - } - } - else - $error[] = ' * '.strtoupper($conf).' is empty'; - } - - return empty($error); - } - - function testAcc(array &$error) : bool - { - $error = []; - return !!DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE `userPerms` = 1'); - } - - - /********************/ - /* get current step */ - /********************/ - - $startStep = 0; - if (file_exists('cache/firstrun')) - { - $rows = file('cache/firstrun'); - if ((int)$rows[0] == AOWOW_REVISION) - $startStep = (int)$rows[1]; - } - - if (CLISetup::getOpt('help')) - { - CLI::write(); - CLI::write(' usage: php aowow --setup [--locales: --mpqDataDir:]', -1, false); - CLI::write(); - CLI::write(' Initially essential connection information are set up and basic connectivity tests are run afterwards.', -1, false); - CLI::write(' In the main stage dbc and world data is compiled into the database and required sound, image and data files are generated.', -1, false); - CLI::write(' This does not require further input and will take about 15-20 minutes, plus 10 minutes per additional locale.', -1, false); - CLI::write(' Lastly pending updates are applied and you are prompted to create an administrator account.', -1, false); - - if ($startStep) - { - CLI::write(); - CLI::write(' You are currently on step '.($startStep + 1).' / '.count($steps).'. You can resume or restart the setup process.', -1, false); - } - - CLI::write(); - return; - } - - if ($startStep) - { - - CLI::write('Found firstrun progression info. (Halted on subscript '.($steps[$startStep][1][0] ? $steps[$startStep][1] : $steps[$startStep][0]).')', CLI::LOG_INFO); - $msg = ''; - if (!CLI::read(['x' => ['continue setup? (y/n)', true, true, '/y|n/i']], $uiYN) || !$uiYN || strtolower($uiYN['x']) == 'n') - { - $msg = 'Starting setup from scratch...'; - $startStep = 0; - } - else - $msg = 'Resuming setup from step '.$startStep.'...'; - - CLI::write(); - CLI::write($msg); - sleep(1); - } - - - /*******/ - /* run */ - /*******/ - - CLISetup::siteLock(CLISetup::LOCK_ON); - - foreach ($steps as $idx => $step) - { - if ($startStep > $idx) - continue; - - if (!strpos($step[0], '::') && !is_callable($step[0])) - require_once 'setup/tools/clisetup/'.$step[0].'.func.php'; - - if ($step[3]) - { - CLI::write($step[3]); - - if (!CLI::read([['Press any key to continue', true]])) // we don't actually care about the input - return; - } - - while (true) - { - if (strpos($step[0], '::')) - $res = call_user_func($step[0], $step[1]); - else - { - $args = &$step[1]; // see: https://github.com/php/php-src/issues/14202 - $res = $step[0]($args[0], $args[1]); - } - - // check script result - if ($step[2]) - { - $errors = []; - if (!$step[2]($errors)) - { - CLI::write($step[4], CLI::LOG_ERROR); - foreach ($errors as $e) - CLI::write($e); - } - else - { - $saveProgress($idx); - break; - } - } - else if ($res !== false) - { - $saveProgress($idx); - break; - } - - if (CLI::read(['x' => ['['.CLI::bold('c').']ontinue anyway? ['.CLI::bold('r').']etry? ['.CLI::bold('a').']bort?', true, true, '/c|r|a/i']], $uiCRA) && $uiCRA) - { - CLI::write(); - switch(strtolower($uiCRA['x'])) - { - case 'c': - $saveProgress($idx); - break 2; - case 'r': - break; - case 'a': - return; - } - } - else - { - CLI::write(); - return; - } - } - } - - unlink('cache/firstrun'); - CLISetup::siteLock(CLISetup::LOCK_OFF); - CLI::write('setup finished', CLI::LOG_OK); - return; -} - -?> diff --git a/setup/tools/clisetup/setup.us.php b/setup/tools/clisetup/setup.us.php new file mode 100644 index 000000000..bd08a8d68 --- /dev/null +++ b/setup/tools/clisetup/setup.us.php @@ -0,0 +1,184 @@ + [], 'doBuild' => []]; // ref to pass commands from 'update' to 'sync' + private $steps = array( + // [staticUS, $name, [...args]] + ['database', '', []], + ['configure', '', []], + // sql- and build- stuff here + ['update', '', []], + ['sync', '', []], + ['account', '', []] + ); + + private const STEP_FILE = 'cache/setup/firstrun'; + + // Note! Must be loaded after all SetupScripts have been registered + public function __construct() + { + /********************/ + /* get current step */ + /********************/ + + if (file_exists(self::STEP_FILE)) + { + $rows = file(self::STEP_FILE); + if ((int)$rows[0] == AOWOW_REVISION) + $this->startStep = (int)$rows[1]; + } + + + /****************/ + /* define steps */ + /****************/ + + # link required steps with param + $this->steps[2][2] = &$this->dynArgs; // update + $this->steps[3][2] = &$this->dynArgs; // sync + + # from /sqlgen + /filegen .. already sorted by CLISetup + foreach (CLISetup::getSubScripts() as $name => [$invoker, $ssRef]) + { + if ($ssRef->isOptional) + continue; + + if ($invoker == 'sql') + array_splice($this->steps, -3, 0, [[$invoker, $name, ['doSql' => $name]]]); + else if ($invoker == 'build') + array_splice($this->steps, -3, 0, [[$invoker, $name, ['doBuild' => $name]]]); + } + } + + // args: null, null, null, null // nnnn + public function run(&$args) : bool + { + if ($this->startStep) + { + CLI::write('[setup] found firstrun progression info. (Halted on subscript: '.($this->steps[$this->startStep][1] ?: $this->steps[$this->startStep][0]).')', CLI::LOG_INFO); + $msg = ''; + if (!CLI::read(['x' => ['continue setup? (y/n)', true, true, '/y|n/i']], $uiN) || !$uiN || strtolower($uiN['x']) == 'n') + { + $msg = '[setup] starting from scratch...'; + $this->startStep = 0; + } + else + $msg = '[setup] resuming from step '.($this->startStep + 1).'...'; + + CLI::write(); + CLI::write($msg); + sleep(1); + } + + // init temp setup dir + if ($info = new SplFileInfo(self::STEP_FILE)) + CLISetup::writeDir($info->getPath()); + + + /*******/ + /* run */ + /*******/ + + foreach ($this->steps as $idx => [$usName, , $param]) + { + if ($this->startStep > $idx) + continue; + + while (true) + { + CLI::write('[setup] step '.($idx + 1).' / '.count($this->steps)); + if (CLISetup::run($usName, $param)) + { + $this->saveProgress($idx); + break; + } + + if (CLI::read(['x' => ['['.CLI::bold('c').']ontinue anyway? ['.CLI::bold('r').']etry? ['.CLI::bold('a').']bort?', true, true, '/c|r|a/i']], $uiCRA) && $uiCRA) + { + CLI::write(); + switch(strtolower($uiCRA['x'])) + { + case 'c': + $this->saveProgress($idx); + break 2; + case 'r': + break; + case 'a': + return false; + } + } + else + { + CLI::write(); + return false; + } + } + } + + unlink(self::STEP_FILE); + + return true; + } + + public function writeCLIHelp() : bool + { + CLI::write(' usage: php aowow --setup [--locales: --mpqDataDir:]', -1, false); + CLI::write(); + CLI::write(' Initially essential connection information are set up and basic connectivity tests run afterwards.', -1, false); + CLI::write(' In the main stage dbc and world data is compiled into the database and required sound, image and data files are generated.', -1, false); + CLI::write(' This should not require further input and will take about 15-20 minutes, plus 10 minutes per additional locale.', -1, false); + CLI::write(' Lastly pending updates are applied and you are prompted to create an administrator account.', -1, false); + + if ($this->startStep) + { + CLI::write(); + CLI::write(' You are currently on step '.($this->startStep + 1).' / '.count($this->steps).' ('.($this->steps[$this->startStep][1] ?: $this->steps[$this->startStep][0][1]).'). You can resume or restart the setup process.', -1, false); + } + + CLI::write(); + CLI::write(); + + return true; + } + + + /**********/ + /* helper */ + /**********/ + + private function saveProgress (int $nStep) : void + { + if ($h = fopen(self::STEP_FILE, 'w')) + { + fwrite($h, AOWOW_REVISION."\n".($nStep + 1)."\n"); + fclose($h); + } + else + CLI::write(' * UtilScript::setup - Could not access step file', CLI::LOG_ERROR); + } +}); + +?> diff --git a/setup/tools/clisetup/siteconfig.func.php b/setup/tools/clisetup/siteconfig.func.php deleted file mode 100644 index ee876665f..000000000 --- a/setup/tools/clisetup/siteconfig.func.php +++ /dev/null @@ -1,315 +0,0 @@ -' : ''); - - if ($flags & Cfg::FLAG_OPT_LIST) - return '[opt] '.toOptList($opts, $value, false); - - if ($flags & Cfg::FLAG_BITMASK) - return '[mask] '.toOptList($opts, $value, true); - - if ($flags & Cfg::FLAG_TYPE_FLOAT) - return '[float] '.floatVal($value); - - if ($flags & Cfg::FLAG_TYPE_INT) - return '[int] '.intVal($value); - - // if ($flags & Cfg::FLAG_TYPE_STRING) - if ($value === '') - return '[str] '.(($flags & Cfg::FLAG_REQUIRED) ? CLI::red('') : CLI::grey('')); - else - return '[str] "'.$value.'"'; - } - - while (true) - { - CLI::write('select a numerical index or name to use the corresponding entry'); - CLI::write(); - - $sumNum = 0; - $cfgList = []; - $hasEmpty = false; - $listBuff = []; - - foreach (Cfg::$categories as $idx => $cat) - { - $listBuff[] = '===== '.$cat.' ====='; - - foreach (Cfg::forCategory($idx) as $key => [$value, $flags, $catg, $default, $comment]) - { - $isPhp = $flags & Cfg::FLAG_PHP; - - if ($value === '' && ($flags & Cfg::FLAG_REQUIRED)) - $hasEmpty = true; - - $cfgList[$sumNum] = strtolower($key); - - $row = '['.CLI::bold($sumNum).'] '.(($sumNum) > 9 ? '' : ' ').($isPhp ? ' PHP ' : ' AOWOW '); - $row .= str_pad($isPhp ? strtolower($key) : strtoupper($key), 35); - - $opts = explode(' - ', $comment); - $row .= formatValue($value, $flags, $opts[1] ?? ''); - - $listBuff[] = $row; - $sumNum++; - } - } - - foreach ($listBuff as $b) - CLI::write($b); - - CLI::write(str_pad('['.CLI::bold($sumNum).']', 21).'add another php configuration'); - CLI::write(); - - if ($hasEmpty) - { - CLI::write('please configure the required empty settings', CLI::LOG_WARN); - CLI::write(); - } - - if (CLI::read(['idx' => ['', false, false, Cfg::PATTERN_CONF_KEY]], $uiIndex) && $uiIndex && $uiIndex['idx'] !== '') - { - $idx = array_search(strtolower($uiIndex['idx']), $cfgList); - if ($idx === false) - $idx = intVal($uiIndex['idx']); - - // add new php setting - if ($idx == $sumNum) - { - CLI::write('Adding additional php configuration.'); - CLI::write(); - - while (true) - { - $setting = array( - 'key' => ['option name', false, false, Cfg::PATTERN_CONF_KEY], - 'val' => ['value', ] - ); - if (CLI::read($setting, $uiSetting) && $uiSetting) - { - $key = strtolower($uiSetting['key']); - if ($err = Cfg::add($key, $uiSetting['val'])) - CLI::write($err, CLI::LOG_ERROR); - else - CLI::write('new php configuration added', CLI::LOG_OK); - - sleep(1); - CLI::write(); - break; - } - else - { - CLI::write('edit canceled! returning to list...', CLI::LOG_INFO); - CLI::write(); - sleep(1); - break; - } - } - } - // edit existing setting - else if ($idx >= 0 && $idx < $sumNum) - { - [$value, $flags, , $default, $comment] = Cfg::get($cfgList[$idx], false, true); - $key = $cfgList[$idx]; - $info = explode(' - ', $comment); - $buff = ''; - - $buff .= $flags & Cfg::FLAG_PHP ? 'PHP: ' : 'AOWOW: '; - $buff .= $flags & Cfg::FLAG_PHP ? $key : 'Cfg::'.strtoupper($key); - - if (!empty($info[0])) - $buff .= ' - '.$info[0]; - - CLI::write($buff); - CLI::write(); - CLI::write('VALUE: '.formatValue($value, $flags, $info[1] ?? '')); - CLI::write(); - CLI::write('['.CLI::bold('E').']dit'); - - if (!($flags & Cfg::FLAG_PERSISTENT)) - CLI::write('['.CLI::bold('D').']elete'); - - if ($default) - CLI::write('['.CLI::bold('R').']estore Default - '.$default); - - CLI::write(); - - while (true) - { - if (CLI::read(['idx' => ['', true, true, '/[edr]/i']], $uiEDR) && $uiEDR) - { - switch (strtoupper($uiEDR['idx'])) - { - case 'E': // edit value - $pattern = false; - $single = false; - $prompt = ['idx' => ['Select new value', false, &$single, &$pattern]]; - - if ($flags & Cfg::FLAG_OPT_LIST) - { - foreach (explode(', ', $info[1]) as $option) - { - [$val, $name] = explode(':', $option); - CLI::write('['.CLI::bold($val).'] '.$name); - } - $single = true; - $pattern = '/\d/'; - } - else if ($flags & Cfg::FLAG_BITMASK) - { - CLI::write('Bitmask: sum fields to select multiple options'); - foreach (explode(', ', $info[1]) as $option) - { - [$val, $name] = explode(':', $option); - CLI::write('['.CLI::bold(1 << $val).']'.str_pad('', 6 - strlen(1 << $val)).$name); - } - $pattern = '/\d+/'; - } - else if ($flags & Cfg::FLAG_TYPE_BOOL) - { - CLI::write('['.CLI::bold(0).'] Disabled'); - CLI::write('['.CLI::bold(1).'] Enabled'); - - $single = true; - $pattern = '/[01]/'; - } - - while (true) - { - if (CLI::read($prompt, $uiValue)) - { - CLI::write(); - - $inp = $uiValue['idx'] ?? ''; - - if ($err = Cfg::set($key, $inp, $updScripts)) - { - CLI::write($err, CLI::LOG_ERROR); - sleep(1); - continue; - } - else - { - CLI::write('setting updated', CLI::LOG_OK); - sleep(1); - break 3; - } - } - else - { - CLI::write('edit canceled! returning to selection...', CLI::LOG_INFO); - sleep(1); - break; - } - } - - break 2; - case 'R': // restore default - if (!$default) - continue 2; - - if ($err = Cfg::reset($key, $updScripts)) - CLI::write($err, CLI::LOG_ERROR); - else - CLI::write('default value restored', CLI::LOG_OK); - - sleep(1); - break 2; - case 'D': // delete config pair - if ($flags & Cfg::FLAG_PERSISTENT) - continue 2; - - if ($err = Cfg::delete($key)) - CLI::write($err, CLI::LOG_ERROR); - else - CLI::write("php setting deleted ['".$key."': '".$value."']", CLI::LOG_OK); - - sleep(1); - break 2; - } - } - else - { - CLI::write('edit canceled! returning to list...', CLI::LOG_INFO); - CLI::write(); - sleep(1); - break; - } - } - } - else - { - CLI::write('invalid selection', CLI::LOG_ERROR); - CLI::write(); - sleep(1); - } - } - else - { - CLI::write('leaving site configuration...', CLI::LOG_INFO); - CLI::write(); - break; - } - - // propagate changes to static files - if ($updScripts && (!class_exists('FileGen') || FileGen::getMode() != FileGen::MODE_FIRSTRUN)) - { - require_once 'setup/tools/clisetup/build.func.php'; - CLI::write(); - CLI::write('regenerating affected static content', CLI::LOG_INFO); - CLI::write(); - sleep(1); - - if ($_ = array_diff($updScripts, build($updScripts))) - { - CLI::write(' - the following updates returned with errors, please recheck those - '.implode(', ', $_), CLI::LOG_ERROR); - sleep(1); - } - - sleep(1); - $updScripts = []; - } - } -} - -?> diff --git a/setup/tools/clisetup/siteconfig.us.php b/setup/tools/clisetup/siteconfig.us.php new file mode 100644 index 000000000..45c055509 --- /dev/null +++ b/setup/tools/clisetup/siteconfig.us.php @@ -0,0 +1,468 @@ + cfgName [newValue]]'; + public const DESCRIPTION = 'Configure site variables.'; + public const PROMPT = 'SITE_HOST and STATIC_HOST *must* be set. Also enable FORCE_SSL if needed. You may also want to change other variables such as NAME, NAME_SHORT or LOCALES.'; + public const NOTE_ERROR = 'could not access:'; + + public const REQUIRED_DB = [DB_AOWOW]; + + public const USE_CLI_ARGS = true; + + private const HTTP_STATUS_OK = 200; + private const HTTP_STATUS_MOVED_PERM = 301; + private const HTTP_STATUS_MOVED_TEMP = 302; + + private $updScripts = []; + + // args: action, configName, configValue, pendingUpdates[] // iiio + public function run(&$args) : bool + { + $action = $args[0] ?? ''; + $name = $args[1] ?? ''; + $value = $args[2] ?? ''; + + $result = true; + switch (strtoupper($action)) + { + case 'E': + $result = $this->doEdit($name, $value); + break; + case 'R': + $result = $this->doRestore($name); + break; + case 'N': + $result = $this->doNew($name, $value); + break; + case 'D': + $result = $this->doDelete($name); + break; + default: + $this->showConfigList(); + } + + $args['doBuild'] = $this->updScripts; // push files to rebuild one level up + + return $result; + } + + private function showConfigList() : void + { + while (true) + { + CLI::write('select a numerical index or name to use the corresponding entry', -1, false); + CLI::write(); + + $sumNum = 0; + $cfgList = []; + $hasEmpty = false; + $listBuff = []; + + foreach (Cfg::$categories as $idx => $cat) + { + $listBuff[] = '===== '.$cat.' ====='; + + foreach (Cfg::forCategory($idx) as $key => [$value, $flags, $catg, $default, $comment]) + { + $isPhp = $flags & Cfg::FLAG_PHP; + + if ($value === '' && ($flags & Cfg::FLAG_REQUIRED)) + $hasEmpty = true; + + $cfgList[$sumNum] = strtolower($key); + + $row = '['.CLI::bold($sumNum).'] '.(($sumNum) > 9 ? '' : ' ').($isPhp ? ' PHP ' : ' AOWOW '); + $row .= str_pad($isPhp ? strtolower($key) : strtoupper($key), 35); + + $opts = explode(' - ', $comment); + $row .= $this->formatValue($value, $flags, $opts[1] ?? ''); + + $listBuff[] = $row; + $sumNum++; + } + } + + foreach ($listBuff as $b) + CLI::write($b, -1, false); + + CLI::write(str_pad('['.CLI::bold($sumNum).']', 21).'add another php configuration', -1, false); + CLI::write(); + + if ($hasEmpty) + { + CLI::write('please configure the required empty settings', CLI::LOG_WARN); + CLI::write(); + } + + if (CLI::read(['idx' => ['', false, false, Cfg::PATTERN_CONF_KEY]], $uiIndex) && $uiIndex && $uiIndex['idx'] !== '') + { + $idx = array_search(strtolower($uiIndex['idx']), $cfgList); + if ($idx === false) + $idx = intVal($uiIndex['idx']); + + // add new php setting + if ($idx == $sumNum) + $this->showNewConfig(); + // edit existing setting + else if ($idx >= 0 && $idx < $sumNum) + $this->showEditConfig($cfgList[$idx] ?? ''); + else + CLI::write('invalid selection', CLI::LOG_ERROR); + + CLI::write(); + sleep(1); + } + else + { + CLI::write('leaving site configuration...', CLI::LOG_INFO); + CLI::write(); + break; + } + } + } + + private function showNewConfig() : void + { + CLI::write('Adding additional php configuration.'); + CLI::write(); + + $setting = array( + 'key' => ['option name', false, false, Cfg::PATTERN_CONF_KEY], + 'val' => ['value', ] + ); + if (CLI::read($setting, $uiSetting) && $uiSetting) + $this->doNew($uiSetting['key'], $uiSetting['val']); + else + CLI::write('edit canceled! returning to list...', CLI::LOG_INFO); + } + + private function showEditConfig(string $key) : void + { + [$value, $flags, , $default, $comment] = Cfg::get($key, false, true); + $info = explode(' - ', $comment); + $buff = ''; + + $buff .= $flags & Cfg::FLAG_PHP ? 'PHP: ' : 'AOWOW: '; + $buff .= $flags & Cfg::FLAG_PHP ? $key : 'Cfg::'.strtoupper($key); + + if (!empty($info[0])) + $buff .= ' - '.$info[0]; + + CLI::write($buff); + CLI::write(); + + CLI::write('VALUE: '.$this->formatValue($value, $flags, $info[1] ?? '')); + CLI::write(); + CLI::write('['.CLI::bold('E').']dit'); + + if (!($flags & Cfg::FLAG_PERSISTENT)) + CLI::write('['.CLI::bold('D').']elete'); + + if ($default) + CLI::write('['.CLI::bold('R').']estore Default - '.$default); + + CLI::write(); + + while (true) + { + CLI::write(); + sleep(1); + + if (CLI::read(['idx' => ['', true, true, '/[edr]/i']], $uiEDR) && $uiEDR) + { + switch (strtoupper($uiEDR['idx'])) + { + case 'E': // edit value + if (!$this->doEdit($key)) + continue 2; + break 2; + case 'R': // restore default + if (!$this->doRestore($key)) + continue 2; + break 2; + case 'D': // delete config pair + if (!$this->doDelete($key)) + continue 2; + break 2; + } + } + else + { + CLI::write('edit canceled! returning to list...', CLI::LOG_INFO); + break; + } + } + } + + private function doEdit(string $key, ?string $newVal = null) : bool + { + [, $flags, , , $comment] = Cfg::get($key, false, true); + $info = explode(' - ', $comment); + + $pattern = '/.*/'; + $single = false; + $typeHint = []; + + if ($flags & Cfg::FLAG_OPT_LIST) + { + foreach (explode(', ', $info[1]) as $option) + { + [$val, $name] = explode(':', $option); + CLI::write('['.CLI::bold($val).'] '.$name); + } + $single = true; + $pattern = '/^\d$/'; + } + else if ($flags & Cfg::FLAG_BITMASK) + { + $typeHint[] = 'Bitmask: sum fields to select multiple options'; + foreach (explode(', ', $info[1]) as $option) + { + [$val, $name] = explode(':', $option); + CLI::write('['.CLI::bold(1 << $val).']'.str_pad('', 6 - strlen(1 << $val)).$name); + } + $pattern = '/^\d+$/'; + } + else if ($flags & Cfg::FLAG_TYPE_BOOL) + { + $typeHint[] = '['.CLI::bold(0).'] Disabled'; + $typeHint[] = '['.CLI::bold(1).'] Enabled'; + + $single = true; + $pattern = '/^[01]$/'; + } + else if ($flags & Cfg::FLAG_TYPE_INT) + $pattern = '/^-?\d+$/'; + else if ($flags & Cfg::FLAG_TYPE_FLOAT) + $pattern = '/^-?\d*(,|.)?\d+$/i'; + + while (true) + { + if (!isset($newVal)) + foreach ($typeHint as $th) + CLI::write($th); + + if ((isset($newVal) && preg_match($pattern, $newVal)) || CLI::read(['idx' => ['Select new value', false, $single, $pattern]], $uiValue)) + { + CLI::write(); + + $val = $newVal ?? $uiValue['idx'] ?? ''; + if ($err = Cfg::set($key, $val, $this->updScripts)) + { + CLI::write($err, CLI::LOG_ERROR); + continue; + } + else + { + CLI::write('setting updated', CLI::LOG_OK); + return true; + } + } + else + { + CLI::write('edit canceled! returning to selection...', CLI::LOG_INFO); + return false; + } + } + } + + private function doRestore(string $key) : bool + { + if ($err = Cfg::reset($key, $this->updScripts)) + { + CLI::write($err, CLI::LOG_ERROR); + return false; + } + + CLI::write('default value restored', CLI::LOG_OK); + return true; + } + + private function doNew(string $key, string $val) : bool + { + if ($err = Cfg::add($key, $val)) + { + CLI::write($err, CLI::LOG_ERROR); + return false; + } + + CLI::write('new php configuration added', CLI::LOG_OK); + return true; + } + + private function doDelete(string $key) : bool + { + if ($err = Cfg::delete($key)) + { + CLI::write($err, CLI::LOG_ERROR); + return false; + } + + CLI::write('php setting deleted: '.$key, CLI::LOG_OK); + return true; + } + + + /******************/ + /* Unit formating */ + /******************/ + + private function toOptList(string $options, $curVal, bool $bitmask = false) : string + { + $result = ''; + foreach (explode(', ', $options) as $opt) + { + [$val, $name] = explode(':', $opt); + $equal = $bitmask ? ($curVal & (1 << $val)) : $curVal == $val; + + $result .= '['.($equal ? 'x' : ' ').']'.$name.' '; + } + + return substr($result, 0, -1); + } + + private function formatValue($value, int $flags, string $opts) : string + { + if ($flags & Cfg::FLAG_TYPE_BOOL) + return '[bool] '.($value ? '' : ''); + + if ($flags & Cfg::FLAG_OPT_LIST) + return '[opt] '.$this->toOptList($opts, $value, false); + + if ($flags & Cfg::FLAG_BITMASK) + return '[mask] '.$this->toOptList($opts, $value, true); + + if ($flags & Cfg::FLAG_TYPE_FLOAT) + return '[float] '.floatVal($value); + + if ($flags & Cfg::FLAG_TYPE_INT) + return '[int] '.intVal($value); + + // if ($flags & Cfg::FLAG_TYPE_STRING) + if ($value === '') + return '[str] '.(($flags & Cfg::FLAG_REQUIRED) ? CLI::red('') : CLI::grey('')); + else + return '[str] "'.$value.'"'; + } + + + /****************/ + /* Help display */ + /****************/ + + public function writeCLIHelp(string ...$ss) : bool + { + CLI::write(' usage: php aowow --configure [action cfgName [newValue]]', -1, false); + CLI::write(); + CLI::write(' Configures site and php variables. If incomplete parameters are passed an interactive prompt will open.', -1, false); + CLI::write(); + CLI::write(' action:', -1, false); + CLI::write(' E - Edit variable named '.CLI::bold('cfgName').'. Optionally directly pass a new value.', -1, false); + CLI::write(' R - Restore default value of a variable '.CLI::bold('cfgName').'.', -1, false); + CLI::write(' N - Create a new php config value. Must be a valid php directive. Optionally directly pass a new value.', -1, false); + CLI::write(' D - Delete an existing php config value.', -1, false); + CLI::write(); + CLI::write(); + + return true; + } + + + /***********/ + /* DB test */ + /***********/ + + public function test(?array &$error = []) : bool + { + $error = []; + + if (!DB::isConnected(DB_AOWOW)) + { + $error[] = ' * not connected to DB'; + return false; + } + + $prot = Cfg::get('FORCE_SSL') ? 'https://' : 'http://'; + $cases = array( + 'site_host' => [$prot, Cfg::get('SITE_HOST'), '/robots.txt'], + 'static_host' => [$prot, Cfg::get('STATIC_HOST'), '/css/aowow.css'] + ); + + foreach ($cases as $conf => [$protocol, $host, $testFile]) + { + if ($host) + { + $resp = 0; + if (!$this->testCase($protocol, $host, $testFile, $resp)) + { + if ($resp == self::HTTP_STATUS_MOVED_PERM || $resp == self::HTTP_STATUS_MOVED_TEMP) + { + CLI::write('self test received status '.CLI::bold($resp).' (page moved) for '.$conf.', pointing to: '.$protocol.$host.$testFile, CLI::LOG_WARN); + if (!CLI::read(['x' => ['should '.CLI::bold($conf).' be set to '.CLI::bold($host).' and force_ssl be updated? (y/n)', true, true, '/y|n/i']], $uiN) || !$uiN || strtolower($uiN['x']) == 'n') + $error[] = ' * '.$protocol.$host.$testFile.' ['.$resp.']'; + else + { + Cfg::set($conf, $host); + Cfg::set('FORCE_SSL', $protocol == 'https://'); + } + + CLI::write(); + } + else + $error[] = ' * '.$protocol.$host.$testFile.' ['.$resp.']'; + } + } + else + $error[] = ' * '.strtoupper($conf).' is empty'; + } + + return empty($error); + } + + private function testCase(&$protocol, &$host, $testFile, &$status) : bool + { + $res = get_headers($protocol.$host.$testFile, true); + + if (!preg_match('/HTTP\/[0-9\.]+\s+([0-9]+)/', $res[0], $m)) + return false; + + $status = $m[1]; + + if ($status == self::HTTP_STATUS_OK) + return true; + + if ($status == self::HTTP_STATUS_MOVED_PERM || $status == self::HTTP_STATUS_MOVED_TEMP) + { + if (!empty($res['Location']) && preg_match('/(https?:\/\/)(.*)'.strtr($testFile, ['/' => '\/', '.' => '\.']).'/i', is_array($res['Location']) ? $res['Location'][0] : $res['Location'], $n)) + { + $protocol = $n[1]; + $host = $n[2]; + } + + return false; + } + + $status = 0; + return false; + } +}); + +?> diff --git a/setup/tools/clisetup/sql.func.php b/setup/tools/clisetup/sql.func.php deleted file mode 100644 index 796f1eef0..000000000 --- a/setup/tools/clisetup/sql.func.php +++ /dev/null @@ -1,68 +0,0 @@ - diff --git a/setup/tools/clisetup/sync.func.php b/setup/tools/clisetup/sync.func.php deleted file mode 100644 index 58412ba42..000000000 --- a/setup/tools/clisetup/sync.func.php +++ /dev/null @@ -1,59 +0,0 @@ - [--locales: --mpqDataDir: --force -f]', -1, false); - CLI::write(); - CLI::write(' Truncates and recreates AoWoW tables and static data files that depend on the given TC world table. Use this command after you updated your world database.', -1, false); - CLI::write(); - CLI::write(' e.g.: "php aowow --sync=creature_queststarter" causes the table aowow_quests_startend to be recreated.', -1, false); - CLI::write(' Also quest-related profiler files will be recreated as they depend on aowow_quests_startend and thus indirectly on creature_queststarter.', -1, false); - CLI::write(); - return; - } - - if (!DB::isConnected(DB_AOWOW) || !DB::isConnected(DB_WORLD)) - { - CLI::write('Database not yet set up!', CLI::LOG_WARN); - CLI::write('Please use '.CLI::bold('"php aowow --dbconfig"').' for setup', CLI::LOG_BLANK); - CLI::write(); - return; - } - - $_s = sql($s); - if ($s) - { - $_ = array_diff($s, $_s); - DB::Aowow()->query('UPDATE ?_dbversion SET `sql` = ?', $_ ? implode(' ', $_) : ''); - } - - $_b = build($b); - if ($b) - { - $_ = array_diff($b, $_b); - DB::Aowow()->query('UPDATE ?_dbversion SET `build` = ?', $_ ? implode(' ', $_) : ''); - } - - if (!$s && !$_s && !$b && !$_b && !CLISetup::getOpt('setup')) - CLI::write('no valid table names supplied', CLI::LOG_ERROR); -} - -?> diff --git a/setup/tools/clisetup/sync.us.php b/setup/tools/clisetup/sync.us.php new file mode 100644 index 000000000..ad3cd75e6 --- /dev/null +++ b/setup/tools/clisetup/sync.us.php @@ -0,0 +1,104 @@ +'; + + public const REQUIRED_DB = [DB_AOWOW, DB_WORLD]; + + // sqlToDo, buildToDo, null, null // iinn + public function run(&$args) : bool + { + $s = &$args['doSql']; + $b = &$args['doBuild']; + + // called manually + if ($s === null && $b === null) + { + [$s, $b] = $this->handleCLIOpt(); + if (!$s && !$b && !CLISetup::getOpt('setup')) + { + CLI::write('[sync] no valid table names supplied', CLI::LOG_ERROR); + return false; + } + } + + if ($s) + { + $io = ['doSql' => $s, 'doneSql' => []]; + CLISetup::run('sql', $io); + DB::Aowow()->query('UPDATE ?_dbversion SET `sql` = ?', implode(' ', array_diff($io['doSql'], $io['doneSql']))); + } + + if ($b) + { + $io = ['doBuild' => $b, 'doneBuild' => []]; + CLISetup::run('build', $io); + DB::Aowow()->query('UPDATE ?_dbversion SET `build` = ?', implode(' ', array_diff($io['doBuild'], $io['doneBuild']))); + } + + return true; + } + + private function handleCLIOpt() : array + { + $sql = []; + $build = []; + + $sync = CLISetup::getOpt('sync'); + if (!$sync) + return [$sql, $build]; + + foreach (CLISetup::getSubScripts() as $name => [$invoker, $ssRef]) + if (array_intersect($ssRef->getRemoteDependencies(), $sync)) + $$invoker[] = $name; + + do + { + $n = count($sql); + foreach (CLISetup::getSubScripts('sql') as $name => [, $ssRef]) + if (!in_array($name, $sql) && array_intersect($ssRef->getSelfDependencies()[0], $sql)) + $sql[] = $name; + } + while ($n != count($sql)); + + if ($sql) + foreach (CLISetup::getSubScripts('build') as $name => [, $ssRef]) + if (array_intersect($ssRef->getSelfDependencies()[0], $sql)) + $build[] = $name; + + return [array_unique($sql), array_unique($build)]; + } + + public function writeCLIHelp() : bool + { + CLI::write(' usage: php aowow --sync= [--locales: --mpqDataDir: --force -f]', -1, false); + CLI::write(); + CLI::write(' Truncates and recreates AoWoW tables and static data files that depend on the given TC world table. Use this command after you updated your world database.', -1, false); + CLI::write(); + CLI::write(' e.g.: "php aowow --sync=creature_queststarter" causes the table aowow_quests_startend to be recreated.', -1, false); + CLI::write(' Also quest-related profiler files will be recreated as they depend on aowow_quests_startend and thus indirectly on creature_queststarter.', -1, false); + CLI::write(); + CLI::write(); + + return true; + } +}) + +?> diff --git a/setup/tools/clisetup/update.func.php b/setup/tools/clisetup/update.func.php deleted file mode 100644 index 05acbb60d..000000000 --- a/setup/tools/clisetup/update.func.php +++ /dev/null @@ -1,99 +0,0 @@ -selectRow('SELECT `date`, `part` FROM ?_dbversion')); - - if (CLISetup::getOpt('help')) - { - CLI::write(); - CLI::write(' usage: php aowow --update', -1, false); - CLI::write(); - CLI::write(' Checks /setup/updates for new *.sql files and applies them. If required by an applied update, the --sql and --build command are triggered afterwards.', -1, false); - CLI::write(' Use this after fetching the latest rev. from Github.', -1, false); - CLI::write(); - CLI::write(' Last Update: '.date(Util::$dateFormatInternal, $date).' (Part #'.$part.')', -1, false); - CLI::write(); - return; - } - - CLI::write('checking sql updates'); - CLISetup::siteLock(CLISetup::LOCK_ON); - - $nFiles = 0; - foreach (glob('setup/updates/*.sql') as $file) - { - $pi = pathinfo($file); - [$fDate, $fPart] = explode('_', $pi['filename']); - - $fDate = intVal($fDate); - - if ($date && $fDate < $date) - continue; - else if ($part && $date && $fDate == $date && $fPart <= $part) - continue; - - $nFiles++; - - $updQuery = ''; - $nQuerys = 0; - foreach (file($file) as $line) - { - // skip comments - if (substr($line, 0, 2) == '--' || $line == '') - continue; - - $updQuery .= $line; - - // semicolon at the end -> end of query - if (substr(trim($line), -1, 1) == ';') - { - if (DB::Aowow()->query($updQuery)) - $nQuerys++; - - $updQuery = ''; - } - } - - DB::Aowow()->query('UPDATE ?_dbversion SET `date`= ?d, `part` = ?d', $fDate, $fPart); - CLI::write(' -> '.date('d.m.Y', $fDate).' #'.$fPart.': '.$nQuerys.' queries applied', CLI::LOG_OK); - } - - CLISetup::siteLock(CLISetup::LOCK_RESTORE); - CLI::write($nFiles ? 'applied '.$nFiles.' update(s)' : 'db is already up to date', CLI::LOG_OK); - - // fetch sql/build after applying updates, as they may contain sync-prompts - [$sql, $build] = array_values(DB::Aowow()->selectRow('SELECT `sql`, `build` FROM ?_dbversion')); - - sleep(1); - - $sql = trim($sql) ? array_unique(explode(' ', trim(preg_replace('/[^a-z_]+/i', ' ', $sql)))) : []; - $build = trim($build) ? array_unique(explode(' ', trim(preg_replace('/[^a-z_]+/i', ' ', $build)))) : []; - - if ($sql) - CLI::write('The following table(s) require syncing: '.implode(', ', $sql)); - - if ($build) - CLI::write('The following file(s) require syncing: '.implode(', ', $build)); -} - -?> diff --git a/setup/tools/clisetup/update.us.php b/setup/tools/clisetup/update.us.php new file mode 100644 index 000000000..45e9cf734 --- /dev/null +++ b/setup/tools/clisetup/update.us.php @@ -0,0 +1,121 @@ +date, $this->part] = array_values(DB::Aowow()->selectRow('SELECT `date`, `part` FROM ?_dbversion')); + } + + // args: null, null, sqlToDo, buildToDo // nnoo + public function run(&$args) : bool + { + $sql = &$args['doSql']; + $build = &$args['doBuild']; + + CLI::write('[update] checking for sql updates...'); + + $nFiles = 0; + foreach (glob('setup/updates/*.sql') as $file) + { + $pi = pathinfo($file); + + // invalid file + if (!preg_match('/(\d{10})_(\d{2})/', $pi['filename'], $m)) + continue; + + $fDate = intVal($m[1]); + $fPart = intVal($m[2]); + + if ($this->date && $fDate < $this->date) + continue; + else if ($this->part && $this->date && $fDate == $this->date && $fPart <= $this->part) + continue; + + $nFiles++; + + $updQuery = ''; + $nQuerys = 0; + foreach (file($file) as $line) + { + // skip comments + if (substr($line, 0, 2) == '--' || $line == '') + continue; + + $updQuery .= $line; + + // semicolon at the end -> end of query + if (substr(trim($line), -1, 1) == ';') + { + if (DB::Aowow()->query($updQuery)) + $nQuerys++; + + $updQuery = ''; + } + } + + DB::Aowow()->query('UPDATE ?_dbversion SET `date`= ?d, `part` = ?d', $fDate, $fPart); + CLI::write(' -> '.date('d.m.Y', $fDate).' #'.$fPart.': '.$nQuerys.' queries applied', CLI::LOG_OK); + } + + CLI::write('[update] ' . ($nFiles ? 'applied '.$nFiles.' update(s)' : 'db is already up to date'), CLI::LOG_OK); + + // fetch sql/build after applying updates, as they may contain sync-prompts + [$sql, $build] = DB::Aowow()->selectRow('SELECT `sql` AS "0", `build` AS "1" FROM ?_dbversion'); + + $sql = trim($sql) ? array_unique(explode(' ', trim(preg_replace('/[^a-z]+/i', ' ', $sql)))) : []; + $build = trim($build) ? array_unique(explode(' ', trim(preg_replace('/[^a-z]+/i', ' ', $build)))) : []; + + sleep(1); + + if ($sql) + CLI::write('[update] The following sql scripts have been scheduled: '.implode(', ', $sql)); + + if ($build) + CLI::write('[update] The following build scripts have been scheduled: '.implode(', ', $build)); + + return true; + } + + public function writeCLIHelp() : bool + { + CLI::write(' usage: php aowow --update', -1, false); + CLI::write(); + CLI::write(' Checks /setup/updates for new *.sql files and applies them. If required by an applied update, the --sql and --build command are triggered afterwards.', -1, false); + CLI::write(' Use this after fetching the latest rev. from Github.', -1, false); + CLI::write(); + CLI::write(' Last Update: '.date(Util::$dateFormatInternal, $this->date).' (Part #'.$this->part.')', -1, false); + CLI::write(); + CLI::write(); + + return true; + } +}); + +?> diff --git a/setup/tools/fileGen.class.php b/setup/tools/fileGen.class.php deleted file mode 100644 index b43563d19..000000000 --- a/setup/tools/fileGen.class.php +++ /dev/null @@ -1,243 +0,0 @@ - [file, path, TCDeps] - 'searchplugin' => ['aowow.xml', 'static/download/searchplugins/', []], - 'power' => ['power.js', 'static/widgets/', []], - 'searchboxScript' => ['searchbox.js', 'static/widgets/', []], - 'demo' => ['demo.html', 'static/widgets/power/', []], - 'searchboxBody' => ['searchbox.html', 'static/widgets/searchbox/', []], - 'realmMenu' => ['profile_all.js', 'static/js/', ['realmlist']], - 'locales' => ['locale.js', 'static/js/', []], - 'markup' => ['Markup.js', 'static/js/', []], - 'itemScaling' => ['item-scaling', 'datasets/', []] - ); - public static $datasets = array( // name => [AowowDeps, TCDeps, info] - 'realms' => [null, ['realmlist'], 'datasets/realms'], - 'statistics' => [null, ['player_levelstats', 'player_classlevelstats'], 'datasets/statistics'], - 'simpleImg' => [null, null, 'static/images/wow/[icons, Interface, ]/*'], - 'complexImg' => [null, null, 'static/images/wow/[maps, talents/backgrounds, ]/*'], - 'talentCalc' => [null, null, 'datasets//talents-*'], - 'pets' => [['spawns', 'creature'], null, 'datasets//pets'], - 'talentIcons' => [null, null, 'static/images/wow/talents/icons/*'], - 'glyphs' => [['items', 'spell'], null, 'datasets//glyphs'], - 'itemsets' => [['itemset', 'spell'], null, 'datasets//itemsets'], - 'enchants' => [['items', 'spell', 'itemenchantment'], null, 'datasets//enchants'], - 'gems' => [['items', 'spell', 'itemenchantment'], null, 'datasets//gems'], - 'profiler' => [['quests', 'quests_startend', 'spell', 'currencies', 'achievement', 'titles'], null, 'datasets//p-*'], - 'weightPresets' => [null, null, 'datasets/weight-presets'], - 'soundfiles' => [['sounds'], null, 'static/wowsounds/*'] - ); - - public static $defaultExecTime = 30; - - private static $reqDirs = array( - 'static/uploads/screenshots/normal/', - 'static/uploads/screenshots/pending/', - 'static/uploads/screenshots/resized/', - 'static/uploads/screenshots/temp/', - 'static/uploads/screenshots/thumb/', - 'static/uploads/temp/', - 'static/uploads/guide/images/', - 'static/download/searchplugins/', - 'static/wowsounds/' - ); - - public static function init(int $mode = self::MODE_NORMAL, array $updScripts = []) : bool - { - self::$defaultExecTime = ini_get('max_execution_time'); - self::$mode = $mode; - - if (!CLISetup::$localeIds) - { - CLI::write('No valid locale specified. Check your config or --locales parameter, if used', CLI::LOG_ERROR); - return false; - } - - // create directory structure - CLI::write('FileGen::init() - creating required directories'); - $pathOk = 0; - foreach (self::$reqDirs as $rd) - if (CLISetup::writeDir($rd)) - $pathOk++; - - CLI::write('created '.$pathOk.' extra paths'.($pathOk == count(self::$reqDirs) ? '' : ' with errors')); - CLI::write(); - - // handle command prompts - if (!self::handleCLIOpts($doScripts) && !$updScripts) - return false; - - // check passed subscript names; limit to real scriptNames - self::$subScripts = array_merge(array_keys(self::$tplFiles), array_keys(self::$datasets)); - if ($doScripts || $updScripts) - self::$subScripts = array_intersect($doScripts ?: $updScripts, self::$subScripts); - - return true; - } - - private static function handleCLIOpts(?array &$doScripts) : bool - { - $doScripts = []; - - if (CLISetup::getOpt('help') && self::$mode == self::MODE_NORMAL) - { - if (in_array('simpleImg', CLISetup::getOpt('build'))) - CLISetup::optHelp(1 << 3); - else if (in_array('complexImg', CLISetup::getOpt('build'))) - CLISetup::optHelp(1 << 4); - else - self::printCLIHelp(); - - return false; - } - - // required subScripts - if ($sync = CLISetup::getOpt('sync')) - { - foreach (self::$tplFiles as $name => $info) - if (!empty($info[2]) && array_intersect($sync, $info[2])) - $doScripts[] = $name; - - foreach (self::$datasets as $name => $info) - { - // recursive deps from SqlGen - if (!empty($info[0]) && array_intersect(SqlGen::$subScripts, $info[0])) - $doScripts[] = $name; - else if (!empty($info[1]) && array_intersect($sync, $info[1])) - $doScripts[] = $name; - } - - if (!$doScripts) - return false; - - $doScripts = array_unique($doScripts); - return true; - } - else if (is_array($_ = CLISetup::getOpt('build'))) - { - $doScripts = $_; - return true; - } - - return false; - } - - private static function printCLIHelp() - { - CLI::write(); - CLI::write(' usage: php aowow --build= [--mpqDataDir: --locales:]', -1, false); - CLI::write(); - CLI::write(' Compiles files for a given subScript. Existing files are kept by default. Dependencies are taken into account by the triggered calls of --sync and --update', -1, false); - - $lines = [['available subScripts', 'affected files', 'TC dependencies', 'AoWoW dependencies']]; - foreach (array_merge(array_keys(self::$tplFiles), array_keys(self::$datasets)) as $s) - $lines[] = array( - ' * '.$s, - isset(self::$tplFiles[$s]) ? self::$tplFiles[$s][1].self::$tplFiles[$s][0] : self::$datasets[$s][2], - !empty(self::$tplFiles[$s][2]) ? implode(' ', self::$tplFiles[$s][2]) : (!empty(self::$datasets[$s][1]) ? implode(' ', self::$datasets[$s][1]) : ''), - !empty(self::$datasets[$s][0]) ? implode(' ', self::$datasets[$s][0]) : '' - ); - - CLI::writeTable($lines); - } - - public static function generate($key, array $updateIds = []) - { - $success = false; - $reqDBC = []; - - if (file_exists('setup/tools/filegen/'.$key.'.func.php')) - require_once 'setup/tools/filegen/'.$key.'.func.php'; - else if (empty(self::$tplFiles[$key])) - { - CLI::write(sprintf(ERR_MISSING_INCL, $key, 'setup/tools/filegen/'.$key.'.func.php', CLI::LOG_ERROR)); - return false; - } - - CLI::write('FileGen::generate() - gathering data for '.$key); - - if (!empty(self::$tplFiles[$key])) - { - [$file, $destPath, $deps] = self::$tplFiles[$key]; - - if ($content = file_get_contents(self::$tplPath.$file.'.in')) - { - // replace constants - $content = Cfg::applyToString($content); - - // check for required auxiliary DBC files - foreach ($reqDBC as $req) - if (!CLISetup::loadDBC($req)) - continue; - - // must generate content - // PH format: /*setup:*/ - $funcOK = true; - if (preg_match_all('/\/\*setup:([\w\-_]+)\*\//i', $content, $m)) - { - foreach ($m[1] as $func) - { - if (function_exists($func)) - $content = str_replace('/*setup:'.$func.'*/', $func(), $content); - else - { - $funcOK = false; - CLI::write('No function for was registered for placeholder '.$func.'().', CLI::LOG_ERROR); - if (!array_reduce(get_included_files(), function ($inArray, $itr) use ($func) { return $inArray || false !== strpos($itr, $func); }, false)) - CLI::write('Also, expected include setup/tools/filegen/'.$name.'.func.php was not found.'); - } - } - } - - if ($content && $funcOK) - if (CLISetup::writeFile($destPath.$file, $content)) - $success = true; - } - else - CLI::write(sprintf(ERR_READ_FILE, CLI::bold(self::$tplPath.$file.'.in')), CLI::LOG_ERROR); - } - else if (!empty(self::$datasets[$key])) - { - if (function_exists($key)) - { - // check for required auxiliary DBC files - foreach ($reqDBC as $req) - if (!CLISetup::loadDBC($req)) - return false; - - $success = $key($updateIds); - } - else - CLI::write(' - subscript \''.$key.'\' not defined in included file', CLI::LOG_ERROR); - } - - set_time_limit(self::$defaultExecTime); // reset to default for the next script - - return $success; - } - - public static function getMode() - { - return self::$mode; - } -} - -?> diff --git a/setup/tools/filegen/complexImg.func.php b/setup/tools/filegen/complexImg.func.php deleted file mode 100644 index 0b448d8b2..000000000 --- a/setup/tools/filegen/complexImg.func.php +++ /dev/null @@ -1,749 +0,0 @@ - - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -if (!defined('AOWOW_REVISION')) - die('illegal access'); - -if (!CLI) - die('not in cli mode'); - - // note: for the sake of simplicity, this function handles all images, that must be stitched together (which are mostly maps) - - $reqDBC = ['talenttab', 'chrclasses', 'worldmapoverlay', 'worldmaparea']; - - function complexImg() - { - $mapWidth = 1002; - $mapHeight = 668; - $threshold = 95; // alpha threshold to define subZones: set it too low and you have unspawnable areas inside a zone; set it too high and the border regions overlap - $runTime = ini_get('max_execution_time'); - $locStr = null; - $imgPath = CLISetup::$srcDir.'%sInterface/'; - $destDir = 'static/images/wow/'; - $success = true; - $modeMask = 0x7; // talentBGs, regular maps, spawn-related alphaMaps - $paths = array( - 0x16 => ['WorldMap/', true, null], - 0x01 => ['TalentFrame/', false, null], - 0x08 => ['Glues/Credits/',false, null] - ); - - $createAlphaImage = function($w, $h) - { - $img = imagecreatetruecolor($w, $h); - - imagesavealpha($img, true); - imagealphablending($img, false); - - $bgColor = imagecolorallocatealpha($img, 0, 0, 0, 127); - imagefilledrectangle($img, 0, 0, imagesx($img) - 1, imagesy($img) - 1, $bgColor); - - imagecolortransparent($img, $bgColor); - imagealphablending($img, true); - - imagecolordeallocate($img, $bgColor); - - return $img; - }; - - // prefer manually converted PNG files (as the imagecreatefromblp-script has issues with some formats) - // alpha channel issues observed with locale deDE Hilsbrad and Elwynn - maps - // see: https://github.com/Kanma/BLPConverter - $loadImageFile = function($path) - { - $result = null; - - $file = $path.'.png'; - if (CLISetup::fileExists($file)) - { - CLI::write('manually converted png file present for '.$path.'.', CLI::LOG_INFO); - $result = imagecreatefrompng($file); - } - - if (!$result) - { - $file = $path.'.blp'; - if (CLISetup::fileExists($file)) - $result = imagecreatefromblp($file); - } - - return $result; - }; - - $assembleImage = function($baseName, $order, $w, $h) use ($loadImageFile) - { - $dest = imagecreatetruecolor($w, $h); - imagesavealpha($dest, true); - imagealphablending($dest, false); - - $_h = $h; - foreach ($order as $y => $row) - { - $_w = $w; - foreach ($row as $x => $suffix) - { - $src = $loadImageFile($baseName.$suffix); - if (!$src) - { - CLI::write(' - complexImg: tile '.$baseName.$suffix.'.blp missing.', CLI::LOG_ERROR); - unset($dest); - return null; - } - - imagecopyresampled($dest, $src, 256 * $x, 256 * $y, 0, 0, min($_w, 256), min($_h, 256), min($_w, 256), min($_h, 256)); - $_w -= 256; - - unset($src); - } - $_h -= 256; - } - - return $dest; - }; - - $writeImage = function($name, $ext, $src, $w, $h, $done) - { - $ok = false; - $dest = imagecreatetruecolor($w, $h); - imagesavealpha($dest, true); - imagealphablending($dest, false); - imagecopyresampled($dest, $src, 0, 0, 0, 0, $w, $h, imagesx($src), imagesy($src)); - - switch ($ext) - { - case 'jpg': - $ok = imagejpeg($dest, $name.'.'.$ext, 85); - break; - case 'png': - $ok = imagepng($dest, $name.'.'.$ext); - break; - default: - CLI::write($done.' - unsupported file fromat: '.$ext, CLI::LOG_WARN); - } - - imagedestroy($dest); - - if ($ok) - { - chmod($name.'.'.$ext, Util::FILE_ACCESS); - CLI::write($done.' - image '.$name.'.'.$ext.' written', CLI::LOG_OK, true, true); - } - else - CLI::write($done.' - could not create image '.$name.'.'.$ext, CLI::LOG_ERROR); - - return $ok; - }; - - $createSpawnMap = function($img, $zoneId) use ($mapHeight, $mapWidth, $threshold) - { - CLI::write(' - creating spawn map'); - - $tmp = imagecreate(1000, 1000); - $cbg = imagecolorallocate($tmp, 255, 255, 255); - $cfg = imagecolorallocate($tmp, 0, 0, 0); - - for ($y = 0; $y < 1000; $y++) - { - for ($x = 0; $x < 1000; $x++) - { - $a = imagecolorat($img, ($x * $mapWidth) / 1000, ($y * $mapHeight) / 1000) >> 24; - imagesetpixel($tmp, $x, $y, $a < $threshold ? $cfg : $cbg); - } - } - - imagepng($tmp, 'setup/generated/alphaMaps/' . $zoneId . '.png'); - - imagecolordeallocate($tmp, $cbg); - imagecolordeallocate($tmp, $cfg); - imagedestroy($tmp); - }; - - $checkSourceDirs = function($sub) use ($imgPath, &$paths, $modeMask) - { - $hasMissing = false; - foreach ($paths as $idx => [$subDir, $isLocalized, $realPath]) - { - if ($realPath && !$isLocalized) - continue; - - $p = sprintf($imgPath, $sub).$subDir; - if (CLISetup::fileExists($p)) - { - if ($isLocalized) - $paths[$idx][2][substr($sub, 0, -1)] = $p; - else - $paths[$idx][2] = $p; - } - else - $hasMissing = true; - } - - return !$hasMissing; - }; - - // do not change order of params! - $o = CLISetup::getOpt('talentbgs', 'maps', 'spawn-maps', 'artwork', 'area-maps'); - $m = 0x0; - $i = 0; - foreach ($o as $k => $v) - { - if ($v) - $m |= 1 << $i; - $i++; - } - - if ($m) - $modeMask = $m; - - foreach ($paths as $mode => $__) - if (!($mode & $modeMask)) - unset($paths[$mode]); - - foreach (CLISetup::$expectedPaths as $xp => $locId) - { - if (!in_array($locId, CLISetup::$localeIds)) - continue; - - if ($xp) // if in subDir add trailing slash - $xp .= '/'; - - $checkSourceDirs($xp); // do not break; maps are localized - } - - $locList = []; - foreach (CLISetup::$expectedPaths as $xp => $locId) - if (in_array($locId, CLISetup::$localeIds)) - $locList[] = $xp; - - CLI::write('required resources overview:', CLI::LOG_INFO); - foreach ($paths as [$path, $isLocalized, $realPath]) - { - if (!$realPath) - CLI::write(CLI::red('MISSING').' - '.str_pad($path, 14).' @ '.sprintf($imgPath, '['.implode(',', $locList).']/').$path); - else if ($isLocalized) - { - $foundLoc = []; - foreach (CLISetup::$localeIds as $locId) - foreach (CLISetup::$expectedPaths as $xp => $lId) - if ($locId == $lId && isset($realPath[$xp]) && !isset($foundLoc[$locId])) - $foundLoc[$locId] = $xp; - - if ($diff = array_diff(CLISetup::$localeIds, array_keys($foundLoc))) - { - $buff = []; - foreach ($diff as $d) - $buff[] = CLI::yellow(Util::$localeStrings[$d]); - foreach ($foundLoc as $str) - $buff[] = CLI::green($str); - - CLI::write(CLI::yellow('PARTIAL').' - '.str_pad($path, 14).' @ '.sprintf($imgPath, '['.implode(',', $buff).']/').$path); - } - else - CLI::write(CLI::green(' FOUND ').' - '.str_pad($path, 14).' @ '.sprintf($imgPath, '['.implode(',', $foundLoc).']/').$path); - } - else - CLI::write(CLI::green(' FOUND ').' - '.str_pad($path, 14).' @ '.$realPath); - } - - CLI::write(); - - // if no subdir had sufficient data, diaf - if (count(array_filter(array_column($paths, 2))) != count($paths)) - { - CLI::write('one or more required directories are missing:', CLI::LOG_ERROR); - return; - } - else - sleep(1); - - /**************/ - /* TalentTabs */ - /**************/ - - if ($modeMask & 0x01) - { - if (CLISetup::writeDir($destDir.'hunterpettalents/') && CLISetup::writeDir($destDir.'talents/backgrounds/')) - { - // [classMask, creatureFamilyMask, tabNr, textureStr] - - $tTabs = DB::Aowow()->select('SELECT tt.creatureFamilyMask, tt.textureFile, tt.tabNumber, cc.fileString FROM dbc_talenttab tt LEFT JOIN dbc_chrclasses cc ON cc.id = (LOG(2, tt.classMask) + 1)'); - $order = array( - ['-TopLeft', '-TopRight'], - ['-BottomLeft', '-BottomRight'] - ); - - if ($tTabs) - { - $sum = 0; - $total = count($tTabs); - CLI::write('Processing '.$total.' files from TalentFrame/ ...'); - - foreach ($tTabs as $tt) - { - ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time) - $sum++; - $done = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9); - - if ($tt['creatureFamilyMask']) // is PetCalc - { - $size = [244, 364]; - $name = $destDir.'hunterpettalents/bg_'.(log($tt['creatureFamilyMask'], 2) + 1); - } - else - { - $size = [204, 554]; - $name = $destDir.'talents/backgrounds/'.strtolower($tt['fileString']).'_'.($tt['tabNumber'] + 1); - } - - if (!CLISetup::getOpt('force') && file_exists($name.'.jpg')) - { - CLI::write($done.' - file '.$name.'.jpg was already processed', CLI::LOG_BLANK, true, true); - continue; - } - - $im = $assembleImage($paths[0x1][2].'/'.$tt['textureFile'], $order, 256 + 44, 256 + 75); - if (!$im) - { - CLI::write(' - could not assemble file '.$tt['textureFile'], CLI::LOG_ERROR); - continue; - } - - if (!$writeImage($name, 'jpg', $im, $size[0], $size[1], $done)) - $success = false; - } - } - else - $success = false; - - ini_set('max_execution_time', $runTime); - } - else - $success = false; - } - - /************/ - /* Worldmap */ - /************/ - - if ($modeMask & 0x16) - { - $mapDirs = array( - ['maps/%snormal/', 'jpg', 488, 325], - ['maps/%soriginal/', 'jpg', 0, 0], // 1002, 668 - ['maps/%ssmall/', 'jpg', 224, 149], - ['maps/%szoom/', 'jpg', 772, 515] - ); - - // as the js expects them - $baseLevelFix = array( - // WotLK maps - // Halls of Stone; The Nexus; Violet Hold; Gundrak; Obsidian Sanctum; Eye of Eternity; Vault of Archavon; Trial of the Champion; The Forge of Souls; Pit of Saron; Halls of Reflection - 4264 => 1, 4265 => 1, 4415 => 1, 4416 => 1, 4493 => 0, 4500 => 1, 4603 => 1, 4723 => 1, 4809 => 1, 4813 => 1, 4820 => 1, - // Cata maps for WotLK instances - // TheStockade; TheBloodFurnace; Ragefire; TheUnderbog; TheBotanica; WailingCaverns; TheSlavePens; TheShatteredHalls; HellfireRamparts; RazorfenDowns; RazorfenKraul; ManaTombs - // ShadowLabyrinth; TheTempleOfAtalHakkar (simplified layout); BlackTemple; TempestKeep; MoltenCore; GruulsLair; CoilfangReservoir; MagtheridonsLair; OnyxiasLair; SunwellPlateau; - 717 => 1, 3713 => 1, 2437 => 1, 3716 => 1, 3847 => 1, 718 => 1, 3717 => 1, 3714 => 1, 3562 => 1, 722 => 1, 491 => 1, 3792 => 1, - 3789 => 1, 1477 => 1, 3959 => 0, 3845 => 1, 2717 => 1, 3923 => 1, 3607 => 1, 3836 => 1, 2159 => 1, 4075 => 0 - ); - - $wmo = DB::Aowow()->select('SELECT *, worldMapAreaId AS ARRAY_KEY, id AS ARRAY_KEY2 FROM dbc_worldmapoverlay WHERE textureString <> ""'); - $wma = DB::Aowow()->select('SELECT * FROM dbc_worldmaparea'); - if (!$wma || !$wmo) - { - $success = false; - CLI::write(' - could not read required dbc files: WorldMapArea.dbc ['.count($wma).' entries]; WorldMapOverlay.dbc ['.count($wmo).' entries]', CLI::LOG_ERROR); - return; - } - - // fixups... - foreach ($wma as &$a) - { - if ($a['areaId']) - continue; - - switch ($a['id']) - { - case 13: $a['areaId'] = -6; break; // Kalimdor - case 14: $a['areaId'] = -3; break; // Eastern Kingdoms - case 466: $a['areaId'] = -2; break; // Outland - case 485: $a['areaId'] = -5; break; // Northrend - } - } - array_unshift($wma, ['id' => -1, 'areaId' => -1, 'nameINT' => 'World'], ['id' => -4, 'areaId' => -4, 'nameINT' => 'Cosmic']); - - $sumMaps = count(CLISetup::$localeIds) * count($wma); - - CLI::write('Processing '.$sumMaps.' files from WorldMap/ ...'); - - foreach (CLISetup::$localeIds as $progressLoc => $l) - { - // create destination directories - $dirError = false; - foreach ($mapDirs as $md) - if (!CLISetup::writeDir($destDir . sprintf($md[0], strtolower(Util::$localeStrings[$l]).'/'))) - $dirError = true; - - if ($modeMask & 0x04) - if (!CLISetup::writeDir('setup/generated/alphaMaps')) - $dirError = true; - - if ($dirError) - { - $success = false; - CLI::write(' - complexImg: could not create map directories for locale '.$l.'. skipping...', CLI::LOG_ERROR); - continue; - } - - - // source for mapFiles - $mapSrcDir = null; - $locDirs = array_reverse(array_filter(CLISetup::$expectedPaths, function($var) use ($l) { return !$var || $var == $l; }), true); - foreach ($locDirs as $mapLoc => $__) - { - if(!isset($paths[0x16][2][$mapLoc])) - continue; - - $p = sprintf($imgPath, $mapLoc.'/').$paths[0x16][0]; - if (CLISetup::fileExists($p)) - { - CLI::write(' - using files from '.($mapLoc ?: '/').' for locale '.Util::$localeStrings[$l], CLI::LOG_INFO); - $mapSrcDir = $p.'/'; - break; - } - } - - if ($mapSrcDir === null) - { - $success = false; - CLI::write(' - no suitable localized map files found for locale '.$l, CLI::LOG_ERROR); - continue; - } - - - foreach ($wma as $progressArea => $areaEntry) - { - $curMap = $progressArea + count($wma) * $progressLoc; - $progress = ' - ' . str_pad($curMap.'/'.($sumMaps), 10) . str_pad('('.number_format($curMap * 100 / $sumMaps, 2).'%)', 9); - - $wmaId = $areaEntry['id']; - $zoneId = $areaEntry['areaId']; - $textureStr = $areaEntry['nameINT']; - - $path = $mapSrcDir.$textureStr; - if (!CLISetup::fileExists($path)) - { - $success = false; - CLI::write('worldmap file '.$path.' missing for selected locale '.Util::$localeStrings[$l], CLI::LOG_ERROR); - continue; - } - - $fmt = array( - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12] - ); - - CLI::write($textureStr . " [" . $zoneId . "]"); - - $overlay = $createAlphaImage($mapWidth, $mapHeight); - - // zone has overlays (is in open world; is not multiLeveled) - if (isset($wmo[$wmaId])) - { - CLI::write(' - area has '.count($wmo[$wmaId]).' overlays'); - - foreach ($wmo[$wmaId] as &$row) - { - $i = 1; - $y = 0; - while ($y < $row['h']) - { - $x = 0; - while ($x < $row['w']) - { - $img = $loadImageFile($path . '/' . $row['textureString'] . $i); - if (!$img) - { - CLI::write(' - complexImg: tile '.$path.'/'.$row['textureString'].$i.'.blp missing.', CLI::LOG_ERROR); - break 2; - } - - imagecopy($overlay, $img, $row['x'] + $x, $row['y'] + $y, 0, 0, imagesx($img), imagesy($img)); - - // prepare subzone image - if ($modeMask & 0x10) - { - if (!isset($row['maskimage'])) - { - $row['maskimage'] = $createAlphaImage($row['w'], $row['h']); - $row['maskcolor'] = imagecolorallocatealpha($row['maskimage'], 255, 64, 192, 64); - } - - for ($my = 0; $my < imagesy($img); $my++) - for ($mx = 0; $mx < imagesx($img); $mx++) - if ((imagecolorat($img, $mx, $my) >> 24) < $threshold) - imagesetpixel($row['maskimage'], $x + $mx, $y + $my, $row['maskcolor']); - } - - imagedestroy($img); - - $x += 256; - $i++; - } - $y += 256; - } - } - - // create spawn-maps if wanted - if ($modeMask & 0x04) - $createSpawnMap($overlay, $zoneId); - } - - // check, if the current zone is multiLeveled - // if there are also files present without layer-suffix assume them as layer: 0 - $multiLeveled = false; - $multiLevel = 0; - do - { - if (!CLISetup::filesInPath('/'.$textureStr.'\/'.$textureStr.($multiLevel + 1).'_\d\.(blp|png)/i', true)) - break; - - $multiLevel++; - $multiLeveled = true; - } - while ($multiLevel < 18); // Karazhan has 17 frickin floors - - // check if we can create base map anyway - $png = $path.'/'.$textureStr.'1.png'; - $blp = $path.'/'.$textureStr.'1.blp'; - $hasBaseMap = CLISetup::fileExists($blp) || CLISetup::fileExists($png); - - CLI::write(' - area has '.($multiLeveled ? $multiLevel . ' levels' : 'only base level')); - - $map = null; - for ($i = 0; $i <= $multiLevel; $i++) - { - ini_set('max_execution_time', 120); // max 120sec per image - - $file = $path.'/'.$textureStr; - - if (!$i && !$hasBaseMap) - continue; - - // if $multiLeveled also suffix -0 to baseMap if it exists - if ($i && $multiLeveled) - $file .= $i.'_'; - - $doSkip = 0x0; - $outFile = []; - - foreach ($mapDirs as $idx => $info) - { - $outFile[$idx] = $destDir . sprintf($info[0], strtolower(Util::$localeStrings[$l]).'/') . $zoneId; - - $floor = $i; - if ($zoneId == 4100) // ToCStratholme: map order fix - $floor += 1; - - if ($multiLeveled && !(isset($baseLevelFix[$zoneId]) && $i == $baseLevelFix[$zoneId])) - $outFile[$idx] .= '-'.$floor; - - if (!CLISetup::getOpt('force') && file_exists($outFile[$idx].'.'.$info[1])) - { - CLI::write($progress.' - file '.$outFile[$idx].'.'.$info[1].' was already processed', CLI::LOG_BLANK, true, true); - $doSkip |= (1 << $idx); - } - } - - if ($doSkip == 0xF) - continue; - - $map = $assembleImage($file, $fmt, $mapWidth, $mapHeight); - if (!$map) - { - $success = false; - CLI::write(' - could not create image resource for map '.$zoneId.($multiLevel ? ' level '.$i : '')); - continue; - } - - if (!$multiLeveled) - { - imagecopymerge($map, $overlay, 0, 0, 0, 0, imagesx($overlay), imagesy($overlay), 100); - imagedestroy($overlay); - } - - // create map - if ($modeMask & 0x02) - { - foreach ($mapDirs as $idx => $info) - { - if ($doSkip & (1 << $idx)) - continue; - - if (!$writeImage($outFile[$idx], $info[1], $map, $info[2] ?: $mapWidth, $info[3] ?: $mapHeight, $progress)) - $success = false; - } - } - } - - // also create subzone-maps - if ($map && isset($wmo[$wmaId]) && $modeMask & 0x10) - { - foreach ($wmo[$wmaId] as &$row) - { - $doSkip = 0x0; - $outFile = []; - - foreach ($mapDirs as $idx => $info) - { - $outFile[$idx] = $destDir . sprintf($info[0], strtolower(Util::$localeStrings[$l]).'/') . $row['areaTableId']; - if (!CLISetup::getOpt('force') && file_exists($outFile[$idx].'.'.$info[1])) - { - CLI::write($progress.' - file '.$outFile[$idx].'.'.$info[1].' was already processed', CLI::LOG_BLANK, true, true); - $doSkip |= (1 << $idx); - } - } - - if ($doSkip == 0xF) - continue; - - $subZone = imagecreatetruecolor($mapWidth, $mapHeight); - imagecopy($subZone, $map, 0, 0, 0, 0, imagesx($map), imagesy($map)); - imagecopy($subZone, $row['maskimage'], $row['x'], $row['y'], 0, 0, imagesx($row['maskimage']), imagesy($row['maskimage'])); - - foreach ($mapDirs as $idx => $info) - { - if ($doSkip & (1 << $idx)) - continue; - - if (!$writeImage($outFile[$idx], $info[1], $subZone, $info[2] ?: $mapWidth, $info[3] ?: $mapHeight, $progress)) - $success = false; - } - - imagedestroy($subZone); - } - } - - if ($map) - imagedestroy($map); - - // this takes a while; ping mysql just in case - DB::Aowow()->selectCell('SELECT 1'); - } - } - } - - /***********/ - /* Credits */ - /***********/ - - if ($modeMask & 0x08) // optional tidbits (not used by default) - { - if (CLISetup::writeDir($destDir.'Interface/Glues/Credits/')) - { - // tile ordering - $order = array( - 1 => array( - [1] - ), - 2 => array( - [1], - [2] - ), - 4 => array( - [1, 2], - [3, 4] - ), - 6 => array( - [1, 2, 3], - [4, 5, 6] - ), - 8 => array( - [1, 2, 3, 4], - [5, 6, 7, 8] - ) - ); - - $imgGroups = []; - $files = CLISetup::filesInPath('/'.str_replace('/', '\\/', $paths[0x8][2]).'/i', true); - foreach ($files as $f) - { - if (preg_match('/([^\/]+)(\d).(blp|png)/i', $f, $m)) - { - if ($m[1] && $m[2]) - { - if (!isset($imgGroups[$m[1]])) - $imgGroups[$m[1]] = $m[2]; - else if ($imgGroups[$m[1]] < $m[2]) - $imgGroups[$m[1]] = $m[2]; - } - } - } - - // errör-korrekt - $imgGroups['Desolace'] = 4; - $imgGroups['BloodElf_Female'] = 6; - - $total = count($imgGroups); - $sum = 0; - - CLI::write('Processing '.$total.' files from Glues/Credits/...'); - - foreach ($imgGroups as $file => $fmt) - { - ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time) - - $sum++; - $done = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9); - $name = $destDir.'Interface/Glues/Credits/'.$file; - - if (!CLISetup::getOpt('force') && file_exists($name.'.png')) - { - CLI::write($done.' - file '.$name.'.png was already processed', CLI::LOG_BLANK, true, true); - continue; - } - - if (!isset($order[$fmt])) - { - CLI::write(' - pattern for file '.$name.' not set. skipping', CLI::LOG_WARN); - continue; - } - - $im = $assembleImage($paths[0x8][2].'/'.$file, $order[$fmt], count($order[$fmt][0]) * 256, count($order[$fmt]) * 256); - if (!$im) - { - CLI::write(' - could not assemble file '.$name, CLI::LOG_ERROR); - continue; - } - - if (!$writeImage($name, 'png', $im, count($order[$fmt][0]) * 256, count($order[$fmt]) * 256, $done)) - $success = false; - } - - ini_set('max_execution_time', $runTime); - } - else - $success = false; - } - - return $success; - } - -?> diff --git a/setup/tools/filegen/demo.ss.php b/setup/tools/filegen/demo.ss.php new file mode 100644 index 000000000..54cfe6dbe --- /dev/null +++ b/setup/tools/filegen/demo.ss.php @@ -0,0 +1,22 @@ + [[], CLISetup::ARGV_PARAM, 'Fills powered tooltip demo page (static/widgets/power/demo.html) with site variables.'] + ); + + protected $fileTemplateSrc = ['demo.html.in']; + protected $fileTemplateDest = ['static/widgets/power/demo.html']; +}); + +?> diff --git a/setup/tools/filegen/enchants.func.php b/setup/tools/filegen/enchants.ss.php similarity index 74% rename from setup/tools/filegen/enchants.func.php rename to setup/tools/filegen/enchants.ss.php index efd043c86..9645b4dd5 100644 --- a/setup/tools/filegen/enchants.func.php +++ b/setup/tools/filegen/enchants.ss.php @@ -7,91 +7,95 @@ die('not in cli mode'); - // Create 'enchants'-file for available locales - // this script requires the following dbc-files to be parsed and available - // Spells, SkillLineAbility, SpellItemEnchantment - - /* Examples - 15: { - name:'Leichtes Rüstungsset', - quality:1, - icon:'INV_Misc_ArmorKit_17', - source:-2304, - skill:-1, - slots:525008, - enchantment:'Verstärkt (+8 Rüstung)', - jsonequip:{"armor":8,"reqlevel":1}, - temp:0, - classes:0, - gearscore:1 - }, - 2928: { - name:'Ring - Zaubermacht', - quality:-1, - icon:'INV_Misc_Note_01', - source:27924, - skill:333, - slots:1024, - enchantment:'+12 Zaubermacht', - jsonequip:{"splpwr":12}, - temp:0, - classes:0, - gearscore:15 - }, - 3231: { - name:['Handschuhe - Waffenkunde','Armschiene - Waffenkunde'], - quality:-1, - icon:'spell_holy_greaterheal', - source:[44484,44598], - skill:333, - slots:[512,256], - enchantment:'+15 Waffenkundewertung', - jsonequip:{reqlevel:60,"exprtng":15}, - temp:0, - classes:0, - gearscore:20 // fuck, i'm doing this.. - }, - */ - - function enchants() +/* Examples + 15: { + name:'Leichtes Rüstungsset', + quality:1, + icon:'INV_Misc_ArmorKit_17', + source:-2304, + skill:-1, + slots:525008, + enchantment:'Verstärkt (+8 Rüstung)', + jsonequip:{"armor":8,"reqlevel":1}, + temp:0, + classes:0, + gearscore:1 + }, + 2928: { + name:'Ring - Zaubermacht', + quality:-1, + icon:'INV_Misc_Note_01', + source:27924, + skill:333, + slots:1024, + enchantment:'+12 Zaubermacht', + jsonequip:{"splpwr":12}, + temp:0, + classes:0, + gearscore:15 + }, + 3231: { + name:['Handschuhe - Waffenkunde','Armschiene - Waffenkunde'], + quality:-1, + icon:'spell_holy_greaterheal', + source:[44484,44598], + skill:333, + slots:[512,256], + enchantment:'+15 Waffenkundewertung', + jsonequip:{reqlevel:60,"exprtng":15}, + temp:0, + classes:0, + gearscore:20 // fuck, i'm doing this.. + }, +*/ + +CLISetup::registerSetup("build", new class extends SetupScript +{ + protected $info = array( + 'enchants' => [[], CLISetup::ARGV_PARAM, 'Compiles enchantment effects to file for the item comparison tool and profiler tool.'] + ); + + protected $setupAfter = [['items', 'spell', 'itemenchantment', 'icons', 'source'], []]; + protected $requiredDirs = ['datasets/']; + protected $localized = true; + + public function generate() : bool { // from g_item_slots: 13:"One-Hand", 15:"Ranged", 17:"Two-Hand", $slotPointer = [13, 17, 15, 15, 13, 17, 17, 13, 17, null, 17, null, null, 13, null, 13, null, null, null, null, 17]; + $castItems = []; - $successs = true; - $enchantSpells = DB::Aowow()->select(' - SELECT - s.id AS ARRAY_KEY, - effect1MiscValue, - equippedItemClass, equippedItemInventoryTypeMask, equippedItemSubClassMask, - skillLine1, - IFNULL(i.name, ?) AS iconString, - name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 - FROM - ?_spell s - LEFT JOIN - ?_icons i ON i.id = s.iconId - WHERE - effect1Id = ?d AND - name_loc0 NOT LIKE "QA%"' - ,DEFAULT_ICON, SPELL_EFFECT_ENCHANT_ITEM); - - // check directory-structure - foreach (Util::$localeStrings as $dir) - if (!CLISetup::writeDir('datasets/'.$dir)) - $success = false; + $enchantSpells = DB::Aowow()->select( + 'SELECT s.id AS ARRAY_KEY, + effect1MiscValue, + equippedItemClass, equippedItemInventoryTypeMask, equippedItemSubClassMask, + skillLine1, + IFNULL(i.name, ?) AS iconString, + name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 + FROM ?_spell s + LEFT JOIN ?_icons i ON i.id = s.iconId + WHERE effect1Id = ?d AND + name_loc0 NOT LIKE "QA%"', + DEFAULT_ICON, SPELL_EFFECT_ENCHANT_ITEM + ); $enchIds = array_column($enchantSpells, 'effect1MiscValue'); $enchantments = new EnchantmentList(array(['id', $enchIds], Cfg::get('SQL_LIMIT_NONE'))); if ($enchantments->error) { - CLI::write('Required table ?_itemenchantment seems to be empty! Leaving enchants()...', CLI::LOG_ERROR); + CLI::write('[enchants] Required table ?_itemenchantment seems to be empty!', CLI::LOG_ERROR); CLI::write(); return false; } $castItems = new ItemList(array(['spellId1', array_keys($enchantSpells)], ['src.typeId', null, '!'], Cfg::get('SQL_LIMIT_NONE'))); + if ($castItems->error) + { + CLI::write('[enchants] Required table ?_items seems to be empty!', CLI::LOG_ERROR); + CLI::write(); + return false; + } foreach (CLISetup::$localeIds as $lId) { @@ -106,20 +110,20 @@ function enchants() $eId = $es['effect1MiscValue']; if (!$enchantments->getEntry($eId)) { - CLI::write(' * could not find enchantment #'.$eId.' referenced by spell #'.$esId, CLI::LOG_WARN); + CLI::write('[enchants] * could not find enchantment #'.$eId.' referenced by spell #'.$esId, CLI::LOG_WARN); continue; } // slots have to be recalculated $slot = 0; - if ($es['equippedItemClass'] == 4) // armor + if ($es['equippedItemClass'] == ITEM_CLASS_ARMOR) { if ($invType = $es['equippedItemInventoryTypeMask']) $slot = $invType >> 1; else /* if (equippedItemSubClassMask == 64) */ // shields have it their own way <_< $slot = (1 << (14 - 1)); } - else if ($es['equippedItemClass'] == 2) // weapon + else if ($es['equippedItemClass'] == ITEM_CLASS_WEAPON) { foreach ($slotPointer as $i => $sp) { @@ -253,10 +257,11 @@ function enchants() $file = 'datasets/'.User::$localeString.'/enchants'; if (!CLISetup::writeFile($file, $toFile)) - $success = false; + $this->success = false; } - return $successs; + return $this->success; } +}); ?> diff --git a/setup/tools/filegen/gems.func.php b/setup/tools/filegen/gems.func.php deleted file mode 100644 index b3781b516..000000000 --- a/setup/tools/filegen/gems.func.php +++ /dev/null @@ -1,104 +0,0 @@ -Select( - 'SELECT i.id AS itemId, - i.name_loc0, i.name_loc2, i.name_loc3, i.name_loc4, i.name_loc6, i.name_loc8, - IF (i.id < 36000 OR i.itemLevel < 70, ?d, ?d) AS expansion, - i.quality, - ic.name AS icon, - i.gemEnchantmentId AS enchId, - i.gemColorMask AS colors, - i.requiredSkill, - i.itemLevel - FROM ?_items i - JOIN ?_icons ic ON ic.id = i.iconId - WHERE i.gemEnchantmentId <> 0 - ORDER BY i.id DESC', - EXP_BC, EXP_WOTLK - ); - $success = true; - - // check directory-structure - foreach (Util::$localeStrings as $dir) - if (!CLISetup::writeDir('datasets/'.$dir)) - $success = false; - - $enchIds = []; - foreach ($gems as $pop) - $enchIds[] = $pop['enchId']; - - $enchantments = new EnchantmentList(array(['id', $enchIds], Cfg::get('SQL_LIMIT_NONE'))); - if ($enchantments->error) - { - CLI::write('Required table ?_itemenchantment seems to be empty! Leaving gems()...', CLI::LOG_ERROR); - CLI::write(); - return false; - } - - foreach (CLISetup::$localeIds as $lId) - { - set_time_limit(5); - - User::useLocale($lId); - Lang::load($lId); - - $gemsOut = []; - foreach ($gems as $pop) - { - if (!$enchantments->getEntry($pop['enchId'])) - { - CLI::write(' * could not find enchantment #'.$pop['enchId'].' referenced by item #'.$pop['itemId'], CLI::LOG_WARN); - continue; - } - - $gemsOut[$pop['itemId']] = array( - 'name' => Util::localizedString($pop, 'name'), - 'quality' => $pop['quality'], - 'icon' => strToLower($pop['icon']), - 'enchantment' => $enchantments->getField('name', true), - 'jsonequip' => $enchantments->getStatGainForCurrent(), - 'colors' => $pop['colors'], - 'expansion' => $pop['expansion'], - 'gearscore' => Util::getGemScore($pop['itemLevel'], $pop['quality'], $pop['requiredSkill'] == SKILL_JEWELCRAFTING, $pop['itemId']) - ); - } - - $toFile = "var g_gems = ".Util::toJSON($gemsOut).";"; - $file = 'datasets/'.User::$localeString.'/gems'; - - if (!CLISetup::writeFile($file, $toFile)) - $success = false; - } - - return $success; - } - -?> diff --git a/setup/tools/filegen/gems.ss.php b/setup/tools/filegen/gems.ss.php new file mode 100644 index 000000000..b7f3c4daa --- /dev/null +++ b/setup/tools/filegen/gems.ss.php @@ -0,0 +1,101 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles gems to file for the item comparison tool and profiler tool.'] + ); + + protected $setupAfter = [['items', 'itemenchantment', 'icons'], []]; + protected $requiredDirs = ['datasets/']; + protected $localized = true; + + public function generate() : bool + { + // sketchy, but should work + // id < 36'000 || ilevel < 70 ? BC : WOTLK + $gems = DB::Aowow()->select( + 'SELECT i.id AS itemId, + i.name_loc0, i.name_loc2, i.name_loc3, i.name_loc4, i.name_loc6, i.name_loc8, + IF (i.id < 36000 OR i.itemLevel < 70, ?d, ?d) AS expansion, + i.quality, + ic.name AS icon, + i.gemEnchantmentId AS enchId, + i.gemColorMask AS colors, + i.requiredSkill, + i.itemLevel + FROM ?_items i + JOIN ?_icons ic ON ic.id = i.iconId + WHERE i.gemEnchantmentId <> 0 + ORDER BY i.id DESC', + EXP_BC, EXP_WOTLK + ); + + $enchantments = new EnchantmentList(array(['id', array_column($gems, 'enchId')], Cfg::get('SQL_LIMIT_NONE'))); + if ($enchantments->error) + { + CLI::write('[gems] Required table ?_itemenchantment seems to be empty!', CLI::LOG_ERROR); + CLI::write(); + return false; + } + + foreach (CLISetup::$localeIds as $lId) + { + set_time_limit(5); + + User::useLocale($lId); + Lang::load($lId); + + $gemsOut = []; + foreach ($gems as $g) + { + if (!$enchantments->getEntry($g['enchId'])) + { + CLI::write('[gems] * could not find enchantment #'.$g['enchId'].' referenced by item #'.$g['itemId'], CLI::LOG_WARN); + continue; + } + + $gemsOut[$g['itemId']] = array( + 'name' => Util::localizedString($g, 'name'), + 'quality' => $g['quality'], + 'icon' => strToLower($g['icon']), + 'enchantment' => $enchantments->getField('name', true), + 'jsonequip' => $enchantments->getStatGainForCurrent(), + 'colors' => $g['colors'], + 'expansion' => $g['expansion'], + 'gearscore' => Util::getGemScore($g['itemLevel'], $g['quality'], $g['requiredSkill'] == SKILL_JEWELCRAFTING, $g['itemId']) + ); + } + + $toFile = "var g_gems = ".Util::toJSON($gemsOut).";"; + $file = 'datasets/'.User::$localeString.'/gems'; + + if (!CLISetup::writeFile($file, $toFile)) + $this->success = false; + } + + return $this->success; + } +}); + +?> diff --git a/setup/tools/filegen/glyphs.func.php b/setup/tools/filegen/glyphs.ss.php similarity index 68% rename from setup/tools/filegen/glyphs.func.php rename to setup/tools/filegen/glyphs.ss.php index 90d742a55..560eab7d9 100644 --- a/setup/tools/filegen/glyphs.func.php +++ b/setup/tools/filegen/glyphs.ss.php @@ -7,24 +7,30 @@ die('not in cli mode'); - /* Example - 40896: { - "name":"Glyph of Frenzied Regeneration", - "description":"For 6 sec after activating Frenzied Regeneration, healing effects on you are 40% more powerful. However, your Frenzied Regeneration now always costs 60 Rage and no longer converts Rage into health.", - "icon":"ability_bullrush", - "type":1, - "classs":11, - "skill":798, - "level":25, - }, - */ - - // Create 'glyphs'-file for available locales - // this script requires the following dbc-files to be parsed and available - - function glyphs() +/* Example + 40896: { + "name":"Glyph of Frenzied Regeneration", + "description":"For 6 sec after activating Frenzied Regeneration, healing effects on you are 40% more powerful. However, your Frenzied Regeneration now always costs 60 Rage and no longer converts Rage into health.", + "icon":"ability_bullrush", + "type":1, + "classs":11, + "skill":798, + "level":25, + }, +*/ + +CLISetup::registerSetup("build", new class extends SetupScript +{ + protected $info = array( + 'glyphs' => [[], CLISetup::ARGV_PARAM, 'Compiles glyphs to file for the talent calculator tool.'] + ); + + protected $setupAfter = [['items', 'spell', 'glyphproperties', 'icons'], []]; + protected $requiredDirs = ['datasets/']; + protected $localized = true; + + public function generate() : bool { - $success = true; $glyphList = DB::Aowow()->Select( 'SELECT i.id AS itemId, i.*, @@ -41,12 +47,8 @@ function glyphs() JOIN ?_glyphproperties g ON g.id = s1.effect1MiscValue JOIN ?_spell s2 ON s2.id = g.spellId JOIN ?_icons ic ON ic.id = s1.iconIdAlt - WHERE i.classBak = 16'); - - // check directory-structure - foreach (Util::$localeStrings as $dir) - if (!CLISetup::writeDir('datasets/'.$dir)) - $success = false; + WHERE i.classBak = ?d', + ITEM_CLASS_GLYPH); $glyphSpells = new SpellList(array(['s.id', array_keys($glyphList)], Cfg::get('SQL_LIMIT_NONE'))); @@ -83,9 +85,11 @@ function glyphs() $file = 'datasets/'.User::$localeString.'/glyphs'; if (!CLISetup::writeFile($file, $toFile)) - $success = false; + $this->success = false; } - return $success; + return $this->success; } +}); + ?> diff --git a/setup/tools/filegen/img-artwork.ss.php b/setup/tools/filegen/img-artwork.ss.php new file mode 100644 index 000000000..0786ef7f7 --- /dev/null +++ b/setup/tools/filegen/img-artwork.ss.php @@ -0,0 +1,130 @@ + [[], CLISetup::ARGV_PARAM, 'Generate images from /glues/credits (not used on page)'], + ); + + public $isOptional = true; + + private const TILEORDER = array( + 1 => [ [1] ], + 2 => [ [1], + [2] ], + 4 => [ [1, 2], + [3, 4] ], + 6 => [ [1, 2, 3], + [4, 5, 6] ], + 8 => [ [1, 2, 3, 4], + [5, 6, 7, 8] ], + 9 => [ [1, 2, 3], + [4, 5, 6], + [7, 8, 9] ] + ); + + // src, resourcePath, localized, [tileOrder], [[dest, destW, destH]] + private $genSteps = array( + ['Glues/Credits/', null, false, self::TILEORDER, [['cache/Artworks/', 0, 0]]] + ); + + public function __construct() + { + $this->imgPath = CLISetup::$srcDir.$this->imgPath; + $this->maxExecTime = ini_get('max_execution_time'); + + foreach ($this->genSteps[0][self::$GEN_IDX_DEST_INFO] as $dir) + $this->requiredDirs[] = $dir[0]; + } + + public function generate() : bool + { + if (!$this->checkSourceDirs()) + { + CLI::write('one or more source directories are missing:', CLI::LOG_ERROR); + $this->success = false; + return false; + } + + sleep(2); + + [, $realPath, , $tileOrder, $outInfo] = $this->genSteps[0]; + + $sum = 0; + $imgGroups = []; + $files = CLISetup::filesInPath('/'.str_replace('/', '\\/', $realPath).'/i', true); + $fileTpl = $outInfo[0][0].'%s.png'; + + foreach ($files as $f) + { + if (preg_match('/([^\/]+)(\d).blp/i', $f, $m)) + { + if (!$m[1] || !$m[2]) + continue; + + if (!isset($imgGroups[$m[1]])) + $imgGroups[$m[1]] = $m[2]; + else if ($imgGroups[$m[1]] < $m[2]) + $imgGroups[$m[1]] = $m[2]; + } + } + + // errör-korrekt + if (isset($imgGroups['Desolace'])) + $imgGroups['Desolace'] = 4; + + $total = count($imgGroups); + + CLI::write('Processing '.$total.' files from '.$realPath.' ...'); + + foreach ($imgGroups as $name => $fmt) + { + ini_set('max_execution_time', $this->maxExecTime); + + $sum++; + $this->status = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9); + $file = sprintf($fileTpl, $name); + + if (!CLISetup::getOpt('force') && file_exists($file)) + { + CLI::write($this->status.' - file '.$file.' was already processed', CLI::LOG_BLANK, true, true); + continue; + } + + if (!isset($tileOrder[$fmt])) + { + CLI::write(' - pattern for file '.$name.' not set. skipping', CLI::LOG_WARN); + $this->success = false; + continue; + } + + $order = $tileOrder[$fmt]; + + $im = $this->assembleImage($realPath.'/'.$name, $order, count($order[0]) * 256, count($order) * 256); + if (!$im) + { + CLI::write(' - could not assemble file '.$name, CLI::LOG_ERROR); + $this->success = false; + continue; + } + + if (!$this->writeImageFile($im, $file, count($order[0]) * 256, count($order) * 256)) + $this->success = false; + } + + ini_set('max_execution_time', $this->maxExecTime); + + return $this->success; + } +}); + +?> diff --git a/setup/tools/filegen/img-maps.ss.php b/setup/tools/filegen/img-maps.ss.php new file mode 100644 index 000000000..4e05757cd --- /dev/null +++ b/setup/tools/filegen/img-maps.ss.php @@ -0,0 +1,1082 @@ + [[ ], CLISetup::ARGV_PARAM, 'Generate zone and continental maps and the corresponding \'zones\' datasets.' ], +/* 1 */ 'spawnmaps' => [['1'], CLISetup::ARGV_OPTIONAL, 'Fallback to generate alpha masks for each zone to match creature and gameobject spawn points.'], +/* 2 */ 'subzones' => [['2'], CLISetup::ARGV_OPTIONAL, 'Generate additional area maps with highlighting for subzones (optional; skipped by default)' ], +/* 4 */ 'skip-zones' => [['3'], CLISetup::ARGV_OPTIONAL, 'Prevent default output of zone maps.' ] + ); + + protected $useGlobalStrings = true; + protected $dbcSourceFiles = ['worldmapoverlay', 'worldmaparea', 'dungeonmap']; + protected $requiredDirs = ['datasets/']; + + private const M_MAPS = (1 << 0); + private const M_SPAWNS = (1 << 1); + private const M_SUBZONES = (1 << 2); + + private $modeMask = self::M_SPAWNS | self::M_MAPS; + + private const SPAWNMAP_WH = 1000; // it is square + private const MAP_W = 1002; + private const MAP_H = 668; + private const A_THRESHOLD = 95; // alpha threshold to define subZones: set it too low and you have unspawnable areas inside a zone; set it too high and the border regions overlap + private const COLOR_WHITE = [255, 255, 255]; // rgb + private const COLOR_BLACK = [ 0, 0, 0]; // rgb + private const COLOR_SUBZONE = [255, 64, 192, 64]; // rgba + + private const AREA_FLAG_DEFAULT_FLOOR_TERRAIN = 0x004; // Default Dungeon Floor is Terrain + private const AREA_FLAG_NO_DEFAULT_FLOOR = 0x100; // Don't use Default Dungeon Floor (typically 1) + + private const CONTINENTS = [0, 1, 530, 571]; // Map.dbc/id + + private const DEST_DIRS = array( + ['static/images/wow/maps/%snormal/', 488, 325], + ['static/images/wow/maps/%soriginal/', 0, 0], // 1002, 668 + ['static/images/wow/maps/%ssmall/', 224, 149], + ['static/images/wow/maps/%szoom/', 772, 515] + ); + + private const TILEORDER = array( + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12] + ); + + private const LEVEL_N = array( // todo: move this hackfix to locales? + LOCALE_EN => 'Level %d', + LOCALE_FR => 'Plancher %d', + LOCALE_DE => '%d. Stockwerk', + LOCALE_CN => '[Level %d]', + LOCALE_ES => 'Nivel %d', + LOCALE_RU => 'Уровень %d' + ); + + private const MAP_FILE_PATTERN = '/((\w{4})\/interface\/worldmap(?:\/microdungeon\/([^\/]+))?\/([^\/]+)\/)(\4)(?:(\d{1,2})_)?(\d{1,2})\.(?:blp|png)/i'; + + // src, resourcePath, localized, [tileOrder], [[dest, destW, destH]] + private $genSteps = array( + self::M_MAPS => ['WorldMap/', null, true, self::TILEORDER, self::DEST_DIRS ], + self::M_SPAWNS => ['WorldMap/', null, true, self::TILEORDER, [['cache/setup/alphaMaps/', 0, 0]]], + self::M_SUBZONES => ['WorldMap/', null, true, self::TILEORDER, self::DEST_DIRS ] + ); + + private $progress = 0; + private $wmOverlays = []; + private $dmFloorData = []; + private $wmAreas = []; + private $multiLevelZones = []; + private $mapFiles = []; // [nameINT][floorIdx][loc][tileIdx] => filePath + private $microDungeons = []; + + public function __construct() + { + $this->imgPath = CLISetup::$srcDir.$this->imgPath; + $this->maxExecTime = ini_get('max_execution_time'); + + // init directories + foreach ($this->genSteps as [, , , , $outInfo]) + { + foreach ($outInfo as $dir) + { + if (strpos($dir[0], '%s') === false) + $this->requiredDirs[] = $dir[0]; + else + foreach (CLISetup::$localeIds as $l) + $this->requiredDirs[] = sprintf($dir[0], strtolower(Util::$localeStrings[$l]).'/'); + } + } + } + + public function generate() : bool + { + // find out what to generate + $opts = array_slice(array_keys($this->info), 1); + $getO = CLISetup::getOpt(...$opts); + $mask = 0x0; + + if ($getO['spawnmaps']) + $mask |= self::M_SPAWNS; + if ($getO['subzones']) + $mask |= self::M_SUBZONES; + if (!$getO['skip-zones']) + $mask |= self::M_MAPS; + + // unless manually prompted drop spawnmap generation if 90% of spawns have core generated area info + $npcPct = DB::World()->selectCell('SELECT SUM(IF(zoneId > 0, 1, 0)) / COUNT(*) FROM creature') ?? 0; + $goPct = DB::World()->selectCell('SELECT SUM(IF(zoneId > 0, 1, 0)) / COUNT(*) FROM gameobject') ?? 0; + + if (!($mask & self::M_SPAWNS) && $npcPct > 0.9 && $goPct > 0.9) + $this->modeMask &= ~self::M_SPAWNS; + + $this->modeMask = $mask ?: $this->modeMask; + + if (!$this->modeMask) // why would you do this..? + return true; + + // removed unused genSteps + foreach ($this->genSteps as $idx => $_) + if (!($idx & $this->modeMask)) + unset($this->genSteps[$idx]); + + if (!$this->checkSourceDirs()) + { + CLI::write('one or more source directories are missing:', CLI::LOG_ERROR); + $this->success = false; + return false; + } + + sleep(2); + + if ($this->prepare()) + { + $this->buildMaps(); + $this->buildZonesFile(); + } + + return $this->success; + } + + private function buildMapsFUTURE() : void + { + $sumFloors = array_sum(array_column($this->dmFloorData, 1)); + $sumAreas = count($this->wmAreas); + $sumMaps = count(CLISetup::$localeIds) * ($sumAreas + $sumFloors); + + CLI::write('Processing '.$sumAreas.' zone maps and '.$sumFloors.' dungeon maps from Interface/WorldMap/ for locale: '.Lang::concat(array_intersect_key(Util::$localeStrings, array_flip(CLISetup::$localeIds)))); + + /* todo: retrain brain and generate maps by given files and GlobalStrings. Then assign dbc data to them not the other way round like it is now. + foreach ($this->mapFiles as $name => [$floors, $isMultilevel]) + { + // skip redundant data of a microDungeons + if (in_array($name, $this->microDungeons)) + continue; + + $this->wmAreas = $this->wmAreas[$name] ?? []; + if (!$this->wmAreas) + { + CLI::write('[img-maps] no WMA data for map file '.CLI::bold($name), CLI::LOG_WARN); + continue; + } + + $wmaId = $this->wmAreas['id']; + $zoneId = $this->wmAreas['areaId']; + $mapId = $this->wmAreas['mapId']; + $flags = $this->wmAreas['flags'] ?? 0; // flags added in 4.x + + if ($isMultilevel) + $this->multiLevelZones[$zoneId] = []; + + // TODO + // - Ahn'Kahet (4494) has a secondary map file, that is not referenced in DungeonMap.dbc but looks nice. Lets manually reference it. + // if (isset($floorData[4494])) + // $floorData[4494][1] = 2; + if ($zoneId == 206) + var_dump($floors); + + + foreach ($floors as $locId => [$floorData, $basePath]) + { + ksort($floorData); + + $overlay = null; + if (!$isMultilevel) + $overlay = $this->generateOverlay($wmaId, $name, $basePath); + + // create spawn-maps if wanted + if ($overlay && $this->modeMask & self::M_SPAWNS) + { + $outFile = $this->genSteps[self::M_SPAWNS][self::$GEN_IDX_DEST_INFO][0][0] . $zoneId . '.png'; + if (!$this->generateSpawnMap($overlay, $outFile)) + $this->success = false; + } + + foreach ($floorData as $floorIdx => $tileData) + { + $outFile = $zoneId; + + // naming of the base floor file is a bit wonky. unsure when the -0 suffix should be implicit or explicit + // just note, that the floor names from GlobalStrings.lua always have a '0' as base level suffix + + if (!$floorIdx && $isMultilevel && !($flags & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN)) + CLI::write('[img-maps] zone '.$name.' is multilevel and has base level map file, but is not flagged for use', CLI::LOG_INFO); + + if ($isMultilevel && !$floorIdx) + { + if (in_array($mapId, self::CONTINENTS)) + $outFile .= '-0'; + else if ($this->wmAreas['defaultDungeonMapId'] < 0) + $outFile .= '-0'; + // else + // implicit -0 + } + else if ($isMultilevel) + $outFile .= '-'.$floorIdx; + + if ($isMultilevel) + $this->multiLevelZones[$zoneId][$floorIdx] = $outFile; + + + foreach ($tileData as $tileIdx => $filePath) + { + + } + } + } + + if ($isMultilevel) + $this->multiLevelZones[$zoneId] = array_values($this->multiLevelZones[$zoneId]); + + if ($this->modeMask & self::M_SUBZONES) + { + // get subzones for mapFile from wmaData and apply overlays + } + } + */ + + foreach (CLISetup::$localeIds as $progressLoc => $l) + { + // source for mapFiles + $mapSrcDir = ''; + if ($this->modeMask & self::M_SPAWNS) + $mapSrcDir = $this->genSteps[self::M_SPAWNS][1][$l] ?? ''; + if (!$mapSrcDir && $this->modeMask & self::M_SUBZONES) + $mapSrcDir = $this->genSteps[self::M_SUBZONES][1][$l] ?? ''; + if (!$mapSrcDir) + $mapSrcDir = $this->genSteps[self::M_MAPS][1][$l] ?? ''; + if (!$mapSrcDir) + { + $this->success = false; + CLI::write(' - no suitable localized map files found for locale '.$l, CLI::LOG_ERROR); + continue; + } + + foreach ($this->wmAreas as $progressArea => $areaEntry) + { + $curMap = $progressArea + count($this->wmAreas) * $progressLoc; + $this->status = ' - ' . str_pad($curMap.'/'.($sumMaps), 10) . str_pad('('.number_format($curMap * 100 / $sumMaps, 2).'%)', 9); + + $wmaId = $areaEntry['id']; + $zoneId = $areaEntry['areaId']; + $mapId = $areaEntry['mapId']; + $textureStr = $areaEntry['nameINT']; + $flags = $areaEntry['flags'] ?? 0; // flags added in 4.x + + [$floorStr, $nFloors] = $this->dmFloorData[in_array($mapId, self::CONTINENTS) ? -$wmaId : $mapId] ?? ['', 0]; + + if ($nFloors && !isset($this->multiLevelZones)) + $this->multiLevelZones[$zoneId] = []; + + CLI::write( + str_pad('['.$areaEntry['areaId'].']', 7) . + str_pad($areaEntry['nameINT'], 22) . + str_pad('Overlays: '.count($this->wmOverlays[$areaEntry['id']] ?? []), 14) . + str_pad('Dungeon Maps: '.($nFloors + ((($flags ?? 0) & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN) ? 1 : 0)), 18) + ); + + $srcPath = $mapSrcDir.'/'.$textureStr; + if (!CLISetup::fileExists($srcPath)) + { + $this->success = false; + CLI::write('worldmap file '.$srcPath.' missing for selected locale '.Util::$localeStrings[$l], CLI::LOG_ERROR); + continue; + } + + $overlay = null; + + // zone has overlays (is in open world; is not multilevel) + if (isset($this->wmOverlays[$wmaId])) + { + $overlay = $this->createAlphaImage(self::MAP_W, self::MAP_H); + + foreach ($this->wmOverlays[$wmaId] as &$row) + { + $i = 1; + $y = 0; + while ($y < $row['h']) + { + $x = 0; + while ($x < $row['w']) + { + $img = $this->loadImageFile($srcPath . '/' . $row['textureString'] . $i); + if (!$img) + { + CLI::write(' - complexImg: tile '.$srcPath.'/'.$row['textureString'].$i.'.blp missing.', CLI::LOG_ERROR); + break 2; + } + + imagecopy($overlay, $img, $row['x'] + $x, $row['y'] + $y, 0, 0, imagesx($img), imagesy($img)); + + // prepare subzone image + if ($this->modeMask & self::M_SUBZONES) + { + if (!isset($row['maskimage'])) + { + $row['maskimage'] = $this->createAlphaImage($row['w'], $row['h']); + $row['maskcolor'] = imagecolorallocatealpha($row['maskimage'], ...self::COLOR_SUBZONE); + } + + for ($my = 0; $my < imagesy($img); $my++) + for ($mx = 0; $mx < imagesx($img); $mx++) + if ((imagecolorat($img, $mx, $my) >> 24) < self::A_THRESHOLD) + imagesetpixel($row['maskimage'], $x + $mx, $y + $my, $row['maskcolor']); + } + + imagedestroy($img); + + $x += 256; + $i++; + } + $y += 256; + } + } + + // create spawn-maps if wanted + if ($this->modeMask & self::M_SPAWNS) + { + $outFile = $this->genSteps[self::M_SPAWNS][self::$GEN_IDX_DEST_INFO][0][0] . $zoneId . '.png'; + + if (CLISetup::getOpt('force') || !file_exists($outFile)) + { + $tmp = imagecreate(self::SPAWNMAP_WH, self::SPAWNMAP_WH); + $cbg = imagecolorallocate($tmp, ...self::COLOR_WHITE); + $cfg = imagecolorallocate($tmp, ...self::COLOR_BLACK); + + for ($y = 0; $y < self::SPAWNMAP_WH; $y++) + { + for ($x = 0; $x < self::SPAWNMAP_WH; $x++) + { + $a = imagecolorat($overlay, ($x * self::MAP_W) / self::SPAWNMAP_WH, ($y * self::MAP_H) / self::SPAWNMAP_WH) >> 24; + imagesetpixel($tmp, $x, $y, $a < self::A_THRESHOLD ? $cfg : $cbg); + } + } + + imagecolordeallocate($tmp, $cbg); + imagecolordeallocate($tmp, $cfg); + + if (!$this->writeImageFile($tmp, $outFile, self::SPAWNMAP_WH, self::SPAWNMAP_WH)) + $this->success = false; + } + else + CLI::write($this->status.' - file '.$outFile.' was already processed', CLI::LOG_BLANK, true, true); + } + } + + if (!($this->modeMask & self::M_MAPS)) + continue; + + // check, if the current zone is multiLeveled + $floors = [0]; + if ($floorStr) + $floors = array_merge($floors, explode(' ', $floorStr)); + + // - Ahn'Kahet (4494) has a secondary map file, that is not referenced in DungeonMap.dbc but looks nice. Lets manually reference it. + if ($zoneId == 4494) + $floors[] = 2; + + $map = null; + foreach ($floors as $floorIdx) + { + ini_set('max_execution_time', $this->maxExecTime); + + $file = $srcPath.'/'.$textureStr; + + // todo: Dalaran [4395] has no level 0 but is not skipped here + if (!$floorIdx && !($flags & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN) && !in_array($mapId, self::CONTINENTS)) + continue; + + if ($nFloors && ($floorIdx || $flags & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN)) + $this->multiLevelZones[$zoneId][$floorIdx] = $zoneId . '-' . $floorIdx; + + if ($floorIdx) + $file .= $floorIdx . '_'; + + $doSkip = 0x0; + $outFile = []; + + foreach (self::DEST_DIRS as $sizeIdx => [$path, $width, $height]) + { + $outFile[$sizeIdx] = sprintf($path, strtolower(Util::$localeStrings[$l]).'/') . $zoneId; + + /* dataset 'zones' requires that ... + * 3959 - Black Temple: starts with empty floor suffix + * 4075 - Sunwell: starts with empty floor suffix + * 4723 - Map 650 CoT: 5-man reuses raid map (649) but only the upper floor. Check DungeonMap.dbc + */ + + if ($nFloors && ($floorIdx || $flags & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN)) + $outFile[$sizeIdx] .= '-'.$floorIdx; + + $outFile[$sizeIdx] .= '.jpg'; + + if (!CLISetup::getOpt('force') && file_exists($outFile[$sizeIdx])) + { + CLI::write($this->status.' - file '.$outFile[$sizeIdx].' was already processed', CLI::LOG_BLANK, true, true); + $doSkip |= (1 << $sizeIdx); + } + } + + if ($doSkip == 0xF) + continue; + + $map = $this->assembleImage($file, self::TILEORDER, self::MAP_W, self::MAP_H); + if (!$map) + { + CLI::write(' - could not create image resource for zone '.$zoneId.($nFloors ? ' floor '.$floorIdx : ''), CLI::LOG_ERROR); + $this->success = false; + continue; + } + + if ($overlay && !$floorIdx) + { + imagecopymerge($map, $overlay, 0, 0, 0, 0, imagesx($overlay), imagesy($overlay), 100); + imagedestroy($overlay); + } + + // create map + if ($this->modeMask & self::M_MAPS) + { + foreach (self::DEST_DIRS as $sizeIdx => [, $width, $height]) + { + if ($doSkip & (1 << $sizeIdx)) + continue; + + if (!$this->writeImageFile($map, $outFile[$sizeIdx], $width ?: self::MAP_W, $height ?: self::MAP_H)) + $this->success = false; + } + } + } + + // also create subzone-maps + if ($map && isset($this->wmOverlays[$wmaId]) && $this->modeMask & self::M_SUBZONES) + { + foreach ($this->wmOverlays[$wmaId] as &$row) + { + $doSkip = 0x0; + $outFile = []; + + foreach (self::DEST_DIRS as $sizeIdx => [$path, , ]) + { + $outFile[$sizeIdx] = sprintf($path, strtolower(Util::$localeStrings[$l]).'/') . $row['areaTableId'].'.jpg'; + if (!CLISetup::getOpt('force') && file_exists($outFile[$sizeIdx])) + { + CLI::write($this->status.' - file '.$outFile[$sizeIdx].' was already processed', CLI::LOG_BLANK, true, true); + $doSkip |= (1 << $sizeIdx); + } + } + + if ($doSkip == 0xF) + continue; + + $subZone = imagecreatetruecolor(self::MAP_W, self::MAP_H); + imagecopy($subZone, $map, 0, 0, 0, 0, imagesx($map), imagesy($map)); + imagecopy($subZone, $row['maskimage'], $row['x'], $row['y'], 0, 0, imagesx($row['maskimage']), imagesy($row['maskimage'])); + + foreach (self::DEST_DIRS as $sizeIdx => [, $width, $height]) + { + if ($doSkip & (1 << $sizeIdx)) + continue; + + if (!$this->writeImageFile($subZone, $outFile[$sizeIdx], $width ?: self::MAP_W, $height ?: self::MAP_H)) + $this->success = false; + } + + imagedestroy($subZone); + } + } + + if ($map) + imagedestroy($map); + + // this takes a while; ping mysql just in case + DB::Aowow()->selectCell('SELECT 1'); + } + } + } + + private function prepare() : bool + { + $this->wmOverlays = DB::Aowow()->select('SELECT *, `worldMapAreaId` AS ARRAY_KEY, `id` AS ARRAY_KEY2 FROM dbc_worldmapoverlay WHERE `textureString` <> ""'); + $this->wmAreas = DB::Aowow()->select('SELECT `id`, `mapId`, `areaId`, UPPER(`nameINT`) AS `nameINT`, IF(`areaId`, `areaId`, -`id`) AS ARRAY_KEY FROM dbc_worldmaparea'); + $this->dmFloorData = DB::Aowow()->select('SELECT IF(`mapId` IN (?a), -`worldMapAreaId`, `mapId`) AS ARRAY_KEY, GROUP_CONCAT(DISTINCT `floor` SEPARATOR " ") AS "0", COUNT(DISTINCT `floor`) AS "1" FROM dbc_dungeonmap WHERE `worldMapAreaId` <> 0 GROUP BY ARRAY_KEY', self::CONTINENTS); + if (!$this->wmOverlays || !$this->wmAreas || !$this->dmFloorData) + { + CLI::write(' - could not read required dbc files: WorldMapArea.dbc ['.count($this->wmAreas ?: []).' entries]; WorldMapOverlay.dbc ['.count($this->wmOverlays ?: []).'] entries; DungeonMap.dbc ['.count($this->dmFloorData ?: []).' entries]', CLI::LOG_ERROR); + $this->success = false; + return false; + } + + // DM fixups... + // unpack + sort floors + array_walk($this->dmFloorData, function (&$x) { $x[0] = explode(' ', $x[0]); sort($x[0]); }); + + // move Dalaran from Howling Fjord to .. well .. Dalaran + $this->dmFloorData[-4395] = $this->dmFloorData[-495]; + unset($this->dmFloorData[-495]); + + // "custom" - show second level of Ahn'Kahet not shown but present in-game + $this->dmFloorData[619][0][] = 2; + $this->dmFloorData[619][1]++; + + // WMA fixups... + foreach ($this->wmAreas as &$a) + { + // flags added in 4.x but required for 3.3.5. Where are they? Derived from defaultDungeonMapId (also refered to as defaultDungeonFloor) being < 0 ? + // no idea, hardcode this shit + switch ($a['areaId']) + { // i deem the missing '-0' a mistake > v < this will not be perpetuated + case 4273: // Ulduar > base + 5 > 4273: ['4273-0', '4273-1', '4273-2', '4273-3', '4273-4', '4273-5'] + case 4075: // SunwellPlateau > base + 1 > 4075: ['4075', '4075-1'], + case 3959: // BlackTemple > base + 7 > 3959: ['3959', '3959-1', '3959-2', '3959-3', '3959-4', '3959-5', '3959-6', '3959-7'], + $a['flags'] = self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN; + break; + case 4100: // CoTStratholme > base + 1 > 4100: ['4100-1', '4100-2'], + $a['flags'] = self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN | self::AREA_FLAG_NO_DEFAULT_FLOOR; + break; + default: + $a['flags'] = $a['flags'] ?? 0; // flags added in 4.x + } + + if ($a['areaId']) + continue; + + switch ($a['id']) + { + case 13: $a['areaId'] = -6; break; // Kalimdor + case 14: $a['areaId'] = -3; break; // Eastern Kingdoms + case 466: $a['areaId'] = -2; break; // Outland + case 485: $a['areaId'] = -5; break; // Northrend + } + } + $this->wmAreas[-1] = ['id' => -1, 'areaId' => -1, 'flags' => 0x0, 'mapId' => 0, 'nameINT' => 'World']; + $this->wmAreas[-4] = ['id' => -4, 'areaId' => -4, 'flags' => 0x0, 'mapId' => 0, 'nameINT' => 'Cosmic']; + + ksort($this->wmAreas); // just so we can sift through the log more easily + + /* + i should be walking through interface/worldmap first and THEN check the worldmaparea / dungeonmap from the file pattern + floorIdx is optional and per map. (e.g. continents share their floors and yes continents can have dungeon maps) + + > /interface/worldmap//(_).blp + > /interface/worldmap/microdungeon///(_).blp + + microdungeons (5.x+?) may be redundant with regluar map files. + + e.g.: + > enGB/interface/worldmap/microdungeon/durotar/burningbladecoven/burningbladecoven8_12.blp + + from nameInt "durotar" we get wmaId = 4, areaTableId = 14 and mapId = 1 (floorIdx = 8 from file string) + with mapId and floor (and wmaId) we get the coordinates from dungeonmap.dbc + + thus the map file name is: -.png > 14-8.png + and the floor is named: DUNGEON_FLOOR_ > DUNGEON_FLOOR_DUROTAR8 (Aquelarre del Filo Ardiente) *nyak nyak nyak* + + + note: some map file may have no floorIdx but the tileIdx is still separated by an underscore. Those files should be ignored. + + */ + /* FUTURE + foreach (CLISetup::filesInPath(self::MAP_FILE_PATTERN, true) as $file) + { + if (!preg_match(self::MAP_FILE_PATTERN, $file, $m)) + continue; + + [, $basePath, $locStr, $mdParent, $nameINT, $nameINT, $floorIdx, $tileIdx] = $m; + + $loc = CLISetup::$expectedPaths[strtolower(substr($locStr, 0, 2)).strtoupper(substr($locStr, 2))] ?? LOCALE_EN; + + if ($mdParent) + $this->microDungeons[] = strtolower($nameINT); + + $key = strtolower($mdParent ?: $nameINT); + + $this->mapFiles[$key][0][$loc][0][$floorIdx ?: 0][$tileIdx] = $file; + $this->mapFiles[$key][0][$loc][1] = $basePath; + $this->mapFiles[$key][1] = ($this->mapFiles[$key][1] ?? false) ?: (($floorIdx ?: 0) > 1); + } + */ + + return true; + } + + private function buildMaps() : void + { + $sumFloors = array_sum(array_column($this->dmFloorData, 1)); + $sumAreas = count($this->wmAreas); + $sumMaps = count(CLISetup::$localeIds) * ($sumAreas + $sumFloors); + + CLI::write('Processing '.$sumAreas.' zone maps and '.$sumFloors.' dungeon maps from Interface/WorldMap/ for locale: '.Lang::concat(array_intersect_key(Util::$localeStrings, array_flip(CLISetup::$localeIds)))); + + foreach (CLISetup::$localeIds as $l) + { + // source for mapFiles + $mapSrcDir = ''; + if ($this->modeMask & self::M_SPAWNS) + $mapSrcDir = $this->genSteps[self::M_SPAWNS][1][$l] ?? ''; + if (!$mapSrcDir && $this->modeMask & self::M_SUBZONES) + $mapSrcDir = $this->genSteps[self::M_SUBZONES][1][$l] ?? ''; + if (!$mapSrcDir) + $mapSrcDir = $this->genSteps[self::M_MAPS][1][$l] ?? ''; + if (!$mapSrcDir) + { + $this->success = false; + CLI::write(' - no suitable localized map files found for locale '.$l, CLI::LOG_ERROR); + continue; + } + + foreach ($this->wmAreas as $areaEntry) + { + $wmaId = $areaEntry['id']; + $zoneId = $areaEntry['areaId']; + $mapId = $areaEntry['mapId']; + $textureStr = $areaEntry['nameINT']; + $flags = $areaEntry['flags']; + + [$dmFloors, $nFloors] = $this->dmFloorData[in_array($mapId, self::CONTINENTS) ? -$zoneId : $mapId] ?? [[0], 0]; + + $this->progress += ($nFloors ?: 1) + ($flags & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN ? 1 : 0); + $this->status = ' - ' . str_pad($this->progress.'/'.($sumMaps), 10) . str_pad('('.number_format($this->progress * 100 / $sumMaps, 2).'%)', 9); + + // includes base level... + if ($flags & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN) + { + array_unshift($dmFloors, 0); // 0 => 0, 1 => 1, etc. + $nFloors++; + + // .. which is not set in dbc 0 => 1, 1 => 2, etc. + if ($flags & self::AREA_FLAG_NO_DEFAULT_FLOOR) + $dmFloors = array_combine($dmFloors, array_map(function ($x) { return ++$x; }, $dmFloors)); + } + else if ($dmFloors != [0]) // 1 => 1, 2 => 2, etc. + $dmFloors = array_combine($dmFloors, $dmFloors); + + CLI::write( + '['.Util::$localeStrings[$l].'] ' . + str_pad('['.$areaEntry['areaId'].']', 7) . + str_pad($areaEntry['nameINT'], 22) . + str_pad('Overlays: '.count($this->wmOverlays[$areaEntry['id']] ?? []), 14) . + str_pad('Dungeon Maps: '.$nFloors, 18) + ); + + $srcPath = $mapSrcDir.'/'.$textureStr; + if (!CLISetup::fileExists($srcPath)) + { + $this->success = false; + CLI::write('worldmap file path '.$srcPath.' missing for selected locale '.Util::$localeStrings[$l], CLI::LOG_ERROR); + continue; + } + + $overlay = null; + + // zone has overlays (is in open world; is not multilevel) + if (isset($this->wmOverlays[$wmaId]) && ($this->modeMask & (self::M_MAPS | self::M_SPAWNS | self::M_SUBZONES))) + { + $overlay = $this->createAlphaImage(self::MAP_W, self::MAP_H); + + foreach ($this->wmOverlays[$wmaId] as &$row) + { + $i = 1; + $y = 0; + while ($y < $row['h']) + { + $x = 0; + while ($x < $row['w']) + { + $img = $this->loadImageFile($srcPath . '/' . $row['textureString'] . $i); + if (!$img) + { + CLI::write(' - complexImg: tile '.$srcPath.'/'.$row['textureString'].$i.'.blp missing.', CLI::LOG_ERROR); + break 2; + } + + imagecopy($overlay, $img, $row['x'] + $x, $row['y'] + $y, 0, 0, imagesx($img), imagesy($img)); + + // prepare subzone image + if ($this->modeMask & self::M_SUBZONES) + { + if (!isset($row['maskimage'])) + { + $row['maskimage'] = $this->createAlphaImage($row['w'], $row['h']); + $row['maskcolor'] = imagecolorallocatealpha($row['maskimage'], ...self::COLOR_SUBZONE); + } + + for ($my = 0; $my < imagesy($img); $my++) + for ($mx = 0; $mx < imagesx($img); $mx++) + if ((imagecolorat($img, $mx, $my) >> 24) < self::A_THRESHOLD) + imagesetpixel($row['maskimage'], $x + $mx, $y + $my, $row['maskcolor']); + } + + imagedestroy($img); + + $x += 256; + $i++; + } + $y += 256; + } + } + + // create spawn-maps if wanted + if ($this->modeMask & self::M_SPAWNS) + { + $outFile = $this->genSteps[self::M_SPAWNS][self::$GEN_IDX_DEST_INFO][0][0] . $zoneId . '.png'; + + if (CLISetup::getOpt('force') || !file_exists($outFile)) + { + $tmp = imagecreate(self::SPAWNMAP_WH, self::SPAWNMAP_WH); + $cbg = imagecolorallocate($tmp, ...self::COLOR_WHITE); + $cfg = imagecolorallocate($tmp, ...self::COLOR_BLACK); + + for ($y = 0; $y < self::SPAWNMAP_WH; $y++) + { + for ($x = 0; $x < self::SPAWNMAP_WH; $x++) + { + $a = imagecolorat($overlay, ($x * self::MAP_W) / self::SPAWNMAP_WH, ($y * self::MAP_H) / self::SPAWNMAP_WH) >> 24; + imagesetpixel($tmp, $x, $y, $a < self::A_THRESHOLD ? $cfg : $cbg); + } + } + + imagecolordeallocate($tmp, $cbg); + imagecolordeallocate($tmp, $cfg); + + if (!$this->writeImageFile($tmp, $outFile, self::SPAWNMAP_WH, self::SPAWNMAP_WH)) + $this->success = false; + } + else + CLI::write($this->status.' - file '.$outFile.' was already processed', CLI::LOG_BLANK, true, true); + } + } + + // check if we can create base map anyway + $png = $srcPath.'/'.$textureStr.'1.png'; + $blp = $srcPath.'/'.$textureStr.'1.blp'; + $hasBaseMap = CLISetup::fileExists($blp) || CLISetup::fileExists($png); + + $map = null; + foreach ($dmFloors as $srcFloorIdx => $outFloorIdx) + { + ini_set('max_execution_time', $this->maxExecTime); + + $doSkip = 0x0; + $outPaths = []; + $srcFile = $srcPath.'/'.$textureStr; + $outFile = $zoneId; + + if (!$srcFloorIdx && !$hasBaseMap) + { + CLI::Write('[img-maps] zone has no base floor, but it is referenced with base floor in dmFloors? Smells like an error. areaId: '.$zoneId, CLI::LOG_WARN); + continue; + } + + if ($srcFloorIdx) + $srcFile .= $srcFloorIdx.'_'; + + if ($nFloors > 1) + if ($outFloorIdx || $flags & self::AREA_FLAG_DEFAULT_FLOOR_TERRAIN) + $outFile .= '-'.$outFloorIdx; + + if ($nFloors > 1) + $this->multiLevelZones[$zoneId][$outFile] = $outFile; + + if (!($this->modeMask & (self::M_MAPS | self::M_SUBZONES))) + continue; + + foreach (self::DEST_DIRS as $sizeIdx => [$path, $width, $height]) + { + $outPaths[$sizeIdx] = sprintf($path, strtolower(Util::$localeStrings[$l]).'/') . $outFile . '.jpg'; + + if (!CLISetup::getOpt('force') && file_exists($outPaths[$sizeIdx])) + { + CLI::write($this->status.' - file '.$outPaths[$sizeIdx].' was already processed', CLI::LOG_BLANK, true, true); + $doSkip |= (1 << $sizeIdx); + } + } + + if ($doSkip == 0xF) + continue; + + $map = $this->assembleImage($srcFile, self::TILEORDER, self::MAP_W, self::MAP_H); + if (!$map) + { + CLI::write(' - could not create image resource for zone '.$zoneId.($nFloors ? ' floor '.$srcFloorIdx : '')); + $this->success = false; + continue; + } + + if ($overlay && !$nFloors) + { + imagecopymerge($map, $overlay, 0, 0, 0, 0, imagesx($overlay), imagesy($overlay), 100); + imagedestroy($overlay); + } + + // create map + if ($this->modeMask & self::M_MAPS) + { + foreach (self::DEST_DIRS as $sizeIdx => [, $width, $height]) + { + if ($doSkip & (1 << $sizeIdx)) + continue; + + if (!$this->writeImageFile($map, $outPaths[$sizeIdx], $width ?: self::MAP_W, $height ?: self::MAP_H)) + $this->success = false; + } + } + } + + // also create subzone-maps + if ($map && isset($this->wmOverlays[$wmaId]) && $this->modeMask & self::M_SUBZONES) + { + foreach ($this->wmOverlays[$wmaId] as &$row) + { + $doSkip = 0x0; + $outFiles = []; + + foreach (self::DEST_DIRS as $sizeIdx => [$path, , ]) + { + $outFile[$sizeIdx] = sprintf($path, strtolower(Util::$localeStrings[$l]).'/') . $row['areaTableId'].'.jpg'; + if (!CLISetup::getOpt('force') && file_exists($outFiles[$sizeIdx])) + { + CLI::write($this->status.' - file '.$outFiles[$sizeIdx].' was already processed', CLI::LOG_BLANK, true, true); + $doSkip |= (1 << $sizeIdx); + } + } + + if ($doSkip == 0xF) + continue; + + $subZone = imagecreatetruecolor(self::MAP_W, self::MAP_H); + imagecopy($subZone, $map, 0, 0, 0, 0, imagesx($map), imagesy($map)); + imagecopy($subZone, $row['maskimage'], $row['x'], $row['y'], 0, 0, imagesx($row['maskimage']), imagesy($row['maskimage'])); + + foreach (self::DEST_DIRS as $sizeIdx => [, $width, $height]) + { + if ($doSkip & (1 << $sizeIdx)) + continue; + + if (!$this->writeImageFile($subZone, $outFiles[$sizeIdx], $width ?: self::MAP_W, $height ?: self::MAP_H)) + $this->success = false; + } + + imagedestroy($subZone); + } + } + + if ($map) + imagedestroy($map); + + // this takes a while; ping mysql just in case + DB::Aowow()->selectCell('SELECT 1'); + } + } + } + + private function buildZonesFile() : bool + { + $areaNames = array_combine( + array_column($this->wmAreas, 'areaId'), + array_map(function ($x) { return strtoupper($x); }, array_column($this->wmAreas, 'nameINT')) + ); + + if ($this->multiLevelZones) + { + ksort($this->multiLevelZones); + $this->multiLevelZones = array_map('array_values', $this->multiLevelZones); + } + else + { + CLI::write('[img-maps] No data fetched from either WorldMapArea.dbc or DungeonMap.dbc. Multilevel zones will not display.', CLI::LOG_ERROR); + $this->success = false; + } + + $zoneAreas = []; + // careful: nameINT may end in a number and have > 9 floors attached. see: KARAZHAN17, ULDUAR771 + foreach (CLISetup::searchGlobalStrings('/^DUNGEON_FLOOR_([a-z_]+(?:\d\d)?)(\d{1,2})\s=\s\"(.+)\";$/i') as $lId => [$_, $nameINT, $floor, $nameLOC]) + { + // yes, multiple zones can point to the same map files + if ($zoneIds = array_keys($areaNames, $nameINT)) + { + foreach ($zoneIds as $zId) + if (isset($this->multiLevelZones[$zId])) + $zoneAreas[$lId][$zId][$floor] = $nameLOC; + } + else + CLI::write('[img-maps] internal zone name from GlobalStrings.lua not found in WorldMapArea.dbc ['.$nameINT.']', CLI::LOG_WARN); + } + + foreach (CLISetup::$locales as $lId => $loc) + { + // "custom" - show second level of Ahn'Kahet not shown but present in-game + if (isset($zoneAreas[$lId][4494])) + $zoneAreas[$lId][4494][2] = sprintf(self::LEVEL_N[$lId], 2); + + foreach ($zoneAreas[$lId] as $zoneId => $floorData) + { + $nStrings = count($floorData); + $nFloors = count($this->multiLevelZones[$zoneId] ?? []); + if ($nStrings == $nFloors) + continue; + + // todo: just note for now, try to compensate later? + CLI::write('[img-maps] floor count mismatch for zone #'.$zoneId.' GlobalStrings: '.$nStrings.' image files: '.$nFloors, CLI::LOG_WARN); + } + + ksort($zoneAreas[$lId]); + + $zoneAreas[$lId] = array_map('array_values', $zoneAreas[$lId]); + + // don't convert numbers to int in json + $toFile = "Mapper.multiLevelZones = ".Util::toJSON($this->multiLevelZones, 0x0).";\n\n"; + $toFile .= "var g_zone_areas = ".Util::toJSON($zoneAreas[$lId]).";"; + $file = 'datasets/'.$loc.'/zones'; + + if (!CLISetup::writeFile($file, $toFile)) + $this->success = false; + } + + return $this->success; + } + + private function generateOverlay(int $wmaId, string $name, string $basePath) // : ?GdImage + { + if (!isset($this->wmOverlays[$wmaId])) + return null; + + $overlay = $this->createAlphaImage(self::MAP_W, self::MAP_H); + + foreach ($this->wmOverlays[$wmaId] as &$this->wmOverlays) + { + $i = 1; + $y = 0; + while ($y < $this->wmOverlays['h']) + { + $x = 0; + while ($x < $this->wmOverlays['w']) + { + $img = $this->loadImageFile($basePath . $name . $i); + if (!$img) + { + CLI::write(' - complexImg: tile '.$basePath.$name.$i.'.blp missing.', CLI::LOG_ERROR); + break 2; + } + + imagecopy($overlay, $img, $this->wmOverlays['x'] + $x, $this->wmOverlays['y'] + $y, 0, 0, imagesx($img), imagesy($img)); + + // prepare subzone image + if ($this->modeMask & self::M_SUBZONES) + { + if (!isset($this->wmOverlays['maskimage'])) + { + $this->wmOverlays['maskimage'] = $this->createAlphaImage($this->wmOverlays['w'], $this->wmOverlays['h']); + $this->wmOverlays['maskcolor'] = imagecolorallocatealpha($this->wmOverlays['maskimage'], ...self::COLOR_SUBZONE); + } + + for ($my = 0; $my < imagesy($img); $my++) + for ($mx = 0; $mx < imagesx($img); $mx++) + if ((imagecolorat($img, $mx, $my) >> 24) < self::A_THRESHOLD) + imagesetpixel($this->wmOverlays['maskimage'], $x + $mx, $y + $my, $this->wmOverlays['maskcolor']); + } + + imagedestroy($img); + + $x += 256; + $i++; + } + $y += 256; + } + } + + return $overlay; + } + + private function generateSpawnMap(/*GdImage*/ $overlay, string $outFile) : bool + { + // GdImage: < 8.0 resource; >= 8.0 object + if (gettype($overlay) != 'resource' && gettype($overlay) != 'object') + { + CLI::write('[img-maps] no GdImage passed to generateSpawnMap', CLI::LOG_ERROR); + return false; + } + + if (!CLISetup::getOpt('force') && file_exists($outFile)) + { + CLI::write($this->status.' - file '.$outFile.' was already processed', CLI::LOG_BLANK, true, true); + return true; + } + + $tmp = imagecreate(self::SPAWNMAP_WH, self::SPAWNMAP_WH); + $cbg = imagecolorallocate($tmp, ...self::COLOR_WHITE); + $cfg = imagecolorallocate($tmp, ...self::COLOR_BLACK); + + for ($y = 0; $y < self::SPAWNMAP_WH; $y++) + { + for ($x = 0; $x < self::SPAWNMAP_WH; $x++) + { + $a = imagecolorat($overlay, ($x * self::MAP_W) / self::SPAWNMAP_WH, ($y * self::MAP_H) / self::SPAWNMAP_WH) >> 24; + imagesetpixel($tmp, $x, $y, $a < self::A_THRESHOLD ? $cfg : $cbg); + } + } + + imagecolordeallocate($tmp, $cbg); + imagecolordeallocate($tmp, $cfg); + + return $this->writeImageFile($tmp, $outFile, self::SPAWNMAP_WH, self::SPAWNMAP_WH); + } + + private function generateSubZones(/*GdImage*/ $map, int $wmaId, int $locId) : bool + { + // GdImage: < 8.0 resource; >= 8.0 object + if (gettype($map) != 'resource' && gettype($map) != 'object') + { + CLI::write('[img-maps] no GdImage passed to generateSubZones', CLI::LOG_ERROR); + return false; + } + + foreach ($this->wmOverlays[$wmaId] as &$row) + { + $doSkip = 0x0; + $outFile = []; + + foreach (self::DEST_DIRS as $sizeIdx => [$path, , ]) + { + $outFile[$sizeIdx] = sprintf($path, strtolower(Util::$localeStrings[$locId]).'/') . $row['areaTableId'].'.jpg'; + if (!CLISetup::getOpt('force') && file_exists($outFile[$sizeIdx])) + { + CLI::write($this->status.' - file '.$outFile[$sizeIdx].' was already processed', CLI::LOG_BLANK, true, true); + $doSkip |= (1 << $sizeIdx); + } + } + + if ($doSkip == 0xF) + continue; + + $subZone = imagecreatetruecolor(self::MAP_W, self::MAP_H); + imagecopy($subZone, $map, 0, 0, 0, 0, imagesx($map), imagesy($map)); + imagecopy($subZone, $row['maskimage'], $row['x'], $row['y'], 0, 0, imagesx($row['maskimage']), imagesy($row['maskimage'])); + + foreach (self::DEST_DIRS as $sizeIdx => [, $width, $height]) + { + if ($doSkip & (1 << $sizeIdx)) + continue; + + if (!$this->writeImageFile($subZone, $outFile[$sizeIdx], $width ?: self::MAP_W, $height ?: self::MAP_H)) + $this->success = false; + } + + imagedestroy($subZone); + } + + return true; + } +}); + +?> diff --git a/setup/tools/filegen/img-talentcalc.ss.php b/setup/tools/filegen/img-talentcalc.ss.php new file mode 100644 index 000000000..13b1e34e8 --- /dev/null +++ b/setup/tools/filegen/img-talentcalc.ss.php @@ -0,0 +1,110 @@ + [[], CLISetup::ARGV_PARAM, 'Generate backgrounds for the talent calculator.'], + ); + + protected $dbcSourceFiles = ['talenttab', 'chrclasses']; + + private const DEST_DIRS = array( + ['static/images/wow/hunterpettalents/', 0, 0], + ['static/images/wow/talents/backgrounds/', 0, 0] + ); + + private const TILEORDER = array( + ['-TopLeft', '-TopRight'], + ['-BottomLeft', '-BottomRight'] + ); + + // src, resourcePath, localized, [tileOrder], [[dest, destW, destH]] + private $genSteps = array( + ['TalentFrame/', null, false, self::TILEORDER, self::DEST_DIRS] + ); + + public function __construct() + { + $this->imgPath = CLISetup::$srcDir.$this->imgPath; + $this->maxExecTime = ini_get('max_execution_time'); + + // init directories + foreach (self::DEST_DIRS as $dir) + $this->requiredDirs[] = $dir[0]; + } + + public function generate() : bool + { + if (!$this->checkSourceDirs()) + { + CLI::write('one or more source directories are missing:', CLI::LOG_ERROR); + $this->success = false; + return false; + } + + sleep(2); + + $tTabs = DB::Aowow()->select('SELECT tt.creatureFamilyMask, tt.textureFile, tt.tabNumber, cc.fileString FROM dbc_talenttab tt LEFT JOIN dbc_chrclasses cc ON cc.id = (LOG(2, tt.classMask) + 1)'); + if (!$tTabs) + { + CLI::write(' - TalentTab.dbc or ChrClasses.dbc is empty...?', CLI::LOG_ERROR); + $this->success = false; + return false; + } + + $sum = 0; + $total = count($tTabs); + [, $realPath, , $tileOrder, $outInfo] = $this->genSteps[0]; + + CLI::write('Processing '.$total.' files from '.$realPath.' ...'); + foreach ($tTabs as $tt) + { + ini_set('max_execution_time', $this->maxExecTime); + $sum++; + $this->status = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9); + + if ($tt['creatureFamilyMask']) // is PetCalc + { + $size = [244, 364]; + $outFile = sprintf($outInfo[0][0].'bg_%d.jpg', log($tt['creatureFamilyMask'], 2) + 1); + } + else + { + $size = [204, 554]; + $outFile = sprintf($outInfo[1][0].'%s_%d.jpg', strtolower($tt['fileString']), $tt['tabNumber'] + 1); + } + + if (!CLISetup::getOpt('force') && file_exists($outFile)) + { + CLI::write($this->status.' - file '.$outFile.' was already processed', CLI::LOG_BLANK, true, true); + continue; + } + + $im = $this->assembleImage($realPath.'/'.$tt['textureFile'], $tileOrder, 256 + 44, 256 + 75); + if (!$im) + { + CLI::write(' - could not assemble file '.$tt['textureFile'], CLI::LOG_ERROR); + $this->success = false; + continue; + } + + if (!$this->writeImageFile($im, $outFile, $size[0], $size[1])) + $this->success = false; + } + + ini_set('max_execution_time', $this->maxExecTime); + + return $this->success; + } +}); + +?> diff --git a/setup/tools/filegen/itemScaling.func.php b/setup/tools/filegen/itemScaling.func.php deleted file mode 100644 index a4875ca48..000000000 --- a/setup/tools/filegen/itemScaling.func.php +++ /dev/null @@ -1,114 +0,0 @@ - $row) - { - foreach ($row as &$r) - $r = str_pad($r, 5, " ", STR_PAD_LEFT); - - $buff[] = str_pad($id, 7, " ", STR_PAD_LEFT).": [".implode(', ', $row)."]"; - } - - return "{\r\n".implode(",\r\n", $buff)."\r\n}"; - } - - function itemScalingRB() - { - $ratings = array( - 12 => 1, // ITEM_MOD_DEFENSE_SKILL_RATING => CR_DEFENSE_SKILL - 13 => 2, // ITEM_MOD_DODGE_RATING => CR_DODGE - 14 => 3, // ITEM_MOD_PARRY_RATING => CR_PARRY - 15 => 4, // ITEM_MOD_BLOCK_RATING => CR_BLOCK - 16 => 5, // ITEM_MOD_HIT_MELEE_RATING => CR_HIT_MELEE - 17 => 6, // ITEM_MOD_HIT_RANGED_RATING => CR_HIT_RANGED - 18 => 7, // ITEM_MOD_HIT_SPELL_RATING => CR_HIT_SPELL - 19 => 8, // ITEM_MOD_CRIT_MELEE_RATING => CR_CRIT_MELEE - 20 => 9, // ITEM_MOD_CRIT_RANGED_RATING => CR_CRIT_RANGED - 21 => 10, // ITEM_MOD_CRIT_SPELL_RATING => CR_CRIT_SPELL - 22 => 11, // ITEM_MOD_HIT_TAKEN_MELEE_RATING => CR_HIT_TAKEN_MELEE - 23 => 12, // ITEM_MOD_HIT_TAKEN_RANGED_RATING => CR_HIT_TAKEN_RANGED - 24 => 13, // ITEM_MOD_HIT_TAKEN_SPELL_RATING => CR_HIT_TAKEN_SPELL - 25 => 14, // ITEM_MOD_CRIT_TAKEN_MELEE_RATING => CR_CRIT_TAKEN_MELEE [may be forced 0] - 26 => 15, // ITEM_MOD_CRIT_TAKEN_RANGED_RATING => CR_CRIT_TAKEN_RANGED [may be forced 0] - 27 => 16, // ITEM_MOD_CRIT_TAKEN_SPELL_RATING => CR_CRIT_TAKEN_SPELL [may be forced 0] - 28 => 17, // ITEM_MOD_HASTE_MELEE_RATING => CR_HASTE_MELEE - 29 => 18, // ITEM_MOD_HASTE_RANGED_RATING => CR_HASTE_RANGED - 30 => 19, // ITEM_MOD_HASTE_SPELL_RATING => CR_HASTE_SPELL - 31 => 5, // ITEM_MOD_HIT_RATING => [backRef] - 32 => 8, // ITEM_MOD_CRIT_RATING => [backRef] - 33 => 11, // ITEM_MOD_HIT_TAKEN_RATING => [backRef] [may be forced 0] - 34 => 14, // ITEM_MOD_CRIT_TAKEN_RATING => [backRef] [may be forced 0] - 35 => 14, // ITEM_MOD_RESILIENCE_RATING => [backRef] - 36 => 17, // ITEM_MOD_HASTE_RATING => [backRef] - 37 => 23, // ITEM_MOD_EXPERTISE_RATING => CR_EXPERTISE - 44 => 24 // ITEM_MOD_ARMOR_PENETRATION_RATING => CR_ARMOR_PENETRATION - ); - - $data = $ratings; - - $offsets = array_map(function ($v) { // LookupEntry(cr*GT_MAX_LEVEL+level-1) - return $v * 100 + 60 - 1; - }, $ratings); - $base = DB::Aowow()->selectCol('SELECT CAST((idx + 1 - 60) / 100 AS UNSIGNED) AS ARRAY_KEY, ratio FROM dbc_gtcombatratings WHERE idx IN (?a)', $offsets); - - $offsets = array_map(function ($v) { // LookupEntry((getClass()-1)*GT_MAX_RATING+cr+1) - return (CLASS_WARRIOR - 1) * 32 + $v + 1; - }, $ratings); - $mods = DB::Aowow()->selectCol('SELECT idx - 1 AS ARRAY_KEY, ratio FROM dbc_gtoctclasscombatratingscalar WHERE idx IN (?a)', $offsets); - - foreach ($data as $itemMod => &$val) - $val = Cfg::get('DEBUG') ? $base[$val].' / '.$mods[$val] : $base[$val] / $mods[$val]; - - if (!Cfg::get('DEBUG')) - return Util::toJSON($data); - - $buff = []; - foreach ($data as $k => $v) - $buff[] = $k.': '.$v; - - return "{\r\n ".implode(",\r\n ", $buff)."\r\n}"; - } - - function itemScalingSV() - { - /* so the javascript expects a slightly different structure, than the dbc provides .. f*** it - e.g. - dbc - 80: 97 97 56 41 210 395 878 570 120 156 86 112 108 220 343 131 73 140 280 527 1171 2093 - expected - 80: 97 97 56 131 41 210 395 878 1570 120 156 86 112 108 220 343 0 0 73 140 280 527 1171 2093 - */ - $fields = Util::$ssdMaskFields; - array_walk($fields, function(&$v, $k) { - $v = $v ?: '0 AS idx'.$k; // NULL => 0 (plus some index so we can have 2x 0) - }); - - $data = DB::Aowow()->select('SELECT id AS ARRAY_KEY, '.implode(', ', $fields).' FROM dbc_scalingstatvalues'); - foreach ($data as &$d) - $d = array_values($d); // strip indizes - - return Cfg::get('DEBUG') ? debugify($data) : Util::toJSON($data); - } - - function itemScalingSD() - { - $data = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM dbc_scalingstatdistribution'); - foreach ($data as &$row) - { - $row = array_values($row); - array_splice($row, 0, 1); - } - - return Cfg::get('DEBUG') ? debugify($data) : Util::toJSON($data); - } -?> diff --git a/setup/tools/filegen/itemscaling.ss.php b/setup/tools/filegen/itemscaling.ss.php new file mode 100644 index 000000000..37a2adec1 --- /dev/null +++ b/setup/tools/filegen/itemscaling.ss.php @@ -0,0 +1,136 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles item scaling data to file to make heirloom tooltips interactive.'] + ); + + protected $fileTemplateDest = ['datasets/item-scaling']; + protected $fileTemplateSrc = ['item-scaling.in']; + + protected $dbcSourceFiles = ['scalingstatdistribution', 'scalingstatvalues', 'gtoctclasscombatratingscalar', 'gtcombatratings']; + + private function debugify(array $data) : string + { + $buff = []; + foreach ($data as $id => $row) + { + foreach ($row as &$r) + $r = str_pad($r, 5, " ", STR_PAD_LEFT); + + $buff[] = str_pad($id, 7, " ", STR_PAD_LEFT).": [".implode(', ', $row)."]"; + } + + return "{\r\n".implode(",\r\n", $buff)."\r\n}"; + } + + private function itemScalingRB() : string + { + // data and format observed via wayback machine. Not entirely sure about the redunacy within the combat ratings though. + $ratings = array( + Stat::DEFENSE_RTG => CR_DEFENSE_SKILL, + Stat::DODGE_RTG => CR_DODGE, + Stat::PARRY_RTG => CR_PARRY, + Stat::BLOCK_RTG => CR_BLOCK, + Stat::MELEE_HIT_RTG => CR_HIT_MELEE, + Stat::RANGED_HIT_RTG => CR_HIT_RANGED, + Stat::SPELL_HIT_RTG => CR_HIT_SPELL, + Stat::MELEE_CRIT_RTG => CR_CRIT_MELEE, + Stat::RANGED_CRIT_RTG => CR_CRIT_RANGED, + Stat::SPELL_CRIT_RTG => CR_CRIT_SPELL, + Stat::MELEE_HIT_TAKEN_RTG => CR_HIT_TAKEN_MELEE, + Stat::RANGED_HIT_TAKEN_RTG => CR_HIT_TAKEN_RANGED, + Stat::SPELL_HIT_TAKEN_RTG => CR_HIT_TAKEN_SPELL, + Stat::MELEE_CRIT_TAKEN_RTG => CR_CRIT_TAKEN_MELEE, // may be forced 0 + Stat::RANGED_CRIT_TAKEN_RTG => CR_CRIT_TAKEN_RANGED, // may be forced 0 + Stat::SPELL_CRIT_TAKEN_RTG => CR_CRIT_TAKEN_SPELL, // may be forced 0 + Stat::MELEE_HASTE_RTG => CR_HASTE_MELEE, + Stat::RANGED_HASTE_RTG => CR_HASTE_RANGED, + Stat::SPELL_HASTE_RTG => CR_HASTE_SPELL, + Stat::HIT_RTG => CR_HIT_MELEE, + Stat::CRIT_RTG => CR_CRIT_MELEE, + Stat::HIT_TAKEN_RTG => CR_HIT_TAKEN_MELEE, // may be forced 0 + Stat::CRIT_TAKEN_RTG => CR_CRIT_TAKEN_MELEE, // may be forced 0 + Stat::RESILIENCE_RTG => CR_CRIT_TAKEN_MELEE, + Stat::HASTE_RTG => CR_HASTE_MELEE, + Stat::EXPERTISE_RTG => CR_EXPERTISE, + Stat::ARMOR_PENETRATION_RTG => CR_ARMOR_PENETRATION + ); + + $data = $ratings; + + $offsets = array_map(function ($v) { // LookupEntry(cr*GT_MAX_LEVEL+level-1) + return $v * 100 + 60 - 1; // combat rating where introduced during the transition vanilla > burnig crusade. So at level 60 (at the time) the rating on the item was equal to 1% effect and is still the baseline in 3.3.5a. + }, $ratings); + $base = DB::Aowow()->selectCol('SELECT CAST((idx + 1 - 60) / 100 AS UNSIGNED) AS ARRAY_KEY, ratio FROM dbc_gtcombatratings WHERE idx IN (?a)', $offsets); + + /* non-1 scaler in 3.3.5.12340 + | ratingId | classId | ratio | + | 17 | 2 | 1.3 | + | 17 | 6 | 1.3 | + | 17 | 7 | 1.3 | + | 17 | 11 | 1.3 | + | 24 | < all > | 1.1 | + */ + + $offsets = array_map(function ($v) { // LookupEntry((getClass()-1)*GT_MAX_RATING+cr+1) + return (CLASS_WARRIOR - 1) * 32 + $v + 1; // should this be dynamic per pinned character? ITEM_MOD HASTE has a worse scaler for a subset of classes (see table) + }, $ratings); + $mods = DB::Aowow()->selectCol('SELECT idx - 1 AS ARRAY_KEY, ratio FROM dbc_gtoctclasscombatratingscalar WHERE idx IN (?a)', $offsets); + + foreach ($data as &$val) + $val = Cfg::get('DEBUG') ? $base[$val].' / '.$mods[$val] : $base[$val] / $mods[$val]; + + if (!Cfg::get('DEBUG')) + return Util::toJSON($data); + + $buff = []; + foreach ($data as $k => $v) + $buff[] = $k.': '.$v; + + return "{\r\n ".implode(",\r\n ", $buff)."\r\n}"; + } + + private function itemScalingSV() : string + { + /* so the javascript expects a slightly different structure, than the dbc provides .. f*** it + e.g. + dbc - 80: 97 97 56 41 210 395 878 570 120 156 86 112 108 220 343 131 73 140 280 527 1171 2093 + expected - 80: 97 97 56 131 41 210 395 878 1570 120 156 86 112 108 220 343 0 0 73 140 280 527 1171 2093 + */ + $fields = Util::$ssdMaskFields; + array_walk($fields, function(&$v, $k) { + $v = $v ?: '0 AS idx'.$k; // NULL => 0 (plus some index so we can have 2x 0) + }); + + $data = DB::Aowow()->select('SELECT id AS ARRAY_KEY, '.implode(', ', $fields).' FROM dbc_scalingstatvalues'); + foreach ($data as &$d) + $d = array_values($d); // strip indizes + + return Cfg::get('DEBUG') ? $this->debugify($data) : Util::toJSON($data); + } + + private function itemScalingSD() : string + { + $data = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM dbc_scalingstatdistribution'); + foreach ($data as &$row) + { + $row = array_values($row); + array_splice($row, 0, 1); + } + + return Cfg::get('DEBUG') ? $this->debugify($data) : Util::toJSON($data); + } +}) + +?> diff --git a/setup/tools/filegen/itemsets.func.php b/setup/tools/filegen/itemsets.ss.php similarity index 66% rename from setup/tools/filegen/itemsets.func.php rename to setup/tools/filegen/itemsets.ss.php index 80ccd69eb..8b8277577 100644 --- a/setup/tools/filegen/itemsets.func.php +++ b/setup/tools/filegen/itemsets.ss.php @@ -7,41 +7,43 @@ die('not in cli mode'); - /* Example - "-447": { // internal id, freely chosen - "classes":["6"], // array - "elite":true, - "heroic":false, - "id":"-447", - "idbak":"924", // actual setId - "maxlevel":"390", - "minlevel":"390", - "name":"3Cataclysmic Gladiator's Desecration", - "note":"37", // contentGroup - "pieces":["73742","73741","73740","73739","73738"], - "reqclass":"32", // mask - "type":"4", - "setbonus":{ - "2":{"resirtng":"400","str":"70"}, - "4":{"str":"90"} - } - }, - */ +/* Example + "-447": { // internal id, freely chosen + "classes":["6"], // array + "elite":true, + "heroic":false, + "id":"-447", + "idbak":"924", // actual setId + "maxlevel":"390", + "minlevel":"390", + "name":"3Cataclysmic Gladiator's Desecration", + "note":"37", // contentGroup + "pieces":["73742","73741","73740","73739","73738"], + "reqclass":"32", // mask + "type":"4", + "setbonus":{ + "2":{"resirtng":"400","str":"70"}, + "4":{"str":"90"} + } + }, +*/ + +// Create 'itemsets'-file for available locales +CLISetup::registerSetup("build", new class extends SetupScript +{ + protected $info = array( + 'itemsets' => [[], CLISetup::ARGV_PARAM, 'Compiles available item sets used throughout the page to file.'] + ); - // Create 'itemsets'-file for available locales - // this script requires the following dbc-files to be parsed and available + protected $setupAfter = [['itemset', 'spell'], []]; + protected $requiredDirs = ['datasets/']; + protected $localized = true; - function itemsets() + public function generate() : bool { - $success = true; $setList = DB::Aowow()->Select('SELECT * FROM ?_itemset ORDER BY refSetId DESC'); $jsonBonus = []; - // check directory-structure - foreach (Util::$localeStrings as $dir) - if (!CLISetup::writeDir('datasets/'.$dir)) - $success = false; - foreach (CLISetup::$localeIds as $lId) { User::useLocale($lId); @@ -55,7 +57,7 @@ function itemsets() $setOut = array( 'id' => $set['id'], 'idbak' => $set['refSetId'], - 'name' => (7 - $set['quality']).Util::jsEscape(Util::localizedString($set, 'name')), + 'name' => (ITEM_QUALITY_HEIRLOOM - $set['quality']).Util::jsEscape(Util::localizedString($set, 'name')), 'pieces' => [], 'heroic' => !!$set['heroic'], // should be bool 'maxlevel' => $set['maxLevel'], @@ -84,7 +86,7 @@ function itemsets() $_spells = []; for ($i = 1; $i < 9; $i++) { - if (!$set['spell'.$i] || isset($jsonBonus[$set['spell'.$i]])) + if (!$set['bonus'.$i] || isset($jsonBonus[$set['spell'.$i]])) continue; $_spells[] = $set['spell'.$i]; @@ -121,9 +123,11 @@ function itemsets() $file = 'datasets/'.User::$localeString.'/itemsets'; if (!CLISetup::writeFile($file, $toFile)) - $success = false; + $this->success = false; } - return $success; + return $this->success; } +}); + ?> diff --git a/setup/tools/filegen/locales.func.php b/setup/tools/filegen/locales.ss.php similarity index 82% rename from setup/tools/filegen/locales.func.php rename to setup/tools/filegen/locales.ss.php index abb9de21b..2c89c1376 100644 --- a/setup/tools/filegen/locales.func.php +++ b/setup/tools/filegen/locales.ss.php @@ -7,12 +7,22 @@ die('not in cli mode'); - // Create 'locale.js'-file in static/js - // available locales have to be set in aowow.aowow_config +// Create 'locale.js'-file in static/js +// available locales have to be set in aowow.aowow_config - function locales() +CLISetup::registerSetup("build", new class extends SetupScript +{ + use TrTemplateFile; + + protected $info = array( + 'locales' => [[], CLISetup::ARGV_PARAM, 'Compiles the Locale Object (static/js/locale.js) with available languages.'] + ); + + protected $fileTemplateDest = ['static/js/locale.js']; + protected $fileTemplateSrc = ['locale.js.in']; + + private function locales() : string { - $result = []; $available = array( LOCALE_EN => " 0: { // English\r\n" . " id: LOCALE_ENUS,\r\n" . @@ -52,11 +62,13 @@ function locales() " }", ); + $result = []; foreach (CLISetup::$localeIds as $l) if (isset($available[$l])) $result[] = $available[$l]; return implode(",\r\n", $result); } +}); ?> diff --git a/setup/tools/filegen/markup.ss.php b/setup/tools/filegen/markup.ss.php new file mode 100644 index 000000000..d3481a9ff --- /dev/null +++ b/setup/tools/filegen/markup.ss.php @@ -0,0 +1,22 @@ + [[], CLISetup::ARGV_PARAM, 'Fills the markup parser (static/js/Markup.js) with site variables.'] + ); + + protected $fileTemplateSrc = ['Markup.js.in']; + protected $fileTemplateDest = ['static/js/Markup.js']; +}); + +?> diff --git a/setup/tools/filegen/pets.func.php b/setup/tools/filegen/pets.func.php deleted file mode 100644 index c255efb7d..000000000 --- a/setup/tools/filegen/pets.func.php +++ /dev/null @@ -1,97 +0,0 @@ -Select( - 'SELECT cr.id, - cr.name_loc0, cr.name_loc2, cr.name_loc3, cr.name_loc4, cr.name_loc6, cr.name_loc8, - cr.minLevel, - cr.maxLevel, - ft.A, - ft.H, - cr.rank AS classification, - cr.family, - cr.displayId1 AS displayId, - cr.textureString AS skin, - LOWER(SUBSTRING_INDEX(cf.iconString, "\\\\", -1)) AS icon, - cf.petTalentType AS type - FROM ?_creature cr - JOIN ?_factiontemplate ft ON ft.id = cr.faction - JOIN dbc_creaturefamily cf ON cf.id = cr.family - WHERE cr.typeFlags & 0x1 AND (cr.cuFlags & 0x2) = 0 - ORDER BY cr.id ASC'); - - // check directory-structure - foreach (Util::$localeStrings as $dir) - if (!CLISetup::writeDir('datasets/'.$dir)) - $success = false; - - foreach (CLISetup::$localeIds as $lId) - { - User::useLocale($lId); - Lang::load($lId); - - $petsOut = []; - foreach ($petList as $pet) - { - // get locations - // again: caching will save you time and nerves - if (!isset($locations[$pet['id']])) - $locations[$pet['id']] = DB::Aowow()->SelectCol('SELECT DISTINCT areaId FROM ?_spawns WHERE type = ?d AND typeId = ?d', Type::NPC, $pet['id']); - - $petsOut[$pet['id']] = array( - 'id' => $pet['id'], - 'name' => Util::localizedString($pet, 'name'), - 'minlevel' => $pet['minLevel'], - 'maxlevel' => $pet['maxLevel'], - 'location' => $locations[$pet['id']], - 'react' => [$pet['A'], $pet['H']], - 'classification' => $pet['classification'], - 'family' => $pet['family'], - 'displayId' => $pet['displayId'], - 'skin' => $pet['skin'], - 'icon' => $pet['icon'], - 'type' => $pet['type'] - ); - } - - $toFile = "var g_pets = ".Util::toJSON($petsOut).";"; - $file = 'datasets/'.User::$localeString.'/pets'; - - if (!CLISetup::writeFile($file, $toFile)) - $success = false; - } - - return $success; - } -?> diff --git a/setup/tools/filegen/pets.ss.php b/setup/tools/filegen/pets.ss.php new file mode 100644 index 000000000..5f31aa0df --- /dev/null +++ b/setup/tools/filegen/pets.ss.php @@ -0,0 +1,95 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles tameable hunter pets to file for the talent calculator tool.'] + ); + + protected $dbcSourceFiles = ['creaturefamily']; + protected $setupAfter = [['creature', 'factions', 'spawns'], []]; + protected $requiredDirs = ['datasets/']; + protected $localized = true; + + public function generate() : bool + { + $petList = DB::Aowow()->Select( + 'SELECT cr.id, + cr.name_loc0, cr.name_loc2, cr.name_loc3, cr.name_loc4, cr.name_loc6, cr.name_loc8, + cr.minLevel, cr.maxLevel, + ft.A, ft.H, + cr.rank AS classification, + cr.family, + cr.displayId1 AS displayId, + cr.textureString AS skin, + LOWER(SUBSTRING_INDEX(cf.iconString, "\\\\", -1)) AS icon, + cf.petTalentType AS type + FROM ?_creature cr + JOIN ?_factiontemplate ft ON ft.id = cr.faction + JOIN dbc_creaturefamily cf ON cf.id = cr.family + WHERE cr.typeFlags & 0x1 AND (cr.cuFlags & 0x2) = 0 + ORDER BY cr.id ASC'); + + $locations = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, `areaId` AS ARRAY_KEY2, `areaId` FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`, `areaId`', Type::NPC, array_column($petList, 'id')); + + foreach (CLISetup::$localeIds as $lId) + { + User::useLocale($lId); + Lang::load($lId); + + $petsOut = []; + foreach ($petList as $pet) + { + $petsOut[$pet['id']] = array( + 'id' => $pet['id'], + 'name' => Util::localizedString($pet, 'name'), + 'minlevel' => $pet['minLevel'], + 'maxlevel' => $pet['maxLevel'], + 'location' => $locations[$pet['id']] ?? [], + 'react' => [$pet['A'], $pet['H']], + 'classification' => $pet['classification'], + 'family' => $pet['family'], + 'displayId' => $pet['displayId'], + 'skin' => $pet['skin'], + 'icon' => $pet['icon'], + 'type' => $pet['type'] + ); + } + + $toFile = "var g_pets = ".Util::toJSON($petsOut).";"; + $file = 'datasets/'.User::$localeString.'/pets'; + + if (!CLISetup::writeFile($file, $toFile)) + $this->success = false; + } + + return $this->success; + } +}); + +?> diff --git a/setup/tools/filegen/profiler.func.php b/setup/tools/filegen/profiler.func.php deleted file mode 100644 index 3646f9d32..000000000 --- a/setup/tools/filegen/profiler.func.php +++ /dev/null @@ -1,473 +0,0 @@ - $type, 'typeId' => $typeId, 'groups' => $groups, 'comment' => $comment]; - else - { - $exclusions[$k]['groups'] |= $groups; - if ($comment) - $exclusions[$k]['comment'] .= '; '.$comment; - } - }; - - - $sumTotal = function(array &$sumArr, int $raceMask = -1, int $classMask= -1) - { - for ($i = 0; $i < RACE_MASK_ALL; $i++) - { - if (!((1 << $i) & $raceMask) || !((1 << $i) & RACE_MASK_ALL)) - continue; - - for ($j = 0; $j < CLASS_MASK_ALL; $j++) - { - if (!((1 << $j) & $classMask) || !((1 << $j) & CLASS_MASK_ALL)) - continue; - - if (!isset($sumArr[$i+1][$j+1])) - $sumArr[$i+1][$j+1] = 1; - else - $sumArr[$i+1][$j+1]++; - } - } - }; - - - $spellFactions = DB::World()->selectCol('SELECT `alliance_id` AS ARRAY_KEY, 1 FROM player_factionchange_spells UNION SELECT `horde_id` AS ARRAY_KEY, 2 FROM player_factionchange_spells'); - - /**********/ - /* Quests */ - /**********/ - $scripts[] = function() use ($exAdd, $sumTotal) - { - $success = true; - $questorder = []; - $questtotal = []; - $condition = [ - Cfg::get('SQL_LIMIT_NONE'), - 'AND', - [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW | CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'], 0], - [['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE | QUEST_FLAG_AUTO_REWARDED, '&'], 0], - [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_DUNGEON_FINDER | QUEST_FLAG_SPECIAL_MONTHLY, '&'], 0] - ]; - - foreach (Game::$questClasses as $cat2 => $cat) - { - if ($cat2 < 0) - continue; - - $cond = array_merge($condition, [['zoneOrSort', $cat]]); - $questz = new QuestList($cond); - if ($questz->error) - continue; - - $questorder[] = $cat2; - $questtotal[$cat2] = []; - - // get quests for exclusion - foreach ($questz->iterate() as $id => $__) - { - $sumTotal($questtotal[$cat2], $questz->getField('reqRaceMask') ?: -1, $questz->getField('reqClassMask') ?: -1); - - switch ($questz->getField('reqSkillId')) - { - case 356: - $exAdd(Type::QUEST, $id, PR_EXCLUDE_GROUP_REQ_FISHING); - break; - case 202: - $exAdd(Type::QUEST, $id, PR_EXCLUDE_GROUP_REQ_ENGINEERING); - break; - case 197: - $exAdd(Type::QUEST, $id, PR_EXCLUDE_GROUP_REQ_TAILORING); - break; - } - } - - $_ = []; - $currencies = array_column($questz->rewards, Type::CURRENCY); - foreach ($currencies as $curr) - foreach ($curr as $cId => $qty) - $_[] = $cId; - - $relCurr = new CurrencyList(array(['id', $_])); - - foreach (CLISetup::$localeIds as $l) - { - set_time_limit(20); - - User::useLocale($l); - Lang::load($l); - - if (!$relCurr->error) - { - $buff = "var _ = g_gatheredcurrencies;\n"; - foreach ($relCurr->getListviewData() as $id => $data) - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - } - - $buff .= "var _ = g_quests;\n"; - foreach ($questz->getListviewData() as $id => $data) - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - - if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-quests-'.$cat2, $buff)) - $success = false; - } - } - - $buff = "g_quest_catorder = ".Util::toJSON($questorder).";\n"; - $buff .= "g_quest_catorder_total = {};\n"; - foreach ($questtotal as $cat => $totals) - $buff .= "g_quest_catorder_total[".$cat."] = ".Util::toJSON($totals).";\n"; - - if (!CLISetup::writeFile('datasets/p-quests', $buff)) - $success = false; - - return $success; - }; - - /**********/ - /* Titles */ - /**********/ - $scripts[] = function() use ($exAdd) - { - $success = true; - $condition = array( - Cfg::get('SQL_LIMIT_NONE'), - [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], - ); - $titlez = new TitleList($condition); - - // get titles for exclusion - foreach ($titlez->iterate() as $id => $__) - if (empty($titlez->sources[$id][4]) && empty($titlez->sources[$id][12])) - $exAdd(Type::TITLE, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); - - foreach (CLISetup::$localeIds as $l) - { - set_time_limit(5); - - User::useLocale($l); - Lang::load($l); - - foreach ([0, 1] as $g) // gender - { - $buff = "var _ = g_titles;\n"; - foreach ($titlez->getListviewData() as $id => $data) - { - $data['name'] = Util::localizedString($titlez->getEntry($id), $g ? 'female' : 'male'); - unset($data['namefemale']); - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - } - - if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-titles-'.$g, $buff)) - $success = false; - } - } - - return $success; - }; - - /**********/ - /* Mounts */ - /**********/ - $scripts[] = function() use ($exAdd, $spellFactions) - { - $success = true; - $condition = array( - Cfg::get('SQL_LIMIT_NONE'), - [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], - ['typeCat', -5], - ['castTime', 0, '!'] - ); - $mountz = new SpellList($condition); - - $conditionSet = DB::World()->selectCol('SELECT SourceEntry AS ARRAY_KEY, ConditionValue1 FROM conditions WHERE SourceTypeOrReferenceId = ?d AND ConditionTypeOrReference = ?d AND SourceEntry IN (?a)', Conditions::SRC_SPELL, Conditions::SKILL, $mountz->getFoundIDs()); - - // get mounts for exclusion - foreach ($conditionSet as $mount => $skill) - { - if ($skill == 202) - $exAdd(Type::SPELL, $mount, PR_EXCLUDE_GROUP_REQ_ENGINEERING); - else if ($skill == 197) - $exAdd(Type::SPELL, $mount, PR_EXCLUDE_GROUP_REQ_TAILORING); - } - - foreach ($mountz->iterate() as $id => $_) - if (!$mountz->getSources()) - $exAdd(Type::SPELL, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); - - foreach (CLISetup::$localeIds as $l) - { - set_time_limit(5); - - User::useLocale($l); - Lang::load($l); - - $buff = "var _ = g_spells;\n"; - foreach ($mountz->getListviewData(ITEMINFO_MODEL) as $id => $data) - { - // two cases where the spell is unrestricted but the castitem has class restriction (too lazy to formulate ruleset) - if ($id == 66906) // Argent Charger - $data['reqclass'] = CLASS_PALADIN; - else if ($id == 54729) // Winged Steed of the Ebon Blade - $data['reqclass'] = CLASS_DEATHKNIGHT; - - rsort($data['skill']); // riding (777) expected at pos 0 - - $data['side'] = $spellFactions[$id] ?? SIDE_BOTH; - $data['quality'] = $data['name'][0]; - $data['name'] = mb_substr($data['name'], 1); - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - } - - if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-mounts', $buff)) - $success = false; - } - - return $success; - }; - - /**************/ - /* Companions */ - /**************/ - $scripts[] = function() use ($exAdd, $spellFactions) - { - $success = true; - $condition = array( - Cfg::get('SQL_LIMIT_NONE'), - [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], - ['typeCat', -6] - ); - $companionz = new SpellList($condition); - $legit = DB::Aowow()->selectCol('SELECT `spellId2` FROM ?_items WHERE `class` = ?d AND `subClass` = ?d AND `spellId1` IN (?a) AND `spellId2` IN (?a)', ITEM_CLASS_MISC, 2, LEARN_SPELLS, $companionz->getFoundIDs()); - - foreach ($companionz->iterate() as $id => $_) - if (!$companionz->getSources()) - $exAdd(Type::SPELL, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); - - foreach (CLISetup::$localeIds as $l) - { - set_time_limit(5); - - User::useLocale($l); - Lang::load($l); - - $buff = "var _ = g_spells;\n"; - foreach ($companionz->getListviewData(ITEMINFO_MODEL) as $id => $data) - { - if (!in_array($id, $legit)) - continue; - - $data['side'] = $spellFactions[$id] ?? SIDE_BOTH; - $data['quality'] = $data['name'][0]; - $data['name'] = mb_substr($data['name'], 1); - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - } - - if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-companions', $buff)) - $success = false; - } - - return $success; - }; - - /************/ - /* Factions */ - /************/ - $scripts[] = function() use ($exAdd) - { - $success = true; - $condition = array( // todo (med): exclude non-gaining reputation-header - Cfg::get('SQL_LIMIT_NONE'), - [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0] - ); - $factionz = new FactionList($condition); - - foreach (CLISetup::$localeIds as $l) - { - set_time_limit(5); - - User::useLocale($l); - Lang::load($l); - - $buff = "var _ = g_factions;\n"; - foreach ($factionz->getListviewData() as $id => $data) - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - - $buff .= "\ng_faction_order = [0, 469, 891, 1037, 1118, 67, 1052, 892, 936, 1117, 169, 980, 1097];\n"; - - if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-factions', $buff)) - $success = false; - } - - return $success; - }; - - /***********/ - /* Recipes */ - /***********/ - $scripts[] = function() use ($exAdd, $spellFactions) - { - // special case: secondary skills are always requested, so put them in one single file (185, 129, 356); it also contains g_skill_order - $skills = array_merge(SKILLS_TRADE_PRIMARY, [[185, 129, 356]]); - $success = true; - $baseCnd = array( - Cfg::get('SQL_LIMIT_NONE'), - [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], - ['effect1Id', [6, 45, 57, 127, 33, 158, 99, 28, 95], '!'], // aura, tradeSkill, Tracking, Prospecting, Decipher, Milling, Disenchant, Summon (Engineering), Skinning - ['effect2Id', [118, 60], '!'], // not the skill itself - ['OR', ['typeCat', 9], ['typeCat', 11]] - ); - - foreach ($skills as $s) - { - $file = is_array($s) ? 'sec' : (string)$s; - $cnd = array_merge($baseCnd, [['skillLine1', $s]]); - $recipez = new SpellList($cnd); - $created = ''; - foreach ($recipez->iterate() as $id => $__) - { - if (!$recipez->getSources()) - $exAdd(Type::SPELL, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); - - foreach ($recipez->canCreateItem() as $idx) - { - $id = $recipez->getField('effect'.$idx.'CreateItemId'); - $created .= "g_items.add(".$id.", {'icon':'".$recipez->relItems->getEntry($id)['iconString']."'});\n"; - } - } - - foreach (CLISetup::$localeIds as $l) - { - set_time_limit(10); - - User::useLocale($l); - Lang::load($l); - - $buff = ''; - foreach ($recipez->getListviewData() as $id => $data) - { - $data['side'] = $spellFactions[$id] ?? SIDE_BOTH; - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - } - - if (!$buff) - { - // this behaviour is intended, do not create an error - CLI::write('profiler - file datasets/'.User::$localeString.'/p-recipes-'.$file.' has no content => skipping', CLI::LOG_INFO); - continue; - } - - $buff = $created."\nvar _ = g_spells;\n".$buff; - - if (is_array($s)) - $buff .= "\ng_skill_order = [171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, 185, 129, 356];\n"; - - if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-recipes-'.$file, $buff)) - $success = false; - } - } - - return $success; - }; - - /****************/ - /* Achievements */ - /****************/ - $scripts[] = function() use ($exAdd) - { - $success = true; - $condition = array( - Cfg::get('SQL_LIMIT_NONE'), - [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], - [['flags', 1, '&'], 0], // no statistics - ); - $achievez = new AchievementList($condition); - - foreach (CLISetup::$localeIds as $l) - { - set_time_limit(5); - - User::useLocale($l); - Lang::load($l); - - $sumPoints = 0; - $buff = "var _ = g_achievements;\n"; - foreach ($achievez->getListviewData(ACHIEVEMENTINFO_PROFILE) as $id => $data) - { - $sumPoints += $data['points']; - $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; - } - - // categories to sort by - $buff .= "\ng_achievement_catorder = [92, 14863, 97, 169, 170, 171, 172, 14802, 14804, 14803, 14801, 95, 161, 156, 165, 14806, 14921, 96, 201, 160, 14923, 14808, 14805, 14778, 14865, 14777, 14779, 155, 14862, 14861, 14864, 14866, 158, 162, 14780, 168, 14881, 187, 14901, 163, 14922, 159, 14941, 14961, 14962, 14981, 15003, 15002, 15001, 15041, 15042, 81]"; - // sum points - $buff .= "\ng_achievement_points = [".$sumPoints."];\n"; - - if (!CLISetup::writeFile('datasets/'.User::$localeString.'/achievements', $buff)) - $success = false; - } - - return $success; - }; - - /******************/ - /* Quick Excludes */ - /******************/ - $scripts[] = function() use (&$exclusions) - { - set_time_limit(2); - - CLI::write('applying '.count($exclusions).' baseline exclusions'); - DB::Aowow()->query('DELETE FROM ?_profiler_excludes WHERE comment = ""'); - - foreach ($exclusions as $ex) - DB::Aowow()->query('REPLACE INTO ?_profiler_excludes (?#) VALUES (?a)', array_keys($ex), array_values($ex)); - - // excludes; type => [excludeGroupBit => [typeIds]] - $excludes = []; - - $exData = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, `typeId` AS ARRAY_KEY2, `groups` FROM ?_profiler_excludes'); - for ($i = 0; (1 << $i) < PR_EXCLUDE_GROUP_ANY; $i++) - foreach ($exData as $type => $data) - if ($ids = array_keys(array_filter($data, function ($x) use ($i) { return $x & (1 << $i); } ))) - $excludes[$type][$i + 1] = $ids; - - $buff = "g_excludes = ".Util::toJSON($excludes ?: (new Stdclass)).";\n"; - - return CLISetup::writeFile('datasets/quick-excludes', $buff); - }; - - // check directory-structure - foreach (Util::$localeStrings as $dir) - if (!CLISetup::writeDir('datasets/'.$dir)) - $success = false; - - // run scripts - foreach ($scripts as $func) - if (!$func()) - $success = false; - - return $success; - } - -?> diff --git a/setup/tools/filegen/profiler.ss.php b/setup/tools/filegen/profiler.ss.php new file mode 100644 index 000000000..603a6dae6 --- /dev/null +++ b/setup/tools/filegen/profiler.ss.php @@ -0,0 +1,447 @@ + [[ ], CLISetup::ARGV_PARAM, 'Generates data dumps and completion exclusion filters for the profiler tool.'], + 'quests' => [['1'], CLISetup::ARGV_OPTIONAL, '...available quests by category'], + 'titles' => [['2'], CLISetup::ARGV_OPTIONAL, '...available titles by gender'], + 'mounts' => [['3'], CLISetup::ARGV_OPTIONAL, '...available mounts'], + 'companions' => [['4'], CLISetup::ARGV_OPTIONAL, '...available companions'], + 'factions' => [['5'], CLISetup::ARGV_OPTIONAL, '...available factions'], + 'recipes' => [['6'], CLISetup::ARGV_OPTIONAL, '...available recipes by skill'], + 'achievements' => [['7'], CLISetup::ARGV_OPTIONAL, '...available achievements'], + 'quickexcludes' => [['9'], CLISetup::ARGV_OPTIONAL, '...unobtainable items, mutually exclusive recipes, factions, etc.'], + ); + + protected $localized = true; + protected $requiredDirs = ['datasets/']; + protected $worldDependency = ['player_factionchange_spells', 'conditions']; + protected $setupAfter = [['quests', 'quests_startend', 'items', 'currencies', 'titles', 'spell', 'factions', 'achievement'], []]; + + private $spellFactions = []; + private $exclusions = []; + private $opts = []; + + public function generate() : bool + { + $anyOpt = array_keys($this->info); + $this->opts = CLISetup::getOpt(...$anyOpt); + + $this->opts = array_filter($this->opts); + if (!$this->opts) // none were set -> use default (all) + $this->opts = array_fill_keys(array_slice($anyOpt, 1), true); + + + $this->spellFactions = DB::World()->selectCol('SELECT `alliance_id` AS ARRAY_KEY, 1 FROM player_factionchange_spells UNION SELECT `horde_id` AS ARRAY_KEY, 2 FROM player_factionchange_spells'); + + + foreach ($this->opts as $fn => $_) + $this->$fn(); + + return $this->success; + } + + private function quests() : void + { + $questorder = []; + $questtotal = []; + $condition = [ + Cfg::get('SQL_LIMIT_NONE'), + 'AND', + [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW | CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'], 0], + [['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE | QUEST_FLAG_AUTO_REWARDED, '&'], 0], + [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_DUNGEON_FINDER | QUEST_FLAG_SPECIAL_MONTHLY, '&'], 0] + ]; + + foreach (Game::$questClasses as $cat2 => $cat) + { + if ($cat2 < 0) + continue; + + $cond = array_merge($condition, [['zoneOrSort', $cat]]); + $questz = new QuestList($cond); + if ($questz->error) + continue; + + $questorder[] = $cat2; + $questtotal[$cat2] = []; + + // get quests for exclusion + foreach ($questz->iterate() as $id => $__) + { + $this->sumTotal($questtotal[$cat2], $questz->getField('reqRaceMask') ?: -1, $questz->getField('reqClassMask') ?: -1); + if ($skillEx = $this->getExcludeForSkill($questz->getField('reqSkillId'))) + $this->addExclusion(Type::QUEST, $id, $skillEx); + } + + $_ = []; + $currencies = array_column($questz->rewards, Type::CURRENCY); + foreach ($currencies as $curr) + foreach ($curr as $cId => $qty) + $_[] = $cId; + + $relCurr = new CurrencyList(array(['id', $_])); + + foreach (CLISetup::$localeIds as $l) + { + set_time_limit(20); + + User::useLocale($l); + Lang::load($l); + + if (!$relCurr->error) + { + $buff = "var _ = g_gatheredcurrencies;\n"; + foreach ($relCurr->getListviewData() as $id => $data) + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + } + + $buff .= "var _ = g_quests;\n"; + foreach ($questz->getListviewData() as $id => $data) + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + + if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-quests-'.$cat2, $buff)) + $this->success = false; + } + } + + $buff = "g_quest_catorder = ".Util::toJSON($questorder).";\n"; + $buff .= "g_quest_catorder_total = {};\n"; + foreach ($questtotal as $cat => $totals) + $buff .= "g_quest_catorder_total[".$cat."] = ".Util::toJSON($totals).";\n"; + + if (!CLISetup::writeFile('datasets/p-quests', $buff)) + $this->success = false; + } + + private function titles(): void + { + $condition = array( + Cfg::get('SQL_LIMIT_NONE'), + [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], + ); + $titlez = new TitleList($condition); + + // get titles for exclusion + foreach ($titlez->iterate() as $id => $__) + if (empty($titlez->sources[$id][4]) && empty($titlez->sources[$id][12])) + $this->addExclusion(Type::TITLE, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); + + foreach (CLISetup::$localeIds as $l) + { + set_time_limit(5); + + User::useLocale($l); + Lang::load($l); + + foreach ([GENDER_MALE, GENDER_FEMALE] as $g) + { + $buff = "var _ = g_titles;\n"; + foreach ($titlez->getListviewData() as $id => $data) + { + $data['name'] = Util::localizedString($titlez->getEntry($id), $g ? 'female' : 'male'); + unset($data['namefemale']); + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + } + + if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-titles-'.$g, $buff)) + $this->success = false; + } + } + } + + private function mounts() : void + { + $condition = array( + Cfg::get('SQL_LIMIT_NONE'), + [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], + ['typeCat', -5], + ['castTime', 0, '!'] + ); + $mountz = new SpellList($condition); + + $conditionSet = DB::World()->selectCol('SELECT SourceEntry AS ARRAY_KEY, ConditionValue1 FROM conditions WHERE SourceTypeOrReferenceId = ?d AND ConditionTypeOrReference = ?d AND SourceEntry IN (?a)', Conditions::SRC_SPELL, Conditions::SKILL, $mountz->getFoundIDs()); + + // get mounts for exclusion + foreach ($conditionSet as $mount => $skill) + if ($skillEx = $this->getExcludeForSkill($skill)) + $this->addExclusion(Type::SPELL, $mount, $skillEx); + + foreach ($mountz->iterate() as $id => $_) + if (!$mountz->getSources()) + $this->addExclusion(Type::SPELL, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); + + foreach (CLISetup::$localeIds as $l) + { + set_time_limit(5); + + User::useLocale($l); + Lang::load($l); + + $buff = "var _ = g_spells;\n"; + foreach ($mountz->getListviewData(ITEMINFO_MODEL) as $id => $data) + { + // two cases where the spell is unrestricted but the castitem has class restriction (too lazy to formulate ruleset) + if ($id == 66906) // Argent Charger + $data['reqclass'] = CLASS_PALADIN; + else if ($id == 54729) // Winged Steed of the Ebon Blade + $data['reqclass'] = CLASS_DEATHKNIGHT; + + rsort($data['skill']); // riding (777) expected at pos 0 + + $data['side'] = $this->spellFactions[$id] ?? SIDE_BOTH; + $data['quality'] = $data['name'][0]; + $data['name'] = mb_substr($data['name'], 1); + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + } + + if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-mounts', $buff)) + $this->success = false; + } + } + + private function companions() : void + { + $condition = array( + Cfg::get('SQL_LIMIT_NONE'), + [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], + ['typeCat', -6] + ); + $companionz = new SpellList($condition); + $legit = DB::Aowow()->selectCol('SELECT `spellId2` FROM ?_items WHERE `class` = ?d AND `subClass` = ?d AND `spellId1` IN (?a) AND `spellId2` IN (?a)', ITEM_CLASS_MISC, 2, LEARN_SPELLS, $companionz->getFoundIDs()); + + foreach ($companionz->iterate() as $id => $_) + if (!$companionz->getSources()) + $this->addExclusion(Type::SPELL, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); + + foreach (CLISetup::$localeIds as $l) + { + set_time_limit(5); + + User::useLocale($l); + Lang::load($l); + + $buff = "var _ = g_spells;\n"; + foreach ($companionz->getListviewData(ITEMINFO_MODEL) as $id => $data) + { + if (!in_array($id, $legit)) + continue; + + $data['side'] = $this->spellFactions[$id] ?? SIDE_BOTH; + $data['quality'] = $data['name'][0]; + $data['name'] = mb_substr($data['name'], 1); + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + } + + if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-companions', $buff)) + $this->success = false; + } + } + + private function factions() : void + { + $condition = array( + Cfg::get('SQL_LIMIT_NONE'), + [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0] + ); + $factionz = new FactionList($condition); + + foreach (CLISetup::$localeIds as $l) + { + set_time_limit(5); + + User::useLocale($l); + Lang::load($l); + + $buff = "var _ = g_factions;\n"; + foreach ($factionz->getListviewData() as $id => $data) + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + + $buff .= "\ng_faction_order = [0, 469, 891, 1037, 1118, 67, 1052, 892, 936, 1117, 169, 980, 1097];\n"; + + if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-factions', $buff)) + $this->success = false; + } + } + + private function recipes() : void + { + // special case: secondary skills are always requested, so put them in one single file (185, 129, 356); it also contains g_skill_order + $skills = array( + SKILL_ALCHEMY, SKILL_BLACKSMITHING, SKILL_ENCHANTING, SKILL_ENGINEERING, SKILL_HERBALISM, + SKILL_INSCRIPTION, SKILL_JEWELCRAFTING, SKILL_LEATHERWORKING, SKILL_MINING, SKILL_SKINNING, + SKILL_TAILORING, [SKILL_COOKING, SKILL_FIRST_AID, SKILL_FISHING] + ); + + $baseCnd = array( + Cfg::get('SQL_LIMIT_NONE'), + [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], + ['effect1Id', [6, 45, 57, 127, 33, 158, 99, 28, 95], '!'], // aura, tradeSkill, Tracking, Prospecting, Decipher, Milling, Disenchant, Summon (Engineering), Skinning + ['effect2Id', [118, 60], '!'], // not the skill itself + ['OR', ['typeCat', 9], ['typeCat', 11]] + ); + + foreach ($skills as $s) + { + $file = is_array($s) ? 'sec' : (string)$s; + $cnd = array_merge($baseCnd, [['skillLine1', $s]]); + $recipez = new SpellList($cnd); + $created = ''; + foreach ($recipez->iterate() as $id => $_) + { + if (!$recipez->getSources()) + $this->addExclusion(Type::SPELL, $id, PR_EXCLUDE_GROUP_UNAVAILABLE); + + foreach ($recipez->canCreateItem() as $idx) + { + $id = $recipez->getField('effect'.$idx.'CreateItemId'); + $created .= "g_items.add(".$id.", {'icon':'".$recipez->relItems->getEntry($id)['iconString']."'});\n"; + } + } + + foreach (CLISetup::$localeIds as $l) + { + set_time_limit(10); + + User::useLocale($l); + Lang::load($l); + + $buff = ''; + foreach ($recipez->getListviewData() as $id => $data) + { + $data['side'] = $this->spellFactions[$id] ?? SIDE_BOTH; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + } + + if (!$buff) + { + // this behaviour is intended, do not create an error + CLI::write('[profiler] - file datasets/'.User::$localeString.'/p-recipes-'.$file.' has no content => skipping', CLI::LOG_INFO); + continue; + } + + $buff = $created."\nvar _ = g_spells;\n".$buff; + + if (is_array($s)) + $buff .= "\ng_skill_order = [171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, 185, 129, 356];\n"; + + if (!CLISetup::writeFile('datasets/'.User::$localeString.'/p-recipes-'.$file, $buff)) + $this->success = false; + } + } + } + + private function achievements() : void + { + $condition = array( + Cfg::get('SQL_LIMIT_NONE'), + [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], + [['flags', 1, '&'], 0], // no statistics + ); + $achievez = new AchievementList($condition); + + foreach (CLISetup::$localeIds as $l) + { + set_time_limit(5); + + User::useLocale($l); + Lang::load($l); + + $sumPoints = 0; + $buff = "var _ = g_achievements;\n"; + foreach ($achievez->getListviewData(ACHIEVEMENTINFO_PROFILE) as $id => $data) + { + $sumPoints += $data['points']; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; + } + + // categories to sort by + $buff .= "\ng_achievement_catorder = [92, 14863, 97, 169, 170, 171, 172, 14802, 14804, 14803, 14801, 95, 161, 156, 165, 14806, 14921, 96, 201, 160, 14923, 14808, 14805, 14778, 14865, 14777, 14779, 155, 14862, 14861, 14864, 14866, 158, 162, 14780, 168, 14881, 187, 14901, 163, 14922, 159, 14941, 14961, 14962, 14981, 15003, 15002, 15001, 15041, 15042, 81]"; + // sum points + $buff .= "\ng_achievement_points = [".$sumPoints."];\n"; + + if (!CLISetup::writeFile('datasets/'.User::$localeString.'/achievements', $buff)) + $this->success = false; + } + } + + private function quickexcludes() : void + { + set_time_limit(2); + + CLI::write('[profiler] applying '.count($this->exclusions).' baseline exclusions'); + DB::Aowow()->query('DELETE FROM ?_profiler_excludes WHERE comment = ""'); + + foreach ($this->exclusions as $ex) + DB::Aowow()->query('REPLACE INTO ?_profiler_excludes (?#) VALUES (?a)', array_keys($ex), array_values($ex)); + + // excludes; type => [excludeGroupBit => [typeIds]] + $excludes = []; + + $exData = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, `typeId` AS ARRAY_KEY2, `groups` FROM ?_profiler_excludes'); + for ($i = 0; (1 << $i) < PR_EXCLUDE_GROUP_ANY; $i++) + foreach ($exData as $type => $data) + if ($ids = array_keys(array_filter($data, function ($x) use ($i) { return $x & (1 << $i); } ))) + $excludes[$type][$i + 1] = $ids; + + $buff = "g_excludes = ".Util::toJSON($excludes ?: (new Stdclass)).";\n"; + + if (!CLISetup::writeFile('datasets/quick-excludes', $buff)) + $this->success = false; + } + + private function getExcludeForSkill(int $skillId) : int + { + switch ($skillId) + { + case SKILL_FISHING: return PR_EXCLUDE_GROUP_REQ_FISHING; + case SKILL_ENGINEERING: return PR_EXCLUDE_GROUP_REQ_ENGINEERING; + case SKILL_TAILORING: return PR_EXCLUDE_GROUP_REQ_TAILORING; + default: return 0; + } + } + + private function addExclusion(int $type, int $typeId, int $groups, string $comment = '') : void + { + $k = $type.'-'.$typeId; + + if (!isset($this->exclusions[$k])) + $this->exclusions[$k] = ['type' => $type, 'typeId' => $typeId, 'groups' => $groups, 'comment' => $comment]; + else + { + $this->exclusions[$k]['groups'] |= $groups; + if ($comment) + $this->exclusions[$k]['comment'] .= '; '.$comment; + } + } + + private function sumTotal (array &$sumArr, int $raceMask = -1, int $classMask= -1) : void + { + for ($i = 0; $i < RACE_MASK_ALL; $i++) + { + if (!((1 << $i) & $raceMask) || !((1 << $i) & RACE_MASK_ALL)) + continue; + + for ($j = 0; $j < CLASS_MASK_ALL; $j++) + { + if (!((1 << $j) & $classMask) || !((1 << $j) & CLASS_MASK_ALL)) + continue; + + if (!isset($sumArr[$i+1][$j+1])) + $sumArr[$i+1][$j+1] = 1; + else + $sumArr[$i+1][$j+1]++; + } + } + } +}); + +?> diff --git a/setup/tools/filegen/realmMenu.func.php b/setup/tools/filegen/realmMenu.func.php deleted file mode 100644 index afc8fca41..000000000 --- a/setup/tools/filegen/realmMenu.func.php +++ /dev/null @@ -1,72 +0,0 @@ - $n) - $subs[$idx] = []; - - foreach (Profiler::getRealms() as $row) - { - $idx = array_search($row['region'], Util::$regions); - if ($idx !== false) - { - $set |= (1 << $idx); - $subs[$idx][] = [Profiler::urlize($row['name'], true), $row['name'], null, null, $row['access'] ? ['requiredAccess' => $row['access']] : null]; - } - - } - if (!$set) - CLI::write(' - realmMenu: Auth-DB not set up .. realm menu will be empty', CLI::LOG_WARN); - - // why is this file not localized!? - foreach (Util::$regions as $idx => $n) - if ($set & (1 << $idx)) - $menu[] = [$n, Lang::profiler('regions', $n), null, &$subs[$idx]]; - - return Util::toJSON($menu); - } -?> diff --git a/setup/tools/filegen/realmmenu.ss.php b/setup/tools/filegen/realmmenu.ss.php new file mode 100644 index 000000000..c6dc80800 --- /dev/null +++ b/setup/tools/filegen/realmmenu.ss.php @@ -0,0 +1,85 @@ + [[], CLISetup::ARGV_PARAM, 'Generates \'profile_all.js\'-file that extends the profiler menus.'] + ); + + protected $fileTemplateSrc = ['profile_all.js.in']; + protected $fileTemplateDest = ['static/js/profile_all.js']; + protected $worldDependency = ['realmlist']; + + private function realmMenu() : string + { + $subs = []; + $set = 0x0; + $menu = [ + // skip usage of battlegroup + // ['us', Lang::profiler('regions', 'us'), null,[[Profiler::urlize(Cfg::get('BATTLEGROUP')), Cfg::get('BATTLEGROUP'), null, &$subUS]]], + // ['eu', Lang::profiler('regions', 'eu'), null,[[Profiler::urlize(Cfg::get('BATTLEGROUP')), Cfg::get('BATTLEGROUP'), null, &$subEU]]] + ]; + + foreach (Util::$regions as $idx => $n) + $subs[$idx] = []; + + if (!DB::isConnectable(DB_AUTH)) + CLI::write('[realmmenu] Auth DB not set up .. realm menu will be empty', CLI::LOG_WARN); + else + foreach (Profiler::getRealms() as $row) + { + $idx = array_search($row['region'], Util::$regions); + if ($idx === false) + continue; + + $set |= (1 << $idx); + $subs[$idx][] = [Profiler::urlize($row['name'], true), $row['name'], null, null, $row['access'] ? ['requiredAccess' => $row['access']] : null]; + } + + if (!$set) + CLI::write('[realmmenu] no viable realms found .. realm menu will be empty', CLI::LOG_WARN); + + // why is this file not localized!? + foreach (Util::$regions as $idx => $n) + if ($set & (1 << $idx)) + $menu[] = [$n, Lang::profiler('regions', $n), null, &$subs[$idx]]; + + return Util::toJSON($menu); + } +}); + +?> diff --git a/setup/tools/filegen/realms.func.php b/setup/tools/filegen/realms.func.php deleted file mode 100644 index 543d60728..000000000 --- a/setup/tools/filegen/realms.func.php +++ /dev/null @@ -1,47 +0,0 @@ - 'www.wowarmory.com', - eu => 'eu.wowarmory.com' - }; - */ - - /* Examples - 1 => { - name:'Eldre\'Thalas', - battlegroup:'Reckoning', - region:'us' - }, - */ - - function realms() - { - $realms = Profiler::getRealms(); - if (!$realms) - CLI::write(' - realms: Auth-DB not set up .. static data g_realms will be empty', CLI::LOG_WARN); - // else - // foreach ($realms as &$r) - // $r['battlegroup'] = Cfg::get('BATTLEGROUP'); - - // remove access column - array_walk($realms, function (&$x) { unset($x['access']); }); - - $toFile = "var g_realms = ".Util::toJSON($realms).";"; - $file = 'datasets/realms'; - - return CLISetup::writeFile($file, $toFile); - } - -?> diff --git a/setup/tools/filegen/realms.ss.php b/setup/tools/filegen/realms.ss.php new file mode 100644 index 000000000..74ea4eab6 --- /dev/null +++ b/setup/tools/filegen/realms.ss.php @@ -0,0 +1,56 @@ + 'www.wowarmory.com', + eu => 'eu.wowarmory.com' + }; +*/ + +/* Examples + 1 => { + name:'Eldre\'Thalas', + battlegroup:'Reckoning', + region:'us' + }, +*/ + +CLISetup::registerSetup("build", new class extends SetupScript +{ + protected $info = array( + 'realms' => [[], CLISetup::ARGV_PARAM, 'Generates \'realms\'-file to be referenced by the profiler tool.'] + ); + + protected $worldDependency = ['realmlist']; + protected $requiredDirs = ['datasets/']; + + public function generate() : bool + { + $realms = []; + + if (!DB::isConnectable(DB_AUTH)) + CLI::write('[realms] Auth DB not set up .. static data g_realms will be empty', CLI::LOG_WARN); + else if (!($realms = Profiler::getRealms())) + CLI::write('[realms] no viable realms found .. static data g_realms will be empty', CLI::LOG_WARN); + // else + // foreach ($realms as &$r) + // $r['battlegroup'] = Cfg::get('BATTLEGROUP'); + + // remove access column + array_walk($realms, function (&$x) { unset($x['access']); }); + + $toFile = "var g_realms = ".Util::toJSON($realms).";"; + $file = 'datasets/realms'; + + return CLISetup::writeFile($file, $toFile); + } +}); + +?> diff --git a/setup/tools/filegen/searchbox.ss.php b/setup/tools/filegen/searchbox.ss.php new file mode 100644 index 000000000..e01b7395c --- /dev/null +++ b/setup/tools/filegen/searchbox.ss.php @@ -0,0 +1,22 @@ + [[], CLISetup::ARGV_PARAM, 'Fills search widget files (static/widgets/searchbox*) with site variables.'] + ); + + protected $fileTemplateSrc = ['searchbox.js.in', 'searchbox.html.in']; + protected $fileTemplateDest = ['static/widgets/searchbox.js', 'static/widgets/searchbox/searchbox.html']; +}); + +?> diff --git a/setup/tools/filegen/searchplugin.ss.php b/setup/tools/filegen/searchplugin.ss.php new file mode 100644 index 000000000..855177c8a --- /dev/null +++ b/setup/tools/filegen/searchplugin.ss.php @@ -0,0 +1,23 @@ + [[], CLISetup::ARGV_PARAM, 'Fills browser opensearch plugin (static/download/searchplugins/aowow.xml) with site variables.'] + ); + + protected $fileTemplateSrc = ['aowow.xml.in']; + protected $fileTemplateDest = ['static/download/searchplugins/aowow.xml']; + protected $requiredDirs = ['static/download/searchplugins/']; +}); + +?> diff --git a/setup/tools/filegen/simpleImg.func.php b/setup/tools/filegen/simpleImg.func.php deleted file mode 100644 index fa9348012..000000000 --- a/setup/tools/filegen/simpleImg.func.php +++ /dev/null @@ -1,480 +0,0 @@ - ['Icons/', $iconDirs, '/.*\.(blp|png)$', true, 0, null], - 1 => ['Spellbook/', [['Interface/Spellbook/', '.png', 0, 0, 0]], '/UI-Glyph-Rune-?\d+.(blp|png)$', true, 0, null], - 2 => ['PaperDoll/', array_slice($iconDirs, 0, 3), '/UI-(Backpack|PaperDoll)-.*\.(blp|png)$', true, 0, null], - 3 => ['GLUES/CHARACTERCREATE/', $iconDirs, '/UI-CharacterCreate-Races.(blp|png)', true, 64, null], - 4 => ['GLUES/CHARACTERCREATE/', $iconDirs, '/UI-CharacterCreate-CLASSES.(blp|png)', true, 64, null], - 5 => ['GLUES/CHARACTERCREATE/', $iconDirs, '/UI-CharacterCreate-Factions.(blp|png)', true, 64, null], - // 6 => ['Minimap/', [['icons/tiny/', '.gif', 0, 16, 2]], '/OBJECTICONS.(BLP|png)', true, 32, null], - 7 => ['FlavorImages/', [['Interface/FlavorImages/', '.png', 0, 0, 0]], '/.*\.(blp|png)$', false, 0, null], - 8 => ['Pictures/', [['Interface/Pictures/', '.png', 0, 0, 0]], '/.*\.(blp|png)$', false, 0, null], - 9 => ['PvPRankBadges/', [['Interface/PvPRankBadges/', '.png', 0, 0, 0]], '/.*\.(blp|png)$', false, 0, null], - 10 => ['Calendar/Holidays/', $calendarDirs, '/.*(start|[ayhs])\.(blp|png)$', true, 0, null], - 11 => ['GLUES/LOADINGSCREENS/', $loadScreenDirs, '/lo.*\.(blp|png)$', false, 0, null], - 12 => ['PVPFrame/', array_map(function($x) { $x[4] = 2; return $x; }, $iconDirs), '/PVP-(ArenaPoints|Currency).*\.(blp|png)$', true, 0, null] - ); - // textures are composed of 64x64 icons - // numeric indexed arrays mimick the position on the texture - $cuNames = array( - 2 => array( - 'ui-paperdoll-slot-chest' => 'inventoryslot_chest', - 'ui-backpack-emptyslot' => 'inventoryslot_empty', - 'ui-paperdoll-slot-feet' => 'inventoryslot_feet', - 'ui-paperdoll-slot-finger' => 'inventoryslot_finger', - 'ui-paperdoll-slot-hands' => 'inventoryslot_hands', - 'ui-paperdoll-slot-head' => 'inventoryslot_head', - 'ui-paperdoll-slot-legs' => 'inventoryslot_legs', - 'ui-paperdoll-slot-mainhand' => 'inventoryslot_mainhand', - 'ui-paperdoll-slot-neck' => 'inventoryslot_neck', - 'ui-paperdoll-slot-secondaryhand' => 'inventoryslot_offhand', - 'ui-paperdoll-slot-ranged' => 'inventoryslot_ranged', - 'ui-paperdoll-slot-relic' => 'inventoryslot_relic', - 'ui-paperdoll-slot-shirt' => 'inventoryslot_shirt', - 'ui-paperdoll-slot-shoulder' => 'inventoryslot_shoulder', - 'ui-paperdoll-slot-tabard' => 'inventoryslot_tabard', - 'ui-paperdoll-slot-trinket' => 'inventoryslot_trinket', - 'ui-paperdoll-slot-waist' => 'inventoryslot_waist', - 'ui-paperdoll-slot-wrists' => 'inventoryslot_wrists' - ), - 3 => array( // uses nameINT from ChrRaces.dbc - ['race_human_male', 'race_dwarf_male', 'race_gnome_male', 'race_nightelf_male', 'race_draenei_male' ], - ['race_tauren_male', 'race_scourge_male', 'race_troll_male', 'race_orc_male', 'race_bloodelf_male' ], - ['race_human_female', 'race_dwarf_female', 'race_gnome_female', 'race_nightelf_female', 'race_draenei_female' ], - ['race_tauren_female', 'race_scourge_female', 'race_troll_female', 'race_orc_female', 'race_bloodelf_female'] - ), - 4 => array( // uses nameINT from ChrClasses.dbc - ['class_warrior', 'class_mage', 'class_rogue', 'class_druid' ], - ['class_hunter', 'class_shaman', 'class_priest', 'class_warlock'], - ['class_paladin', 'class_deathknight' ] - ), - 5 => array( - ['faction_alliance', 'faction_horde'] - ), - 6 => array( - [], - [null, 'quest_start', 'quest_end', 'quest_start_daily', 'quest_end_daily'] - ), - 10 => array( // really should have read holidays.dbc... - 'calendar_winterveilstart' => 'calendar_winterveilstart', - 'calendar_noblegardenstart' => 'calendar_noblegardenstart', - 'calendar_childrensweekstart' => 'calendar_childrensweekstart', - 'calendar_fishingextravaganza' => 'calendar_fishingextravaganzastart', - 'calendar_harvestfestivalstart' => 'calendar_harvestfestivalstart', - 'calendar_hallowsendstart' => 'calendar_hallowsendstart', - 'calendar_lunarfestivalstart' => 'calendar_lunarfestivalstart', - 'calendar_loveintheairstart' => 'calendar_loveintheairstart', - 'calendar_midsummerstart' => 'calendar_midsummerstart', - 'calendar_brewfeststart' => 'calendar_brewfeststart', - 'calendar_darkmoonfaireelwynnstart' => 'calendar_darkmoonfaireelwynnstart', - 'calendar_darkmoonfairemulgorestart' => 'calendar_darkmoonfairemulgorestart', - 'calendar_darkmoonfaireterokkarstart' => 'calendar_darkmoonfaireterokkarstart', - 'calendar_piratesday' => 'calendar_piratesdaystart', - 'calendar_wotlklaunch' => 'calendar_wotlklaunchstart', - 'calendar_dayofthedeadstart' => 'calendar_dayofthedeadstart', - 'calendar_fireworks' => 'calendar_fireworksstart' - ) - ); - - $writeImage = function($name, $ext, $src, $srcDims, $destDims, $done) - { - $ok = false; - $dest = imagecreatetruecolor($destDims['w'], $destDims['h']); - - imagesavealpha($dest, true); - if ($ext == '.png') - imagealphablending($dest, false); - - imagecopyresampled($dest, $src, $destDims['x'], $destDims['x'], $srcDims['x'], $srcDims['y'], $destDims['w'], $destDims['h'], $srcDims['w'], $srcDims['h']); - - switch ($ext) - { - case '.jpg': - $ok = imagejpeg($dest, $name.$ext, 85); - break; - case '.gif': - $ok = imagegif($dest, $name.$ext); - break; - case '.png': - $ok = imagepng($dest, $name.$ext); - break; - default: - CLI::write($done.' - unsupported file fromat: '.$ext, CLI::LOG_WARN); - } - - imagedestroy($dest); - - if ($ok) - { - chmod($name.$ext, Util::FILE_ACCESS); - CLI::write($done.' - image '.$name.$ext.' written', CLI::LOG_OK, true, true); - } - else - CLI::write($done.' - could not create image '.$name.$ext, CLI::LOG_ERROR); - - return $ok; - }; - - $checkSourceDirs = function($sub) use ($imgPath, &$paths) - { - $hasMissing = false; - foreach ($paths as $pathIdx => [$subDir, , , , , $realPath]) - { - if ($realPath) - continue; - - $p = sprintf($imgPath, $sub).$subDir; - if (CLISetup::fileExists($p)) - $paths[$pathIdx][5] = $p; - else - $hasMissing = true; - } - - return !$hasMissing; - }; - - if (CLISetup::getOpt('icons')) - array_push($groups, 0, 2, 3, 4, 5, 10, 12); - if (CLISetup::getOpt('glyphs')) - $groups[] = 1; - if (CLISetup::getOpt('pagetexts')) - array_push($groups, 7, 8, 9); - if (CLISetup::getOpt('loadingscreens')) - $groups[] = 11; - - // filter by passed options - if (!$groups) // by default do not generate loadingscreens - unset($paths[11]); - else - foreach (array_keys($paths) as $k) - if (!in_array($k, $groups)) - unset($paths[$k]); - - foreach (CLISetup::$expectedPaths as $xp => $locId) - { - if (!in_array($locId, CLISetup::$localeIds)) - continue; - - if ($xp) // if in subDir add trailing slash - $xp .= '/'; - - if ($checkSourceDirs($xp)) - break; - } - - $locList = []; - foreach (CLISetup::$expectedPaths as $xp => $locId) - if (in_array($locId, CLISetup::$localeIds)) - $locList[] = $xp; - - CLI::write('required resources overview:', CLI::LOG_INFO); - foreach ($paths as [$path, , , , , $realPath]) - { - if ($realPath) - CLI::write(CLI::green(' FOUND ').' - '.str_pad($path, 53).' @ '.$realPath); - else - CLI::write(CLI::red('MISSING').' - '.str_pad($path, 53).' @ '.sprintf($imgPath, '['.implode(',', $locList).']/').$path); - } - - CLI::write(); - - // if no subdir had sufficient data, diaf - if (count(array_filter(array_column($paths, 5))) != count($paths)) - { - CLI::write('one or more required directories are missing:', CLI::LOG_ERROR); - return false; - } - else - sleep(1); - - // init directories - foreach (array_column($paths, 1) as $subDirs) - foreach ($subDirs as $sd) - if (!CLISetup::writeDir($destDir.$sd[0])) - $success = false; - - // ok, departure from std::procedure here - // scan ItemDisplayInfo.dbc and SpellIcon.dbc for expected images and save them to an array - // load all icon paths into another array and xor these two - // excess entries for the directory are fine, excess entries for the dbc's are not - $dbcEntries = []; - - if (isset($paths[0]) || isset($paths[1])) // generates icons or glyphs - { - if (isset($paths[0]) && !isset($paths[1])) - $siRows = DB::Aowow()->selectCol('SELECT iconPath FROM dbc_spellicon WHERE iconPath NOT LIKE "%glyph-rune%"'); - else if (!isset($paths[0]) && isset($paths[1])) - $siRows = DB::Aowow()->selectCol('SELECT iconPath FROM dbc_spellicon WHERE iconPath LIKE "%glyph-rune%"'); - else - $siRows = DB::Aowow()->selectCol('SELECT iconPath FROM dbc_spellicon'); - - foreach ($siRows as $icon) - { - // Icons/ - if (isset($paths[0]) && stristr($icon, $paths[0][0])) - $dbcEntries[] = strtolower($paths[0][5].substr(strrchr($icon, '\\'), 1)); - // Spellbook/ - else if (isset($paths[1]) && stristr($icon, $paths[1][0])) - $dbcEntries[] = strtolower($paths[1][5].substr(strrchr($icon, '\\'), 1)); - } - } - - if (isset($paths[0])) - { - $itemIcons = DB::Aowow()->selectCol('SELECT inventoryIcon1 FROM dbc_itemdisplayinfo WHERE inventoryIcon1 <> ""'); - foreach ($itemIcons as $icon) - $dbcEntries[] = strtolower($paths[0][5].'/'.$icon); - - $eventIcons = DB::Aowow()->selectCol('SELECT textureString FROM dbc_holidays WHERE textureString <> ""'); - foreach ($eventIcons as $icon) - $dbcEntries[] = strtolower($paths[10][5].'/'.$icon.'start'); - } - - // case-insensitive array_unique *vomits silently into a corner* - $dbcEntries = array_intersect_key($dbcEntries, array_unique($dbcEntries)); - - $allPaths = []; - foreach ($paths as $i => [$inPath, $outInfo, $pattern, $isIcon, $tileSize, $path]) - { - $search = $path.$pattern; - if ($pattern) - $search = '/'.str_replace('/', '\\/', $search).'/i'; - - $files = CLISetup::filesInPath($search, !!$pattern); - $allPaths = array_merge($allPaths, array_map(function ($x) { return substr($x, 0, -4); }, $files)); - - if (!$files) - { - CLI::write('source directory "'.CLI::bold($search).'" does not contain files matching "'.CLI::bold($pattern), CLI::LOG_ERROR); - $success = false; - continue; - } - - CLI::write('processing '.count($files).' files in '.$path.'...'); - - $j = 0; - foreach ($files as $f) - { - ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time) - - $src = null; - $na = explode('/', $f); - $img = explode('.', array_pop($na)); - array_pop($img); // there are a hand full of images with multiple file endings or random dots in the name - $img = implode('.', $img); - - // file not from dbc -> name from array or skip file - if (!empty($cuNames[$i])) - { - if (!empty($cuNames[$i][strtolower($img)])) - $img = $cuNames[$i][strtolower($img)]; - else if (!$tileSize) - { - $j += count($outInfo); - CLI::write('skipping extraneous file '.$img.' (+'.count($outInfo).')'); - continue; - } - } - - $nFiles = count($outInfo) * ($tileSize ? array_sum(array_map('count', $cuNames[$i])) : count($files)); - - foreach ($outInfo as [$dest, $ext, $srcSize, $destSize, $borderOffset]) - { - if ($tileSize) - { - foreach ($cuNames[$i] as $y => $row) - { - foreach ($row as $x => $name) - { - $j++; - $img = $isIcon ? strtolower($name) : $name; - $done = ' - '.str_pad($j.'/'.$nFiles, 12).str_pad('('.number_format($j * 100 / $nFiles, 2).'%)', 9); - - if (!CLISetup::getOpt('force') && file_exists($destDir.$dest.$img.$ext)) - { - CLI::write($done.' - file '.$dest.$img.$ext.' was already processed', CLI::LOG_BLANK, true, true); - continue; - } - - if (!$src) - $src = $loadImageFile($f); - - if (!$src) // error should be created by imagecreatefromblp - continue; - - /* - ready for some major bullshitery? well, here it comes anyway! - the class-icon tile [idx: 4] isn't 64x64 but 63x64 .. the right side border is 1px short - so if we don't watch out, the icons start to shift over and show the border - also the icon border is displaced by 1px - */ - $from = array( - 'x' => $borderOffset + 1 + ($tileSize - ($i == 4 ? 1 : 0)) * $x, - 'y' => $borderOffset + 1 + $tileSize * $y, - 'w' => ($tileSize - ($i == 4 ? 1 : 0)) - $borderOffset * 2, - 'h' => $tileSize - $borderOffset * 2 - ); - $to = array( - 'x' => 0, - 'y' => 0, - 'w' => $destSize, - 'h' => $destSize - ); - - if (!$writeImage($destDir.$dest.$img, $ext, $src, $from, $to, $done)) - $success = false; - } - } - - // custom handle for combined icon 'quest_startend' - /* not used due to alphaChannel issues - if ($tileSize == 32) - { - $dest = imagecreatetruecolor(19, 16); - imagesavealpha($dest, true); - imagealphablending($dest, true); - - // excalmationmark, questionmark - imagecopyresampled($dest, $src, 0, 1, 32 + 5, 32 + 2, 8, 15, 18, 30); - imagecopyresampled($dest, $src, 5, 0, 64 + 1, 32 + 1, 10, 16, 18, 28); - - if (imagegif($dest, $destDir.$dest.'quest_startend.gif')) - CLI::write(' extra - image '.$destDir.$dest.'quest_startend.gif written', CLI::LOG_OK); - else - { - CLI::write(' extra - could not create image '.$destDir.$dest.'quest_startend.gif', CLI::LOG_ERROR); - $success = false; - } - - imagedestroy($dest); - } - */ - } - else - { - // icon -> lowercase - if ($isIcon) - $img = strtolower($img); - - $j++; - $done = ' - '.str_pad($j.'/'.$nFiles, 12).str_pad('('.number_format($j * 100 / $nFiles, 2).'%)', 9); - - if (!CLISetup::getOpt('force') && file_exists($destDir.$dest.$img.$ext)) - { - CLI::write($done.' - file '.$dest.$img.$ext.' was already processed', CLI::LOG_BLANK, true, true); - continue; - } - - if (!$src) - $src = $loadImageFile($f); - - if (!$src) // error should be created by imagecreatefromblp - continue; - - $from = array( - 'x' => $borderOffset, - 'y' => $borderOffset, - 'w' => ($srcSize ?: imagesx($src)) - $borderOffset * 2, - 'h' => ($srcSize ?: imagesy($src)) - $borderOffset * 2 - ); - $to = array( - 'x' => 0, - 'y' => 0, - 'w' => $destSize ?: imagesx($src), - 'h' => $destSize ?: imagesy($src) - ); - - if (!$writeImage($destDir.$dest.$img, $ext, $src, $from, $to, $done)) - $success = false; - } - } - - unset($src); - } - } - - // reset execTime - ini_set('max_execution_time', FileGen::$defaultExecTime); - - if ($missing = array_diff(array_map('strtolower', $dbcEntries), array_map('strtolower', $allPaths))) - { - // hide affected icons from listviews - $iconNames = array_map(function($path) { - preg_match('/\/([^\/]+)\.blp$/i', $path, $m); - return $m ? $m[1] : null; - }, $missing); - - DB::Aowow()->query('UPDATE ?_icons SET cuFlags = cuFlags | ?d WHERE name IN (?a)', CUSTOM_EXCLUDE_FOR_LISTVIEW, $iconNames); - - asort($missing); - CLI::write('the following '.count($missing).' images where referenced by DBC but not in the mpqData directory. They may need to be converted by hand later on.', CLI::LOG_WARN); - foreach ($missing as $m) - CLI::write(' - '.$m); - } - - return $success; - } diff --git a/setup/tools/filegen/simpleimg.ss.php b/setup/tools/filegen/simpleimg.ss.php new file mode 100644 index 000000000..5dafd155e --- /dev/null +++ b/setup/tools/filegen/simpleimg.ss.php @@ -0,0 +1,376 @@ + [[ ], CLISetup::ARGV_PARAM, 'Converts and resizes BLP2 images smaller than 255x255 into required formats (mostly icons)'], + 'icons' => [['1'], CLISetup::ARGV_OPTIONAL, 'Generate icons for spells, items, classes, races, ect.'], + 'glyphs' => [['2'], CLISetup::ARGV_OPTIONAL, 'Generate decorative glyph symbols displayed on related item and spell pages.'], + 'pagetexts' => [['3'], CLISetup::ARGV_OPTIONAL, 'Generate images contained in text on readable items and gameobjects.'], + 'loadingscreens' => [['4'], CLISetup::ARGV_OPTIONAL, 'Generate loading screen images (not used on page; skipped by default)'] + ); + + protected $dbcSourceFiles = ['holidays', 'spellicon', 'itemdisplayinfo']; + protected $setupAfter = [['icons'], []]; + + private const ICON_DIRS = array( + ['static/images/wow/icons/large/', 'jpg', 0, 56, 4], + ['static/images/wow/icons/medium/', 'jpg', 0, 36, 4], + ['static/images/wow/icons/small/', 'jpg', 0, 18, 4], + ['static/images/wow/icons/tiny/', 'gif', 0, 15, 4] + ); + + private $genSteps = array( + // srcPath, realPath, localized, [pattern, isIcon, tileSize, localized], [[dest, ext, srcSize, destSize, borderOffset]] + 0 => ['Icons/', null, false, ['/.*\.(blp|png)$', true, 0], self::ICON_DIRS, ], + 1 => ['Spellbook/', null, false, ['/UI-Glyph-Rune-?\d+.(blp|png)$', false, 0], [['static/images/wow/Interface/Spellbook/', 'png', 0, 0, 0]]], + 2 => ['PaperDoll/', null, false, ['/UI-(Backpack|PaperDoll)-.*\.(blp|png)$', true, 0], self::ICON_DIRS, ], + 3 => ['GLUES/CHARACTERCREATE/', null, false, ['/UI-CharacterCreate-Races.(blp|png)', true, 64], self::ICON_DIRS, ], + 4 => ['GLUES/CHARACTERCREATE/', null, false, ['/UI-CharacterCreate-CLASSES.(blp|png)', true, 64], self::ICON_DIRS, ], + 5 => ['GLUES/CHARACTERCREATE/', null, false, ['/UI-CharacterCreate-Factions.(blp|png)', true, 64], self::ICON_DIRS, ], + // 6 => ['Minimap/' , null, false, ['/OBJECTICONS.(BLP|png)', true, 32], [['static/images/wow/icons/tiny/', 'gif', 0, 16, 2]]], + 7 => ['FlavorImages/', null, false, ['/.*\.(blp|png)$', false, 0], [['static/images/wow/Interface/FlavorImages/', 'png', 0, 0, 0]]], + 8 => ['Pictures/', null, false, ['/.*\.(blp|png)$', false, 0], [['static/images/wow/Interface/Pictures/', 'png', 0, 0, 0]]], + 9 => ['PvPRankBadges/', null, false, ['/.*\.(blp|png)$', false, 0], [['static/images/wow/Interface/PvPRankBadges/', 'png', 0, 0, 0]]], + 10 => ['Calendar/Holidays/', null, false, ['/.*(start|[ayhs])\.(blp|png)$', true, 0], self::ICON_DIRS, ], + 11 => ['GLUES/LOADINGSCREENS/', null, false, ['/lo.*\.(blp|png)$', false, 0], [['cache/loadingscreens/', 'png', 0, 0, 0]]], + 12 => ['PVPFrame/', null, false, ['/PVP-(ArenaPoints|Currency).*\.(blp|png)$', true, 0], self::ICON_DIRS, ] + ); + + // textures are composed of 64x64 icons + // numeric indexed arrays mimick the position on the texture + private $cuNames = array( + 2 => array( + 'ui-paperdoll-slot-chest' => 'inventoryslot_chest', + 'ui-backpack-emptyslot' => 'inventoryslot_empty', + 'ui-paperdoll-slot-feet' => 'inventoryslot_feet', + 'ui-paperdoll-slot-finger' => 'inventoryslot_finger', + 'ui-paperdoll-slot-hands' => 'inventoryslot_hands', + 'ui-paperdoll-slot-head' => 'inventoryslot_head', + 'ui-paperdoll-slot-legs' => 'inventoryslot_legs', + 'ui-paperdoll-slot-mainhand' => 'inventoryslot_mainhand', + 'ui-paperdoll-slot-neck' => 'inventoryslot_neck', + 'ui-paperdoll-slot-secondaryhand' => 'inventoryslot_offhand', + 'ui-paperdoll-slot-ranged' => 'inventoryslot_ranged', + 'ui-paperdoll-slot-relic' => 'inventoryslot_relic', + 'ui-paperdoll-slot-shirt' => 'inventoryslot_shirt', + 'ui-paperdoll-slot-shoulder' => 'inventoryslot_shoulder', + 'ui-paperdoll-slot-tabard' => 'inventoryslot_tabard', + 'ui-paperdoll-slot-trinket' => 'inventoryslot_trinket', + 'ui-paperdoll-slot-waist' => 'inventoryslot_waist', + 'ui-paperdoll-slot-wrists' => 'inventoryslot_wrists' + ), + 3 => array( // uses nameINT from ChrRaces.dbc + ['race_human_male', 'race_dwarf_male', 'race_gnome_male', 'race_nightelf_male', 'race_draenei_male' ], + ['race_tauren_male', 'race_scourge_male', 'race_troll_male', 'race_orc_male', 'race_bloodelf_male' ], + ['race_human_female', 'race_dwarf_female', 'race_gnome_female', 'race_nightelf_female', 'race_draenei_female' ], + ['race_tauren_female', 'race_scourge_female', 'race_troll_female', 'race_orc_female', 'race_bloodelf_female'] + ), + 4 => array( // uses nameINT from ChrClasses.dbc + ['class_warrior', 'class_mage', 'class_rogue', 'class_druid' ], + ['class_hunter', 'class_shaman', 'class_priest', 'class_warlock'], + ['class_paladin', 'class_deathknight' ] + ), + 5 => array( + ['faction_alliance', 'faction_horde'] + ), + 6 => array( + [], + [null, 'quest_start', 'quest_end', 'quest_start_daily', 'quest_end_daily'] + ), + 10 => array( // really should have read holidays.dbc... + 'calendar_winterveilstart' => 'calendar_winterveilstart', + 'calendar_noblegardenstart' => 'calendar_noblegardenstart', + 'calendar_childrensweekstart' => 'calendar_childrensweekstart', + 'calendar_fishingextravaganza' => 'calendar_fishingextravaganzastart', + 'calendar_harvestfestivalstart' => 'calendar_harvestfestivalstart', + 'calendar_hallowsendstart' => 'calendar_hallowsendstart', + 'calendar_lunarfestivalstart' => 'calendar_lunarfestivalstart', + 'calendar_loveintheairstart' => 'calendar_loveintheairstart', + 'calendar_midsummerstart' => 'calendar_midsummerstart', + 'calendar_brewfeststart' => 'calendar_brewfeststart', + 'calendar_darkmoonfaireelwynnstart' => 'calendar_darkmoonfaireelwynnstart', + 'calendar_darkmoonfairemulgorestart' => 'calendar_darkmoonfairemulgorestart', + 'calendar_darkmoonfaireterokkarstart' => 'calendar_darkmoonfaireterokkarstart', + 'calendar_piratesday' => 'calendar_piratesdaystart', + 'calendar_wotlklaunch' => 'calendar_wotlklaunchstart', + 'calendar_dayofthedeadstart' => 'calendar_dayofthedeadstart', + 'calendar_fireworks' => 'calendar_fireworksstart' + ) + ); + + public function __construct() + { + $this->imgPath = CLISetup::$srcDir.$this->imgPath; + $this->maxExecTime = ini_get('max_execution_time'); + + // init directories to be checked when registered + foreach (array_column($this->genSteps, self::$GEN_IDX_DEST_INFO) as $subDirs) + foreach ($subDirs as $sd) + $this->requiredDirs[] = $sd[0]; + + // fix genSteps 2 [icons] - no tiny inventory backgrounds + $this->genSteps[2][self::$GEN_IDX_DEST_INFO] = array_slice($this->genSteps[2][self::$GEN_IDX_DEST_INFO], 0, 3); + + // fix genSteps 12 [pvp money icons] - smaller border offset for pvp currency icons + array_walk($this->genSteps[12][self::$GEN_IDX_DEST_INFO], function(&$x) { $x[4] = 2; }); + + // fix genSteps 10 [holoday icons] - img src size is 90px + array_walk($this->genSteps[10][self::$GEN_IDX_DEST_INFO], function(&$x) { $x[2] = 90; }); + } + + public function generate() : bool + { + // find out what to generate + $groups = []; + if (CLISetup::getOpt('icons')) + array_push($groups, 0, 2, 3, 4, 5, 10, 12); + if (CLISetup::getOpt('glyphs')) + $groups[] = 1; + if (CLISetup::getOpt('pagetexts')) + array_push($groups, 7, 8, 9); + if (CLISetup::getOpt('loadingscreens')) + $groups[] = 11; + + if (!$groups) // by default do not generate loadingscreens + $groups = [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 12]; + + // removed unused generators + foreach ($this->genSteps as $idx => $_) + if (!in_array($idx, $groups)) + unset($this->genSteps[$idx]); + + if (!$this->checkSourceDirs()) + { + CLI::write('[simpleimg] one or more required directories are missing:', CLI::LOG_ERROR); + return false; + } + + sleep(2); + + $allPaths = []; + foreach ($this->genSteps as $i => [, $path, , [$pattern, $isIcon, $tileSize], $outInfo]) + { + $search = $path.$pattern; + if ($pattern) + $search = '/'.str_replace('/', '\\/', $search).'/i'; + + $files = CLISetup::filesInPath($search, !!$pattern); + $allPaths = array_merge($allPaths, array_map(function ($x) { return substr($x, 0, -4); }, $files)); + + if (!$files) + { + CLI::write('[simpleimg] source directory "'.CLI::bold($search).'" does not contain files matching "'.CLI::bold($pattern), CLI::LOG_ERROR); + $this->success = false; + continue; + } + + CLI::write('[simpleimg] processing '.count($files).' files in '.$path.'...'); + + $j = 0; + foreach ($files as $f) + { + ini_set('max_execution_time', $this->maxExecTime); + + $src = null; + $na = explode('/', $f); + $img = explode('.', array_pop($na)); + array_pop($img); // there are a hand full of images with multiple file endings or random dots in the name + $img = implode('.', $img); + + if (!empty($this->cuNames[$i])) // file not from dbc -> name from array or skip file + { + if (!empty($this->cuNames[$i][strtolower($img)])) + $img = $this->cuNames[$i][strtolower($img)]; + else if (!$tileSize) + { + $j += count($outInfo); + CLI::write('[simpleimg] skipping extraneous file '.$img.' (+'.count($outInfo).')'); + continue; + } + } + + $nFiles = count($outInfo) * ($tileSize ? array_sum(array_map('count', $this->cuNames[$i])) : count($files)); + + foreach ($outInfo as [$dest, $ext, $srcSize, $destSize, $borderOffset]) + { + if ($tileSize) + { + foreach ($this->cuNames[$i] as $y => $row) + { + foreach ($row as $x => $name) + { + $j++; + $outFile = $dest.($isIcon ? strtolower($name) : $name).'.'.$ext; + + $this->status = ' - '.str_pad($j.'/'.$nFiles, 12).str_pad('('.number_format($j * 100 / $nFiles, 2).'%)', 9); + + if (!CLISetup::getOpt('force') && file_exists($outFile)) + { + CLI::write('[simpleimg] '.$this->status.' - file '.$outFile.' was already processed', CLI::LOG_BLANK, true, true); + continue; + } + + if (!$src) + $src = $this->loadImageFile($f); + + if (!$src) // error should be created by imagecreatefromblp + { + $this->success = false; + continue; + } + + /* + ready for some major bullshitery? well, here it comes anyway! + the class-icon tile [idx: 4] isn't 64x64 but 63x64 .. the right side border is 1px short + so if we don't watch out, the icons start to shift over and show the border + also the icon border is displaced by 1px + */ + $from = array( + 'x' => $borderOffset + 1 + ($tileSize - ($i == 4 ? 1 : 0)) * $x, + 'y' => $borderOffset + 1 + $tileSize * $y, + 'w' => ($tileSize - ($i == 4 ? 1 : 0)) - $borderOffset * 2, + 'h' => $tileSize - $borderOffset * 2 + ); + $to = array( + 'x' => 0, + 'y' => 0, + 'w' => $destSize, + 'h' => $destSize + ); + + if (!$this->writeImageFile($src, $outFile, $from, $to)) + $this->success = false; + } + } + + // custom handle for combined icon 'quest_startend' + /* not used due to alphaChannel issues + if ($tileSize == 32) + { + $dest = imagecreatetruecolor(19, 16); + imagesavealpha($dest, true); + imagealphablending($dest, true); + + // excalmationmark, questionmark + imagecopyresampled($dest, $src, 0, 1, 32 + 5, 32 + 2, 8, 15, 18, 30); + imagecopyresampled($dest, $src, 5, 0, 64 + 1, 32 + 1, 10, 16, 18, 28); + + if (imagegif($dest, $dest.'quest_startend.gif')) + CLI::write(' extra - image '.$dest.'quest_startend.gif written', CLI::LOG_OK); + else + { + CLI::write(' extra - could not create image '.$dest.'quest_startend.gif', CLI::LOG_ERROR); + $this->success = false; + } + + imagedestroy($dest); + } + */ + } + else + { + $j++; + $this->status = ' - '.str_pad($j.'/'.$nFiles, 12).str_pad('('.number_format($j * 100 / $nFiles, 2).'%)', 9); + $outFile = $dest.($isIcon ? strtolower($img) : $img).'.'.$ext; + + if (!CLISetup::getOpt('force') && file_exists($outFile)) + { + CLI::write('[simpleimg] '.$this->status.' - file '.$outFile.' was already processed', CLI::LOG_BLANK, true, true); + continue; + } + + $src = $this->loadImageFile($f); + if (!$src) // error should be created by imagecreatefromblp + { + $this->success = false; + continue; + } + + $from = array( + 'x' => $borderOffset, + 'y' => $borderOffset, + 'w' => ($srcSize ?: imagesx($src)) - $borderOffset * 2, + 'h' => ($srcSize ?: imagesy($src)) - $borderOffset * 2 + ); + $to = array( + 'x' => 0, + 'y' => 0, + 'w' => $destSize ?: imagesx($src), + 'h' => $destSize ?: imagesy($src) + ); + + if (!$this->writeImageFile($src, $outFile, $from, $to)) + $this->success = false; + } + } + + unset($src); + } + } + + // scan ItemDisplayInfo.dbc and SpellIcon.dbc for expected images and save them to an array + // load all icon paths into another array and xor these two + // excess entries for the directory are fine, excess entries for the dbc's are not + $dbcEntries = []; + $gens = array_keys($this->genSteps); + + if (in_array(0, $gens)) // generates icons + { + if ($siRows = DB::Aowow()->selectCol('SELECT `iconPath` FROM dbc_spellicon WHERE `iconPath` NOT LIKE "%glyph-rune%"')) + foreach ($siRows as $icon) + if (stristr($icon, $this->genSteps[0][self::$GEN_IDX_SRC_PATH])) // Icons/ + $dbcEntries[] = strtolower($this->genSteps[0][self::$GEN_IDX_SRC_REAL].substr(strrchr($icon, '\\'), 1)); + + if ($itemIcons = DB::Aowow()->selectCol('SELECT `inventoryIcon1` FROM dbc_itemdisplayinfo WHERE `inventoryIcon1` <> ""')) + foreach ($itemIcons as $icon) + $dbcEntries[] = strtolower($this->genSteps[0][self::$GEN_IDX_SRC_REAL].'/'.$icon); + } + + if (in_array(1, $gens)) // generates glyphs + if ($siRows = DB::Aowow()->selectCol('SELECT `iconPath` FROM dbc_spellicon WHERE `iconPath` LIKE "%glyph-rune%"')) + foreach ($siRows as $icon) + if (stristr($icon, $this->genSteps[1][self::$GEN_IDX_SRC_PATH])) // Spellbook/ + $dbcEntries[] = strtolower($this->genSteps[1][self::$GEN_IDX_SRC_REAL].substr(strrchr($icon, '\\'), 1)); + + if (in_array(10, $gens)) // generates holiday icons + if ($eventIcons = DB::Aowow()->selectCol('SELECT `textureString` FROM dbc_holidays WHERE `textureString` <> ""')) + foreach ($eventIcons as $icon) + $dbcEntries[] = strtolower($this->genSteps[10][self::$GEN_IDX_SRC_REAL].'/'.$icon.'start'); + + // case-insensitive array_unique *vomits silently into a corner* + $dbcEntries = array_intersect_key($dbcEntries, array_unique($dbcEntries)); + + if ($missing = array_diff(array_map('strtolower', $dbcEntries), array_map('strtolower', $allPaths))) + { + // hide affected icons from listviews + $iconNames = array_map(function($path) { + preg_match('/\/([^\/]+)\.blp$/i', $path, $m); + return $m ? $m[1] : null; + }, $missing); + + DB::Aowow()->query('UPDATE ?_icons SET `cuFlags` = `cuFlags` | ?d WHERE `name` IN (?a)', CUSTOM_EXCLUDE_FOR_LISTVIEW, $iconNames); + + CLI::write('[simpleimg] the following '.count($missing).' images where referenced by DBC but not in the mpqData directory. They may need to be converted by hand later on.', CLI::LOG_WARN); + foreach ($missing as $m) + CLI::write(' - '.$m); + } + + return $this->success; + } +}); + +?> diff --git a/setup/tools/filegen/soundfiles.func.php b/setup/tools/filegen/soundfiles.ss.php similarity index 57% rename from setup/tools/filegen/soundfiles.func.php rename to setup/tools/filegen/soundfiles.ss.php index 28fa32065..9716e24db 100644 --- a/setup/tools/filegen/soundfiles.func.php +++ b/setup/tools/filegen/soundfiles.ss.php @@ -6,28 +6,31 @@ if (!CLI) die('not in cli mode'); - function soundfiles() - { - $ok = true; +CLISetup::registerSetup("build", new class extends SetupScript +{ + protected $info = array( + 'soundfiles' => [[], CLISetup::ARGV_PARAM, 'Links converted sound files to database and moves them to destination.'] + ); + + protected $requiredDirs = ['static/wowsounds/']; + protected $setupAfter = [['sounds'], []]; + + public function generate() : bool + { // ALL files $files = DB::Aowow()->selectCol('SELECT ABS(id) AS ARRAY_KEY, CONCAT(path, "/", `file`) FROM ?_sounds_files'); $nFiles = count($files); $qtLen = strlen($nFiles); $sum = 0; - - $intv = 0.5; - $time = microtime(true); - $sum = 0; + $time = new Timer(500); foreach ($files as $fileId => $filePath) { $sum++; - $newTime = microtime(true); - if ($newTime > $time + $intv) + if ($time->update()) { - CLI::write(sprintf(' * %'.$qtLen.'d / %d (%4.1f%%)', $sum, $nFiles, round(100 * $sum / $nFiles, 1)), CLI::LOG_BLANK, true, true); - $time = $newTime; + CLI::write(sprintf('[soundfiles] * %'.$qtLen.'d / %d (%4.1f%%)', $sum, $nFiles, round(100 * $sum / $nFiles, 1)), CLI::LOG_BLANK, true, true); DB::Aowow()->selectCell('SELECT 1'); // keep mysql busy or it may go away } @@ -45,9 +48,9 @@ function soundfiles() // copy over to static/wowsounds/ if (!copy($p, 'static/wowsounds/'.$fileId)) { - $ok = false; - CLI::write(' - could not copy '.CLI::bold($p).' into '.CLI::bold('static/wowsounds/'.$fileId), CLI::LOG_ERROR); - $time = 0; + $this->success = false; + CLI::write('[soundfiles] - could not copy '.CLI::bold($p).' into '.CLI::bold('static/wowsounds/'.$fileId), CLI::LOG_ERROR); + $time->reset(); break 2; } @@ -55,14 +58,14 @@ function soundfiles() } } - CLI::write(' - did not find file: '.CLI::bold(CLI::nicePath($filePath, CLISetup::$srcDir, '['.implode(',', CLISetup::$locales).']')), CLI::LOG_WARN); - $time = 0; + CLI::write('[soundfiles] - did not find file: '.CLI::bold(CLI::nicePath($filePath, CLISetup::$srcDir, '['.implode(',', CLISetup::$locales).']')), CLI::LOG_WARN); + $time->reset(); // flag as unusable in DB DB::Aowow()->query('UPDATE ?_sounds_files SET id = ?d WHERE ABS(id) = ?d', -$fileId, $fileId); } - return $ok; + return $this->success; } +}); ?> - diff --git a/setup/tools/filegen/statistics.func.php b/setup/tools/filegen/statistics.func.php deleted file mode 100644 index 5725af2db..000000000 --- a/setup/tools/filegen/statistics.func.php +++ /dev/null @@ -1,201 +0,0 @@ - [[-20, 2, 0, 3], [-10, 0, 1, 1], null, 0.9560, 3.6640, 88.129021, 5, 47.003525, 5, [], []], - 2 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.4943, 88.129021, 5, 47.003525, 5, [], []], - 3 => [[-20, 1, 1, 2], [-10, 0, 1, 2], null, 0.9880, -4.0873, 145.560408, 5, 145.560408, 0, [], []], - 4 => [[-20, 1, 1, 2], [-10, 0, 1, 1], null, 0.9880, 2.0957, 145.560408, 5, 145.560408, 0, [], []], - 5 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.4178, 150.375940, 0, 0.0, 0, [], []], - 6 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.6640, 88.129021, 5, 47.003525, 0, [], ['parryrtng' => [0.25, 'percentOf', 'str']]], // Forcefull Deflection (49410) - 7 => [[-20, 1, 1, 2], [-10, 0, 1, 0], null, 0.9880, 2.1080, 145.560408, 0, 145.560408, 5, [], []], - 8 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.6587, 150.375940, 0, 0.0, 0, [], []], - 9 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 2.4211, 150.375940, 0, 0.0, 0, [], []], - 11 => [[-20, 2, 0, 0], [-10, 0, 1, 0], null, 0.9720, 5.6097, 116.890707, 0, 0.0, 0, [], []] - ); - - foreach ($dataz as $class => &$data) - $data[2] = array_values(DB::Aowow()->selectRow('SELECT mle.chance*100 cMle, spl.chance*100 cSpl FROM dbc_gtchancetomeleecritbase mle, dbc_gtchancetospellcritbase spl WHERE mle.idx = spl.idx AND mle.idx = ?d', $class - 1)); - - return $dataz; - }; - - $race = function() - { - // { str, agi, sta, int, spi, raceMod1, raceMod2 } - $raceData = DB::World()->select('SELECT `race` AS ARRAY_KEY, MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 GROUP BY `race` ORDER BY `race` ASC'); - foreach ($raceData as &$rd) - $rd = array_values($rd + [[], []]); - - $racials = new SpellList(array(['typeCat', -4], ['reqClassMask', 0])); - $allMods = $racials->getProfilerMods(); - foreach ($allMods as $spellId => $mods) - { - if (!$mods) - continue; - - // if there is ever a case where a racial is shared between races i don't want to know about it! - $raceId = log($racials->getEntry($spellId)['reqRaceMask'], 2) + 1; - if (!isset($raceData[$raceId])) - continue; - - foreach ($mods as $jsonStat => $mod) - { - if (empty($raceData[$raceId][5][$jsonStat])) - $raceData[$raceId][5][$jsonStat] = $mod; - else - $raceData[$raceId][6][$jsonStat] = $mod; - } - } - - return $raceData; - }; - - $combo = function() - { - $result = []; - $critToDodge = array( - 1 => 0.85/1.15, 2 => 1.00/1.15, 3 => 1.11/1.15, - 4 => 2.00/1.15, 5 => 1.00/1.15, 6 => 0.85/1.15, - 7 => 1.60/1.15, 8 => 1.00/1.15, 9 => 0.97/1.15, 11 => 2.00/1.15 - ); - - // TrinityCore claims, DodgePerAgi per level and class can be constituted from critPerAgi (and level (and class)) - // who am i to argue - // rebase stats to a specific race. chosen human as all stats are 20 and tauren for hunter, shaman and druid - // the stat gain per level is only dependant on the class. The race only determines the initial stats at level 0 - // level:{ str, agi, sta, int, spi, hp, mana, mleCrt%Agi, splCrt%Int, dodge%Agi, HealthRegenModToBaseStat, HealthRegenModToBonusStat } - - foreach ($critToDodge as $class => $mod) - { - // humans can't be hunter, shaman, druids (use tauren here) - if (in_array($class, [3, 7, 11])) - $offset = array_values(DB::World()->selectRow('SELECT MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 AND `race` = 6')); - else - $offset = array_values(DB::World()->selectRow('SELECT MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 AND `race` = 1')); - - $gtData = DB::Aowow()->select(' - SELECT mlecrt.idx - ?d AS ARRAY_KEY, mlecrt.chance * 100, splcrt.chance * 100, mlecrt.chance * 100 * ?f, baseHP5.ratio * 1, extraHP5.ratio * 1 - FROM dbc_gtchancetomeleecrit mlecrt - JOIN dbc_gtchancetospellcrit splcrt ON splcrt.idx = mlecrt.idx - JOIN dbc_gtoctregenhp baseHP5 ON baseHP5.idx = mlecrt.idx - JOIN dbc_gtregenhpperspt extraHP5 ON extraHP5.idx = mlecrt.idx - WHERE mlecrt.idx BETWEEN ?d AND ?d', - (($class - 1) * 100) - 1, // class-offset - $mod, - (($class - 1) * 100) + 0, // lvl 1 - (($class - 1) * 100) + 79 // lvl 80 - ); - - $rows = DB::World()->select(' - SELECT - pls.level AS ARRAY_KEY, - pls.str - ?d, pls.agi - ?d, pls.sta - ?d, pls.inte - ?d, pls.spi - ?d, - pcls.basehp, IF(pcls.basemana <> 0, pcls.basemana, 100) - FROM - player_levelstats pls - JOIN - player_classlevelstats pcls ON pls.level = pcls.level AND pls.class = pcls.class - WHERE - pls.race = ?d AND pls.class = ?d ORDER BY pls.level ASC', - $offset[0], $offset[1], $offset[2], $offset[3], $offset[4], - in_array($class, [3, 7, 11]) ? 6 : 1, - $class - ); - - $result[$class] = []; - foreach ($rows as $lvl => $row) - $result[$class][$lvl] = array_values(array_merge($row, $gtData[$lvl])); - } - - return $result; - }; - - $level = function() - { - // base mana regeneration per level - // identical across classes (just use one, that acutally has mana (offset: 100)) - // content of gtRegenMPPerSpt.dbc - - return DB::Aowow()->selectCol('SELECT idx-99 AS ARRAY_KEY, ratio FROM dbc_gtregenmpperspt WHERE idx >= 100 AND idx < 100 + ?d', MAX_LEVEL); - }; - - $skills = function() - { - // profession perks ... too lazy to formulate a search algorithm for two occurences - return array( - SKILL_MINING => array( // mining / toughness - 75 => ['sta' => 3], - 150 => ['sta' => 5], - 225 => ['sta' => 7], - 300 => ['sta' => 10], - 375 => ['sta' => 30], - 450 => ['sta' => 60], - ), - SKILL_SKINNING => array( // skinning / master of anatomy - 75 => ['critstrkrtng' => 3], - 150 => ['critstrkrtng' => 6], - 225 => ['critstrkrtng' => 9], - 300 => ['critstrkrtng' => 12], - 375 => ['critstrkrtng' => 20], - 450 => ['critstrkrtng' => 40], - ) - ); - }; - - $sub = ['classs', 'race', 'combo', 'level', 'skills']; - $out = []; - $success = true; - - foreach ($sub as $s) - { - $res = $$s(); - $out[$s] = $res; - if (!$res) - CLI::write('statistics - generator $'.$s.'() returned empty', CLI::LOG_WARN); - } - - $toFile = 'g_statistics = '.preg_replace('/"\$([^$"]+)"/', '\1', Util::toJSON($out)).';'; - - if (!CLISetup::writeFile('datasets/statistics', $toFile)) - $success = false; - - return $success; - } - -?> diff --git a/setup/tools/filegen/statistics.ss.php b/setup/tools/filegen/statistics.ss.php new file mode 100644 index 000000000..ece83887f --- /dev/null +++ b/setup/tools/filegen/statistics.ss.php @@ -0,0 +1,209 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles player stats into file for the character profiler tool.'] + ); + + protected $worldDependency = ['player_levelstats', 'player_classlevelstats']; + protected $dbcSourceFiles = ['gtchancetomeleecrit', 'gtchancetomeleecritbase', 'gtchancetospellcrit', 'gtchancetospellcritbase', 'gtoctregenhp', 'gtregenmpperspt', 'gtregenhpperspt']; + protected $requiredDirs = ['datasets/']; + + public function generate() : bool + { + $sub = ['classs', 'race', 'combo', 'level', 'skills']; + $out = []; + + foreach ($sub as $s) + { + if ($out[$s] = $this->$s()) + continue; + + CLI::write('[statistics] generator '.$s.'() returned empty', CLI::LOG_WARN); + $this->success = false; + } + + $toFile = 'g_statistics = '.preg_replace('/"\$([^$"]+)"/', '\1', Util::toJSON($out)).';'; + + if (!CLISetup::writeFile('datasets/statistics', $toFile)) + $this->success = false; + + return $this->success; + } + + // constants and mods taken from TrinityCore (Player.cpp, StatSystem.cpp) + private function classs() : array + { + /* content per Index + mleatkpwr[base, strMultiplier, agiMultiplier, levelMultiplier] + rngatkpwr[base, strMultiplier, agiMultiplier, levelMultiplier] + baseCritPct[phys, spell] + diminishingConstant + baseDodgePct + DodgeCap + baseParryPct + ParryCap + baseBlockPct + classMod1 applies mod directly only one class having something worth mentioning: DK + classMod2 applies mod directly so what were they originally used for..? + */ + + $dataz = array( + 1 => [[-20, 2, 0, 3], [-10, 0, 1, 1], null, 0.9560, 3.6640, 88.129021, 5, 47.003525, 5, [], []], + 2 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.4943, 88.129021, 5, 47.003525, 5, [], []], + 3 => [[-20, 1, 1, 2], [-10, 0, 1, 2], null, 0.9880, -4.0873, 145.560408, 5, 145.560408, 0, [], []], + 4 => [[-20, 1, 1, 2], [-10, 0, 1, 1], null, 0.9880, 2.0957, 145.560408, 5, 145.560408, 0, [], []], + 5 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.4178, 150.375940, 0, 0.0, 0, [], []], + 6 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.6640, 88.129021, 5, 47.003525, 0, [], ['parryrtng' => [0.25, 'percentOf', 'str']]], // Forcefull Deflection (49410) + 7 => [[-20, 1, 1, 2], [-10, 0, 1, 0], null, 0.9880, 2.1080, 145.560408, 0, 145.560408, 5, [], []], + 8 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.6587, 150.375940, 0, 0.0, 0, [], []], + 9 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 2.4211, 150.375940, 0, 0.0, 0, [], []], + 11 => [[-20, 2, 0, 0], [-10, 0, 1, 0], null, 0.9720, 5.6097, 116.890707, 0, 0.0, 0, [], []] + ); + + foreach ($dataz as $class => &$data) + $data[2] = array_values(DB::Aowow()->selectRow('SELECT mle.chance*100 cMle, spl.chance*100 cSpl FROM dbc_gtchancetomeleecritbase mle, dbc_gtchancetospellcritbase spl WHERE mle.idx = spl.idx AND mle.idx = ?d', $class - 1)); + + return $dataz; + } + + // { str, agi, sta, int, spi, raceMod1, raceMod2 } + private function race() : array + { + $raceData = DB::World()->select('SELECT `race` AS ARRAY_KEY, MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 GROUP BY `race` ORDER BY `race` ASC'); + foreach ($raceData as &$rd) + $rd = array_values($rd + [[], []]); + + $racials = new SpellList(array(['typeCat', -4], ['reqClassMask', 0])); + $allMods = $racials->getProfilerMods(); + foreach ($allMods as $spellId => $mods) + { + if (!$mods) + continue; + + // if there is ever a case where a racial is shared between races i don't want to know about it! + $raceId = log($racials->getEntry($spellId)['reqRaceMask'], 2) + 1; + if (!isset($raceData[$raceId])) + continue; + + foreach ($mods as $jsonStat => $mod) + { + if (empty($raceData[$raceId][5][$jsonStat])) + $raceData[$raceId][5][$jsonStat] = $mod; + else + $raceData[$raceId][6][$jsonStat] = $mod; + } + } + + return $raceData; + } + + // TrinityCore claims, DodgePerAgi per level and class can be constituted from critPerAgi (and level (and class)) + // who am i to argue + // rebase stats to a specific race. chosen human as all stats are 20 and tauren for hunter, shaman and druid + // level:{ str, agi, sta, int, spi, hp, mana, mleCrt%Agi, splCrt%Int, dodge%Agi, HealthRegenModToBaseStat, HealthRegenModToBonusStat } + private function combo() : array + { + $result = []; + $critToDodge = array( + 1 => 0.85/1.15, 2 => 1.00/1.15, 3 => 1.11/1.15, + 4 => 2.00/1.15, 5 => 1.00/1.15, 6 => 0.85/1.15, + 7 => 1.60/1.15, 8 => 1.00/1.15, 9 => 0.97/1.15, 11 => 2.00/1.15 + ); + + foreach ($critToDodge as $class => $mod) + { + // humans can't be hunter, shaman, druids (use tauren here) + if (in_array($class, [3, 7, 11])) + $offset = array_values(DB::World()->selectRow('SELECT MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 AND `race` = 6')); + else + $offset = array_values(DB::World()->selectRow('SELECT MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 AND `race` = 1')); + + $gtData = DB::Aowow()->select( + 'SELECT mlecrt.idx - ?d AS ARRAY_KEY, mlecrt.chance * 100, splcrt.chance * 100, mlecrt.chance * 100 * ?f, baseHP5.ratio * 1, extraHP5.ratio * 1 + FROM dbc_gtchancetomeleecrit mlecrt + JOIN dbc_gtchancetospellcrit splcrt ON splcrt.idx = mlecrt.idx + JOIN dbc_gtoctregenhp baseHP5 ON baseHP5.idx = mlecrt.idx + JOIN dbc_gtregenhpperspt extraHP5 ON extraHP5.idx = mlecrt.idx + WHERE mlecrt.idx BETWEEN ?d AND ?d', + (($class - 1) * 100) - 1, // class-offset + $mod, + (($class - 1) * 100) + 0, // lvl 1 + (($class - 1) * 100) + 79 // lvl 80 + ); + + $rows = DB::World()->select( + 'SELECT pls.level AS ARRAY_KEY, + pls.str - ?d, pls.agi - ?d, pls.sta - ?d, pls.inte - ?d, pls.spi - ?d, + pcls.basehp, IF(pcls.basemana <> 0, pcls.basemana, 100) + FROM player_levelstats pls + JOIN player_classlevelstats pcls ON pls.level = pcls.level AND pls.class = pcls.class + WHERE pls.race = ?d AND + pls.class = ?d + ORDER BY pls.level ASC', + $offset[0], $offset[1], $offset[2], $offset[3], $offset[4], + in_array($class, [3, 7, 11]) ? 6 : 1, + $class + ); + + $result[$class] = []; + foreach ($rows as $lvl => $row) + $result[$class][$lvl] = array_values(array_merge($row, $gtData[$lvl])); + } + + return $result; + } + + // base mana regeneration per level + // identical across classes (just use one, that acutally has mana (offset: 100)) + // content of gtRegenMPPerSpt.dbc + private function level() : array + { + return DB::Aowow()->selectCol('SELECT idx-99 AS ARRAY_KEY, ratio FROM dbc_gtregenmpperspt WHERE idx >= 100 AND idx < 100 + ?d', MAX_LEVEL); + } + + // profession perks ... too lazy to formulate a search algorithm for two occurences + private function skills() : array + { + // DB::Aowow()->select( + // 'SELECT sk.id AS "skillId", sla.reqSkillLevel, s.effect1AuraId AS "auraId", s.effect1MiscValue, s.effect1BasePoints + s.effect1DieSides AS "qty" + // FROM dbc_skilllineability sla + // JOIN dbc_skillline sk ON sk.id = sla.skilllineid + // JOIN dbc_spell s ON s.id = sla.spellId + // WHERE sla.acquiremethod = 1 AND // learn on skillup + // sk.categoryId = 11 AND // primary profession + // s.effect1Id = 6 AND // ApplyAura + // s.effect1AuraId IN (29, 189) // ModStatFlat, ModRating (need more?) + // '); + + return array( + SKILL_MINING => array( // mining / toughness + 75 => ['sta' => 3], + 150 => ['sta' => 5], + 225 => ['sta' => 7], + 300 => ['sta' => 10], + 375 => ['sta' => 30], + 450 => ['sta' => 60], + ), + SKILL_SKINNING => array( // skinning / master of anatomy + 75 => ['critstrkrtng' => 3], + 150 => ['critstrkrtng' => 6], + 225 => ['critstrkrtng' => 9], + 300 => ['critstrkrtng' => 12], + 375 => ['critstrkrtng' => 20], + 450 => ['critstrkrtng' => 40], + ) + ); + } +}); + +?> diff --git a/setup/tools/filegen/talentCalc.func.php b/setup/tools/filegen/talentCalc.func.php deleted file mode 100644 index a8e9865aa..000000000 --- a/setup/tools/filegen/talentCalc.func.php +++ /dev/null @@ -1,211 +0,0 @@ -getProfilerMods(); - - $buildTree = function ($class) use (&$petFamIcons, &$tSpells, $spellMods) - { - $petCategories = []; - - $mask = $class ? 1 << ($class - 1) : 0; - - // All "tabs" of a given class talent - $tabs = DB::Aowow()->select('SELECT * FROM dbc_talenttab WHERE classMask = ?d ORDER BY `tabNumber`, `creatureFamilyMask`', $mask); - $result = []; - - for ($tabIdx = 0; $tabIdx < count($tabs); $tabIdx++) - { - $talents = DB::Aowow()->select('SELECT t.id AS tId, t.*, s.name_loc0, s.name_loc2, s.name_loc3, s.name_loc4, s.name_loc6, s.name_loc8, LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) AS iconString FROM dbc_talent t, dbc_spell s, dbc_spellicon si WHERE si.`id` = s.`iconId` AND t.`tabId`= ?d AND s.`id` = t.`rank1` ORDER by t.`row`, t.`column`', $tabs[$tabIdx]['id']); - $result[$tabIdx] = array( - 'n' => Util::localizedString($tabs[$tabIdx], 'name'), - 't' => [] - ); - - if (!$class) - { - $petFamId = log($tabs[$tabIdx]['creatureFamilyMask'], 2); - $result[$tabIdx]['icon'] = $petFamIcons[$petFamId]; - $petCategories = DB::Aowow()->SelectCol('SELECT id AS ARRAY_KEY, categoryEnumID FROM dbc_creaturefamily WHERE petTalentType = ?d', $petFamId); - $result[$tabIdx]['f'] = array_keys($petCategories); - } - - // talent dependencies go here - $depLinks = []; - $tNums = []; - - for ($talentIdx = 0; $talentIdx < count($talents); $talentIdx++) - { - $tNums[$talents[$talentIdx]['tId']] = $talentIdx; - - $d = []; - $s = []; - $i = $talents[$talentIdx]['tId']; - $n = Util::localizedString($talents[$talentIdx], 'name'); - $x = $talents[$talentIdx]['column']; - $y = $talents[$talentIdx]['row']; - $r = null; - $t = []; - $j = []; - $icon = $talents[$talentIdx]['iconString']; - $m = $talents[$talentIdx]['rank2'] == 0 ? 1 : ( - $talents[$talentIdx]['rank3'] == 0 ? 2 : ( - $talents[$talentIdx]['rank4'] == 0 ? 3 : ( - $talents[$talentIdx]['rank5'] == 0 ? 4 : 5 - ) - ) - ); - - // duplet handling - $f = []; - foreach ($petCategories as $k => $v) - { - // cant handle 64bit integer .. split - if ($v >= 32 && ((1 << ($v - 32)) & $talents[$talentIdx]['petCategory2'])) - $f[] = $k; - else if ($v < 32 && ((1 << $v) & $talents[$talentIdx]['petCategory1'])) - $f[] = $k; - } - - for ($itr = 0; $itr <= ($m - 1); $itr++) - { - if (!$tSpells->getEntry($talents[$talentIdx]['rank'.($itr + 1)])) - continue; - - $d[] = $tSpells->parseText()[0]; - $s[] = $talents[$talentIdx]['rank'.($itr + 1)]; - if (isset($spellMods[$talents[$talentIdx]['rank'.($itr + 1)]])) - $j[] = $spellMods[$talents[$talentIdx]['rank'.($itr + 1)]]; - else - $j[] = null; - - if ($talents[$talentIdx]['talentSpell']) - $t[] = $tSpells->getTalentHeadForCurrent(); - } - - if ($talents[$talentIdx]['reqTalent']) - { - // we didn't encounter the required talent yet => create reference - if (!isset($tNums[$talents[$talentIdx]['reqTalent']])) - $depLinks[$talents[$talentIdx]['reqTalent']] = $talentIdx; - - $r = [$tNums[$talents[$talentIdx]['reqTalent']] ?? 0, $talents[$talentIdx]['reqRank'] + 1]; - } - - $result[$tabIdx]['t'][$talentIdx] = array( - 'i' => $i, // talent id - 'n' => $n, // talent name - 'm' => $m, // maxRank - 'd' => $d, // [descriptions] - 's' => $s, // [spellIds] - 'x' => $x, // col # - 'y' => $y, // row # - 'j' => $j // spell mods applied when used in profiler - ); - - if (isset($r)) // [reqTalentId, reqRank] - $result[$tabIdx]['t'][$talentIdx]['r'] = $r; - - if (!empty($t)) // talentspell tooltip - $result[$tabIdx]['t'][$talentIdx]['t'] = $t; - - if (!empty($f)) // [petFamilyId] - $result[$tabIdx]['t'][$talentIdx]['f'] = $f; - - if ($class) - $result[$tabIdx]['t'][$talentIdx]['iconname'] = $icon; - - // If this talent is a reference, add it to the array of talent dependencies - if (isset($depLinks[$talents[$talentIdx]['tId']])) - { - $result[$tabIdx]['t'][$depLinks[$talents[$talentIdx]['tId']]]['r'][0] = $talentIdx; - unset($depLinks[$talents[$talentIdx]['tId']]); - } - } - - // Remove all dependencies for which the talent has not been found - foreach ($depLinks as $dep_link) - unset($result[$tabIdx]['t'][$dep_link]['r']); - } - - return $result; - }; - - // my neighbour is noisy as fuck and my head hurts, so .. - $petFamIcons = ['Ability_Druid_KingoftheJungle', 'Ability_Druid_DemoralizingRoar', 'Ability_EyeOfTheOwl']; // .. i've no idea where to fetch these from - $classes = [CLASS_WARRIOR, CLASS_PALADIN, CLASS_HUNTER, CLASS_ROGUE, CLASS_PRIEST, CLASS_DEATHKNIGHT, CLASS_SHAMAN, CLASS_MAGE, CLASS_WARLOCK, CLASS_DRUID]; - $petIcons = ''; - - // check directory-structure - foreach (Util::$localeStrings as $dir) - if (!CLISetup::writeDir('datasets/'.$dir)) - $success = false; - - $tSpellIds = DB::Aowow()->selectCol('SELECT rank1 FROM dbc_talent UNION SELECT rank2 FROM dbc_talent UNION SELECT rank3 FROM dbc_talent UNION SELECT rank4 FROM dbc_talent UNION SELECT rank5 FROM dbc_talent'); - $tSpells = new SpellList(array(['s.id', $tSpellIds], Cfg::get('SQL_LIMIT_NONE'))); - - foreach (CLISetup::$localeIds as $lId) - { - User::useLocale($lId); - Lang::load($lId); - - // TalentCalc - foreach ($classes as $cMask) - { - set_time_limit(20); - - $cId = log($cMask, 2) + 1; - $file = 'datasets/'.User::$localeString.'/talents-'.$cId; - $toFile = '$WowheadTalentCalculator.registerClass('.$cId.', '.Util::toJSON($buildTree($cId)).')'; - - if (!CLISetup::writeFile($file, $toFile)) - $success = false; - } - - // PetCalc - if (empty($petIcons)) - { - $pets = DB::Aowow()->SelectCol('SELECT id AS ARRAY_KEY, LOWER(SUBSTRING_INDEX(iconString, "\\\\", -1)) AS iconString FROM dbc_creaturefamily WHERE petTalentType IN (0, 1, 2)'); - $petIcons = Util::toJSON($pets); - } - - $toFile = "var g_pet_icons = ".$petIcons.";\n\n"; - $toFile .= 'var g_pet_talents = '.Util::toJSON($buildTree(0)).';'; - $file = 'datasets/'.User::$localeString.'/pet-talents'; - - if (!CLISetup::writeFile($file, $toFile)) - $success = false; - } - - return $success; - } -?> diff --git a/setup/tools/filegen/talentIcons.func.php b/setup/tools/filegen/talentIcons.func.php deleted file mode 100644 index 977d7c40c..000000000 --- a/setup/tools/filegen/talentIcons.func.php +++ /dev/null @@ -1,102 +0,0 @@ - $v) - { - if (!$v) - continue; - - set_time_limit(10); - - for ($tree = 0; $tree < 3; $tree++) - { - $what = $k ? 'classMask' : 'creatureFamilyMask'; - $set = $k ? 1 << ($k - 1) : 1 << $tree; - $subset = $k ? $tree : 0; - $path = $k ? 'talents/icons' : 'hunterpettalents'; - $outFile = 'static/images/wow/'.$path.'/'.$v.'_'.($tree + 1).'.jpg'; - $icons = DB::Aowow()->SelectCol($query, $what, $set, $subset); - - if (empty($icons)) - { - CLI::write('talentIcons - query for '.$v.' tree: '.$k.' returned empty', CLI::LOG_ERROR); - $success = false; - continue; - } - - if ($res = imageCreateTrueColor(count($icons) * $dims, 2 * $dims)) - { - for ($i = 0; $i < count($icons); $i++) - { - $imgFile = 'static/images/wow/icons/medium/'.strtolower($icons[$i]).'.jpg'; - if (!file_exists($imgFile)) - { - CLI::write('talentIcons - raw image '.CLI::bold($imgFile). ' not found', CLI::LOG_ERROR); - $success = false; - break; - } - - $im = imagecreatefromjpeg($imgFile); - - // colored - imagecopymerge($res, $im, $i * $dims, 0, 0, 0, imageSX($im), imageSY($im), 100); - - // grayscale - if (imageistruecolor($im)) - imagetruecolortopalette($im, false, 256); - - for ($j = 0; $j < imagecolorstotal($im); $j++) - { - $color = imagecolorsforindex($im, $j); - $gray = round(0.299 * $color['red'] + 0.587 * $color['green'] + 0.114 * $color['blue']); - imagecolorset($im, $j, $gray, $gray, $gray); - } - imagecopymerge($res, $im, $i * $dims, $dims, 0, 0, imageSX($im), imageSY($im), 100); - } - - if (@imagejpeg($res, $outFile)) - CLI::write(sprintf(ERR_NONE, CLI::bold($outFile)), CLI::LOG_OK, true, true); - else - { - $success = false; - CLI::write('talentIcons - '.CLI::bold($outFile.'.jpg').' could not be written', CLI::LOG_ERROR); - } - } - else - { - $success = false; - CLI::write('talentIcons - image resource not created', CLI::LOG_ERROR); - continue; - } - } - } - - return $success; - } - -?> diff --git a/setup/tools/filegen/talentcalc.ss.php b/setup/tools/filegen/talentcalc.ss.php new file mode 100644 index 000000000..597126608 --- /dev/null +++ b/setup/tools/filegen/talentcalc.ss.php @@ -0,0 +1,192 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles talent tree data to file for the talent calculator tool.'] + ); + + protected $dbcSourceFiles = ['talenttab', 'talent', 'spell', 'creaturefamily', 'spellicon']; + protected $setupAfter = [['spell'], []]; + protected $requiredDirs = ['datasets/']; + protected $localized = true; + + private $petFamIcons = []; + private $tSpells = null; + private $spellMods = []; + + public function generate() : bool + { + // target direcotries are missing + if (!$this->success) + return false; + + // my neighbour is noisy as fuck and my head hurts, so .. + $this->petFamIcons = ['Ability_Druid_KingoftheJungle', 'Ability_Druid_DemoralizingRoar', 'Ability_EyeOfTheOwl']; // .. i've no idea where to fetch these from + $this->spellMods = (new SpellList(array(['typeCat', -2], Cfg::get('SQL_LIMIT_NONE'))))->getProfilerMods(); + + $petIcons = Util::toJSON(DB::Aowow()->SelectCol('SELECT id AS ARRAY_KEY, LOWER(SUBSTRING_INDEX(iconString, "\\\\", -1)) AS iconString FROM dbc_creaturefamily WHERE petTalentType IN (0, 1, 2)')); + + $tSpellIds = DB::Aowow()->selectCol('SELECT rank1 FROM dbc_talent UNION SELECT rank2 FROM dbc_talent UNION SELECT rank3 FROM dbc_talent UNION SELECT rank4 FROM dbc_talent UNION SELECT rank5 FROM dbc_talent'); + $this->tSpells = new SpellList(array(['s.id', $tSpellIds], Cfg::get('SQL_LIMIT_NONE'))); + + foreach (CLISetup::$localeIds as $lId) + { + User::useLocale($lId); + Lang::load($lId); + + // TalentCalc + for ($i = 1; (1 << ($i - 1)) < CLASS_MASK_ALL; $i++ ) + { + if (!((1 << ($i - 1)) & CLASS_MASK_ALL)) + continue; + + set_time_limit(20); + + $file = 'datasets/'.User::$localeString.'/talents-'.$i; + $toFile = '$WowheadTalentCalculator.registerClass('.$i.', '.Util::toJSON($this->buildTree(1 << ($i - 1))).')'; + + if (!CLISetup::writeFile($file, $toFile)) + $this->success = false; + } + + // PetCalc + $toFile = "var g_pet_icons = ".$petIcons.";\n\n"; + $toFile .= 'var g_pet_talents = '.Util::toJSON($this->buildTree(0)).';'; + $file = 'datasets/'.User::$localeString.'/pet-talents'; + + if (!CLISetup::writeFile($file, $toFile)) + $this->success = false; + } + + return $this->success; + } + + private function buildTree(int $classMask) : array + { + $petCategories = []; + + // All "tabs" of a given class talent + $tabs = DB::Aowow()->select('SELECT * FROM dbc_talenttab WHERE classMask = ?d ORDER BY `tabNumber`, `creatureFamilyMask`', $classMask); + $result = []; + + for ($tabIdx = 0; $tabIdx < count($tabs); $tabIdx++) + { + $talents = DB::Aowow()->select('SELECT t.id AS tId, t.*, IF(t.rank5, 5, IF(t.rank4, 4, IF(t.rank3, 3, IF(t.rank2, 2, 1)))) AS maxRank, s.name_loc0, s.name_loc2, s.name_loc3, s.name_loc4, s.name_loc6, s.name_loc8, LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) AS iconString FROM dbc_talent t, dbc_spell s, dbc_spellicon si WHERE si.`id` = s.`iconId` AND t.`tabId`= ?d AND s.`id` = t.`rank1` ORDER by t.`row`, t.`column`', $tabs[$tabIdx]['id']); + $result[$tabIdx] = array( + 'n' => Util::localizedString($tabs[$tabIdx], 'name'), + 't' => [] + ); + + if (!$classMask) + { + $petFamId = log($tabs[$tabIdx]['creatureFamilyMask'], 2); + $result[$tabIdx]['icon'] = $this->petFamIcons[$petFamId]; + $petCategories = DB::Aowow()->SelectCol('SELECT id AS ARRAY_KEY, categoryEnumID FROM dbc_creaturefamily WHERE petTalentType = ?d', $petFamId); + $result[$tabIdx]['f'] = array_keys($petCategories); + } + + // talent dependencies go here + $depLinks = []; + $tNums = []; + + for ($talentIdx = 0; $talentIdx < count($talents); $talentIdx++) + { + $tNums[$talents[$talentIdx]['tId']] = $talentIdx; + + $talent = array( + 'i' => $talents[$talentIdx]['tId'], // talent id + 'n' => Util::localizedString($talents[$talentIdx], 'name'), // talent name + 'm' => $talents[$talentIdx]['maxRank'], // maxRank + 'd' => [], // [descriptions] + 's' => [], // [spellIds] + 'x' => $talents[$talentIdx]['column'], // col # + 'y' => $talents[$talentIdx]['row'], // row # + 'j' => [] // [spellMods] that are applied when used in profiler + // 'r' => [] // [reqTalentId, reqRank] (can be omitted) + // 't' => [] // talentspell tooltip (can be omitted) + // 'f' => [] // [petFamilyIds] (can be omitted) + ); + + if ($classMask) + $talent['iconname'] = $talents[$talentIdx]['iconString']; + + for ($itr = 0; $itr <= ($talent['m'] - 1); $itr++) + { + $spell = $talents[$talentIdx]['rank'.($itr + 1)]; + if (!$this->tSpells->getEntry($spell)) + continue; + + $talent['d'][] = $this->tSpells->parseText()[0]; + $talent['s'][] = $talents[$talentIdx]['rank'.($itr + 1)]; + + if ($classMask && isset($this->spellMods[$spell])) + if ($mod = $this->spellMods[$spell]) + $talent['j'][] = $mod; + + if ($talents[$talentIdx]['talentSpell']) + $talent['t'][] = $this->tSpells->getTalentHeadForCurrent(); + } + + foreach ($petCategories as $k => $v) + { + // cant handle 64bit integer .. split + if ($v >= 32 && ((1 << ($v - 32)) & $talents[$talentIdx]['petCategory2'])) + $talent['f'][] = $k; + else if ($v < 32 && ((1 << $v) & $talents[$talentIdx]['petCategory1'])) + $talent['f'][] = $k; + } + + if ($talents[$talentIdx]['reqTalent']) + { + // we didn't encounter the required talent yet => create reference + if (!isset($tNums[$talents[$talentIdx]['reqTalent']])) + $depLinks[$talents[$talentIdx]['reqTalent']] = $talentIdx; + + $talent['r'] = [$tNums[$talents[$talentIdx]['reqTalent']] ?? 0, $talents[$talentIdx]['reqRank'] + 1]; + } + + $result[$tabIdx]['t'][$talentIdx] = $talent; + + // If this talent is a reference, add it to the array of talent dependencies + if (isset($depLinks[$talents[$talentIdx]['tId']])) + { + $result[$tabIdx]['t'][$depLinks[$talents[$talentIdx]['tId']]]['r'][0] = $talentIdx; + unset($depLinks[$talents[$talentIdx]['tId']]); + } + } + + // Remove all dependencies for which the talent has not been found + foreach ($depLinks as $dep_link) + unset($result[$tabIdx]['t'][$dep_link]['r']); + } + + return $result; + } +}); + +?> diff --git a/setup/tools/filegen/talenticons.ss.php b/setup/tools/filegen/talenticons.ss.php new file mode 100644 index 000000000..ac41d8f5a --- /dev/null +++ b/setup/tools/filegen/talenticons.ss.php @@ -0,0 +1,107 @@ + [[], CLISetup::ARGV_PARAM, 'Generates icon textures for the talent calculator tool.'] + ); + + protected $dbcSourceFiles = ['talenttab', 'talent', 'spell']; + protected $setupAfter = [['icons', 'spell'], ['simpleimg']]; + protected $requiredDirs = ['static/images/wow/talents/icons', 'static/images/wow/hunterpettalents']; + + private const ICON_SIZE = 36; // px + + private $filenames = ['icons', 'warrior', 'paladin', 'hunter', 'rogue', 'priest', 'deathknight', 'shaman', 'mage', 'warlock', null, 'druid']; + + public function generate() : bool + { + foreach ($this->filenames as $k => $v) + { + if (!$v) + continue; + + set_time_limit(10); + + for ($tree = 0; $tree < 3; $tree++) + { + $what = $k ? 'classMask' : 'creatureFamilyMask'; + $set = $k ? 1 << ($k - 1) : 1 << $tree; + $subset = $k ? $tree : 0; + $path = $k ? 'talents/icons' : 'hunterpettalents'; + $outFile = 'static/images/wow/'.$path.'/'.$v.'_'.($tree + 1).'.jpg'; + $icons = DB::Aowow()->SelectCol( + 'SELECT ic.name AS iconString + FROM ?_icons ic + JOIN ?_spell s ON s.iconId = ic.id + JOIN dbc_talent t ON t.rank1 = s.id + JOIN dbc_talenttab tt ON tt.id = t.tabId + WHERE tt.?# = ?d AND tt.tabNumber = ?d + ORDER BY t.row, t.column ASC, s.id DESC', + $what, $set, $subset); + + if (empty($icons)) + { + CLI::write('[talenticons] - query for '.$v.' tree: '.$k.' returned empty', CLI::LOG_ERROR); + $this->success = false; + continue; + } + + $res = imageCreateTrueColor(count($icons) * self::ICON_SIZE, 2 * self::ICON_SIZE); + if (!$res) + { + $this->success = false; + CLI::write('[talenticons] - image resource not created', CLI::LOG_ERROR); + continue; + } + + for ($i = 0; $i < count($icons); $i++) + { + $imgFile = 'static/images/wow/icons/medium/'.strtolower($icons[$i]).'.jpg'; + if (!file_exists($imgFile)) + { + CLI::write('[talenticons] - raw image '.CLI::bold($imgFile). ' not found', CLI::LOG_ERROR); + $this->success = false; + break; + } + + $im = imagecreatefromjpeg($imgFile); + + // colored + imagecopymerge($res, $im, $i * self::ICON_SIZE, 0, 0, 0, imageSX($im), imageSY($im), 100); + + // grayscale + if (imageistruecolor($im)) + imagetruecolortopalette($im, false, 256); + + for ($j = 0; $j < imagecolorstotal($im); $j++) + { + $color = imagecolorsforindex($im, $j); + $gray = round(0.299 * $color['red'] + 0.587 * $color['green'] + 0.114 * $color['blue']); + imagecolorset($im, $j, $gray, $gray, $gray); + } + imagecopymerge($res, $im, $i * self::ICON_SIZE, self::ICON_SIZE, 0, 0, imageSX($im), imageSY($im), 100); + } + + if (@imagejpeg($res, $outFile)) + CLI::write('[talenticons] '.sprintf(ERR_NONE, CLI::bold($outFile)), CLI::LOG_OK, true, true); + else + { + $this->success = false; + CLI::write('[talenticons] - '.CLI::bold($outFile.'.jpg').' could not be written', CLI::LOG_ERROR); + } + } + } + + return $this->success; + } +}); + +?> diff --git a/setup/tools/filegen/tooltips.ss.php b/setup/tools/filegen/tooltips.ss.php new file mode 100644 index 000000000..b67822471 --- /dev/null +++ b/setup/tools/filegen/tooltips.ss.php @@ -0,0 +1,28 @@ + [[], CLISetup::ARGV_PARAM, 'Fills powered tooltips (static/widgets/power.js) with site variables.'] + ); + + protected $fileTemplateSrc = ['power.js.in']; + protected $fileTemplateDest = ['static/widgets/power.js']; + + public function generate() : bool + { + $this->templateFill(); + return $this->success; + } +}); + +?> diff --git a/setup/tools/filegen/weightPresets.func.php b/setup/tools/filegen/weightpresets.ss.php similarity index 67% rename from setup/tools/filegen/weightPresets.func.php rename to setup/tools/filegen/weightpresets.ss.php index b7e8ffa52..0f213c60d 100644 --- a/setup/tools/filegen/weightPresets.func.php +++ b/setup/tools/filegen/weightpresets.ss.php @@ -7,14 +7,17 @@ die('not in cli mode'); - // Creates 'weight-presets'-file +// Creates 'weight-presets'-file +CLISetup::registerSetup("build", new class extends SetupScript +{ + protected $info = array( + 'weightpresets' => [[], CLISetup::ARGV_PARAM, 'Generates stat weight presets file for the item comparison tool.'] + ); - function weightPresets() - { - // check directory-structure - if (!CLISetup::writeDir('datasets/')) - return false; + protected $requiredDirs = ['datasets/']; + public function generate() : bool + { $wtPresets = []; $scales = DB::Aowow()->select('SELECT id, name, icon, class FROM ?_account_weightscales WHERE userId = 0 ORDER BY class, id ASC'); @@ -24,7 +27,7 @@ function weightPresets() $wtPresets[$s['class']]['pve'][$s['name']] = array_merge(['__icon' => $s['icon']], $weights); else { - CLI::write('WeightScale \''.CLI::bold($s['name']).'\' has no data set.', CLI::LOG_WARN); + CLI::write('[weightpresets] Scale \''.CLI::bold($s['name']).'\' has no data set.', CLI::LOG_WARN); $wtPresets[$s['class']]['pve'][$s['name']] = ['__icon' => $s['icon']]; } } @@ -37,4 +40,6 @@ function weightPresets() return true; } +}); + ?> diff --git a/setup/tools/setupScript.class.php b/setup/tools/setupScript.class.php index f55cf0244..c8b7837d1 100644 --- a/setup/tools/setupScript.class.php +++ b/setup/tools/setupScript.class.php @@ -9,17 +9,24 @@ trait TrDBCcopy { + public function __construct() + { + $this->info = array( + $this->command => [[], CLISetup::ARGV_PARAM, 'COPY: ' . $this->dbcSourceFiles[0] . '.dbc -> aowow_'.$this->command], + ); + } + public function generate() : bool { if (!$this->dbcSourceFiles) { - CLI::write(' SetupScript '.$this->command.' is set up for DBCcopy but has no source set!', CLI::LOG_ERROR); + CLI::write('[sql] SetupScript '.$this->command.' is set up for DBCcopy but has no source set!', CLI::LOG_ERROR); return false; } else if (count($this->dbcSourceFiles) != 1) - CLI::write(' SetupScript '.$this->command.' is set up for DBCcopy but has multiple sources set!', CLI::LOG_WARN); + CLI::write('[sql] SetupScript '.$this->command.' is set up for DBCcopy but has multiple sources set!', CLI::LOG_WARN); - CLI::write('SqlGen::generate() - copying '.$this->dbcSourceFiles[0].'.dbc into aowow_'.$this->command); + CLI::write('[sql] copying '.$this->dbcSourceFiles[0].'.dbc into aowow_'.$this->command); $dbc = new DBC($this->dbcSourceFiles[0], ['temporary' => false, 'tableName' => 'aowow_'.$this->command]); if ($dbc->error) @@ -36,18 +43,18 @@ public function applyCustomData() : bool { $ok = true; $this->customData = $this->customData ?? []; - if ($cd = DB::Aowow()->selectCol('SELECT `entry` AS ARRAY_KEY, `field` AS ARRAY_KEY2, `value` FROM ?_setup_custom_data WHERE `command` = ?', $this->command)) + if ($cd = DB::Aowow()->selectCol('SELECT `entry` AS ARRAY_KEY, `field` AS ARRAY_KEY2, `value` FROM ?_setup_custom_data WHERE `command` = ?', $this->getName())) $this->customData += $cd; foreach ($this->customData as $id => $data) { try { - DB::Aowow()->query('UPDATE ?_'.$this->command.' SET ?a WHERE id = ?d', $data, $id); + DB::Aowow()->query('UPDATE ?_'.$this->getName().' SET ?a WHERE id = ?d', $data, $id); } catch (Exception $e) { - trigger_error('Custom Data for entry #'.$id.': '.$e->getMessage(), E_USER_ERROR); + trigger_error('custom data for entry #'.$id.': '.$e->getMessage(), E_USER_ERROR); $ok = false; } } @@ -56,17 +63,351 @@ public function applyCustomData() : bool } } +trait TrTemplateFile +{ + public function generate() : bool + { + $this->templateFill(); + return $this->success; + } + + private function &templateCopy() : iterable + { + if (!$this->fileTemplateSrc || count($this->fileTemplateSrc) != count($this->fileTemplateDest)) + { + CLI::write('[build] template file definitions missing or malformed', CLI::LOG_ERROR); + $this->success = false; + return; + } + + foreach ($this->fileTemplateSrc as $idx => $srcFile) + { + $file = $this->fileTemplatePath.$srcFile; + + if (!file_exists($file)) + { + CLI::write('[build] template file is missing - '.CLI::bold($file), CLI::LOG_ERROR); + $this->success = false; + return; + } + + $content = file_get_contents($file); + if (!$content) + { + CLI::write('[build] template file is not readable - '.CLI::bold($file), CLI::LOG_ERROR); + $this->success = false; + return; + } + + // replace constants + $content = Cfg::applyToString($content); + + yield $content; + + if (CLISetup::writeFile($this->fileTemplateDest[$idx], $content)) + continue; + + $this->success = false; + return; + } + } + + private function templateFill() : void + { + foreach ($this->templateCopy() as &$content) + { + if (!$this->success) //templateCopy() fucked up somehow? + return; + + // PH format: /*setup:*/ + if (preg_match_all('/\/\*setup:([\w\-_]+)\*\//i', $content, $m)) + { + foreach ($m[1] as $func) + { + if (method_exists($this, $func)) + $content = str_replace('/*setup:'.$func.'*/', $this->$func(), $content); + else + { + CLI::write('['.$this->getName().'] No function for was registered for placeholder '.$func.'().', CLI::LOG_ERROR); + $this->success = false; + return; + } + } + } + } + } +} + +trait TrImageProcessor +{ + private $imgPath = '%sInterface/'; + private $status = ''; + private $maxExecTime = 30; + + private static $GEN_IDX_SRC_PATH = 0; // php v8.0 make static > const + private static $GEN_IDX_SRC_REAL = 1; + private static $GEN_IDX_LOCALE = 2; + private static $GEN_IDX_SRC_INFO = 3; + private static $GEN_IDX_DEST_INFO = 4; + + private static $JPEG_QUALITY = 85; // 0: worst - 100: best + + private function checkSourceDirs() : bool + { + $outTblLen = 0; + $foundCache = []; + + foreach ($this->genSteps as $i => [$subDir, $realPaths, $localized, , ]) + { + if ($realPaths) + continue; + + // multiple genSteps can require the same resource + if (isset($foundCache[$subDir])) + { + $this->genSteps[$i][self::$GEN_IDX_SRC_REAL] = $foundCache[$subDir]; + continue; + } + + $outTblLen = max($outTblLen, strlen($subDir)); + + $path = $this->imgPath.$subDir; + if ($p = CLISetup::filesInPathLocalized($path, $this->success, $localized)) + { + $foundCache[$subDir] = $p; + $this->genSteps[$i][self::$GEN_IDX_SRC_REAL] = $p; // pvp v7.3+ - make $realPaths areferene + } + else + $this->success = false; + } + + $locList = []; + foreach (CLISetup::$expectedPaths as $xp => $locId) + if (in_array($locId, CLISetup::$localeIds)) + $locList[] = $xp; + + CLI::write('[img-proc] required resources overview:', CLI::LOG_INFO); + + $foundCache = []; + foreach ($this->genSteps as [$subDir, $realPaths, $localized, , ]) + { + // one line per unique resource + if (isset($foundCache[$subDir])) + continue; + + $foundCache[$subDir] = true; + + if (!$realPaths) + { + CLI::write(CLI::red('MISSING').' - '.str_pad($subDir, 14).' @ '.sprintf($this->imgPath, '['.implode('/,', $locList).'/]').$subDir); + $this->success = false; + } + else if ($localized) + { + $foundLoc = []; + foreach (CLISetup::$expectedPaths as $xp => $lId) + if (in_array($lId, CLISetup::$localeIds)) + if (isset($realPaths[$lId]) && ($n = stripos($realPaths[$lId], '/'.$xp.'/'))) + $foundLoc[$lId] = substr($realPaths[$lId], $n + 1, 4); + + if ($diff = array_diff(CLISetup::$localeIds, array_keys($foundLoc))) + { + $buff = []; + foreach ($diff as $d) + $buff[] = CLI::red(Util::$localeStrings[$d]); + foreach ($foundLoc as $str) + $buff[] = CLI::green($str); + + CLI::write(CLI::yellow('PARTIAL').' - '.str_pad($subDir, $outTblLen).' @ '.sprintf($this->imgPath, '['.implode('/,', $buff).'/]').$subDir); + } + else + CLI::write(CLI::green('FOUND ').' - '.str_pad($subDir, $outTblLen).' @ '.sprintf($this->imgPath, '['.implode('/,', $foundLoc).'/]').$subDir); + } + else + CLI::write(CLI::green('FOUND ').' - '.str_pad($subDir, $outTblLen).' @ '.reset($realPaths)); + } + + CLI::write(); + + // if not localized directly return result + foreach ($this->genSteps as $i => [$subDir, $realPaths, $localized, , ]) + if (!$localized) + $this->genSteps[$i][self::$GEN_IDX_SRC_REAL] = reset($realPaths); + + return $this->success; + } + + // prefer manually converted PNG files (as the imagecreatefromblp-script has issues with some formats) + // alpha channel issues observed with locale deDE Hilsbrad and Elwynn - maps + // see: https://github.com/Kanma/BLPConverter + private function loadImageFile(string $path) // : ?GdImage + { + $result = null; + $path = preg_replace('/\.(png|blp)$/i', '', $path); + + $file = $path.'.png'; + if (CLISetup::fileExists($file)) + { + CLI::write('[img-proc] manually converted png file present for '.$file, CLI::LOG_INFO); + $result = imagecreatefrompng($file); + } + + if (!$result) + { + $file = $path.'.blp'; + if (CLISetup::fileExists($file)) + $result = imagecreatefromblp($file); + } + + return $result; + } + + private function writeImageFile(/* GdImage */ $src, string $outFile, array $srcDims, array $destDims) : bool + { + $success = false; + $outRes = imagecreatetruecolor($destDims['w'], $destDims['h']); + $ext = substr($outFile, -3, 3); + + imagesavealpha($outRes, true); + if ($ext == 'png') + { + imagealphablending($outRes, false); + $transparentindex = imagecolorallocatealpha($outRes, 255, 255, 255, 127); + imagefill($outRes, 0, 0, $transparentindex); + } + + imagecopyresampled($outRes, $src, $destDims['x'], $destDims['x'], $srcDims['x'], $srcDims['y'], $destDims['w'], $destDims['h'], $srcDims['w'], $srcDims['h']); + + switch ($ext) + { + case 'jpg': + $success = imagejpeg($outRes, $outFile, self::$JPEG_QUALITY); + break; + case 'gif': + $success = imagegif($outRes, $outFile); + break; + case 'png': + $success = imagepng($outRes, $outFile); + break; + default: + CLI::write('[img-proc] '.$this->status.' - unsupported file fromat: '.$ext, CLI::LOG_WARN); + } + + imagedestroy($outRes); + + if ($success) + { + chmod($outFile, Util::FILE_ACCESS); + CLI::write('[img-proc] '.$this->status.' - image '.$outFile.' written', CLI::LOG_OK, true, true); + } + else + CLI::write('[img-proc] '.$this->status.' - could not create image '.$outFile, CLI::LOG_ERROR); + + return $success; + } +} + +trait TrComplexImage +{ + use TrImageProcessor { TrImageProcessor::writeImageFile as _writeImageFile; } + + private function writeImageFile(/* GdImage */ $src, string $outFile, int $w, int $h) : bool + { + $srcDims = array( + 'x' => 0, + 'y' => 0, + 'w' => imagesx($src), + 'h' => imagesy($src) + ); + $destDims = array( + 'x' => 0, + 'y' => 0, + 'w' => $w, + 'h' => $h + ); + + return $this->_writeImageFile($src, $outFile, $srcDims, $destDims); + } + + private function createAlphaImage(int $w, int $h) //: ?GdImage + { + $img = imagecreatetruecolor($w, $h); + if (!$img) + return null; + + imagesavealpha($img, true); + imagealphablending($img, false); + + $bgColor = imagecolorallocatealpha($img, 0, 0, 0, 127); + imagefilledrectangle($img, 0, 0, imagesx($img) - 1, imagesy($img) - 1, $bgColor); + + imagecolortransparent($img, $bgColor); + imagealphablending($img, true); + + imagecolordeallocate($img, $bgColor); + + return $img; + } + + private function assembleImage(string $baseName, array $tileData, int $destW, int $destH) //: ?GdImage + { + $dest = imagecreatetruecolor($destW, $destH); + if (!$dest) + return null; + + imagesavealpha($dest, true); + imagealphablending($dest, false); + + $tileH = $destH; + foreach ($tileData as $y => $row) + { + $tileW = $destW; + foreach ($row as $x => $suffix) + { + $src = $this->loadImageFile($baseName.$suffix); + if (!$src) + { + CLI::write('[img-proc-c] tile '.$baseName.$suffix.'.blp missing.', CLI::LOG_ERROR); + unset($dest); + return null; + } + + imagecopyresampled($dest, $src, 256 * $x, 256 * $y, 0, 0, min($tileW, 256), min($tileH, 256), min($tileW, 256), min($tileH, 256)); + $tileW -= 256; + + unset($src); + } + $tileH -= 256; + } + + return $dest; + } +} + abstract class SetupScript { - protected $fileTemplatePath = ''; - protected $fileTemplateFile = ''; + // FileGen + protected $requiredDirs = []; + protected $fileTemplateDest = []; + protected $fileTemplatePath = 'setup/tools/filegen/templates/'; + protected $fileTemplateSrc = []; + + // SQLGen + protected $result = ''; + + // FileGen + SQLGen + protected $dbcSourceFiles = []; // relies on these dbc files. Read into db if related table is missing + protected $worldDependency = []; // query when this table changed (--sync command) + + protected $info = []; // arr: 0 => self, n => genSteps cmd => [[arr:optionalArgs], int:argFlags, str:description] + protected $setupAfter = [[], []]; // [[sqlgen], [filegen]] used to sort scripts that rely on each other being executed in the right order (script names are not nessecarily the same as their table names) - protected $tblDependencyAowow = []; - protected $tblDependencyTC = []; + protected $success = true; + protected $localized = false; // push locale directories onto $requiredDirs? + protected $useGlobalStrings = false; // uses data from interface/framexml/globalstrings.lua - protected $dbcSourceFiles = []; + public $isOptional = false; // not a part of the setup chain - // abstract protected $command; abstract public function generate() : bool; @@ -75,23 +416,107 @@ public function getRequiredDBCs() : array return $this->dbcSourceFiles; } - public function getDependencies(bool $aowow) : array + public function getSelfDependencies() : array { - return $aowow ? $this->tblDependencyAowow : $this->tblDependencyTC; + return $this->setupAfter; + } + + public function getRemoteDependencies() : array + { + return $this->worldDependency; } public function getName() : string { - return $this->command; + reset($this->info); + return key($this->info); + } + + public function getInfo() // : string|int + { + // info: name => [param, paramFlags, description] + return (reset($this->info)[2] ?? '').($this->isOptional ? ' - '.Cli::yellow('[omitted by setup]') : ''); + } + + public function getSubCommands() : array + { + $sub = []; + + if (count($this->info) > 1) + $sub = array_slice($this->info, 1, 10, true); + + return $sub; + } + + public function getRequiredDirs(): array + { + return $this->requiredDirs; + } + + public function fulfillRequirements() : bool + { + // create directory structure + $newDirs = 0; + $existed = false; + foreach ($this->getRequiredDirs() as $dir) + { + $dirs = []; + if (!$this->localized) + $dirs[] = $dir; + else + foreach (CLISetup::$locales as $str) + $dirs[] = $dir . $str . '/'; + + foreach ($dirs as $d) + { + if (!CLISetup::writeDir($d, $existed)) + { + CLI::write('[build] could not create directory: '.CLI::bold($d), CLI::LOG_ERROR); + return false; + } + } + + $newDirs += ($existed ? 0 : 1); + } + + if ($newDirs) + CLI::write('[build] created '.$newDirs.' extra paths'); + + // load DBC files + if (!in_array('TrDBCcopy', class_uses($this))) + { + foreach ($this->getRequiredDBCs() as $req) + { + if (CLISetup::loadDBC($req)) + continue; + + CLI::write('[sql/build] '. $this->getName() . ' is missing dbc file ' . $req . '. Skipping...', CLI::LOG_ERROR); + return false; + } + } + + if ($this->useGlobalStrings) + if (!CLISetup::loadGlobalStrings()) + return false; + + return true; + } + + public function writeCLIHelp() : bool + { + // CLI::write('example info', -1, false); // some info + // CLI::write(); // empty new line + // return true; // help was provided, skip help in parent + return false; } protected function reapplyCCFlags(string $tbl, int $type) : void { - // reaply flags for community content as these are lost when the table is rebuild + // reapply flags for community content as these are lost when the table is rebuild - if (preg_match('/[^a-z]/i', $tbl)) + if (preg_match('/\W/i', $tbl)) { - trigger_error('SetupScript::reapplyCCFlags() - invalid table name'); + trigger_error('[sql] reapplyCCFlags() - invalid table name'); return; } @@ -101,4 +526,4 @@ protected function reapplyCCFlags(string $tbl, int $type) : void } } -?> \ No newline at end of file +?> diff --git a/setup/tools/sqlGen.class.php b/setup/tools/sqlGen.class.php deleted file mode 100644 index cc6259c3a..000000000 --- a/setup/tools/sqlGen.class.php +++ /dev/null @@ -1,256 +0,0 @@ - $ts) - { - $depsOK = true; - foreach ($ts->getDependencies(true) as $d) - { - if (isset(self::$tables[$d])) - continue; - - $depsOK = false; - break; - } - - if ($depsOK) - { - if (isset(self::$tables[$ts->getName()])) - { - CLI::write('a SetupScript named '.CLI::bold($ts->getName()).' was already registered. Skipping...', CLI::LOG_WARN); - unset(self::$tmpStore[$idx]); - continue; - } - - self::$tables[$ts->getName()] = $ts; - unset(self::$tmpStore[$idx]); - } - } - - if ($nDepsMissing == count(self::$tmpStore)) - { - CLI::write('the following SetupScripts have unresolved dependencies and have not been registered:', CLI::LOG_ERROR); - foreach (self::$tmpStore as $ts) - CLI::write(' * '.CLI::bold($ts->getName()).' => '.implode(', ', $ts->getDependencies(true))); - - self::$tmpStore = []; - break; - } - } - - // handle command prompts - if (!self::handleCLIOpts($doScripts) && !$updScripts) - return false; - - // check passed subscript names; limit to real scriptNames - self::$subScripts = array_keys(self::$tables); - if ($doScripts || $updScripts) - self::$subScripts = array_intersect($doScripts ?: $updScripts, self::$subScripts); - - return true; - } - - public static function register(SetupScript $ssRef) : void - { - // if dependencies haven't been stored yet, put aside for later use - foreach ($ssRef->getDependencies(true) as $d) - { - if (isset(self::$tables[$d])) - continue; - - self::$tmpStore[] = $ssRef; - return; - } - - if (isset(self::$tables[$ssRef->getName()])) - { - CLI::write('a SetupScript named '.CLI::bold($ssRef->getName()).' was already registered. Skipping...', CLI::LOG_WARN); - return; - } - - self::$tables[$ssRef->getName()] = $ssRef; - - // recheck temp stored dependencies - foreach (self::$tmpStore as $idx => $ts) - { - $depsOK = true; - foreach ($ts->getDependencies(true) as $d) - { - if (isset(self::$tables[$d])) - continue; - - $depsOK = false; - break; - } - - if ($depsOK) - { - self::$tables[$ts->getName()] = $ts; - unset(self::$tmpStore[$idx]); - } - } - } - - private static function handleCLIOpts(?array &$doTbls) : bool - { - $doTbls = []; - - if (CLISetup::getOpt('help') && self::$mode == self::MODE_NORMAL) - { - self::printCLIHelp(); - return false; - } - - // required subScripts - if ($sync = CLISetup::getOpt('sync')) - { - foreach (self::$tables as $name => &$ssRef) - if (array_intersect($sync, $ssRef->getDependencies(false))) - $doTbls[] = $name; - - // recursive dependencies - foreach (self::$tables as $name => &$ssRef) - if (array_intersect($sync, $ssRef->getDependencies(true))) - $doTbls[] = $name; - - if (!$doTbls) - return false; - - $doTbls = array_unique($doTbls); - return true; - } - else if (is_array($_ = CLISetup::getOpt('sql'))) - { - $doTbls = $_; - return true; - } - - return false; - } - - private static function printCLIHelp() : void - { - CLI::write(); - CLI::write(' usage: php aowow --sql= [--mpqDataDir: --locales:]', -1, false); - CLI::write(); - CLI::write(' Regenerates DB table content for a given subScript. Dependencies are taken into account by the triggered calls of --sync and --update', -1, false); - - $lines = [['available subScripts', 'TC dependencies', 'AoWoW dependencies']]; - foreach (self::$tables as $tbl => &$ssRef) - { - $aoRef = $ssRef->getDependencies(true); - $len = 0; - $first = true; - - $row = [" * ".$tbl, '', '']; - - if ($tc = $ssRef->getDependencies(false)) - { - $len = 0; - foreach ($tc as $t) - { - $n = strlen($t) + 1; - if ($n + $len > 60) - { - // max 2 tables, no multi-row shenanigans required - if ($first && $aoRef) - $row[2] = implode(' ', $aoRef); - - $lines[] = $row; - $row = ['', '', '']; - $len = $n; - $first = false; - } - else - $len += $n; - - $row[1] .= $t." "; - } - } - - if ($first && $aoRef) - $row[2] = implode(' ', $aoRef); - - $lines[] = $row; - } - - CLI::writeTable($lines); - } - - public static function generate(string $tableName, array $updateIds = []) : bool - { - if (!isset(self::$tables[$tableName])) - { - CLI::write('SqlGen::generate - invalid table given', CLI::LOG_ERROR); - return false; - } - - $ssRef = &self::$tables[$tableName]; - - CLI::write('SqlGen::generate() - filling aowow_'.$tableName.' with data'); - - // check for required auxiliary DBC files - if (!in_array('TrDBCcopy', class_uses($ssRef))) - foreach ($ssRef->getRequiredDBCs() as $req) - if (!CLISetup::loadDBC($req)) - return false; - - if ($ssRef->generate($updateIds)) - { - if (method_exists($ssRef, 'applyCustomData')) - return $ssRef->applyCustomData(); - - return true; - } - - return false; - } - - public static function getMode() : int - { - return self::$mode; - } -} - -?> diff --git a/setup/tools/sqlgen/achievement.func.php b/setup/tools/sqlgen/achievement.func.php deleted file mode 100644 index 829259a42..000000000 --- a/setup/tools/sqlgen/achievement.func.php +++ /dev/null @@ -1,132 +0,0 @@ -query('REPLACE INTO ?_achievementcategory SELECT ac.id, IFNULL(ac.parentcategory, 0), IFNULL(ac1.parentcategory, 0) - FROM dbc_achievement_category ac LEFT JOIN dbc_achievement_category ac1 ON ac1.id = ac.parentCategory'); - - /************/ - /* dbc data */ - /************/ - - CLI::write(' - basic dbc data'); - - DB::Aowow()->query(' - REPLACE INTO - ?_achievement - SELECT - a.id, - 2 - a.faction, - a.map, - 0, - 0, - a.category, - ac.parentCategory, - a.points, - a.orderInGroup, - IFNULL(i.id, 0), - a.iconId, - a.flags, - a.reqCriteriaCount, - a.refAchievement, - 0, - 0, - a.name_loc0, a.name_loc2, a.name_loc3, a.name_loc4, a.name_loc6, a.name_loc8, - a.description_loc0, a.description_loc2, a.description_loc3, a.description_loc4, a.description_loc6, a.description_loc8, - a.reward_loc0, a.reward_loc2, a.reward_loc3, a.reward_loc4, a.reward_loc6, a.reward_loc8 - FROM - dbc_achievement a - LEFT JOIN - dbc_achievement_category ac ON ac.id = a.category - LEFT JOIN - dbc_spellicon si ON si.id = a.iconId - LEFT JOIN - ?_icons i ON LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) = i.name - { WHERE a.id IN (?a) } - ', $ids ?: DBSIMPLE_SKIP); - - - /*******************/ - /* serverside data */ - /*******************/ - - CLI::write(' - serverside achievement data'); - - $serverAchievements = DB::World()->select('SELECT ID, IF(requiredFaction = -1, 3, IF(requiredFaction = 0, 2, 1)) AS "faction", mapID, points, flags, count, refAchievement FROM achievement_dbc{ WHERE id IN (?a)}', - $ids ?: DBSIMPLE_SKIP - ); - foreach ($serverAchievements as $sa) - DB::Aowow()->query('REPLACE INTO ?_achievement (id, faction, map, points, flags, reqCriteriaCount, refAchievement, cuFlags, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8) VALUES (?d, ?d, ?d, ?d, ?d, ?d, ?d, ?d, ?, ?, ?, ?, ?, ?)', - $sa['ID'], $sa['faction'], $sa['mapID'], $sa['points'], $sa['flags'], $sa['count'], $sa['refAchievement'], CUSTOM_SERVERSIDE, - 'Serverside - #'.$sa['ID'], 'Serverside - #'.$sa['ID'], 'Serverside - #'.$sa['ID'], 'Serverside - #'.$sa['ID'], 'Serverside - #'.$sa['ID'], 'Serverside - #'.$sa['ID'] - ); - - - /********************************/ - /* create chain of achievements */ - /********************************/ - - CLI::write(' - linking achievements to chain'); - - $chainIdx = 0; - $parents = DB::Aowow()->selectCol('SELECT a.id FROM dbc_achievement a JOIN dbc_achievement b ON b.previous = a.id WHERE a.previous = 0'); - foreach ($parents as $chainId => $next) - { - $tree = [null, $next]; - while ($next = DB::Aowow()->selectCell('SELECT id FROM dbc_achievement WHERE previous = ?d', $next)) - $tree[] = $next; - - foreach ($tree as $idx => $aId) - { - if (!$aId) - continue; - - DB::Aowow()->query('UPDATE ?_achievement SET cuFlags = cuFlags | ?d, chainId = ?d, chainPos = ?d WHERE id = ?d', - $idx == 1 ? ACHIEVEMENT_CU_FIRST_SERIES : (count($tree) == $idx + 1 ? ACHIEVEMENT_CU_LAST_SERIES : 0), - $chainId + 1, - $idx, - $aId - ); - } - } - - - /*********************/ - /* applying disables */ - /*********************/ - - CLI::write(' - disabling disabled achievements from table disables'); - - if ($criteria = DB::World()->selectCol('SELECT entry FROM disables WHERE sourceType = 4')) - DB::Aowow()->query('UPDATE aowow_achievement a JOIN aowow_achievementcriteria ac ON a.id = ac.refAchievementId SET a.cuFlags = ?d WHERE ac.id IN (?a)', CUSTOM_DISABLED, $criteria); - - $this->reapplyCCFlags('achievement', Type::ACHIEVEMENT); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/achievement.ss.php b/setup/tools/sqlgen/achievement.ss.php new file mode 100644 index 000000000..791874566 --- /dev/null +++ b/setup/tools/sqlgen/achievement.ss.php @@ -0,0 +1,138 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Achievement from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['achievement_category', 'achievement', 'spellicon']; + protected $worldDependency = ['dbc_achievement', 'disables']; + protected $setupAfter = [['icons'], []]; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_achievement'); + DB::Aowow()->query('TRUNCATE ?_achievementcategory'); + + /**************/ + /* categories */ + /**************/ + + CLI::write('[achievement] - resolving categories'); + + DB::Aowow()->query('INSERT INTO ?_achievementcategory SELECT ac.id, GREATEST(ac.parentcategory, 0), GREATEST(IFNULL(ac1.parentcategory, 0), 0) FROM dbc_achievement_category ac LEFT JOIN dbc_achievement_category ac1 ON ac1.id = ac.parentCategory'); + + /************/ + /* dbc data */ + /************/ + + CLI::write('[achievement] - basic dbc data'); + + DB::Aowow()->query( + 'INSERT INTO ?_achievement + SELECT a.id, + 2 - a.faction, + a.map, + 0, + 0, + a.category, + ac.parentCategory, + a.points, + a.orderInGroup, + IFNULL(i.id, 0), + a.iconId, + a.flags, + a.reqCriteriaCount, + a.refAchievement, + 0, + 0, + a.name_loc0, a.name_loc2, a.name_loc3, a.name_loc4, a.name_loc6, a.name_loc8, + a.description_loc0, a.description_loc2, a.description_loc3, a.description_loc4, a.description_loc6, a.description_loc8, + a.reward_loc0, a.reward_loc2, a.reward_loc3, a.reward_loc4, a.reward_loc6, a.reward_loc8 + FROM dbc_achievement a + LEFT JOIN dbc_achievement_category ac ON ac.id = a.category + LEFT JOIN dbc_spellicon si ON si.id = a.iconId + LEFT JOIN ?_icons i ON LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) = i.name + { WHERE a.id IN (?a) }', + $ids ?: DBSIMPLE_SKIP + ); + + + /*******************/ + /* serverside data */ + /*******************/ + + CLI::write('[achievement] - serverside achievement data'); + + $serverAchievements = DB::World()->select('SELECT ID AS "id", IF(requiredFaction = -1, 3, IF(requiredFaction = 0, 2, 1)) AS "faction", mapID AS "map", points, flags, count AS "reqCriteriaCount", refAchievement FROM achievement_dbc{ WHERE id IN (?a)}', + $ids ?: DBSIMPLE_SKIP + ); + + foreach ($serverAchievements as &$sa) + { + $sa['cuFlags'] = CUSTOM_SERVERSIDE; + foreach (Util::$localeStrings as $i => $_) + if ($_) + $sa['name_loc'.$i] = 'Serverside - #'.$sa['id']; + } + + unset($sa); + + foreach ($serverAchievements as $sa) + DB::Aowow()->query('INSERT INTO ?_achievement (?#) VALUES (?a)', array_keys($sa), array_values($sa)); + + + /********************************/ + /* create chain of achievements */ + /********************************/ + + CLI::write('[achievement] - linking achievements to chain'); + + $parents = DB::Aowow()->selectCol('SELECT a.id FROM dbc_achievement a JOIN dbc_achievement b ON b.previous = a.id WHERE a.previous = 0'); + foreach ($parents as $chainId => $next) + { + $tree = [null, $next]; + while ($next = DB::Aowow()->selectCell('SELECT id FROM dbc_achievement WHERE previous = ?d', $next)) + $tree[] = $next; + + foreach ($tree as $idx => $aId) + { + if (!$aId) + continue; + + DB::Aowow()->query('UPDATE ?_achievement SET cuFlags = cuFlags | ?d, chainId = ?d, chainPos = ?d WHERE id = ?d', + $idx == 1 ? ACHIEVEMENT_CU_FIRST_SERIES : (count($tree) == $idx + 1 ? ACHIEVEMENT_CU_LAST_SERIES : 0), + $chainId + 1, + $idx, + $aId + ); + } + } + + + /*********************/ + /* applying disables */ + /*********************/ + + CLI::write('[achievement] - disabling disabled achievements from table disables'); + + if ($criteria = DB::World()->selectCol('SELECT entry FROM disables WHERE sourceType = 4')) + DB::Aowow()->query('UPDATE ?_achievement a JOIN ?_achievementcriteria ac ON a.id = ac.refAchievementId SET a.cuFlags = ?d WHERE ac.id IN (?a)', CUSTOM_DISABLED, $criteria); + + $this->reapplyCCFlags('achievement', Type::ACHIEVEMENT); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/achievementcriteria.func.php b/setup/tools/sqlgen/achievementcriteria.func.php deleted file mode 100644 index 7ba793df9..000000000 --- a/setup/tools/sqlgen/achievementcriteria.func.php +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/setup/tools/sqlgen/achievementcriteria.ss.php b/setup/tools/sqlgen/achievementcriteria.ss.php new file mode 100644 index 000000000..443b1282c --- /dev/null +++ b/setup/tools/sqlgen/achievementcriteria.ss.php @@ -0,0 +1,18 @@ + diff --git a/setup/tools/sqlgen/areatrigger.func.php b/setup/tools/sqlgen/areatrigger.ss.php similarity index 60% rename from setup/tools/sqlgen/areatrigger.func.php rename to setup/tools/sqlgen/areatrigger.ss.php index 6df6d0369..757835bdb 100644 --- a/setup/tools/sqlgen/areatrigger.func.php +++ b/setup/tools/sqlgen/areatrigger.ss.php @@ -7,12 +7,15 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup('sql', new class extends SetupScript { - protected $command = 'areatrigger'; + protected $info = array( + 'areatrigger' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Areatrigger from dbc and world db.'] + ); - protected $tblDependencyTC = ['areatrigger_involvedrelation', 'areatrigger_scripts', 'areatrigger_tavern', 'areatrigger_teleport', 'quest_template', 'quest_template_addon']; - protected $dbcSourceFiles = ['areatrigger', 'worldmaparea', 'dungeonmap']; + protected $dbcSourceFiles = ['areatrigger']; + protected $worldDependency = ['areatrigger_involvedrelation', 'areatrigger_scripts', 'areatrigger_tavern', 'areatrigger_teleport', 'quest_template', 'quest_template_addon']; + protected $setupAfter = [['dungeonmap', 'worldmaparea'], []]; public function generate(array $ids = []) : bool { @@ -25,22 +28,22 @@ public function generate(array $ids = []) : bool */ // 1: Taverns - CLI::write(' - fetching taverns'); + CLI::write('[areatrigger] - fetching taverns'); $addData = DB::World()->select('SELECT `id` AS ARRAY_KEY, `name`, ?d AS `type` FROM areatrigger_tavern', AT_TYPE_TAVERN); foreach ($addData as $id => $ad) DB::Aowow()->query('UPDATE ?_areatrigger SET ?a WHERE `id` = ?d', $ad, $id); // 2: Teleporter + teleporting 4: Smart Trigger - CLI::write(' - calculation teleporter coordinates'); + CLI::write('[areatrigger] - calculation teleporter coordinates'); $addData = DB::World()->select( - 'SELECT `ID` AS ARRAY_KEY, `Name` AS `name`, `target_map` AS `map`, `target_position_x` AS `posY`, `target_position_y` AS `posX`, `target_orientation` AS `orientation` + 'SELECT ID AS ARRAY_KEY, Name AS `name`, target_map AS `map`, target_position_x AS `posY`, target_position_y AS `posX`, target_orientation AS `orientation` FROM areatrigger_teleport UNION - SELECT `entryorguid` AS ARRAY_KEY, "TBD" AS `name`, `action_param1` AS `map`, `target_x` AS `posY`, `target_y` AS `posX`, `target_o` AS `orientation` + SELECT entryorguid AS ARRAY_KEY, "TBD" AS `name`, action_param1 AS `map`, target_x AS `posY`, target_y AS `posX`, target_o AS `orientation` FROM smart_scripts - WHERE `source_type` = 2 AND `action_type` = 62' + WHERE source_type = 2 AND action_type = 62' ); foreach ($addData as $id => $ad) @@ -48,7 +51,7 @@ public function generate(array $ids = []) : bool $points = Game::worldPosToZonePos($ad['map'], $ad['posX'], $ad['posY']); if (!$points) { - CLI::write(' * AT '.$id.' teleporter endpoint '.CLI::bold($ad['name']).' could not be matched to displayable area [M:'.$ad['map'].'; X:'.$ad['posY'].'; Y:'.$ad['posX'].']', CLI::LOG_WARN); + CLI::write('[areatrigger] '.str_pad('['.$id.']', 8).' teleporter endpoint '.CLI::bold($ad['name']).' could not be matched to displayable area [M:'.$ad['map'].'; X:'.$ad['posY'].'; Y:'.$ad['posX'].']', CLI::LOG_WARN); DB::Aowow()->query('UPDATE ?_areatrigger SET `name` = ?, `type` = ?d WHERE `id` = ?d', $ad['name'], AT_TYPE_TELEPORT, $id); continue; } @@ -67,19 +70,19 @@ public function generate(array $ids = []) : bool } // 3: Quest Objectives - CLI::write(' - satisfying quest objectives'); + CLI::write('[areatrigger] - satisfying quest objectives'); - $addData = DB::World()->select('SELECT atir.`id` AS ARRAY_KEY, qt.`ID` AS `quest`, NULLIF(qt.AreaDescription, "") AS `name`, qta.`SpecialFlags` FROM quest_template qt LEFT JOIN quest_template_addon qta ON qta.`ID` = qt.`ID` JOIN areatrigger_involvedrelation atir ON atir.`quest` = qt.`ID`'); + $addData = DB::World()->select('SELECT atir.`id` AS ARRAY_KEY, `qt`.ID AS `quest`, NULLIF(qt.`AreaDescription`, "") AS `name`, qta.`SpecialFlags` FROM quest_template qt LEFT JOIN quest_template_addon qta ON qta.`ID` = qt.`ID` JOIN areatrigger_involvedrelation atir ON atir.`quest` = qt.`ID`'); foreach ($addData as $id => $ad) { if (!($ad['SpecialFlags'] & QUEST_FLAG_SPECIAL_EXT_COMPLETE)) - CLI::write(' * Areatrigger '.CLI::bold($id).' is involved in quest '.CLI::bold($ad['quest']).', but quest is not flagged for external completion (SpecialFlags & '.Util::asHex(QUEST_FLAG_SPECIAL_EXT_COMPLETE).')', CLI::LOG_WARN); + CLI::write('[areatrigger] '.str_pad('['.$id.']', 8).' is involved in quest '.CLI::bold($ad['quest']).', but quest is not flagged for external completion (SpecialFlags & '.Util::asHex(QUEST_FLAG_SPECIAL_EXT_COMPLETE).')', CLI::LOG_WARN); - DB::Aowow()->query('UPDATE ?_areatrigger SET `name` = ?, `type` = ?d, `quest` = ?d WHERE `id` = ?d', $ad['name'], AT_TYPE_OBJECTIVE, $ad['quest'], $id); + DB::Aowow()->query('UPDATE ?_areatrigger SET `name` = ?, type = ?d, `quest` = ?d WHERE `id` = ?d', $ad['name'], AT_TYPE_OBJECTIVE, $ad['quest'], $id); } // 4/5 Scripted - CLI::write(' - assigning scripts'); + CLI::write('[areatrigger] - assigning scripts'); $addData = DB::World()->select('SELECT `entry` AS ARRAY_KEY, IF(`ScriptName` = "SmartTrigger", NULL, `ScriptName`) AS `name`, IF(`ScriptName` = "SmartTrigger", 4, 5) AS `type` FROM areatrigger_scripts'); foreach ($addData as $id => $ad) diff --git a/setup/tools/sqlgen/classes.func.php b/setup/tools/sqlgen/classes.ss.php similarity index 86% rename from setup/tools/sqlgen/classes.func.php rename to setup/tools/sqlgen/classes.ss.php index c3fb703f6..7c6dd6ca0 100644 --- a/setup/tools/sqlgen/classes.func.php +++ b/setup/tools/sqlgen/classes.ss.php @@ -7,16 +7,20 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrCustomData; // import custom data from DB - protected $command = 'classes'; + protected $info = array( + 'classes' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: PlayerClass from dbc.'] + ); protected $dbcSourceFiles = ['spell', 'charbaseinfo', 'skillraceclassinfo', 'skilllineability', 'chrclasses']; public function generate(array $ids = []) : bool { + DB::Aowow()->query('TRUNCATE ?_classes'); + $classes = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM dbc_chrclasses'); // add raceMask @@ -36,7 +40,7 @@ public function generate(array $ids = []) : bool } foreach ($classes as $cl) - DB::Aowow()->query('REPLACE INTO ?_classes (?#) VALUES (?a)', array_keys($cl), array_values($cl)); + DB::Aowow()->query('INSERT INTO ?_classes (?#) VALUES (?a)', array_keys($cl), array_values($cl)); $this->reapplyCCFlags('classes', Type::CHR_CLASS); diff --git a/setup/tools/sqlgen/creature.func.php b/setup/tools/sqlgen/creature.func.php deleted file mode 100644 index 245a01dc0..000000000 --- a/setup/tools/sqlgen/creature.func.php +++ /dev/null @@ -1,185 +0,0 @@ - 4, 0, `rank`), - dmgSchool, - DamageModifier, - BaseAttackTime, - RangeAttackTime, - BaseVariance, - RangeVariance, - unit_class, - unit_flags, unit_flags2, dynamicflags, - family, - IFNULL(t.Type, 0), - IFNULL(t.Requirement, 0), - (CASE ct.exp WHEN 0 THEN min.damage_base WHEN 1 THEN min.damage_exp1 ELSE min.damage_exp2 END) AS dmgMin, - (CASE ct.exp WHEN 0 THEN max.damage_base WHEN 1 THEN max.damage_exp1 ELSE max.damage_exp2 END) AS dmgMax, - min.attackpower AS mleAtkPwrMin, - max.attackpower AS mleAtkPwrMax, - min.rangedattackpower AS rmgAtkPwrMin, - max.rangedattackpower AS rmgAtkPwrMax, - ct.type, - type_flags, - lootid, pickpocketloot, skinloot, - IFNULL(cts0.Spell, 0), IFNULL(cts1.Spell, 0), IFNULL(cts2.Spell, 0), IFNULL(cts3.Spell, 0), IFNULL(cts4.Spell, 0), IFNULL(cts5.Spell, 0), IFNULL(cts6.Spell, 0), IFNULL(cts7.Spell, 0), - PetSpellDataId, - VehicleId, - mingold, maxgold, - AIName, - (CASE ct.exp WHEN 0 THEN min.basehp0 WHEN 1 THEN min.basehp1 ELSE min.basehp2 END) * ct.HealthModifier AS healthMin, - (CASE ct.exp WHEN 0 THEN max.basehp0 WHEN 1 THEN max.basehp1 ELSE max.basehp2 END) * ct.HealthModifier AS healthMax, - min.basemana * ct.ManaModifier AS manaMin, - max.basemana * ct.ManaModifier AS manaMax, - min.basearmor * ct.ArmorModifier AS armorMin, - max.basearmor * ct.ArmorModifier AS armorMax, - IFNULL(ctr1.Resistance, 0), IFNULL(ctr2.Resistance, 0), IFNULL(ctr3.Resistance, 0), IFNULL(ctr4.Resistance, 0), IFNULL(ctr5.Resistance, 0), IFNULL(ctr6.Resistance, 0), - RacialLeader, - mechanic_immune_mask, - flags_extra, - ScriptName - FROM - creature_template ct - JOIN - creature_classlevelstats min ON ct.unit_class = min.class AND ct.minlevel = min.level - JOIN - creature_classlevelstats max ON ct.unit_class = max.class AND ct.maxlevel = max.level - LEFT JOIN - creature_default_trainer cdt ON cdt.CreatureId = ct.entry - LEFT JOIN - trainer t ON t.Id = cdt.TrainerId - LEFT JOIN - creature_template_locale ctl2 ON ct.entry = ctl2.entry AND ctl2.`locale` = "frFR" - LEFT JOIN - creature_template_locale ctl3 ON ct.entry = ctl3.entry AND ctl3.`locale` = "deDE" - LEFT JOIN - creature_template_locale ctl4 ON ct.entry = ctl4.entry AND ctl4.`locale` = "zhCN" - LEFT JOIN - creature_template_locale ctl6 ON ct.entry = ctl6.entry AND ctl6.`locale` = "esES" - LEFT JOIN - creature_template_locale ctl8 ON ct.entry = ctl8.entry AND ctl8.`locale` = "ruRU" - LEFT JOIN - (SELECT creditEntry FROM instance_encounters WHERE creditType = 0 GROUP BY creditEntry) ie ON ie.creditEntry = ct.entry - LEFT JOIN - creature_template_spell cts0 ON ct.entry = cts0.CreatureID AND cts0.Index = 0 - LEFT JOIN - creature_template_spell cts1 ON ct.entry = cts1.CreatureID AND cts1.Index = 1 - LEFT JOIN - creature_template_spell cts2 ON ct.entry = cts2.CreatureID AND cts2.Index = 2 - LEFT JOIN - creature_template_spell cts3 ON ct.entry = cts3.CreatureID AND cts3.Index = 3 - LEFT JOIN - creature_template_spell cts4 ON ct.entry = cts4.CreatureID AND cts4.Index = 4 - LEFT JOIN - creature_template_spell cts5 ON ct.entry = cts5.CreatureID AND cts5.Index = 5 - LEFT JOIN - creature_template_spell cts6 ON ct.entry = cts6.CreatureID AND cts6.Index = 6 - LEFT JOIN - creature_template_spell cts7 ON ct.entry = cts7.CreatureID AND cts7.Index = 7 - LEFT JOIN - creature_template_resistance ctr1 ON ct.entry = ctr1.CreatureID AND ctr1.School = 1 - LEFT JOIN - creature_template_resistance ctr2 ON ct.entry = ctr2.CreatureID AND ctr2.School = 2 - LEFT JOIN - creature_template_resistance ctr3 ON ct.entry = ctr3.CreatureID AND ctr3.School = 3 - LEFT JOIN - creature_template_resistance ctr4 ON ct.entry = ctr4.CreatureID AND ctr4.School = 4 - LEFT JOIN - creature_template_resistance ctr5 ON ct.entry = ctr5.CreatureID AND ctr5.School = 5 - LEFT JOIN - creature_template_resistance ctr6 ON ct.entry = ctr6.CreatureID AND ctr6.School = 6 - { - WHERE - ct.entry IN (?a) - } - LIMIT - ?d, ?d'; - - $dummyQuery = ' - UPDATE - ?_creature a - JOIN - ( - SELECT b.difficultyEntry1 AS dummy FROM ?_creature b UNION - SELECT c.difficultyEntry2 AS dummy FROM ?_creature c UNION - SELECT d.difficultyEntry3 AS dummy FROM ?_creature d - ) j - SET - a.cuFlags = a.cuFlags | ?d - WHERE - a.id = j.dummy'; - - $displayInfoQuery = ' - UPDATE - ?_creature c - JOIN - dbc_creaturedisplayinfo cdi ON c.displayId1 = cdi.id - LEFT JOIN - dbc_creaturedisplayinfoextra cdie ON cdi.extraInfoId = cdie.id - SET - c.textureString = IFNULL(cdie.textureString, cdi.skin1), - c.modelId = cdi.modelId, - c.iconString = cdi.iconString, - c.humanoid = IF(cdie.id IS NULL, 0, 1)'; - - $i = 0; - DB::Aowow()->query('TRUNCATE ?_creature'); - while ($npcs = DB::World()->select($baseQuery, NPC_CU_INSTANCE_BOSS, $ids ?: DBSIMPLE_SKIP, SqlGen::$sqlBatchSize * $i, SqlGen::$sqlBatchSize)) - { - CLI::write(' * batch #' . ++$i . ' (' . count($npcs) . ')', CLI::LOG_BLANK, true, true); - - foreach ($npcs as $npc) - DB::Aowow()->query('INSERT INTO ?_creature VALUES (?a)', array_values($npc)); - } - - // apply "textureString", "modelId" and "iconSring" - DB::Aowow()->query($displayInfoQuery); - - // apply cuFlag: difficultyDummy - DB::Aowow()->query($dummyQuery, NPC_CU_DIFFICULTY_DUMMY | CUSTOM_EXCLUDE_FOR_LISTVIEW); - - // apply cuFlag: excludeFromListview [for trigger-creatures] - DB::Aowow()->query('UPDATE ?_creature SET cuFlags = cuFlags | ?d WHERE flagsExtra & ?d', CUSTOM_EXCLUDE_FOR_LISTVIEW, 0x80); - - // apply cuFlag: exCludeFromListview [for nameparts indicating internal usage] - DB::Aowow()->query('UPDATE ?_creature SET cuFlags = cuFlags | ?d WHERE name_loc0 LIKE "%[%" OR name_loc0 LIKE "%(%" OR name_loc0 LIKE "%visual%" OR name_loc0 LIKE "%trigger%" OR name_loc0 LIKE "%credit%" OR name_loc0 LIKE "%marker%"', CUSTOM_EXCLUDE_FOR_LISTVIEW); - - $this->reapplyCCFlags('creature', Type::NPC); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/creature.ss.php b/setup/tools/sqlgen/creature.ss.php new file mode 100644 index 000000000..35add3a01 --- /dev/null +++ b/setup/tools/sqlgen/creature.ss.php @@ -0,0 +1,146 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: NPC from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['creaturedisplayinfo', 'creaturedisplayinfoextra']; + protected $worldDependency = ['creature_template', 'creature_template_locale', 'creature_template_resistance', 'creature_template_spell', 'creature_classlevelstats', 'creature_default_trainer', 'trainer', 'instance_encounters']; + + public function generate(array $ids = []) : bool + { + $baseQuery = + 'SELECT ct.entry, + IF(ie.creditEntry IS NULL, 0, ?d) AS cuFlags, + difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, + KillCredit1, KillCredit2, + modelid1, modelid2, modelid3, modelid4, + "" AS textureString, + 0 AS modelId, + 0 AS humanoid, -- uses creaturedisplayinfoextra + "" AS iconString, + ct.name, IFNULL(ctl2.`Name`, "") AS n2, IFNULL(ctl3.`Name`, "") AS n3, IFNULL(ctl4.`Name`, "") AS n4, IFNULL(ctl6.`Name`, "") AS n6, IFNULL(ctl8.`Name`, "") AS n8, + subname, IFNULL(ctl2.`Title`, "") AS t2, IFNULL(ctl3.`Title`, "") AS t3, IFNULL(ctl4.`Title`, "") AS t4, IFNULL(ctl6.`Title`, "") AS t6, IFNULL(ctl8.`Title`, "") AS t8, + minLevel, maxLevel, + exp, + faction, + npcflag, + IF(`rank` > 4, 0, `rank`), + dmgSchool, + DamageModifier, + BaseAttackTime, + RangeAttackTime, + BaseVariance, + RangeVariance, + unit_class, + unit_flags, unit_flags2, dynamicflags, + family, + IFNULL(t.Type, 0), + IFNULL(t.Requirement, 0), + (CASE ct.exp WHEN 0 THEN min.damage_base WHEN 1 THEN min.damage_exp1 ELSE min.damage_exp2 END) AS dmgMin, + (CASE ct.exp WHEN 0 THEN max.damage_base WHEN 1 THEN max.damage_exp1 ELSE max.damage_exp2 END) AS dmgMax, + min.attackpower AS mleAtkPwrMin, + max.attackpower AS mleAtkPwrMax, + min.rangedattackpower AS rmgAtkPwrMin, + max.rangedattackpower AS rmgAtkPwrMax, + ct.type, + type_flags, + lootid, pickpocketloot, skinloot, + IFNULL(cts0.Spell, 0), IFNULL(cts1.Spell, 0), IFNULL(cts2.Spell, 0), IFNULL(cts3.Spell, 0), IFNULL(cts4.Spell, 0), IFNULL(cts5.Spell, 0), IFNULL(cts6.Spell, 0), IFNULL(cts7.Spell, 0), + PetSpellDataId, + VehicleId, + mingold, maxgold, + AIName, + (CASE ct.exp WHEN 0 THEN min.basehp0 WHEN 1 THEN min.basehp1 ELSE min.basehp2 END) * ct.HealthModifier AS healthMin, + (CASE ct.exp WHEN 0 THEN max.basehp0 WHEN 1 THEN max.basehp1 ELSE max.basehp2 END) * ct.HealthModifier AS healthMax, + min.basemana * ct.ManaModifier AS manaMin, + max.basemana * ct.ManaModifier AS manaMax, + min.basearmor * ct.ArmorModifier AS armorMin, + max.basearmor * ct.ArmorModifier AS armorMax, + IFNULL(ctr1.Resistance, 0), IFNULL(ctr2.Resistance, 0), IFNULL(ctr3.Resistance, 0), IFNULL(ctr4.Resistance, 0), IFNULL(ctr5.Resistance, 0), IFNULL(ctr6.Resistance, 0), + RacialLeader, + mechanic_immune_mask, + flags_extra, + ScriptName + FROM creature_template ct + JOIN creature_classlevelstats min ON ct.unit_class = min.class AND ct.minlevel = min.level + JOIN creature_classlevelstats max ON ct.unit_class = max.class AND ct.maxlevel = max.level + LEFT JOIN creature_default_trainer cdt ON cdt.CreatureId = ct.entry + LEFT JOIN trainer t ON t.Id = cdt.TrainerId + LEFT JOIN creature_template_locale ctl2 ON ct.entry = ctl2.entry AND ctl2.`locale` = "frFR" + LEFT JOIN creature_template_locale ctl3 ON ct.entry = ctl3.entry AND ctl3.`locale` = "deDE" + LEFT JOIN creature_template_locale ctl4 ON ct.entry = ctl4.entry AND ctl4.`locale` = "zhCN" + LEFT JOIN creature_template_locale ctl6 ON ct.entry = ctl6.entry AND ctl6.`locale` = "esES" + LEFT JOIN creature_template_locale ctl8 ON ct.entry = ctl8.entry AND ctl8.`locale` = "ruRU" + LEFT JOIN (SELECT creditEntry FROM instance_encounters WHERE creditType = 0 GROUP BY creditEntry) ie ON ie.creditEntry = ct.entry + LEFT JOIN creature_template_spell cts0 ON ct.entry = cts0.CreatureID AND cts0.Index = 0 + LEFT JOIN creature_template_spell cts1 ON ct.entry = cts1.CreatureID AND cts1.Index = 1 + LEFT JOIN creature_template_spell cts2 ON ct.entry = cts2.CreatureID AND cts2.Index = 2 + LEFT JOIN creature_template_spell cts3 ON ct.entry = cts3.CreatureID AND cts3.Index = 3 + LEFT JOIN creature_template_spell cts4 ON ct.entry = cts4.CreatureID AND cts4.Index = 4 + LEFT JOIN creature_template_spell cts5 ON ct.entry = cts5.CreatureID AND cts5.Index = 5 + LEFT JOIN creature_template_spell cts6 ON ct.entry = cts6.CreatureID AND cts6.Index = 6 + LEFT JOIN creature_template_spell cts7 ON ct.entry = cts7.CreatureID AND cts7.Index = 7 + LEFT JOIN creature_template_resistance ctr1 ON ct.entry = ctr1.CreatureID AND ctr1.School = 1 + LEFT JOIN creature_template_resistance ctr2 ON ct.entry = ctr2.CreatureID AND ctr2.School = 2 + LEFT JOIN creature_template_resistance ctr3 ON ct.entry = ctr3.CreatureID AND ctr3.School = 3 + LEFT JOIN creature_template_resistance ctr4 ON ct.entry = ctr4.CreatureID AND ctr4.School = 4 + LEFT JOIN creature_template_resistance ctr5 ON ct.entry = ctr5.CreatureID AND ctr5.School = 5 + LEFT JOIN creature_template_resistance ctr6 ON ct.entry = ctr6.CreatureID AND ctr6.School = 6 + { WHERE ct.entry IN (?a) } + LIMIT ?d, ?d'; + + $i = 0; + DB::Aowow()->query('TRUNCATE ?_creature'); + while ($npcs = DB::World()->select($baseQuery, NPC_CU_INSTANCE_BOSS, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) + { + CLI::write(' * batch #' . ++$i . ' (' . count($npcs) . ')', CLI::LOG_BLANK, true, true); + + foreach ($npcs as $npc) + DB::Aowow()->query('INSERT INTO ?_creature VALUES (?a)', array_values($npc)); + } + + // apply "textureString", "modelId" and "iconSring" + DB::Aowow()->query( + 'UPDATE ?_creature c + JOIN dbc_creaturedisplayinfo cdi ON c.displayId1 = cdi.id + LEFT JOIN dbc_creaturedisplayinfoextra cdie ON cdi.extraInfoId = cdie.id + SET c.textureString = IFNULL(cdie.textureString, cdi.skin1), + c.modelId = cdi.modelId, + c.iconString = cdi.iconString, + c.humanoid = IF(cdie.id IS NULL, 0, 1)' + ); + + // apply cuFlag: difficultyDummy + DB::Aowow()->query( + 'UPDATE ?_creature a + JOIN (SELECT b.difficultyEntry1 AS dummy FROM ?_creature b UNION + SELECT c.difficultyEntry2 AS dummy FROM ?_creature c UNION + SELECT d.difficultyEntry3 AS dummy FROM ?_creature d) j + SET a.cuFlags = a.cuFlags | ?d + WHERE a.id = j.dummy', + NPC_CU_DIFFICULTY_DUMMY | CUSTOM_EXCLUDE_FOR_LISTVIEW + ); + + // apply cuFlag: excludeFromListview [for trigger-creatures] + DB::Aowow()->query('UPDATE ?_creature SET cuFlags = cuFlags | ?d WHERE flagsExtra & ?d', CUSTOM_EXCLUDE_FOR_LISTVIEW, 0x80); + + // apply cuFlag: exCludeFromListview [for nameparts indicating internal usage] + DB::Aowow()->query('UPDATE ?_creature SET cuFlags = cuFlags | ?d WHERE name_loc0 LIKE "%[%" OR name_loc0 LIKE "%(%" OR name_loc0 LIKE "%visual%" OR name_loc0 LIKE "%trigger%" OR name_loc0 LIKE "%credit%" OR name_loc0 LIKE "%marker%"', CUSTOM_EXCLUDE_FOR_LISTVIEW); + + $this->reapplyCCFlags('creature', Type::NPC); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/currencies.func.php b/setup/tools/sqlgen/currencies.func.php deleted file mode 100644 index ca748fdd9..000000000 --- a/setup/tools/sqlgen/currencies.func.php +++ /dev/null @@ -1,84 +0,0 @@ -query('REPLACE INTO ?_currencies (`id`, `category`, `itemId`) SELECT `id`, LEAST(`category`, 41), `itemId` FROM dbc_currencytypes'); - - $moneyItems = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `itemId` FROM dbc_currencytypes{ WHERE `id` IN (?a)}', $ids ?: DBSIMPLE_SKIP); - - // apply names & cap - $moneyNames = DB::World()->select(' - SELECT - it.entry AS ARRAY_KEY, - it.name AS name_loc0, IFNULL(itl2.Name, "") AS name_loc2, IFNULL(itl3.Name, "") AS name_loc3, IFNULL(itl4.Name, "") AS name_loc4, IFNULL(itl6.Name, "") AS name_loc6, IFNULL(itl8.Name, "") AS name_loc8, - it.maxCount AS cap - FROM - item_template it - LEFT JOIN - item_template_locale itl2 ON it.entry = itl2.ID AND itl2.locale = "frFR" - LEFT JOIN - item_template_locale itl3 ON it.entry = itl3.ID AND itl3.locale = "deDE" - LEFT JOIN - item_template_locale itl4 ON it.entry = itl4.ID AND itl4.locale = "zhCN" - LEFT JOIN - item_template_locale itl6 ON it.entry = itl6.ID AND itl6.locale = "esES" - LEFT JOIN - item_template_locale itl8 ON it.entry = itl8.ID AND itl8.locale = "ruRU" - WHERE - it.entry IN (?a)', - $moneyItems); - - foreach ($moneyItems as $cId => $itemId) - { - if (!empty($moneyNames[$itemId])) - $strings = $moneyNames[$itemId]; - else - { - CLI::write('item #'.$itemId.' referenced by currency #'.$cId.' not in item_template', CLI::LOG_WARN); - $strings = ['name_loc0' => 'Item #'.$itemId.' not in DB', 'iconId' => 0, 'cuFlags' => CUSTOM_EXCLUDE_FOR_LISTVIEW, 'category' => 3]; - } - - DB::Aowow()->query('UPDATE ?_currencies SET ?a WHERE itemId = ?d', $strings, $itemId); - } - - // apply icons - $displayIds = DB::World()->selectCol('SELECT entry AS ARRAY_KEY, displayid FROM item_template WHERE entry IN (?a)', $moneyItems); - foreach ($displayIds as $itemId => $iconId) - DB::Aowow()->query(' - UPDATE - ?_currencies c, - ?_icons i, - dbc_itemdisplayinfo idi - SET - c.iconId = i.id - WHERE - i.name = LOWER(idi.inventoryIcon1) AND - idi.id = ?d AND - c.itemId = ?d - ', $iconId, $itemId); - - $this->reapplyCCFlags('currencies', Type::CURRENCY); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/currencies.ss.php b/setup/tools/sqlgen/currencies.ss.php new file mode 100644 index 000000000..70924937d --- /dev/null +++ b/setup/tools/sqlgen/currencies.ss.php @@ -0,0 +1,73 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Currency from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['itemdisplayinfo', 'currencytypes']; + protected $worldDependency = ['item_template', 'item_template_locale']; + protected $setupAfter = [['icons'], []]; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_currencies'); + DB::Aowow()->query('INSERT INTO ?_currencies (`id`, `category`, `itemId`) SELECT `id`, LEAST(`category`, 41), `itemId` FROM dbc_currencytypes'); + + $moneyItems = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `itemId` FROM dbc_currencytypes{ WHERE `id` IN (?a)}', $ids ?: DBSIMPLE_SKIP); + + // apply names & cap + $moneyNames = DB::World()->select( + 'SELECT it.`entry` AS ARRAY_KEY, + it.`name` AS `name_loc0`, IFNULL(itl2.`Name`, "") AS `name_loc2`, IFNULL(itl3.`Name`, "") AS `name_loc3`, IFNULL(itl4.`Name`, "") AS `name_loc4`, IFNULL(itl6.`Name`, "") AS `name_loc6`, IFNULL(itl8.`Name`, "") AS `name_loc8`, + it.`maxCount` AS `cap` + FROM item_template it + LEFT JOIN item_template_locale itl2 ON it.entry = itl2.ID AND itl2.locale = "frFR" + LEFT JOIN item_template_locale itl3 ON it.entry = itl3.ID AND itl3.locale = "deDE" + LEFT JOIN item_template_locale itl4 ON it.entry = itl4.ID AND itl4.locale = "zhCN" + LEFT JOIN item_template_locale itl6 ON it.entry = itl6.ID AND itl6.locale = "esES" + LEFT JOIN item_template_locale itl8 ON it.entry = itl8.ID AND itl8.locale = "ruRU" + WHERE it.entry IN (?a)', + $moneyItems + ); + + foreach ($moneyItems as $cId => $itemId) + { + if (!empty($moneyNames[$itemId])) + $strings = $moneyNames[$itemId]; + else + { + CLI::write('[currencies] '.str_pad('['.$cId.']', 6).' referenced item '.CLI::bold($itemId).' is not in item_template', CLI::LOG_WARN); + $strings = ['name_loc0' => 'Item #'.$itemId.' not in DB', 'iconId' => 0, 'cuFlags' => CUSTOM_EXCLUDE_FOR_LISTVIEW, 'category' => 3]; + } + + DB::Aowow()->query('UPDATE ?_currencies SET ?a WHERE itemId = ?d', $strings, $itemId); + } + + // apply icons + $displayIds = DB::World()->selectCol('SELECT `entry` AS ARRAY_KEY, `displayid` FROM item_template WHERE `entry` IN (?a)', $moneyItems); + foreach ($displayIds as $itemId => $iconId) + DB::Aowow()->query( + 'UPDATE ?_currencies c, ?_icons i, dbc_itemdisplayinfo idi + SET c.`iconId` = i.`id` + WHERE i.`name` = LOWER(idi.`inventoryIcon1`) AND idi.`id` = ?d AND c.`itemId` = ?d', + $iconId, $itemId + ); + + $this->reapplyCCFlags('currencies', Type::CURRENCY); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/declinedword.func.php b/setup/tools/sqlgen/declinedword.ss.php similarity index 54% rename from setup/tools/sqlgen/declinedword.func.php rename to setup/tools/sqlgen/declinedword.ss.php index a5992c6d7..28b4b739c 100644 --- a/setup/tools/sqlgen/declinedword.func.php +++ b/setup/tools/sqlgen/declinedword.ss.php @@ -7,18 +7,21 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'declinedwords'; + protected $info = array( + 'declinedwords' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Enchantment from dbc and world db.'] + ); + protected $dbcSourceFiles = ['declinedword', 'declinedwordcases']; - public function generate() : bool + public function generate(array $ids = []) : bool { - CLI::write('SqlGen::generate() - copying declinedword.dbc into aowow_declinedword'); + CLI::write('[declinedwords] - copying declinedword.dbc into aowow_declinedword'); DB::Aowow()->query('TRUNCATE ?_declinedword'); DB::Aowow()->query('INSERT INTO ?_declinedword SELECT * FROM dbc_declinedword'); - CLI::write('SqlGen::generate() - copying declinedwordcases.dbc into aowow_declinedwordcases'); + CLI::write('[declinedwords] - copying declinedwordcases.dbc into aowow_declinedwordcases'); DB::Aowow()->query('TRUNCATE ?_declinedwordcases'); DB::Aowow()->query('INSERT INTO ?_declinedwordcases SELECT `wordId`, `caseIdx`, `word` FROM dbc_declinedwordcases'); diff --git a/setup/tools/sqlgen/dungeonmap.func.php b/setup/tools/sqlgen/dungeonmap.ss.php similarity index 79% rename from setup/tools/sqlgen/dungeonmap.func.php rename to setup/tools/sqlgen/dungeonmap.ss.php index 0537c18b4..42d2d3727 100644 --- a/setup/tools/sqlgen/dungeonmap.func.php +++ b/setup/tools/sqlgen/dungeonmap.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup('sql', new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/emotes.func.php b/setup/tools/sqlgen/emotes.ss.php similarity index 81% rename from setup/tools/sqlgen/emotes.func.php rename to setup/tools/sqlgen/emotes.ss.php index b9aecc8c8..eca512d60 100644 --- a/setup/tools/sqlgen/emotes.func.php +++ b/setup/tools/sqlgen/emotes.ss.php @@ -7,116 +7,38 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'emotes'; + protected $info = array( + 'emotes' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Emote from dbc and GlobalStrings.lua.'] + ); - protected $dbcSourceFiles = ['emotes', 'emotestext', 'emotestextdata']; + protected $useGlobalStrings = true; + protected $dbcSourceFiles = ['emotes', 'emotestext', 'emotestextdata']; private $textData = []; - private function mergeGenderedStrings(int $maleTextId, int $femaleTextId, int $locale) : string + public function generate(array $ids = []) : bool { - $maleText = $this->textData[$maleTextId][$locale] ?? ''; - $femaleText = $this->textData[$femaleTextId][$locale] ?? ''; - - if (!$maleText && !$femaleText) - return ''; - else if (!$maleText) - return $femaleText; - else if (!$femaleText) - return $maleText; - else if ($maleText == $femaleText) - return $maleText; - - $front = $back = []; - - $m = explode(' ', $maleText); - $f = explode(' ', $femaleText); - $n = max(count($m), count($f)); - - // advance from left until diff -> store common string - // advance from right until diff -> store common string - // merge leftovers as gendered switch and recombine string - - $i = 0; - for (; $i < $n; $i++) - { - if (!isset($m[$i]) || !isset($f[$i])) - break; - else if ($m[$i] != $f[$i]) - break; - else - $front[] = $m[$i]; - } - - if ($i + 1 == $n) // loop completed all elements - return implode(' ', $m); - - $m = array_reverse($m); - $f = array_reverse($f); + $globStrPath = CLISetup::$srcDir.'%sInterface/FrameXML/GlobalStrings.lua'; + $allOK = true; + $locPath = CLISetup::filesInPathLocalized($globStrPath, $allOK); - $j = 0; - for (; $j < $n - $i; $j++) + if ($x = array_diff(CLISetup::$localeIds, array_keys($locPath))) { - if (!isset($m[$j]) || !isset($f[$j])) - break; - else if ($m[$j] != $f[$j]) - break; - else - $back[] = $m[$j]; + $locs = array_intersect_key(CLISetup::$locales, array_flip($x)); + CLI::write('[emotes] '.sprintf($globStrPath, CLI::bold('['.implode('/,', $locs).'/]')) . ' not found!', CLI::LOG_WARN); + CLI::write(' Emote aliasses can not be generated for affected locales!', CLI::LOG_WARN); } - $m = array_reverse($m); - $f = array_reverse($f); - - $midM = array_slice($m, $i, count($m) - $i - $j); - $midF = array_slice($f, $i, count($f) - $i - $j); - - $mid = '$g' . implode(' ', $midM ?? []) . ':' . implode(' ', $midF ?? []) . ';'; - - return implode(' ', array_merge($front, [$mid], array_reverse($back))); - } - - public function generate(array $ids = []) : bool - { - /**********/ - /* Basics */ - /**********/ - - $globStrPath = CLISetup::$srcDir.'%sInterface/FrameXML/GlobalStrings.lua'; - $allOK = true; - $locPath = []; - DB::Aowow()->query('TRUNCATE ?_emotes'); DB::Aowow()->query('TRUNCATE ?_emotes_aliasses'); + /*********************/ /* Player controlled */ /*********************/ - foreach (CLISetup::$localeIds as $lId) - { - foreach (CLISetup::$expectedPaths as $xp => $locId) - { - if ($lId != $locId) - continue; - - if ($xp) // if in subDir add trailing slash - $xp .= '/'; - - $path = sprintf($globStrPath, $xp); - if (CLISetup::fileExists($path)) - { - $locPath[$lId] = $path; - continue 2; - } - } - - CLI::write('GlobalStrings.lua not found for selected locale '.CLI::bold(Util::$localeStrings[$lId]), CLI::LOG_WARN); - $allOK = false; - } - /* EmotesText Data offsets gender seenBy hasTarget mergedWith example 0 male others yes 8 %s raises fist in anger at %s. @@ -158,10 +80,8 @@ public function generate(array $ids = []) : bool // i have no idea, how the indexing in this file works. // sometimes the \d+ after EMOTE is the emoteTextId, but not nearly often enough $aliasses = []; - foreach ($locPath as $lId => $path) - foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) - if (preg_match('/^EMOTE(\d+)_CMD\d+\s=\s\"\/([^"]+)\";$/', $line, $m)) - $aliasses[$m[1]][] = [$lId, $m[2]]; + foreach (CLISetup::searchGlobalStrings('/^EMOTE(\d+)_CMD\d+\s=\s\"\/([^"]+)\";$/') as $lId => $match) + $aliasses[$match[1]][] = [$lId, $match[2]]; $emotes = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, cmd FROM ?_emotes'); @@ -169,18 +89,19 @@ public function generate(array $ids = []) : bool { foreach ($aliasses as $data) { - if (in_array($cmd, array_column($data, 1))) - { - foreach ($data as $d) - DB::Aowow()->query('INSERT IGNORE INTO ?_emotes_aliasses VALUES (?d, ?d, ?) ON DUPLICATE KEY UPDATE locales = locales | ?d', $eId, (1 << $d[0]), strtolower($d[1]), (1 << $d[0])); + if (!in_array($cmd, array_column($data, 1))) + continue; + + foreach ($data as $d) + DB::Aowow()->query('INSERT IGNORE INTO ?_emotes_aliasses VALUES (?d, ?d, ?) ON DUPLICATE KEY UPDATE locales = locales | ?d', $eId, (1 << $d[0]), strtolower($d[1]), (1 << $d[0])); - continue 2; - } + continue 2; } DB::Aowow()->query('UPDATE ?_emotes SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', CUSTOM_EXCLUDE_FOR_LISTVIEW | EMOTE_CU_MISSING_CMD, $eId); } + /*********************/ /* Server controlled */ /*********************/ @@ -192,6 +113,69 @@ public function generate(array $ids = []) : bool return $allOK; } + + private function mergeGenderedStrings(int $maleTextId, int $femaleTextId, int $locale) : string + { + $maleText = $this->textData[$maleTextId][$locale] ?? ''; + $femaleText = $this->textData[$femaleTextId][$locale] ?? ''; + + if (!$maleText && !$femaleText) + return ''; + else if (!$maleText) + return $femaleText; + else if (!$femaleText) + return $maleText; + else if ($maleText == $femaleText) + return $maleText; + + $front = $back = []; + + $m = explode(' ', $maleText); + $f = explode(' ', $femaleText); + $n = max(count($m), count($f)); + + // advance from left until diff -> store common string + // advance from right until diff -> store common string + // merge leftovers as gendered switch and recombine string + + $i = 0; + for (; $i < $n; $i++) + { + if (!isset($m[$i]) || !isset($f[$i])) + break; + else if ($m[$i] != $f[$i]) + break; + else + $front[] = $m[$i]; + } + + if ($i + 1 == $n) // loop completed all elements + return implode(' ', $m); + + $m = array_reverse($m); + $f = array_reverse($f); + + $j = 0; + for (; $j < $n - $i; $j++) + { + if (!isset($m[$j]) || !isset($f[$j])) + break; + else if ($m[$j] != $f[$j]) + break; + else + $back[] = $m[$j]; + } + + $m = array_reverse($m); + $f = array_reverse($f); + + $midM = array_slice($m, $i, count($m) - $i - $j); + $midF = array_slice($f, $i, count($f) - $i - $j); + + $mid = '$g' . implode(' ', $midM ?? []) . ':' . implode(' ', $midF ?? []) . ';'; + + return implode(' ', array_merge($front, [$mid], array_reverse($back))); + } }); ?> diff --git a/setup/tools/sqlgen/events.func.php b/setup/tools/sqlgen/events.func.php deleted file mode 100644 index 5c618983e..000000000 --- a/setup/tools/sqlgen/events.func.php +++ /dev/null @@ -1,51 +0,0 @@ -select($eventQuery, $ids ?: DBSIMPLE_SKIP); - - foreach ($events as $e) - DB::Aowow()->query('REPLACE INTO ?_events VALUES (?a)', array_values($e)); - - $this->reapplyCCFlags('events', Type::WORLDEVENT); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/events.ss.php b/setup/tools/sqlgen/events.ss.php new file mode 100644 index 000000000..a614148ea --- /dev/null +++ b/setup/tools/sqlgen/events.ss.php @@ -0,0 +1,48 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Event from world db.'] + ); + + protected $worldDependency = ['game_event', 'game_event_prerequisite']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_events'); + + $events = DB::World()->select( + 'SELECT ge.eventEntry, + holiday, + 0, -- cuFlags + IFNULL(UNIX_TIMESTAMP(start_time), 0), + IFNULL(UNIX_TIMESTAMP(end_time), 0), + occurence * 60, + length * 60, + IF (gep.eventEntry IS NOT NULL, GROUP_CONCAT(prerequisite_event SEPARATOR " "), NULL), + description + FROM game_event ge + LEFT JOIN game_event_prerequisite gep ON gep.eventEntry = ge.eventEntry + { WHERE ge.eventEntry IN (?a) } + GROUP BY ge.eventEntry', + $ids ?: DBSIMPLE_SKIP + ); + + foreach ($events as $e) + DB::Aowow()->query('INSERT INTO ?_events VALUES (?a)', array_values($e)); + + $this->reapplyCCFlags('events', Type::WORLDEVENT); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/factions.func.php b/setup/tools/sqlgen/factions.func.php deleted file mode 100644 index 05c7671c5..000000000 --- a/setup/tools/sqlgen/factions.func.php +++ /dev/null @@ -1,105 +0,0 @@ - 0 AND ( - top.id IN (?a) OR - top.id = mid.id OR - top.id = low.id - )'; - - $excludeQuery = ' - UPDATE - ?_factions x - JOIN - dbc_faction f ON f.id = x.id - LEFT JOIN - dbc_factiontemplate ft ON f.id = ft.factionId - SET - cuFlags = cuFlags | ?d - WHERE - f.repIdx < 0 OR - ( - f.repIdx > 0 AND - (f.repFlags1 & 0x8 OR ft.id IS NULL) AND - (f.repFlags1 & 0x80) = 0 - )'; - - $pairs = array( - [[980], ['expansion' => 1]], - [[1097], ['expansion' => 2]], - [[469, 891, 1037], ['side' => 1]], - [[ 67, 892, 1052], ['side' => 2]], - ); - - DB::Aowow()->query($factionQuery); - DB::Aowow()->query($templateQuery); - DB::Aowow()->query($excludeQuery, CUSTOM_EXCLUDE_FOR_LISTVIEW); - - foreach ($pairs as $p) - DB::Aowow()->query($recursiveUpdateQuery, $p[0], $p[1], $p[0]); - - $this->reapplyCCFlags('factions', Type::FACTION); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/factions.ss.php b/setup/tools/sqlgen/factions.ss.php new file mode 100644 index 000000000..c94345910 --- /dev/null +++ b/setup/tools/sqlgen/factions.ss.php @@ -0,0 +1,94 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Faction from dbc.'] + ); + + protected $dbcSourceFiles = ['faction', 'factiontemplate']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_factions'); + DB::Aowow()->query('TRUNCATE ?_factiontemplate'); + + DB::Aowow()->query( + 'INSERT INTO ?_factions + SELECT f.id, + f.repIdx, + baseRepRaceMask1, baseRepRaceMask2, baseRepRaceMask3, baseRepRaceMask4, + baseRepClassMask1, baseRepClassMask2, baseRepClassMask3, baseRepClassMask4, + baseRepValue1, baseRepValue2, baseRepValue3, baseRepValue4, + IF(SUM(ft.ourMask & 0x6) / COUNT(1) = 0x4, 2, IF(SUM(ft.ourMask & 0x6) / COUNT(1) = 0x2, 1, 0)) as side, + 0, -- expansion + "", -- quartermasterNpcIds + "", -- factionTemplateIds + 0, -- cuFlags + parentFaction, + spilloverRateIn, spilloverRateOut, spilloverMaxRank, + name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 + FROM dbc_faction f + LEFT JOIN dbc_factiontemplate ft ON ft.factionid = f.id + GROUP BY f.id' + ); + + DB::Aowow()->query( + 'INSERT INTO ?_factiontemplate + SELECT id, + factionId, + IF(friendFactionId1 = 1 OR friendFactionId2 = 1 OR friendFactionId3 = 1 OR friendFactionId4 = 1 OR friendlyMask & 0x3, 1, + IF(enemyFactionId1 = 1 OR enemyFactionId2 = 1 OR enemyFactionId3 = 1 OR enemyFactionId4 = 1 OR hostileMask & 0x3, -1, 0)), + IF(friendFactionId1 = 2 OR friendFactionId2 = 2 OR friendFactionId3 = 2 OR friendFactionId4 = 2 OR friendlyMask & 0x5, 1, + IF(enemyFactionId1 = 2 OR enemyFactionId2 = 2 OR enemyFactionId3 = 2 OR enemyFactionId4 = 2 OR hostileMask & 0x5, -1, 0)) + FROM dbc_factiontemplate' + ); + + DB::Aowow()->query( + 'UPDATE ?_factions f + JOIN (SELECT ft.factionId, GROUP_CONCAT(ft.id SEPARATOR " ") AS tplIds FROM dbc_factiontemplate ft GROUP BY ft.factionId) temp ON f.id = temp.factionId + SET f.templateIds = temp.tplIds' + ); + + DB::Aowow()->query( + 'UPDATE ?_factions x + JOIN dbc_faction f ON f.id = x.id + LEFT JOIN dbc_factiontemplate ft ON f.id = ft.factionId + SET cuFlags = cuFlags | ?d + WHERE f.repIdx < 0 OR ( f.repIdx > 0 AND (f.repFlags1 & 0x8 OR ft.id IS NULL) AND (f.repFlags1 & 0x80) = 0 )', + CUSTOM_EXCLUDE_FOR_LISTVIEW + ); + + $pairs = array( + [[980], ['expansion' => 1]], + [[1097], ['expansion' => 2]], + [[469, 891, 1037], ['side' => 1]], + [[ 67, 892, 1052], ['side' => 2]], + ); + + foreach ($pairs as $p) + DB::Aowow()->query( + 'UPDATE ?_factions top + JOIN (SELECT id, parentFactionId FROM ?_factions) mid ON mid.parentFactionId IN (?a) + LEFT JOIN (SELECT id, parentFactionId FROM ?_factions) low ON low.parentFactionId = mid.id + SET ?a + WHERE repIdx > 0 AND (top.id IN (?a) OR top.id = mid.id OR top.id = low.id)', + $p[0], $p[1], $p[0] + ); + + $this->reapplyCCFlags('factions', Type::FACTION); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/factiontemplate.func.php b/setup/tools/sqlgen/factiontemplate.func.php deleted file mode 100644 index 04d251f93..000000000 --- a/setup/tools/sqlgen/factiontemplate.func.php +++ /dev/null @@ -1,41 +0,0 @@ -query($query); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/glyphproperties.func.php b/setup/tools/sqlgen/glyphproperties.func.php deleted file mode 100644 index c46d30d9e..000000000 --- a/setup/tools/sqlgen/glyphproperties.func.php +++ /dev/null @@ -1,27 +0,0 @@ -query('REPLACE INTO ?_glyphproperties SELECT id, spellId, typeFlags, 0, iconId FROM dbc_glyphproperties'); - - DB::Aowow()->query('UPDATE ?_glyphproperties gp, ?_icons ic, dbc_spellicon si SET gp.iconId = ic.id WHERE gp.iconIdBak = si.id AND ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1))'); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/glyphproperties.ss.php b/setup/tools/sqlgen/glyphproperties.ss.php new file mode 100644 index 000000000..7e053bf2c --- /dev/null +++ b/setup/tools/sqlgen/glyphproperties.ss.php @@ -0,0 +1,30 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Item & Spell from dbc.'] + ); + + protected $dbcSourceFiles = ['glyphproperties', 'spellicon']; + protected $setupAfter = [['icons'], []]; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_glyphproperties'); + DB::Aowow()->query('INSERT INTO ?_glyphproperties SELECT id, spellId, typeFlags, 0, iconId FROM dbc_glyphproperties'); + + DB::Aowow()->query('UPDATE ?_glyphproperties gp, ?_icons ic, dbc_spellicon si SET gp.iconId = ic.id WHERE gp.iconIdBak = si.id AND ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1))'); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/holidays.func.php b/setup/tools/sqlgen/holidays.func.php deleted file mode 100644 index 3eb44a062..000000000 --- a/setup/tools/sqlgen/holidays.func.php +++ /dev/null @@ -1,39 +0,0 @@ -query($query); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/holidays.ss.php b/setup/tools/sqlgen/holidays.ss.php new file mode 100644 index 000000000..d0ad0a99d --- /dev/null +++ b/setup/tools/sqlgen/holidays.ss.php @@ -0,0 +1,36 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Event from dbc.'] + ); + + protected $dbcSourceFiles = ['holidays', 'holidaydescriptions', 'holidaynames']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_holidays'); + DB::Aowow()->query( + 'INSERT INTO ?_holidays (id, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, description_loc0, description_loc2, description_loc3, description_loc4, description_loc6, description_loc8, looping, scheduleType, textureString) + SELECT h.id, n.name_loc0, n.name_loc2, n.name_loc3, n.name_loc4, n.name_loc6, n.name_loc8, d.description_loc0, d.description_loc2, d.description_loc3, d.description_loc4, d.description_loc6, d.description_loc8, h.looping, h.scheduleType, h.textureString + FROM dbc_holidays h + LEFT JOIN dbc_holidaynames n ON n.id = h.nameId + LEFT JOIN dbc_holidaydescriptions d ON d.id = h.descriptionId' + ); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/icons.func.php b/setup/tools/sqlgen/icons.ss.php similarity index 50% rename from setup/tools/sqlgen/icons.func.php rename to setup/tools/sqlgen/icons.ss.php index 342a7e551..4f19d9b69 100644 --- a/setup/tools/sqlgen/icons.func.php +++ b/setup/tools/sqlgen/icons.ss.php @@ -7,9 +7,11 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'icons'; + protected $info = array( + 'icons' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Icons from dbc.'] + ); protected $dbcSourceFiles = ['spellicon', 'itemdisplayinfo', 'creaturefamily']; @@ -17,20 +19,14 @@ public function generate(array $ids = []) : bool { DB::Aowow()->query('TRUNCATE ?_icons'); DB::Aowow()->query('ALTER TABLE ?_icons AUTO_INCREMENT = 1'); - - $baseQuery = ' - INSERT INTO ?_icons (`name`) SELECT x FROM + DB::Aowow()->query( + 'INSERT INTO ?_icons (`name`) SELECT x FROM ( - (SELECT LOWER(SUBSTRING_INDEX(iconPath, "\\\\", -1)) AS x FROM dbc_spellicon WHERE iconPath LIKE "%icons%") - UNION - (SELECT LOWER(inventoryIcon1) AS x FROM dbc_itemdisplayinfo WHERE inventoryIcon1 <> "") - UNION - (SELECT LOWER(SUBSTRING_INDEX(iconString, "\\\\", -1)) AS x FROM dbc_creaturefamily WHERE iconString LIKE "%icons%") - ) y - GROUP BY - x'; - - DB::Aowow()->query($baseQuery); + (SELECT LOWER(SUBSTRING_INDEX(iconPath, "\\\\", -1)) AS x FROM dbc_spellicon WHERE iconPath LIKE "%icons%") UNION + (SELECT LOWER(inventoryIcon1) AS x FROM dbc_itemdisplayinfo WHERE inventoryIcon1 <> "") UNION + (SELECT LOWER(SUBSTRING_INDEX(iconString, "\\\\", -1)) AS x FROM dbc_creaturefamily WHERE iconString LIKE "%icons%") + ) y GROUP BY x' + ); $this->reapplyCCFlags('icons', Type::ICON); diff --git a/setup/tools/sqlgen/itemenchantment.func.php b/setup/tools/sqlgen/itemenchantment.func.php deleted file mode 100644 index ef8870af3..000000000 --- a/setup/tools/sqlgen/itemenchantment.func.php +++ /dev/null @@ -1,43 +0,0 @@ -query($baseQuery); - - $cuProcs = DB::World()->select('SELECT EnchantID AS ARRAY_KEY, Chance AS procChance, ProcsPerMinute AS ppmRate FROM spell_enchant_proc_data'); - foreach ($cuProcs as $id => $vals) - DB::Aowow()->query('UPDATE ?_itemenchantment SET ?a WHERE id = ?d', $vals, $id); - - // hide strange stuff - DB::Aowow()->query('UPDATE ?_itemenchantment SET cuFlags = ?d WHERE type1 = 0 AND type2 = 0 AND type3 = 0', CUSTOM_EXCLUDE_FOR_LISTVIEW); - DB::Aowow()->query('UPDATE ?_itemenchantment SET cuFlags = ?d WHERE name_loc0 LIKE "%test%"', CUSTOM_EXCLUDE_FOR_LISTVIEW); - - $this->reapplyCCFlags('itemenchantment', Type::ENCHANTMENT); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/itemenchantment.ss.php b/setup/tools/sqlgen/itemenchantment.ss.php new file mode 100644 index 000000000..e2da2ecff --- /dev/null +++ b/setup/tools/sqlgen/itemenchantment.ss.php @@ -0,0 +1,42 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Enchantment from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['spellitemenchantment']; + protected $worldDependency = ['spell_enchant_proc_data']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_itemenchantment'); + DB::Aowow()->query( + 'INSERT INTO ?_itemenchantment + SELECT `Id`, `charges`, 0, 0, 0, `type1`, `type2`, `type3`, `amount1`, `amount2`, `amount3`, `object1`, `object2`, `object3`, `name_loc0`, `name_loc2`, `name_loc3`, `name_loc4`, `name_loc6`, `name_loc8`, `conditionId`, `skillLine`, `skillLevel`, `requiredLevel` + FROM dbc_spellitemenchantment' + ); + + $cuProcs = DB::World()->select('SELECT `EnchantID` AS ARRAY_KEY, `Chance` AS `procChance`, `ProcsPerMinute` AS `ppmRate` FROM spell_enchant_proc_data'); + foreach ($cuProcs as $id => $vals) + DB::Aowow()->query('UPDATE ?_itemenchantment SET ?a WHERE `id` = ?d', $vals, $id); + + // hide strange stuff + DB::Aowow()->query('UPDATE ?_itemenchantment SET `cuFlags` = ?d WHERE `type1` = 0 AND `type2` = 0 AND `type3` = 0', CUSTOM_EXCLUDE_FOR_LISTVIEW); + DB::Aowow()->query('UPDATE ?_itemenchantment SET `cuFlags` = ?d WHERE `name_loc0` LIKE "%test%"', CUSTOM_EXCLUDE_FOR_LISTVIEW); + + $this->reapplyCCFlags('itemenchantment', Type::ENCHANTMENT); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/itemenchantmentcondition.func.php b/setup/tools/sqlgen/itemenchantmentcondition.ss.php similarity index 81% rename from setup/tools/sqlgen/itemenchantmentcondition.func.php rename to setup/tools/sqlgen/itemenchantmentcondition.ss.php index 5a8773ace..4667bd90c 100644 --- a/setup/tools/sqlgen/itemenchantmentcondition.func.php +++ b/setup/tools/sqlgen/itemenchantmentcondition.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/itemextendedcost.func.php b/setup/tools/sqlgen/itemextendedcost.ss.php similarity index 80% rename from setup/tools/sqlgen/itemextendedcost.func.php rename to setup/tools/sqlgen/itemextendedcost.ss.php index 97e1548cc..211130e2a 100644 --- a/setup/tools/sqlgen/itemextendedcost.func.php +++ b/setup/tools/sqlgen/itemextendedcost.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/itemlimitcategory.func.php b/setup/tools/sqlgen/itemlimitcategory.ss.php similarity index 80% rename from setup/tools/sqlgen/itemlimitcategory.func.php rename to setup/tools/sqlgen/itemlimitcategory.ss.php index 78f27d9d4..1bc3c29a2 100644 --- a/setup/tools/sqlgen/itemlimitcategory.func.php +++ b/setup/tools/sqlgen/itemlimitcategory.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/itemrandomenchant.func.php b/setup/tools/sqlgen/itemrandomenchant.ss.php similarity index 56% rename from setup/tools/sqlgen/itemrandomenchant.func.php rename to setup/tools/sqlgen/itemrandomenchant.ss.php index c310bb7bb..ae7afafd1 100644 --- a/setup/tools/sqlgen/itemrandomenchant.func.php +++ b/setup/tools/sqlgen/itemrandomenchant.ss.php @@ -7,21 +7,22 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'itemrandomenchant'; + protected $info = array( + 'itemrandomenchant' => [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Item from dbc.'] + ); protected $dbcSourceFiles = ['itemrandomsuffix', 'itemrandomproperties']; public function generate(array $ids = []) : bool { - $query = ' - REPLACE INTO ?_itemrandomenchant - SELECT -id, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, nameINT, enchantId1, enchantId2, enchantId3, enchantId4, enchantId5, allocationPct1, allocationPct2, allocationPct3, allocationPct4, allocationPct5 FROM dbc_itemrandomsuffix - UNION - SELECT id, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, nameINT, enchantId1, enchantId2, enchantId3, enchantId4, enchantId5, 0, 0, 0, 0, 0 FROM dbc_itemrandomproperties'; - - DB::Aowow()->query($query); + DB::Aowow()->query('TRUNCATE ?_itemrandomenchant'); + DB::Aowow()->query( + 'INSERT INTO ?_itemrandomenchant + SELECT -id, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, nameINT, enchantId1, enchantId2, enchantId3, enchantId4, enchantId5, allocationPct1, allocationPct2, allocationPct3, allocationPct4, allocationPct5 FROM dbc_itemrandomsuffix UNION + SELECT id, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, nameINT, enchantId1, enchantId2, enchantId3, enchantId4, enchantId5, 0, 0, 0, 0, 0 FROM dbc_itemrandomproperties' + ); return true; } diff --git a/setup/tools/sqlgen/itemrandomproppoints.func.php b/setup/tools/sqlgen/itemrandomproppoints.ss.php similarity index 80% rename from setup/tools/sqlgen/itemrandomproppoints.func.php rename to setup/tools/sqlgen/itemrandomproppoints.ss.php index 6f2f5707b..c9ad9867f 100644 --- a/setup/tools/sqlgen/itemrandomproppoints.func.php +++ b/setup/tools/sqlgen/itemrandomproppoints.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/items.func.php b/setup/tools/sqlgen/items.func.php deleted file mode 100644 index 13ef14571..000000000 --- a/setup/tools/sqlgen/items.func.php +++ /dev/null @@ -1,265 +0,0 @@ - 11, - SKILL_FISHING => 9, - SKILL_MINING => 12, - SKILL_COOKING => 5, - SKILL_ALCHEMY => 6 - ); - - public function generate(array $ids = []) : bool - { - $baseQuery = ' - SELECT - it.entry, - class, class as classBak, - subclass, subclass AS subClassBak, - SoundOverrideSubclass, - IFNULL(sg.id, 0) AS subSubClass, - it.name, IFNULL(itl2.Name, ""), IFNULL(itl3.Name, ""), IFNULL(itl4.Name, ""), IFNULL(itl6.Name, ""), IFNULL(itl8.Name, ""), - 0 AS iconId, - displayid, - 0 AS spellVisualId, - Quality, - Flags, FlagsExtra, - BuyCount, BuyPrice, SellPrice, - 0 AS repairPrice, - InventoryType AS slot, InventoryType AS slotBak, - AllowableClass, AllowableRace, - ItemLevel, - RequiredLevel, - RequiredSkill, RequiredSkillRank, - requiredspell, - requiredhonorrank, - RequiredCityRank, - RequiredReputationFaction, - RequiredReputationRank, - maxcount, - 0 AS cuFlags, - 0 AS model, - stackable, - ContainerSlots, - stat_type1, stat_value1, - stat_type2, stat_value2, - stat_type3, stat_value3, - stat_type4, stat_value4, - stat_type5, stat_value5, - stat_type6, stat_value6, - stat_type7, stat_value7, - stat_type8, stat_value8, - stat_type9, stat_value9, - stat_type10, stat_value10, - ScalingStatDistribution, - ScalingStatValue, - dmg_min1, dmg_max1, dmg_type1, - dmg_min2, dmg_max2, dmg_type2, - delay, - armor, ArmorDamageModifier, - block, - holy_res, fire_res, nature_res, frost_res, shadow_res, arcane_res, - ammo_type, - RangedModRange, - spellid_1, spelltrigger_1, spellcharges_1, spellppmRate_1, spellcooldown_1, spellcategory_1, spellcategorycooldown_1, - spellid_2, spelltrigger_2, spellcharges_2, spellppmRate_2, spellcooldown_2, spellcategory_2, spellcategorycooldown_2, - spellid_3, spelltrigger_3, spellcharges_3, spellppmRate_3, spellcooldown_3, spellcategory_3, spellcategorycooldown_3, - spellid_4, spelltrigger_4, spellcharges_4, spellppmRate_4, spellcooldown_4, spellcategory_4, spellcategorycooldown_4, - spellid_5, spelltrigger_5, spellcharges_5, spellppmRate_5, spellcooldown_5, spellcategory_5, spellcategorycooldown_5, - bonding, - it.description, IFNULL(itl2.Description, ""), IFNULL(itl3.Description, ""), IFNULL(itl4.Description, ""), IFNULL(itl6.Description, ""), IFNULL(itl8.Description, ""), - PageText, - LanguageID, - startquest, - lockid, - Material, - IF(RandomProperty > 0, RandomProperty, -RandomSuffix) AS randomEnchant, - itemset, - MaxDurability, - area, - Map, - BagFamily, - TotemCategory, - socketColor_1, socketContent_1, - socketColor_2, socketContent_2, - socketColor_3, socketContent_3, - socketBonus, - GemProperties, - RequiredDisenchantSkill, - DisenchantID, - duration, - ItemLimitCategory, - IFNULL(ge.eventEntry, 0), - ScriptName, - FoodType, - 0 AS gemEnchantmentId, - minMoneyLoot, maxMoneyLoot, - 0 AS pickUpSoundId, - 0 AS dropDownSoundId, - 0 AS sheatheSoundId, - 0 AS unsheatheSoundId, - flagsCustom - FROM - item_template it - LEFT JOIN - item_template_locale itl2 ON it.entry = itl2.ID AND itl2.locale = "frFR" - LEFT JOIN - item_template_locale itl3 ON it.entry = itl3.ID AND itl3.locale = "deDE" - LEFT JOIN - item_template_locale itl4 ON it.entry = itl4.ID AND itl4.locale = "zhCN" - LEFT JOIN - item_template_locale itl6 ON it.entry = itl6.ID AND itl6.locale = "esES" - LEFT JOIN - item_template_locale itl8 ON it.entry = itl8.ID AND itl8.locale = "ruRU" - LEFT JOIN - spell_group sg ON sg.spell_id = it.spellid_1 AND it.class = 0 AND it.subclass = 2 AND sg.id IN (1, 2) - LEFT JOIN - game_event ge ON ge.holiday = it.HolidayId AND it.HolidayId > 0 - { - WHERE - it.entry IN (?a) - } - LIMIT - ?d, ?d'; - - $i = 0; - DB::Aowow()->query('TRUNCATE ?_items'); - while ($items = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, SqlGen::$sqlBatchSize * $i, SqlGen::$sqlBatchSize)) - { - CLI::write(' * batch #' . ++$i . ' (' . count($items) . ')', CLI::LOG_BLANK, true, true); - - foreach ($items as $item) - DB::Aowow()->query('INSERT INTO ?_items VALUES (?a)', array_values($item)); - } - - // merge with gemProperties - DB::Aowow()->query('UPDATE ?_items i, dbc_gemproperties gp SET i.gemEnchantmentId = gp.enchantmentId, i.gemColorMask = gp.colorMask WHERE i.gemColorMask = gp.id'); - - // get modelString - DB::Aowow()->query('UPDATE ?_items i, dbc_itemdisplayinfo idi SET i.model = IF(idi.leftModelName = "", idi.rightModelName, idi.leftModelName) WHERE i.displayId = idi.id'); - - // get iconId - DB::Aowow()->query('UPDATE ?_items i, dbc_itemdisplayinfo idi, ?_icons ic SET i.iconId = ic.id WHERE i.displayId = idi.id AND LOWER(idi.inventoryIcon1) = ic.name'); - - // unify slots: Robes => Chest; Ranged (right) => Ranged - DB::Aowow()->query('UPDATE ?_items SET slot = 15 WHERE slotbak = 26'); - DB::Aowow()->query('UPDATE ?_items SET slot = 5 WHERE slotbak = 20'); - - // custom sub-classes - DB::Aowow()->query(' - UPDATE ?_items SET subclass = IF( - slotbak = 4, -8, IF( -- shirt - slotbak = 19, -7, IF( -- tabard - slotbak = 16, -6, IF( -- cloak - slotbak = 23, -5, IF( -- held in offhand - slotbak = 12, -4, IF( -- trinket - slotbak = 2, -3, IF( -- amulet - slotbak = 11, -2, subClassBak -- ring - ))))))) WHERE class = 4'); - - // move alchemist stones to trinkets (Armor) - DB::Aowow()->query('UPDATE ?_items SET class = 4, subClass = -4 WHERE classBak = 7 AND subClassBak = 11 AND slotBak = 12'); - - // mark keys as key (if not quest items) - DB::Aowow()->query('UPDATE ?_items SET class = 13, subClass = 0 WHERE classBak IN (0, 15) AND bagFamily & 0x100'); - - // set subSubClass for Glyphs (major/minor) - DB::Aowow()->query('UPDATE ?_items i, dbc_spell s, dbc_glyphproperties gp SET i.subSubClass = IF(gp.typeFlags & 0x1, 2, 1) WHERE i.spellId1 = s.id AND s.effect1MiscValue = gp.id AND i.classBak = 16'); - - // filter misc(class:15) junk(subclass:0) to appropriate categories - - // assign pets and mounts to category - DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET subClass = IF(effect1AuraId <> 78, 2, IF(effect2AuraId = 207 OR effect3AuraId = 207 OR (s.id <> 65917 AND effect2AuraId = 4 AND effect3Id = 77), -7, 5)) WHERE s.id = spellId2 AND class = 15 AND spellId1 IN (?a)', LEARN_SPELLS); - - // more corner cases (mounts that are not actualy learned) - DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.subClass = -7 WHERE (effect1Id = 64 OR (effect1AuraId = 78 AND effect2AuraId = 4 AND effect3Id = 77) OR effect1AuraId = 207 OR effect2AuraId = 207 OR effect3AuraId = 207) AND s.id = i.spellId1 AND i.class = 15 AND i.subClass = 5'); - DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.subClass = 5 WHERE s.effect1AuraId = 78 AND s.id = i.spellId1 AND i.class = 15 AND i.subClass = 0'); - - // move some permanent enchantments to own category - DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.class = 0, i.subClass = 6 WHERE s.effect1Id = 53 AND s.id = i.spellId1 AND i.class = 15'); - - // move temporary enchantments to own category - DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.subClass = -3 WHERE s.effect1Id = 54 AND s.id = i.spellId1 AND i.class = 0 AND i.subClassBak = 8'); - - // move armor tokens to own category - DB::Aowow()->query('UPDATE ?_items SET subClass = -2 WHERE quality = 4 AND class = 15 AND subClassBak = 0 AND requiredClass AND (requiredClass & 0x5FF) <> 0x5FF'); - - // move some junk to holiday if it requires one - DB::Aowow()->query('UPDATE ?_items SET subClass = 3 WHERE classBak = 15 AND subClassBak = 0 AND eventId <> 0'); - - // move misc items that start quests to class: quest (except Sayges scrolls for consistency) - DB::Aowow()->query('UPDATE ?_items SET class = 12 WHERE classBak = 15 AND startQuest <> 0 AND name_loc0 NOT LIKE "sayge\'s fortune%"'); - - // move perm. enchantments into appropriate cat/subcat - DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.class = 0, i.subClass = 6 WHERE s.id = i.spellId1 AND s.effect1Id = 53 AND i.classBak = 12'); - - // move some generic recipes into appropriate sub-categories - foreach ($this->skill2cat as $skill => $cat) - DB::Aowow()->query('UPDATE ?_items SET subClass = ?d WHERE classBak = 9 AND subClassBak = 0 AND requiredSkill = ?d', $cat, $skill); - - // calculate durabilityCosts - DB::Aowow()->query(' - UPDATE - ?_items i - JOIN - dbc_durabilityquality dq ON dq.id = ((i.quality + 1) * 2) - JOIN - dbc_durabilitycosts dc ON dc.id = i.itemLevel - SET - i.repairPrice = (durability* dq.mod * IF(i.classBak = 2, - CASE i.subClassBak - WHEN 0 THEN w0 WHEN 1 THEN w1 WHEN 2 THEN w2 WHEN 3 THEN w3 WHEN 4 THEN w4 - WHEN 5 THEN w5 WHEN 6 THEN w6 WHEN 7 THEN w7 WHEN 8 THEN w8 WHEN 10 THEN w10 - WHEN 11 THEN w11 WHEN 12 THEN w12 WHEN 13 THEN w13 WHEN 14 THEN w14 WHEN 15 THEN w15 - WHEN 16 THEN w16 WHEN 17 THEN w17 WHEN 18 THEN w18 WHEN 19 THEN w19 WHEN 20 THEN w20 - END, - CASE i.subClassBak - WHEN 1 THEN a1 WHEN 2 THEN a2 WHEN 3 THEN a3 WHEN 4 THEN a4 WHEN 6 THEN a6 - END - )) - WHERE - durability > 0 AND ((classBak = 4 AND subClassBak IN (1, 2, 3, 4, 6)) OR (classBak = 2 AND subClassBak <> 9))'); - - // hide some nonsense - DB::Aowow()->query('UPDATE ?_items SET `cuFlags` = `cuFlags` | ?d WHERE - `name_loc0` LIKE "Monster - %" OR `name_loc0` LIKE "Creature - %" OR - `name_loc0` LIKE "%[PH]%" OR `name_loc0` LIKE "% PH %" OR - `name_loc0` LIKE "%(new)%" OR `name_loc0` LIKE "%(old)%" OR - `name_loc0` LIKE "%deprecated%" OR `name_loc0` LIKE "%obsolete%" OR - `name_loc0` LIKE "%1H%" OR `name_loc0` LIKE "%QA%" OR - `name_loc0` LIKE "%(test)%" OR `name_loc0` LIKE "test %" OR (`name_loc0` LIKE "% test %" AND `class` > 0)', - CUSTOM_EXCLUDE_FOR_LISTVIEW - ); - - // sanity check weapon class and invtype relation - $checks = array( - [[INVTYPE_WEAPONOFFHAND, INVTYPE_WEAPONMAINHAND, INVTYPE_WEAPON], [0, 4, 7, 13, 14, 15]], - [[INVTYPE_2HWEAPON], [1, 5, 6, 8, 10, 14, 20]], - [[INVTYPE_RANGED, INVTYPE_RANGEDRIGHT], [2, 3, 16, 18, 14, 19]] - ); - foreach ($checks as [$slots, $subclasses]) - DB::Aowow()->query('UPDATE ?_items SET `cuFlags` = `cuFlags` | ?d WHERE `class`= ?d AND `slotBak` IN (?a) AND `subClass` NOT IN (?a)', CUSTOM_EXCLUDE_FOR_LISTVIEW, ITEM_CLASS_WEAPON, $slots, $subclasses); - - $this->reapplyCCFlags('items', Type::ITEM); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/items.ss.php b/setup/tools/sqlgen/items.ss.php new file mode 100644 index 000000000..e92502db9 --- /dev/null +++ b/setup/tools/sqlgen/items.ss.php @@ -0,0 +1,266 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Item from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['gemproperties', 'itemdisplayinfo', 'spell', 'glyphproperties', 'durabilityquality', 'durabilitycosts']; + protected $worldDependency = ['item_template', 'item_template_locale', 'spell_group', 'game_event']; + protected $setupAfter = [['icons'], []]; + + private $skill2cat = array( + SKILL_INSCRIPTION => 11, + SKILL_FISHING => 9, + SKILL_MINING => 12, + SKILL_COOKING => 5, + SKILL_ALCHEMY => 6 + ); + + public function generate(array $ids = []) : bool + { + $baseQuery = + 'SELECT it.entry, + class, class as classBak, + subclass, subclass AS subClassBak, + SoundOverrideSubclass, + IFNULL(sg.id, 0) AS subSubClass, + it.name, IFNULL(itl2.Name, ""), IFNULL(itl3.Name, ""), IFNULL(itl4.Name, ""), IFNULL(itl6.Name, ""), IFNULL(itl8.Name, ""), + 0 AS iconId, + displayid, + 0 AS spellVisualId, + Quality, + Flags, FlagsExtra, + BuyCount, BuyPrice, SellPrice, + 0 AS repairPrice, + InventoryType AS slot, InventoryType AS slotBak, + AllowableClass, AllowableRace, + ItemLevel, + RequiredLevel, + RequiredSkill, RequiredSkillRank, + requiredspell, + requiredhonorrank, + RequiredCityRank, + RequiredReputationFaction, + RequiredReputationRank, + maxcount, + 0 AS cuFlags, + 0 AS model, + stackable, + ContainerSlots, + stat_type1, stat_value1, + stat_type2, stat_value2, + stat_type3, stat_value3, + stat_type4, stat_value4, + stat_type5, stat_value5, + stat_type6, stat_value6, + stat_type7, stat_value7, + stat_type8, stat_value8, + stat_type9, stat_value9, + stat_type10, stat_value10, + ScalingStatDistribution, + ScalingStatValue, + dmg_min1, dmg_max1, dmg_type1, + dmg_min2, dmg_max2, dmg_type2, + delay, + armor, ArmorDamageModifier, + block, + holy_res, fire_res, nature_res, frost_res, shadow_res, arcane_res, + ammo_type, + RangedModRange, + spellid_1, spelltrigger_1, spellcharges_1, spellppmRate_1, spellcooldown_1, spellcategory_1, spellcategorycooldown_1, + spellid_2, spelltrigger_2, spellcharges_2, spellppmRate_2, spellcooldown_2, spellcategory_2, spellcategorycooldown_2, + spellid_3, spelltrigger_3, spellcharges_3, spellppmRate_3, spellcooldown_3, spellcategory_3, spellcategorycooldown_3, + spellid_4, spelltrigger_4, spellcharges_4, spellppmRate_4, spellcooldown_4, spellcategory_4, spellcategorycooldown_4, + spellid_5, spelltrigger_5, spellcharges_5, spellppmRate_5, spellcooldown_5, spellcategory_5, spellcategorycooldown_5, + bonding, + it.description, IFNULL(itl2.Description, ""), IFNULL(itl3.Description, ""), IFNULL(itl4.Description, ""), IFNULL(itl6.Description, ""), IFNULL(itl8.Description, ""), + PageText, + LanguageID, + startquest, + lockid, + Material, + IF(RandomProperty > 0, RandomProperty, -RandomSuffix) AS randomEnchant, + itemset, + MaxDurability, + area, + Map, + BagFamily, + TotemCategory, + socketColor_1, socketContent_1, + socketColor_2, socketContent_2, + socketColor_3, socketContent_3, + socketBonus, + GemProperties, + RequiredDisenchantSkill, + DisenchantID, + duration, + ItemLimitCategory, + IFNULL(ge.eventEntry, 0), + ScriptName, + FoodType, + 0 AS gemEnchantmentId, + minMoneyLoot, maxMoneyLoot, + 0 AS pickUpSoundId, + 0 AS dropDownSoundId, + 0 AS sheatheSoundId, + 0 AS unsheatheSoundId, + flagsCustom + FROM item_template it + LEFT JOIN item_template_locale itl2 ON it.entry = itl2.ID AND itl2.locale = "frFR" + LEFT JOIN item_template_locale itl3 ON it.entry = itl3.ID AND itl3.locale = "deDE" + LEFT JOIN item_template_locale itl4 ON it.entry = itl4.ID AND itl4.locale = "zhCN" + LEFT JOIN item_template_locale itl6 ON it.entry = itl6.ID AND itl6.locale = "esES" + LEFT JOIN item_template_locale itl8 ON it.entry = itl8.ID AND itl8.locale = "ruRU" + LEFT JOIN spell_group sg ON sg.spell_id = it.spellid_1 AND it.class = 0 AND it.subclass = 2 AND sg.id IN (1, 2) + LEFT JOIN game_event ge ON ge.holiday = it.HolidayId AND it.HolidayId > 0 + { WHERE it.entry IN (?a) } + LIMIT ?d, ?d'; + + $i = 0; + DB::Aowow()->query('TRUNCATE ?_items'); + while ($items = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) + { + CLI::write(' * batch #' . ++$i . ' (' . count($items) . ')', CLI::LOG_BLANK, true, true); + + foreach ($items as $item) + DB::Aowow()->query('INSERT INTO ?_items VALUES (?a)', array_values($item)); + } + + // merge with gemProperties + DB::Aowow()->query('UPDATE ?_items i, dbc_gemproperties gp SET i.gemEnchantmentId = gp.enchantmentId, i.gemColorMask = gp.colorMask WHERE i.gemColorMask = gp.id'); + + // get modelString + DB::Aowow()->query('UPDATE ?_items i, dbc_itemdisplayinfo idi SET i.model = IF(idi.leftModelName = "", idi.rightModelName, idi.leftModelName) WHERE i.displayId = idi.id'); + + // get iconId + DB::Aowow()->query('UPDATE ?_items i, dbc_itemdisplayinfo idi, ?_icons ic SET i.iconId = ic.id WHERE i.displayId = idi.id AND LOWER(idi.inventoryIcon1) = ic.name'); + + // unify slots: Robes => Chest; Ranged (right) => Ranged + DB::Aowow()->query('UPDATE ?_items SET slot = ?d WHERE slotbak = ?d', INVTYPE_RANGED, INVTYPE_RANGEDRIGHT); + DB::Aowow()->query('UPDATE ?_items SET slot = ?d WHERE slotbak = ?d', INVTYPE_CHEST, INVTYPE_ROBE); + + // custom sub-classes + DB::Aowow()->query( + 'UPDATE ?_items SET subclass = IF( + slotbak = 4, -8, IF( -- shirt + slotbak = 19, -7, IF( -- tabard + slotbak = 16, -6, IF( -- cloak + slotbak = 23, -5, IF( -- held in offhand + slotbak = 12, -4, IF( -- trinket + slotbak = 2, -3, IF( -- amulet + slotbak = 11, -2, subClassBak -- ring + ))))))) WHERE class = 4' + ); + + // move alchemist stones to trinkets (Armor) + DB::Aowow()->query('UPDATE ?_items SET class = 4, subClass = -4 WHERE classBak = 7 AND subClassBak = 11 AND slotBak = ?d', INVTYPE_TRINKET); + + // mark keys as key (if not quest items) + DB::Aowow()->query('UPDATE ?_items SET class = 13, subClass = 0 WHERE classBak IN (0, 15) AND bagFamily & 0x100'); + + // set subSubClass for Glyphs (major/minor) + DB::Aowow()->query('UPDATE ?_items i, dbc_spell s, dbc_glyphproperties gp SET i.subSubClass = IF(gp.typeFlags & 0x1, 2, 1) WHERE i.spellId1 = s.id AND s.effect1MiscValue = gp.id AND i.classBak = 16'); + + // filter misc(class:15) junk(subclass:0) to appropriate categories + + // assign pets and mounts to category + DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET subClass = IF(effect1AuraId <> 78, 2, IF(effect2AuraId = 207 OR effect3AuraId = 207 OR (s.id <> 65917 AND effect2AuraId = 4 AND effect3Id = 77), -7, 5)) WHERE s.id = spellId2 AND class = 15 AND spellId1 IN (?a)', LEARN_SPELLS); + + // more corner cases (mounts that are not actualy learned) + DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.subClass = -7 WHERE (effect1Id = 64 OR (effect1AuraId = 78 AND effect2AuraId = 4 AND effect3Id = 77) OR effect1AuraId = 207 OR effect2AuraId = 207 OR effect3AuraId = 207) AND s.id = i.spellId1 AND i.class = 15 AND i.subClass = 5'); + DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.subClass = 5 WHERE s.effect1AuraId = 78 AND s.id = i.spellId1 AND i.class = 15 AND i.subClass = 0'); + + // move some permanent enchantments to own category + DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.class = 0, i.subClass = 6 WHERE s.effect1Id = 53 AND s.id = i.spellId1 AND i.class = 15'); + + // move temporary enchantments to own category + DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.subClass = -3 WHERE s.effect1Id = 54 AND s.id = i.spellId1 AND i.class = 0 AND i.subClassBak = 8'); + + // move armor tokens to own category + DB::Aowow()->query('UPDATE ?_items SET subClass = -2 WHERE quality = 4 AND class = 15 AND subClassBak = 0 AND requiredClass AND (requiredClass & 0x5FF) <> 0x5FF'); + + // move some junk to holiday if it requires one + DB::Aowow()->query('UPDATE ?_items SET subClass = 3 WHERE classBak = 15 AND subClassBak = 0 AND eventId <> 0'); + + // move misc items that start quests to class: quest (except Sayges scrolls for consistency) + DB::Aowow()->query('UPDATE ?_items SET class = 12 WHERE classBak = 15 AND startQuest <> 0 AND name_loc0 NOT LIKE "sayge\'s fortune%"'); + + // move perm. enchantments into appropriate cat/subcat + DB::Aowow()->query('UPDATE ?_items i, dbc_spell s SET i.class = 0, i.subClass = 6 WHERE s.id = i.spellId1 AND s.effect1Id = 53 AND i.classBak = 12'); + + // move some generic recipes into appropriate sub-categories + foreach ($this->skill2cat as $skill => $cat) + DB::Aowow()->query('UPDATE ?_items SET subClass = ?d WHERE classBak = 9 AND subClassBak = 0 AND requiredSkill = ?d', $cat, $skill); + + // assign slot from onUse spell to item (todo (med): handle multi slot enchantments (like armor kits)) + DB::Aowow()->query( + 'UPDATE ?_items i + JOIN (SELECT id, LOG(2, equippedItemInventoryTypeMask & ~?d) AS mask + FROM dbc_spell + WHERE equippedItemInventoryTypeMask > 0 + HAVING CAST(mask AS INT) = CAST(mask AS FLOAT)) s + ON s.id = i.spellId1 + SET i.slot = s.mask + WHERE i.spellId1 > 0 AND i.class = 0 AND i.subClass IN (6, -3)', + 1 << INVTYPE_ROBE | 1 << INVTYPE_RANGEDRIGHT // just unset. _CHEST and _RANGED are set in parallel + ); + + // calculate durabilityCosts + DB::Aowow()->query( + 'UPDATE ?_items i + JOIN dbc_durabilityquality dq ON dq.id = ((i.quality + 1) * 2) + JOIN dbc_durabilitycosts dc ON dc.id = i.itemLevel + SET i.repairPrice = (durability * dq.mod * IF(i.classBak = 2, + CASE i.subClassBak + WHEN 0 THEN w0 WHEN 1 THEN w1 WHEN 2 THEN w2 WHEN 3 THEN w3 WHEN 4 THEN w4 + WHEN 5 THEN w5 WHEN 6 THEN w6 WHEN 7 THEN w7 WHEN 8 THEN w8 WHEN 10 THEN w10 + WHEN 11 THEN w11 WHEN 12 THEN w12 WHEN 13 THEN w13 WHEN 14 THEN w14 WHEN 15 THEN w15 + WHEN 16 THEN w16 WHEN 17 THEN w17 WHEN 18 THEN w18 WHEN 19 THEN w19 WHEN 20 THEN w20 + END, + CASE i.subClassBak + WHEN 1 THEN a1 WHEN 2 THEN a2 WHEN 3 THEN a3 WHEN 4 THEN a4 WHEN 6 THEN a6 + END + )) + WHERE durability > 0 AND ((classBak = 4 AND subClassBak IN (1, 2, 3, 4, 6)) OR (classBak = 2 AND subClassBak <> 9))' + ); + + // hide some nonsense + DB::Aowow()->query( + 'UPDATE ?_items + SET `cuFlags` = `cuFlags` | ?d + WHERE `name_loc0` LIKE "Monster - %" OR `name_loc0` LIKE "Creature - %" OR + `name_loc0` LIKE "%[PH]%" OR `name_loc0` LIKE "% PH %" OR + `name_loc0` LIKE "%(new)%" OR `name_loc0` LIKE "%(old)%" OR + `name_loc0` LIKE "%deprecated%" OR `name_loc0` LIKE "%obsolete%" OR + `name_loc0` LIKE "%1H%" OR `name_loc0` LIKE "%QA%" OR + `name_loc0` LIKE "%(test)%" OR `name_loc0` LIKE "test %" OR (`name_loc0` LIKE "% test %" AND `class` > 0)', + CUSTOM_EXCLUDE_FOR_LISTVIEW + ); + + // sanity check weapon class and invtype relation + $checks = array( + [[INVTYPE_WEAPONOFFHAND, INVTYPE_WEAPONMAINHAND, INVTYPE_WEAPON], [0, 4, 7, 13, 14, 15]], + [[INVTYPE_2HWEAPON], [1, 5, 6, 8, 10, 14, 20]], + [[INVTYPE_RANGED, INVTYPE_RANGEDRIGHT], [2, 3, 16, 18, 14, 19]] + ); + foreach ($checks as [$slots, $subclasses]) + DB::Aowow()->query('UPDATE ?_items SET `cuFlags` = `cuFlags` | ?d WHERE `class`= ?d AND `slotBak` IN (?a) AND `subClass` NOT IN (?a)', CUSTOM_EXCLUDE_FOR_LISTVIEW, ITEM_CLASS_WEAPON, $slots, $subclasses); + + $this->reapplyCCFlags('items', Type::ITEM); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/itemset.func.php b/setup/tools/sqlgen/itemset.ss.php similarity index 94% rename from setup/tools/sqlgen/itemset.func.php rename to setup/tools/sqlgen/itemset.ss.php index 93db167c3..d79ad36d1 100644 --- a/setup/tools/sqlgen/itemset.func.php +++ b/setup/tools/sqlgen/itemset.ss.php @@ -13,13 +13,15 @@ and i have no idea how to merge the prefixes/suffixes for wotlk-raidsets and arena-sets in gereral onto the name.. at least not for all locales and i'll be damned if i have to skip one */ -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'itemset'; + protected $info = array( + 'itemset' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Itemset from dbc and world db.'] + ); - protected $tblDependencyAowow = ['spell']; - protected $tblDependencyTC = ['item_template', 'game_event']; protected $dbcSourceFiles = ['itemset']; + protected $worldDependency = ['item_template', 'game_event']; + protected $setupAfter = [['spell'], []]; private $setToHoliday = array ( 761 => 141, // Winterveil @@ -127,7 +129,7 @@ private function getSetType(array $items) : int case INVTYPE_TRINKET: $type = 11; break; default: if ($item['subclass'] != 0) - $type = $item['subclass']; // 'armor' set by armor class + $type = $item['subclass']; // 'armor' set by armor class } } } @@ -243,7 +245,6 @@ public function generate(array $ids = []) : bool foreach ($bonusSpells->iterate() as $__) { - if (!isset($descText[$loc])) $descText[$loc] = ''; @@ -271,8 +272,8 @@ public function generate(array $ids = []) : bool if (count($pieces) < 2) { $row['cuFlags'] = CUSTOM_EXCLUDE_FOR_LISTVIEW; - DB::Aowow()->query('REPLACE INTO ?_itemset (?#) VALUES (?a)', array_keys($row), array_values($row)); - CLI::write(' - item set #'.$setId.' has no associated items', CLI::LOG_INFO); + DB::Aowow()->query('INSERT INTO ?_itemset (?#) VALUES (?a)', array_keys($row), array_values($row)); + CLI::write('[item set] '.str_pad('['.$setId.']', 7).CLI::bold($setData['name_loc0']).' has no associated items', CLI::LOG_INFO); continue; } @@ -308,7 +309,7 @@ public function generate(array $ids = []) : bool if ($i) $vRow['id'] = --$virtualId; - DB::Aowow()->query('REPLACE INTO ?_itemset (?#) VALUES (?a)', array_keys($vRow), array_values($vRow)); + DB::Aowow()->query('INSERT INTO ?_itemset (?#) VALUES (?a)', array_keys($vRow), array_values($vRow)); } } diff --git a/setup/tools/sqlgen/item_stats.func.php b/setup/tools/sqlgen/itemstats.ss.php similarity index 83% rename from setup/tools/sqlgen/item_stats.func.php rename to setup/tools/sqlgen/itemstats.ss.php index e635bc6b6..61beb8692 100644 --- a/setup/tools/sqlgen/item_stats.func.php +++ b/setup/tools/sqlgen/itemstats.ss.php @@ -32,7 +32,7 @@ public function __construct($start, $limit, array $ids, array $relEnchants, arra $this->relEnchants = $relEnchants; } - public function writeStatsTable() + public function writeStatsTable() : void { foreach ($this->iterate() as $id => $curTpl) { @@ -52,18 +52,20 @@ public function writeStatsTable() } } -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'item_stats'; // and enchantment stats + protected $info = array( + 'stats' => [[], CLISetup::ARGV_PARAM, 'Compiles stats data for type: Item & Enchantment from dbc and world db.'] + ); - protected $tblDependencyAowow = ['items', 'spell']; - protected $dbcSourceFiles = ['spellitemenchantment']; + protected $dbcSourceFiles = ['spellitemenchantment']; + protected $setupAfter = [['items', 'spell'], []]; private $relSpells = []; private function enchantment_stats(?int &$total = 0, ?int &$effective = 0) : array { - $enchants = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM dbc_spellitemenchantment'); + $enchants = DB::Aowow()->select('SELECT *, `id` AS ARRAY_KEY FROM dbc_spellitemenchantment'); $spells = []; $result = []; $effective = 0; @@ -91,22 +93,22 @@ public function generate(array $ids = []) : bool { DB::Aowow()->query('TRUNCATE ?_item_stats'); - CLI::write(' - applying stats for enchantments'); + CLI::write('[stats] - applying stats for enchantments'); $enchStats = $this->enchantment_stats($total, $effective); CLI::write(' '.$effective.'+'.($total - $effective).' enchantments parsed'); - CLI::write(' - applying stats for items'); + CLI::write('[stats] - applying stats for items'); $i = 0; $offset = 0; while (true) { - $items = new ItemStatSetup($offset, SqlGen::$sqlBatchSize, $ids, $enchStats, $this->relSpells); + $items = new ItemStatSetup($offset, CLISetup::SQL_BATCH, $ids, $enchStats, $this->relSpells); if ($items->error) break; - CLI::write(' * batch #' . ++$i . ' (' . count($items->getFoundIDs()) . ')', CLI::LOG_BLANK, true, true); + CLI::write('[stats] * batch #' . ++$i . ' (' . count($items->getFoundIDs()) . ')', CLI::LOG_BLANK, true, true); $offset = max($items->getFoundIDs()); diff --git a/setup/tools/sqlgen/lock.func.php b/setup/tools/sqlgen/lock.ss.php similarity index 78% rename from setup/tools/sqlgen/lock.func.php rename to setup/tools/sqlgen/lock.ss.php index 8a0b9186f..c779508e7 100644 --- a/setup/tools/sqlgen/lock.func.php +++ b/setup/tools/sqlgen/lock.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/mailtemplate.func.php b/setup/tools/sqlgen/mailtemplate.func.php deleted file mode 100644 index abf4872eb..000000000 --- a/setup/tools/sqlgen/mailtemplate.func.php +++ /dev/null @@ -1,68 +0,0 @@ -query('TRUNCATE aowow_mails'); - - // copy data over from dbc - DB::Aowow()->query(' - INSERT INTO ?_mails SELECT - id, 0, - subject_loc0, subject_loc2, subject_loc3, subject_loc4, subject_loc6, subject_loc8, - text_loc0, text_loc2, text_loc3, text_loc4, text_loc6, text_loc8, - 0 - FROM dbc_mailtemplate - '); - - CLI::write('SqlGen::generate() - merging achievement_reward into aowow_mails'); - - $acvMail = DB::World()->select(' - SELECT - -ar.ID, 0, - IFNULL(ar.Subject, "") AS s0, IFNULL(arl2.Subject, "") AS s2, IFNULL(arl3.Subject, "") AS s3, IFNULL(arl4.Subject, "") AS s4, IFNULL(arl6.Subject, "") AS s6, IFNULL(arl8.Subject, "") AS s8, - IFNULL(ar.Body, "") AS t0, IFNULL(arl2.Body, "") AS t2, IFNULL(arl3.Body, "") AS t3, IFNULL(arl4.Body, "") AS t4, IFNULL(arl6.Body, "") AS t6, IFNULL(arl8.Body, "") AS t8, - ItemID - FROM - achievement_reward ar - LEFT JOIN - achievement_reward_locale arl2 ON ar.ID = arl2.ID AND arl2.Locale = "frFR" - LEFT JOIN - achievement_reward_locale arl3 ON ar.ID = arl3.ID AND arl3.Locale = "deDE" - LEFT JOIN - achievement_reward_locale arl4 ON ar.ID = arl4.ID AND arl4.Locale = "zhCN" - LEFT JOIN - achievement_reward_locale arl6 ON ar.ID = arl6.ID AND arl6.Locale = "esES" - LEFT JOIN - achievement_reward_locale arl8 ON ar.ID = arl8.ID AND arl8.Locale = "ruRU" - WHERE - ar.MailTemplateID = 0 AND ar.Body <> "" - '); - - DB::Aowow()->query('INSERT INTO aowow_mails VALUES (?a)', array_values($acvMail)); - - CLI::write('SqlGen::generate() - merging mail_loot_template into aowow_mails'); - - // assume mails to only contain one single item, wich works for an unmodded installation - $mlt = DB::World()->selectCol('SELECT Entry AS ARRAY_KEY, Item FROM mail_loot_template'); - foreach ($mlt as $k => $v) - DB::Aowow()->query('UPDATE ?_mails SET attachment = ?d WHERE id = ?d', $v, $k); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/mailtemplate.ss.php b/setup/tools/sqlgen/mailtemplate.ss.php new file mode 100644 index 000000000..62644f715 --- /dev/null +++ b/setup/tools/sqlgen/mailtemplate.ss.php @@ -0,0 +1,55 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Mail from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['mailtemplate']; + protected $worldDependency = ['achievement_reward', 'achievement_reward_locale', 'mail_loot_template']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_mails'); + + // copy data over from dbc + DB::Aowow()->query('INSERT INTO ?_mails SELECT `id`, 0, `subject_loc0`, `subject_loc2`, `subject_loc3`, `subject_loc4`, `subject_loc6`, `subject_loc8`, `text_loc0`, `text_loc2`, `text_loc3`, `text_loc4`, `text_loc6`, `text_loc8`, 0 FROM dbc_mailtemplate'); + + CLI::write('[mails] - loading data from achievement_reward'); + + $acvMail = DB::World()->select( + 'SELECT -ar.ID, 0, + IFNULL(ar.Subject, "") AS s0, IFNULL(arl2.Subject, "") AS s2, IFNULL(arl3.Subject, "") AS s3, IFNULL(arl4.Subject, "") AS s4, IFNULL(arl6.Subject, "") AS s6, IFNULL(arl8.Subject, "") AS s8, + IFNULL(ar.Body, "") AS t0, IFNULL(arl2.Body, "") AS t2, IFNULL(arl3.Body, "") AS t3, IFNULL(arl4.Body, "") AS t4, IFNULL(arl6.Body, "") AS t6, IFNULL(arl8.Body, "") AS t8, + ItemID + FROM achievement_reward ar + LEFT JOIN achievement_reward_locale arl2 ON ar.ID = arl2.ID AND arl2.Locale = "frFR" + LEFT JOIN achievement_reward_locale arl3 ON ar.ID = arl3.ID AND arl3.Locale = "deDE" + LEFT JOIN achievement_reward_locale arl4 ON ar.ID = arl4.ID AND arl4.Locale = "zhCN" + LEFT JOIN achievement_reward_locale arl6 ON ar.ID = arl6.ID AND arl6.Locale = "esES" + LEFT JOIN achievement_reward_locale arl8 ON ar.ID = arl8.ID AND arl8.Locale = "ruRU" + WHERE ar.MailTemplateID = 0 AND ar.Body <> ""' + ); + + DB::Aowow()->query('INSERT INTO ?_mails VALUES (?a)', array_values($acvMail)); + + CLI::write('[mails] - loading data from mail_loot_template'); + + // assume mails to only contain one single item, wich works for an unmodded installation + $mlt = DB::World()->selectCol('SELECT Entry AS ARRAY_KEY, Item FROM mail_loot_template'); + foreach ($mlt as $k => $v) + DB::Aowow()->query('UPDATE ?_mails SET attachment = ?d WHERE id = ?d', $v, $k); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/objects.func.php b/setup/tools/sqlgen/objects.func.php deleted file mode 100644 index 9f81fcc06..000000000 --- a/setup/tools/sqlgen/objects.func.php +++ /dev/null @@ -1,123 +0,0 @@ - 0, -2, -- quests 2 - IF(`type` IN (3, 6, 9, 25), `type`, 0)))), -- regular chests, traps, books, fishing pools - 0 AS event, -- linked worldevent - displayId, - go.name, - IFNULL(gtl2.`name`, "") AS name_loc2, - IFNULL(gtl3.`name`, "") AS name_loc3, - IFNULL(gtl4.`name`, "") AS name_loc4, - IFNULL(gtl6.`name`, "") AS name_loc6, - IFNULL(gtl8.`name`, "") AS name_loc8, - IFNULL(goa.faction, 0), - IFNULL(goa.flags, 0), - 0 AS cuFlags, -- custom Flags - IF(`type` IN (3, 25), Data1, 0), -- lootId - IF(`type` IN (2, 3, 6, 10, 13, 24, 26), Data0, IF(`type` IN (0, 1), Data1, 0)), -- lockId - 0 AS reqSkill, -- reqSkill - IF(`type` = 9, Data0, IF(`type` = 10, Data7, 0)), -- pageTextId - IF(`type` = 1, Data3, -- linkedTrapIds - IF(`type` = 3, Data7, - IF(`type` = 10, Data12, - IF(`type` = 8, Data2, 0)))), - IF(`type` = 5, Data5, -- reqQuest - IF(`type` = 3, Data8, - IF(`type` = 10, Data1, - IF(`type` = 8, Data4, 0)))), - IF(`type` = 8, Data0, 0), -- spellFocusId - IF(`type` = 10, Data10, -- onUseSpell - IF(`type` IN (18, 24), Data1, - IF(`type` = 26, Data2, - IF(`type` = 22, Data0, 0)))), - IF(`type` = 18, Data4, 0), -- onSuccessSpell - IF(`type` = 18, Data2, IF(`type` = 24, Data3, 0)), -- auraSpell - IF(`type` = 30, Data2, IF(`type` = 24, Data4, IF(`type` = 6, Data3, 0))), -- triggeredSpell - IF(`type` = 29, CONCAT_WS(" ", Data14, Data15, Data16, Data17, Data0), -- miscInfo: capturePoint - IF(`type` = 3, CONCAT_WS(" ", Data4, Data5, Data2), -- miscInfo: loot v - IF(`type` = 25, CONCAT_WS(" ", Data2, Data3, 0), - IF(`type` = 23, CONCAT_WS(" ", Data0, Data1, Data2), "")))), -- miscInfo: meetingStone - IF(ScriptName <> "", ScriptName, AIName) - FROM - gameobject_template go - LEFT JOIN - gameobject_template_addon goa ON go.entry = goa.entry - LEFT JOIN - gameobject_template_locale gtl2 ON go.entry = gtl2.entry AND gtl2.`locale` = "frFR" - LEFT JOIN - gameobject_template_locale gtl3 ON go.entry = gtl3.entry AND gtl3.`locale` = "deDE" - LEFT JOIN - gameobject_template_locale gtl4 ON go.entry = gtl4.entry AND gtl4.`locale` = "zhCN" - LEFT JOIN - gameobject_template_locale gtl6 ON go.entry = gtl6.entry AND gtl6.`locale` = "esES" - LEFT JOIN - gameobject_template_locale gtl8 ON go.entry = gtl8.entry AND gtl8.`locale` = "ruRU" - LEFT JOIN - gameobject_questitem gqi ON gqi.GameObjectEntry = go.entry - { - WHERE - go.entry IN (?a) - } - GROUP BY - go.entry - LIMIT - ?d, ?d'; - - $updateQuery = ' - UPDATE - ?_objects o - LEFT JOIN - dbc_lock l ON l.id = IF(o.`type` = 3, lockId, null) - SET - typeCat = IF(`type` = 3 AND (l.properties1 = 1 OR l.properties2 = 1), -5, -- footlocker - IF(`type` = 3 AND (l.properties1 = 2), -3, -- herb - IF(`type` = 3 AND (l.properties1 = 3), -4, typeCat))), -- ore - reqSkill = IF(`type` = 3 AND l.properties1 IN (1, 2, 3), IF(l.reqSkill1 > 1, l.reqSkill1, 1), - IF(`type` = 3 AND l.properties2 = 1, IF(l.reqSkill2 > 1, l.reqSkill2, 1), 0)) - { - WHERE - o.id IN (?a) - }'; - - $i = 0; - DB::Aowow()->query('TRUNCATE ?_objects'); - while ($objects = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, SqlGen::$sqlBatchSize * $i, SqlGen::$sqlBatchSize)) - { - CLI::write(' * batch #' . ++$i . ' (' . count($objects) . ')', CLI::LOG_BLANK, true, true); - - foreach ($objects as $object) - DB::Aowow()->query('INSERT INTO ?_objects VALUES (?a)', array_values($object)); - } - - // apply typeCat and reqSkill depending on locks - DB::Aowow()->query($updateQuery, $ids ?: DBSIMPLE_SKIP); - - $this->reapplyCCFlags('objects', Type::OBJECT); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/objects.ss.php b/setup/tools/sqlgen/objects.ss.php new file mode 100644 index 000000000..5fe1878c4 --- /dev/null +++ b/setup/tools/sqlgen/objects.ss.php @@ -0,0 +1,96 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Gameobject from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['lock']; + protected $worldDependency = ['gameobject_template', 'gameobject_template_addon', 'gameobject_template_locale', 'gameobject_questitem']; + + public function generate(array $ids = []) : bool + { + $baseQuery = + 'SELECT go.entry, + `type`, + IF(`type` = 2, -2, -- quests 1 + IF(`type` = 8 AND data0 IN (1, 2, 3, 4, 1552), -6, -- tools + IF(`type` = 3 AND IFNULL(gqi.ItemId, 0) <> 0, -2, -- quests 2 + IF(`type` IN (3, 6, 9, 25), `type`, 0)))), -- regular chests, traps, books, pools + 0 AS event, -- linked worldevent + displayId, + go.name, + IFNULL(gtl2.`name`, "") AS name_loc2, + IFNULL(gtl3.`name`, "") AS name_loc3, + IFNULL(gtl4.`name`, "") AS name_loc4, + IFNULL(gtl6.`name`, "") AS name_loc6, + IFNULL(gtl8.`name`, "") AS name_loc8, + IFNULL(goa.faction, 0), + IFNULL(goa.flags, 0), + 0 AS cuFlags, + IF(`type` IN (3, 25), data1, 0) AS lootId, + IF(`type` IN (2, 3, 6, 10, 13, 24, 26), data0, IF(`type` IN (0, 1), data1, 0)) AS lockId, + 0 AS reqSkill, + IF(`type` = 9, data0, IF(`type` = 10, data7, 0)) AS pageTextId, + IF(`type` = 1, data3, IF(`type` = 3, data7, IF(`type` = 10, data12, IF(`type` = 8, data2, 0)))) AS linkedTrapId, + GREATEST(IF(`type` = 5, data5, IF(`type` = 3, data8, IF(`type` = 10, data1, IF(`type` = 8, data4, 0)))), 0) AS reqQuest, + IF(`type` = 8, data0, 0) AS spellFocusId, + IF(`type` = 10, data10, IF(`type` IN (18, 24), data1, IF(`type` = 26, data2, IF(`type` = 22, data0, 0)))) AS onUseSpellId, + IF(`type` = 18, data4, 0) AS onSuccessSpell, + IF(`type` = 18, data2, IF(`type` = 24, data3, 0)) AS auraSpellId, + IF(`type` = 30, data2, IF(`type` = 24, data4, IF(`type` = 6, data3, 0))) AS triggeredSpellId, + IF(`type` = 29, CONCAT_WS(" ", data14, data15, data16, data17, data0), -- miscInfo: capturePoint + IF(`type` = 3, CONCAT_WS(" ", data4, data5, data2), -- miscInfo: loot v + IF(`type` = 25, CONCAT_WS(" ", data2, data3, 0), + IF(`type` = 23, CONCAT_WS(" ", data0, data1, data2), "")))), -- miscInfo: meetingStone + IF(ScriptName <> "", ScriptName, AIName) + FROM gameobject_template go + LEFT JOIN gameobject_template_addon goa ON go.entry = goa.entry + LEFT JOIN gameobject_template_locale gtl2 ON go.entry = gtl2.entry AND gtl2.`locale` = "frFR" + LEFT JOIN gameobject_template_locale gtl3 ON go.entry = gtl3.entry AND gtl3.`locale` = "deDE" + LEFT JOIN gameobject_template_locale gtl4 ON go.entry = gtl4.entry AND gtl4.`locale` = "zhCN" + LEFT JOIN gameobject_template_locale gtl6 ON go.entry = gtl6.entry AND gtl6.`locale` = "esES" + LEFT JOIN gameobject_template_locale gtl8 ON go.entry = gtl8.entry AND gtl8.`locale` = "ruRU" + LEFT JOIN gameobject_questitem gqi ON gqi.GameObjectEntry = go.entry + { WHERE go.entry IN (?a) } + GROUP BY go.entry + LIMIT ?d, ?d'; + + $i = 0; + DB::Aowow()->query('TRUNCATE ?_objects'); + while ($objects = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) + { + CLI::write(' * batch #' . ++$i . ' (' . count($objects) . ')', CLI::LOG_BLANK, true, true); + + foreach ($objects as $object) + DB::Aowow()->query('INSERT INTO ?_objects VALUES (?a)', array_values($object)); + } + + // apply typeCat and reqSkill depending on locks + DB::Aowow()->query( + 'UPDATE ?_objects o + LEFT JOIN dbc_lock l ON l.id = IF(o.`type` = 3, lockId, null) + SET typeCat = IF(`type` = 3 AND (l.properties1 = 1 OR l.properties2 = 1), -5, -- footlocker + IF(`type` = 3 AND (l.properties1 = 2), -3, -- herb + IF(`type` = 3 AND (l.properties1 = 3), -4, typeCat))), -- ore + reqSkill = IF(`type` = 3 AND l.properties1 IN (1, 2, 3), IF(l.reqSkill1 > 1, l.reqSkill1, 1), + IF(`type` = 3 AND l.properties2 = 1, IF(l.reqSkill2 > 1, l.reqSkill2, 1), 0)) + { WHERE o.id IN (?a) }', + $ids ?: DBSIMPLE_SKIP + ); + + $this->reapplyCCFlags('objects', Type::OBJECT); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/pet.func.php b/setup/tools/sqlgen/pet.func.php deleted file mode 100644 index 691ec7ca3..000000000 --- a/setup/tools/sqlgen/pet.func.php +++ /dev/null @@ -1,128 +0,0 @@ - -1'; - - $spawnQuery = ' - SELECT - ct.family AS ARRAY_KEY, - MIN(ct.minlevel) AS minLevel, - MAX(ct.maxlevel) AS maxLevel, - IF(ct.type_flags & 0x10000, 1, 0) AS exotic - FROM - creature_template ct - JOIN - creature c ON ct.entry = c.id - WHERE - ct.type_flags & 0x1 - GROUP BY - ct.family'; - - $bonusQuery = ' - UPDATE - ?_pet p, - dbc_skilllineability sla, - dbc_spell s - SET - armor = s.effect2BasePoints + s.effect2DieSides, - damage = s.effect1BasePoints + s.effect1DieSides, - health = s.effect3BasePoints + s.effect3DieSides - WHERE - p.skillLineId = sla.skillLineId AND - sla.spellId = s.id AND - s.name_loc0 = "Tamed Pet Passive (DND)"'; - - $spellQuery = ' - SELECT - p.id, - MAX(s.id) AS spell - FROM - dbc_skilllineability sla - JOIN - ?_pet p ON p.skillLineId = sla.skillLineId - JOIN - dbc_spell s ON sla.spellId = s.id - LEFT OUTER JOIN - dbc_talent t ON s.id = t.rank1 - WHERE - (s.attributes0 & 0x40) = 0 AND - t.id IS NULL - GROUP BY - s.name_loc0, p.id'; - - // basic copy from creaturefamily.dbc - DB::Aowow()->query($baseQuery); - - // stats from craeture_template - $spawnInfo = DB::World()->query($spawnQuery); - foreach ($spawnInfo as $id => $info) - DB::Aowow()->query('UPDATE ?_pet SET ?a WHERE id = ?d', $info, $id); - - // add petFamilyModifier to health, mana, dmg - DB::Aowow()->query($bonusQuery); - - // add expansion manually - DB::Aowow()->query('UPDATE ?_pet SET expansion = 1 WHERE id IN (30, 31, 32, 33, 34)'); - DB::Aowow()->query('UPDATE ?_pet SET expansion = 2 WHERE id IN (37, 38, 39, 41, 42, 43, 44, 45, 46)'); - - // assign pet spells - $pets = DB::Aowow()->select($spellQuery); - $res = []; - - foreach ($pets as $set) // convert to usable structure - { - if (!isset($res[$set['id']])) - $res[$set['id']] = []; - - $res[$set['id']]['spellId'.(count($res[$set['id']]) + 1)] = $set['spell']; - } - - foreach ($res as $pId => $row) - DB::Aowow()->query('UPDATE ?_pet SET ?a WHERE id = ?d', $row, $pId); - - $this->reapplyCCFlags('pet', Type::PET); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/pet.ss.php b/setup/tools/sqlgen/pet.ss.php new file mode 100644 index 000000000..11da37436 --- /dev/null +++ b/setup/tools/sqlgen/pet.ss.php @@ -0,0 +1,109 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Pet from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['talent', 'spell', 'skilllineability', 'creaturefamily']; + protected $worldDependency = ['creature_template', 'creature']; + protected $setupAfter = [['icons'], []]; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_pet'); + + // basic copy from creaturefamily.dbc + DB::Aowow()->query( + 'INSERT INTO ?_pet + SELECT f.id, + categoryEnumId, + 0, -- cuFlags + 0, -- minLevel + 0, -- maxLevel + petFoodMask, + petTalentType, + 0, -- exotic + 0, -- expansion + name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, + ic.id, + skillLine1, + 0, 0, 0, 0, -- spell[1-4] + 0, 0, 0 -- armor, damage, health + FROM dbc_creaturefamily f + LEFT JOIN ?_icons ic ON ic.name = LOWER(SUBSTRING_INDEX(f.iconString, "\\\\", -1)) + WHERE petTalentType <> -1' + ); + + // stats from craeture_template + $spawnInfo = DB::World()->query( + 'SELECT ct.family AS ARRAY_KEY, + MIN(ct.minlevel) AS minLevel, + MAX(ct.maxlevel) AS maxLevel, + IF(ct.type_flags & 0x10000, 1, 0) AS exotic + FROM creature_template ct + JOIN creature c ON ct.entry = c.id + WHERE ct.type_flags & 0x1 + GROUP BY ct.family' + ); + foreach ($spawnInfo as $id => $info) + DB::Aowow()->query('UPDATE ?_pet SET ?a WHERE id = ?d', $info, $id); + + // add petFamilyModifier to health, mana, dmg + DB::Aowow()->query( + 'UPDATE ?_pet p, + dbc_skilllineability sla, + dbc_spell s + SET armor = s.effect2BasePoints + s.effect2DieSides, + damage = s.effect1BasePoints + s.effect1DieSides, + health = s.effect3BasePoints + s.effect3DieSides + WHERE p.skillLineId = sla.skillLineId AND + sla.spellId = s.id AND + s.name_loc0 = "Tamed Pet Passive (DND)"' + ); + + // add expansion manually + /********************/ + /* TODO: MOVE TO DB */ + /********************/ + DB::Aowow()->query('UPDATE ?_pet SET expansion = 1 WHERE id IN (30, 31, 32, 33, 34)'); + DB::Aowow()->query('UPDATE ?_pet SET expansion = 2 WHERE id IN (37, 38, 39, 41, 42, 43, 44, 45, 46)'); + + // assign pet spells + $pets = DB::Aowow()->select( + 'SELECT p.id, MAX(s.id) AS spell + FROM dbc_skilllineability sla + JOIN ?_pet p ON p.skillLineId = sla.skillLineId + JOIN dbc_spell s ON sla.spellId = s.id + LEFT OUTER JOIN dbc_talent t ON s.id = t.rank1 + WHERE (s.attributes0 & 0x40) = 0 AND t.id IS NULL + GROUP BY s.name_loc0, p.id' + ); + + $petSpells = []; + foreach ($pets as $pet) // convert to usable structure + { + if (!isset($petSpells[$pet['id']])) + $petSpells[$pet['id']] = []; + + $petSpells[$pet['id']]['spellId'.(count($petSpells[$pet['id']]) + 1)] = $pet['spell']; + } + + foreach ($petSpells as $petId => $row) + DB::Aowow()->query('UPDATE ?_pet SET ?a WHERE id = ?d', $row, $petId); + + $this->reapplyCCFlags('pet', Type::PET); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/quests.func.php b/setup/tools/sqlgen/quests.func.php deleted file mode 100644 index e0e02ac5d..000000000 --- a/setup/tools/sqlgen/quests.func.php +++ /dev/null @@ -1,226 +0,0 @@ - Quest::CalculateHonorGain(playerLevel) - IFNULL(qa.RewardMailTemplateId, 0), IFNULL(qa.RewardMailDelay, 0), - RewardTitle, - RewardTalents, - RewardArenaPoints, - RewardItem1, RewardItem2, RewardItem3, RewardItem4, - RewardAmount1, RewardAmount2, RewardAmount3, RewardAmount4, - RewardChoiceItemID1, RewardChoiceItemID2, RewardChoiceItemID3, RewardChoiceItemID4, RewardChoiceItemID5, RewardChoiceItemID6, - RewardChoiceItemQuantity1, RewardChoiceItemQuantity2, RewardChoiceItemQuantity3, RewardChoiceItemQuantity4, RewardChoiceItemQuantity5, RewardChoiceItemQuantity6, - RewardFactionID1, RewardFactionID2, RewardFactionID3, RewardFactionID4, RewardFactionID5, - IF (RewardFactionOverride1 <> 0, RewardFactionOverride1 / 100, RewardFactionValue1), - IF (RewardFactionOverride2 <> 0, RewardFactionOverride2 / 100, RewardFactionValue2), - IF (RewardFactionOverride3 <> 0, RewardFactionOverride3 / 100, RewardFactionValue3), - IF (RewardFactionOverride4 <> 0, RewardFactionOverride4 / 100, RewardFactionValue4), - IF (RewardFactionOverride5 <> 0, RewardFactionOverride5 / 100, RewardFactionValue5), - q.LogTitle, IFNULL(qtl2.Title, ""), IFNULL(qtl3.Title, ""), IFNULL(qtl4.Title, ""), IFNULL(qtl6.Title, ""), IFNULL(qtl8.Title, ""), - q.LogDescription, IFNULL(qtl2.Objectives, ""), IFNULL(qtl3.Objectives, ""), IFNULL(qtl4.Objectives, ""), IFNULL(qtl6.Objectives, ""), IFNULL(qtl8.Objectives, ""), - q.QuestDescription, IFNULL(qtl2.Details, ""), IFNULL(qtl3.Details, ""), IFNULL(qtl4.Details, ""), IFNULL(qtl6.Details, ""), IFNULL(qtl8.Details, ""), - q.AreaDescription, IFNULL(qtl2.EndText, ""), IFNULL(qtl3.EndText, ""), IFNULL(qtl4.EndText, ""), IFNULL(qtl6.EndText, ""), IFNULL(qtl8.EndText, ""), - IFNULL(qor.RewardText, ""), IFNULL(qorl2.RewardText, ""), IFNULL(qorl3.RewardText, ""), IFNULL(qorl4.RewardText, ""), IFNULL(qorl6.RewardText, ""), IFNULL(qorl8.RewardText, ""), - IFNULL(qri.CompletionText, ""), IFNULL(qril2.CompletionText, ""), IFNULL(qril3.CompletionText, ""), IFNULL(qril4.CompletionText, ""), IFNULL(qril6.CompletionText, ""), IFNULL(qril8.CompletionText, ""), - q.QuestCompletionLog, IFNULL(qtl2.CompletedText, ""), IFNULL(qtl3.CompletedText, ""), IFNULL(qtl4.CompletedText, ""), IFNULL(qtl6.CompletedText, ""), IFNULL(qtl8.CompletedText, ""), - RequiredNpcOrGo1, RequiredNpcOrGo2, RequiredNpcOrGo3, RequiredNpcOrGo4, - RequiredNpcOrGoCount1, RequiredNpcOrGoCount2, RequiredNpcOrGoCount3, RequiredNpcOrGoCount4, - ItemDrop1, ItemDrop2, ItemDrop3, ItemDrop4, - ItemDropQuantity1, ItemDropQuantity2, ItemDropQuantity3, ItemDropQuantity4, - RequiredItemId1, RequiredItemId2, RequiredItemId3, RequiredItemId4, RequiredItemId5, RequiredItemId6, - RequiredItemCount1, RequiredItemCount2, RequiredItemCount3, RequiredItemCount4, RequiredItemCount5, RequiredItemCount6, - q.ObjectiveText1, IFNULL(qtl2.ObjectiveText1, ""), IFNULL(qtl3.ObjectiveText1, ""), IFNULL(qtl4.ObjectiveText1, ""), IFNULL(qtl6.ObjectiveText1, ""), IFNULL(qtl8.ObjectiveText1, ""), - q.ObjectiveText2, IFNULL(qtl2.ObjectiveText2, ""), IFNULL(qtl3.ObjectiveText2, ""), IFNULL(qtl4.ObjectiveText2, ""), IFNULL(qtl6.ObjectiveText2, ""), IFNULL(qtl8.ObjectiveText2, ""), - q.ObjectiveText3, IFNULL(qtl2.ObjectiveText3, ""), IFNULL(qtl3.ObjectiveText3, ""), IFNULL(qtl4.ObjectiveText3, ""), IFNULL(qtl6.ObjectiveText3, ""), IFNULL(qtl8.ObjectiveText3, ""), - q.ObjectiveText4, IFNULL(qtl2.ObjectiveText4, ""), IFNULL(qtl3.ObjectiveText4, ""), IFNULL(qtl4.ObjectiveText4, ""), IFNULL(qtl6.ObjectiveText4, ""), IFNULL(qtl8.ObjectiveText4, "") - FROM - quest_template q - LEFT JOIN - quest_template_locale qtl2 ON q.ID = qtl2.ID AND qtl2.locale = "frFR" - LEFT JOIN - quest_template_locale qtl3 ON q.ID = qtl3.ID AND qtl3.locale = "deDE" - LEFT JOIN - quest_template_locale qtl4 ON q.ID = qtl4.ID AND qtl4.locale = "zhCN" - LEFT JOIN - quest_template_locale qtl6 ON q.ID = qtl6.ID AND qtl6.locale = "esES" - LEFT JOIN - quest_template_locale qtl8 ON q.ID = qtl8.ID AND qtl8.locale = "ruRU" - LEFT JOIN - quest_offer_reward qor ON q.ID = qor.ID - LEFT JOIN - quest_offer_reward_locale qorl2 ON q.ID = qorl2.ID AND qorl2.locale = "frFR" - LEFT JOIN - quest_offer_reward_locale qorl3 ON q.ID = qorl3.ID AND qorl3.locale = "deDE" - LEFT JOIN - quest_offer_reward_locale qorl4 ON q.ID = qorl4.ID AND qorl4.locale = "zhCN" - LEFT JOIN - quest_offer_reward_locale qorl6 ON q.ID = qorl6.ID AND qorl6.locale = "esES" - LEFT JOIN - quest_offer_reward_locale qorl8 ON q.ID = qorl8.ID AND qorl8.locale = "ruRU" - LEFT JOIN - quest_request_items qri ON q.ID = qri.ID - LEFT JOIN - quest_request_items_locale qril2 ON q.ID = qril2.ID AND qril2.locale = "frFR" - LEFT JOIN - quest_request_items_locale qril3 ON q.ID = qril3.ID AND qril3.locale = "deDE" - LEFT JOIN - quest_request_items_locale qril4 ON q.ID = qril4.ID AND qril4.locale = "zhCN" - LEFT JOIN - quest_request_items_locale qril6 ON q.ID = qril6.ID AND qril6.locale = "esES" - LEFT JOIN - quest_request_items_locale qril8 ON q.ID = qril8.ID AND qril8.locale = "ruRU" - LEFT JOIN - quest_template_addon qa ON q.ID = qa.ID - LEFT JOIN - game_event_seasonal_questrelation gesqr ON gesqr.questId = q.ID - LEFT JOIN - disables d ON d.entry = q.ID AND d.sourceType = 1 - { - WHERE - q.ID IN (?a) - } - LIMIT - ?d, ?d'; - - $xpQuery = ' - UPDATE - ?_quests q, - dbc_questxp xp - SET - rewardXP = (CASE rewardXP - WHEN 0 THEN xp.Field1 WHEN 1 THEN xp.Field2 WHEN 2 THEN xp.Field3 WHEN 3 THEN xp.Field4 WHEN 4 THEN xp.Field5 - WHEN 5 THEN xp.Field6 WHEN 6 THEN xp.Field7 WHEN 7 THEN xp.Field8 WHEN 8 THEN xp.Field9 WHEN 9 THEN xp.Field10 - ELSE 0 - END) - WHERE - xp.id = q.level { AND - q.id IN(?a) - }'; - - $repQuery = ' - UPDATE - ?_quests q - LEFT JOIN - dbc_questfactionreward rep ON rep.id = IF(rewardFactionValue?d > 0, 1, 2) - SET - rewardFactionValue?d = (CASE ABS(rewardFactionValue?d) - WHEN 0 THEN rep.Field1 WHEN 1 THEN rep.Field2 WHEN 2 THEN rep.Field3 WHEN 3 THEN rep.Field4 WHEN 4 THEN rep.Field5 - WHEN 5 THEN rep.Field6 WHEN 6 THEN rep.Field7 WHEN 7 THEN rep.Field8 WHEN 8 THEN rep.Field9 WHEN 9 THEN rep.Field10 - ELSE 0 - END) - WHERE - ABS(rewardFactionValue?d) BETWEEN 1 AND 10 { AND - q.id IN(?a) - }'; - - - $i = 0; - DB::Aowow()->query('TRUNCATE ?_quests'); - while ($quests = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, SqlGen::$sqlBatchSize * $i, SqlGen::$sqlBatchSize)) - { - CLI::write(' * batch #' . ++$i . ' (' . count($quests) . ')', CLI::LOG_BLANK, true, true); - - foreach ($quests as $quest) - DB::Aowow()->query('INSERT INTO ?_quests VALUES (?a)', array_values($quest)); - } - - /* - just some random thoughts here .. - quest-custom-flags are derived from flags and specialFlags - since they are not used further than being sent to JS as wFlags this is fine.. - should they be saved to db anyway..? - same with QUEST_FLAG_UNAVAILABLE => CUSTOM_EXCLUDE_FOR_LISTVIEW - */ - - // unpack XP-reward - DB::Aowow()->query($xpQuery, $ids ?: DBSIMPLE_SKIP); - - // unpack Rep-rewards - for ($i = 1; $i < 6; $i++) - DB::Aowow()->query($repQuery, $i, $i, $i, $i, $ids ?: DBSIMPLE_SKIP); - - foreach (Game::$questSortFix as $child => $parent) - DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE zoneOrSortBak = ?d', $parent, $child); - - // move quests liked to holidays into appropirate quests-sorts. create dummy sorts as needed - $eventSet = DB::World()->selectCol('SELECT holiday AS ARRAY_KEY, eventEntry FROM game_event WHERE holiday <> 0'); - $holidaySorts = array( - 141 => -1001, 181 => -374, 201 => -1002, // Winter Veil Noblegarden Childrens Week - 321 => -1005, 324 => -1003, 404 => -375, // Harvest Fest. Hallows End Pilgrims Bounty - 327 => -366, 341 => -369, 372 => -370, // Lunar Fest. Midsummer Brewfest - 423 => -376 // Love is in the Air - ); - - foreach ($holidaySorts as $hId => $sort) - if (!empty($eventSet[$hId])) - DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE eventId = ?d{ AND id IN (?a)}', $sort, $eventSet[$hId], $ids ?: DBSIMPLE_SKIP); - - // 'special' special cases - // fishing quests to stranglethorn extravaganza - DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE id IN (?a){ AND id IN (?a)}', -101, [8228, 8229], $ids ?: DBSIMPLE_SKIP); - // dungeon quests to Misc/Dungeon Finder - DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE (specialFlags & ?d OR id IN (?a)){ AND id IN (?a)}', -1010, QUEST_FLAG_SPECIAL_DUNGEON_FINDER, [24789, 24791, 24923], $ids ?: DBSIMPLE_SKIP); - - $this->reapplyCCFlags('quests', Type::QUEST); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/quests.ss.php b/setup/tools/sqlgen/quests.ss.php new file mode 100644 index 000000000..2a6b6d6e9 --- /dev/null +++ b/setup/tools/sqlgen/quests.ss.php @@ -0,0 +1,198 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Quest from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['questxp', 'questfactionreward']; + protected $worldDependency = ['quest_template', 'quest_template_addon', 'quest_template_locale', 'game_event', 'game_event_seasonal_questrelation', 'quest_request_items', 'quest_request_items_locale', 'quest_offer_reward', 'quest_offer_reward_locale', 'disables']; + + public function generate(array $ids = []) : bool + { + $baseQuery = + 'SELECT q.ID, + QuestType, + QuestLevel, + MinLevel, + IFNULL(qa.MaxLevel, 0), + QuestSortID, + QuestSortID AS zoneOrSortBak, -- ZoneOrSortBak + QuestInfoID, -- QuestType + SuggestedGroupNum, + TimeAllowed, + IFNULL(gesqr.eventEntry, 0) AS eventId, + IFNULL(qa.PrevQuestId, 0), + IFNULL(qa.NextQuestId, 0), + IFNULL(qa.BreadcrumbForQuestId, 0), + IFNULL(qa.ExclusiveGroup, 0), + RewardNextQuest, + q.Flags, + IFNULL(qa.SpecialFlags, 0), + ( + IF(d.entry IS NULL, 0, 134217728) + -- disabled + IF(q.Flags & 16384, 536870912, 0) -- unavailable + ) AS cuFlags, + IFNULL(qa.AllowableClasses, 0), + AllowableRaces, + IFNULL(qa.RequiredSkillId, 0), IFNULL(qa.RequiredSkillPoints, 0), + RequiredFactionId1, RequiredFactionId2, + RequiredFactionValue1, RequiredFactionValue2, + IFNULL(qa.RequiredMinRepFaction, 0),IFNULL(qa.RequiredMaxRepFaction, 0), + IFNULL(qa.RequiredMinRepValue, 0), IFNULL(qa.RequiredMaxRepValue, 0), + RequiredPlayerKills, + StartItem, + IFNULL(qa.ProvidedItemCount, 0), + IFNULL(qa.SourceSpellId, 0), + RewardXPDifficulty, -- QuestXP.dbc x level + RewardMoney, + RewardBonusMoney, + RewardDisplaySpell, RewardSpell, + RewardHonor * 124 * RewardKillHonor, -- alt calculation in QuestDef.cpp -> Quest::CalculateHonorGain(playerLevel) + IFNULL(qa.RewardMailTemplateId, 0), IFNULL(qa.RewardMailDelay, 0), + RewardTitle, + RewardTalents, + RewardArenaPoints, + RewardItem1, RewardItem2, RewardItem3, RewardItem4, + RewardAmount1, RewardAmount2, RewardAmount3, RewardAmount4, + RewardChoiceItemID1, RewardChoiceItemID2, RewardChoiceItemID3, RewardChoiceItemID4, RewardChoiceItemID5, RewardChoiceItemID6, + RewardChoiceItemQuantity1, RewardChoiceItemQuantity2, RewardChoiceItemQuantity3, RewardChoiceItemQuantity4, RewardChoiceItemQuantity5, RewardChoiceItemQuantity6, + RewardFactionID1, RewardFactionID2, RewardFactionID3, RewardFactionID4, RewardFactionID5, + IF (RewardFactionOverride1 <> 0, RewardFactionOverride1 / 100, RewardFactionValue1), + IF (RewardFactionOverride2 <> 0, RewardFactionOverride2 / 100, RewardFactionValue2), + IF (RewardFactionOverride3 <> 0, RewardFactionOverride3 / 100, RewardFactionValue3), + IF (RewardFactionOverride4 <> 0, RewardFactionOverride4 / 100, RewardFactionValue4), + IF (RewardFactionOverride5 <> 0, RewardFactionOverride5 / 100, RewardFactionValue5), + q.LogTitle, IFNULL(qtl2.Title, ""), IFNULL(qtl3.Title, ""), IFNULL(qtl4.Title, ""), IFNULL(qtl6.Title, ""), IFNULL(qtl8.Title, ""), + q.LogDescription, IFNULL(qtl2.Objectives, ""), IFNULL(qtl3.Objectives, ""), IFNULL(qtl4.Objectives, ""), IFNULL(qtl6.Objectives, ""), IFNULL(qtl8.Objectives, ""), + q.QuestDescription, IFNULL(qtl2.Details, ""), IFNULL(qtl3.Details, ""), IFNULL(qtl4.Details, ""), IFNULL(qtl6.Details, ""), IFNULL(qtl8.Details, ""), + q.AreaDescription, IFNULL(qtl2.EndText, ""), IFNULL(qtl3.EndText, ""), IFNULL(qtl4.EndText, ""), IFNULL(qtl6.EndText, ""), IFNULL(qtl8.EndText, ""), + IFNULL(qor.RewardText, ""), IFNULL(qorl2.RewardText, ""), IFNULL(qorl3.RewardText, ""), IFNULL(qorl4.RewardText, ""), IFNULL(qorl6.RewardText, ""), IFNULL(qorl8.RewardText, ""), + IFNULL(qri.CompletionText, ""), IFNULL(qril2.CompletionText, ""), IFNULL(qril3.CompletionText, ""), IFNULL(qril4.CompletionText, ""), IFNULL(qril6.CompletionText, ""), IFNULL(qril8.CompletionText, ""), + q.QuestCompletionLog, IFNULL(qtl2.CompletedText, ""), IFNULL(qtl3.CompletedText, ""), IFNULL(qtl4.CompletedText, ""), IFNULL(qtl6.CompletedText, ""), IFNULL(qtl8.CompletedText, ""), + RequiredNpcOrGo1, RequiredNpcOrGo2, RequiredNpcOrGo3, RequiredNpcOrGo4, + RequiredNpcOrGoCount1, RequiredNpcOrGoCount2, RequiredNpcOrGoCount3, RequiredNpcOrGoCount4, + ItemDrop1, ItemDrop2, ItemDrop3, ItemDrop4, + ItemDropQuantity1, ItemDropQuantity2, ItemDropQuantity3, ItemDropQuantity4, + RequiredItemId1, RequiredItemId2, RequiredItemId3, RequiredItemId4, RequiredItemId5, RequiredItemId6, + RequiredItemCount1, RequiredItemCount2, RequiredItemCount3, RequiredItemCount4, RequiredItemCount5, RequiredItemCount6, + q.ObjectiveText1, IFNULL(qtl2.ObjectiveText1, ""), IFNULL(qtl3.ObjectiveText1, ""), IFNULL(qtl4.ObjectiveText1, ""), IFNULL(qtl6.ObjectiveText1, ""), IFNULL(qtl8.ObjectiveText1, ""), + q.ObjectiveText2, IFNULL(qtl2.ObjectiveText2, ""), IFNULL(qtl3.ObjectiveText2, ""), IFNULL(qtl4.ObjectiveText2, ""), IFNULL(qtl6.ObjectiveText2, ""), IFNULL(qtl8.ObjectiveText2, ""), + q.ObjectiveText3, IFNULL(qtl2.ObjectiveText3, ""), IFNULL(qtl3.ObjectiveText3, ""), IFNULL(qtl4.ObjectiveText3, ""), IFNULL(qtl6.ObjectiveText3, ""), IFNULL(qtl8.ObjectiveText3, ""), + q.ObjectiveText4, IFNULL(qtl2.ObjectiveText4, ""), IFNULL(qtl3.ObjectiveText4, ""), IFNULL(qtl4.ObjectiveText4, ""), IFNULL(qtl6.ObjectiveText4, ""), IFNULL(qtl8.ObjectiveText4, "") + FROM quest_template q + LEFT JOIN quest_template_locale qtl2 ON q.ID = qtl2.ID AND qtl2.locale = "frFR" + LEFT JOIN quest_template_locale qtl3 ON q.ID = qtl3.ID AND qtl3.locale = "deDE" + LEFT JOIN quest_template_locale qtl4 ON q.ID = qtl4.ID AND qtl4.locale = "zhCN" + LEFT JOIN quest_template_locale qtl6 ON q.ID = qtl6.ID AND qtl6.locale = "esES" + LEFT JOIN quest_template_locale qtl8 ON q.ID = qtl8.ID AND qtl8.locale = "ruRU" + LEFT JOIN quest_offer_reward qor ON q.ID = qor.ID + LEFT JOIN quest_offer_reward_locale qorl2 ON q.ID = qorl2.ID AND qorl2.locale = "frFR" + LEFT JOIN quest_offer_reward_locale qorl3 ON q.ID = qorl3.ID AND qorl3.locale = "deDE" + LEFT JOIN quest_offer_reward_locale qorl4 ON q.ID = qorl4.ID AND qorl4.locale = "zhCN" + LEFT JOIN quest_offer_reward_locale qorl6 ON q.ID = qorl6.ID AND qorl6.locale = "esES" + LEFT JOIN quest_offer_reward_locale qorl8 ON q.ID = qorl8.ID AND qorl8.locale = "ruRU" + LEFT JOIN quest_request_items qri ON q.ID = qri.ID + LEFT JOIN quest_request_items_locale qril2 ON q.ID = qril2.ID AND qril2.locale = "frFR" + LEFT JOIN quest_request_items_locale qril3 ON q.ID = qril3.ID AND qril3.locale = "deDE" + LEFT JOIN quest_request_items_locale qril4 ON q.ID = qril4.ID AND qril4.locale = "zhCN" + LEFT JOIN quest_request_items_locale qril6 ON q.ID = qril6.ID AND qril6.locale = "esES" + LEFT JOIN quest_request_items_locale qril8 ON q.ID = qril8.ID AND qril8.locale = "ruRU" + LEFT JOIN quest_template_addon qa ON q.ID = qa.ID + LEFT JOIN game_event_seasonal_questrelation gesqr ON gesqr.questId = q.ID + LEFT JOIN disables d ON d.entry = q.ID AND d.sourceType = 1 + { WHERE q.Id IN (?a) } + LIMIT ?d, ?d'; + + $i = 0; + DB::Aowow()->query('TRUNCATE ?_quests'); + while ($quests = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) + { + CLI::write(' * batch #' . ++$i . ' (' . count($quests) . ')', CLI::LOG_BLANK, true, true); + + foreach ($quests as $quest) + DB::Aowow()->query('INSERT INTO ?_quests VALUES (?a)', array_values($quest)); + } + + /* + just some random thoughts here .. + quest-custom-flags are derived from flags and specialFlags + since they are not used further than being sent to JS as wFlags this is fine.. + should they be saved to db anyway..? + same with QUEST_FLAG_UNAVAILABLE => CUSTOM_EXCLUDE_FOR_LISTVIEW + */ + + // unpack XP-reward + DB::Aowow()->query( + 'UPDATE ?_quests q, dbc_questxp xp + SET rewardXP = (CASE rewardXP + WHEN 0 THEN xp.Field1 WHEN 1 THEN xp.Field2 WHEN 2 THEN xp.Field3 WHEN 3 THEN xp.Field4 WHEN 4 THEN xp.Field5 + WHEN 5 THEN xp.Field6 WHEN 6 THEN xp.Field7 WHEN 7 THEN xp.Field8 WHEN 8 THEN xp.Field9 WHEN 9 THEN xp.Field10 + ELSE 0 END) + WHERE xp.id = q.level { AND q.id IN (?a) }', + $ids ?: DBSIMPLE_SKIP + ); + + // unpack Rep-rewards + for ($i = 1; $i < 6; $i++) + DB::Aowow()->query( + 'UPDATE ?_quests q + LEFT JOIN dbc_questfactionreward rep ON rep.id = IF(rewardFactionValue?d > 0, 1, 2) + SET rewardFactionValue?d = (CASE ABS(rewardFactionValue?d) + WHEN 0 THEN rep.Field1 WHEN 1 THEN rep.Field2 WHEN 2 THEN rep.Field3 WHEN 3 THEN rep.Field4 WHEN 4 THEN rep.Field5 + WHEN 5 THEN rep.Field6 WHEN 6 THEN rep.Field7 WHEN 7 THEN rep.Field8 WHEN 8 THEN rep.Field9 WHEN 9 THEN rep.Field10 + ELSE 0 END) + WHERE ABS(rewardFactionValue?d) BETWEEN 1 AND 10 { AND q.id IN (?a) }', + $i, $i, $i, $i, + $ids ?: DBSIMPLE_SKIP + ); + + foreach (Game::$questSortFix as $child => $parent) + DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE zoneOrSortBak = ?d', $parent, $child); + + // move quests linked to holidays into appropirate quests-sorts. create dummy sorts as needed + $eventSet = DB::World()->selectCol('SELECT holiday AS ARRAY_KEY, eventEntry FROM game_event WHERE holiday <> 0'); + $holidaySorts = array( + 141 => -1001, 181 => -374, 201 => -1002, // Winter Veil Noblegarden Childrens Week + 321 => -1005, 324 => -1003, 404 => -375, // Harvest Fest. Hallows End Pilgrims Bounty + 327 => -366, 341 => -369, 372 => -370, // Lunar Fest. Midsummer Brewfest + 423 => -376 // Love is in the Air + ); + + foreach ($holidaySorts as $hId => $sort) + if (!empty($eventSet[$hId])) + DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE eventId = ?d{ AND id IN (?a)}', $sort, $eventSet[$hId], $ids ?: DBSIMPLE_SKIP); + + // 'special' special cases + // fishing quests to stranglethorn extravaganza + DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE id IN (?a){ AND id IN (?a)}', -101, [8228, 8229], $ids ?: DBSIMPLE_SKIP); + // dungeon quests to Misc/Dungeon Finder + DB::Aowow()->query('UPDATE ?_quests SET zoneOrSort = ?d WHERE (specialFlags & ?d OR id IN (?a)){ AND id IN (?a)}', -1010, QUEST_FLAG_SPECIAL_DUNGEON_FINDER, [24789, 24791, 24923], $ids ?: DBSIMPLE_SKIP); + + DB::Aowow()->query( + 'UPDATE ?_quests + SET `cuFlags` = `cuFlags` | ?d + WHERE `name_loc0` LIKE "%<%" OR `name_loc0` LIKE "%[%" OR + `name_loc0` LIKE "%deprecated%" OR + `name_loc0` LIKE "%unused%" OR + `name_loc0` LIKE "temp %"', + CUSTOM_EXCLUDE_FOR_LISTVIEW + ); + + $this->reapplyCCFlags('quests', Type::QUEST); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/quests_startend.func.php b/setup/tools/sqlgen/questsstartend.ss.php similarity index 70% rename from setup/tools/sqlgen/quests_startend.func.php rename to setup/tools/sqlgen/questsstartend.ss.php index 639158d21..61a43ebbc 100644 --- a/setup/tools/sqlgen/quests_startend.func.php +++ b/setup/tools/sqlgen/questsstartend.ss.php @@ -7,32 +7,34 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'quests_startend'; + protected $info = array( + 'quests_startend' => [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Quest from world db.'] + ); - protected $tblDependencyTC = ['creature_queststarter', 'creature_questender', 'game_event_creature_quest', 'gameobject_queststarter', 'gameobject_questender', 'game_event_gameobject_quest', 'item_template']; + protected $worldDependency = ['creature_queststarter', 'creature_questender', 'game_event_creature_quest', 'gameobject_queststarter', 'gameobject_questender', 'game_event_gameobject_quest', 'item_template']; public function generate(array $ids = []) : bool { - $query['creature'] = ' - SELECT 1 AS type, id AS typeId, quest AS questId, 1 AS method, 0 AS eventId FROM creature_queststarter UNION + $query['NPC'] = + 'SELECT 1 AS type, id AS typeId, quest AS questId, 1 AS method, 0 AS eventId FROM creature_queststarter UNION SELECT 1 AS type, id AS typeId, quest AS questId, 2 AS method, 0 AS eventId FROM creature_questender UNION SELECT 1 AS type, id AS typeId, quest AS questId, 1 AS method, eventEntry AS eventId FROM game_event_creature_quest'; - $query['object'] = ' - SELECT 2 AS type, id AS typeId, quest AS questId, 1 AS method, 0 AS eventId FROM gameobject_queststarter UNION + $query['Object'] = + 'SELECT 2 AS type, id AS typeId, quest AS questId, 1 AS method, 0 AS eventId FROM gameobject_queststarter UNION SELECT 2 AS type, id AS typeId, quest AS questId, 2 AS method, 0 AS eventId FROM gameobject_questender UNION SELECT 2 AS type, id AS typeId, quest AS questId, 1 AS method, eventEntry AS eventId FROM game_event_gameobject_quest'; - $query['item'] = 'SELECT 3 AS type, entry AS typeId, startquest AS questId, 1 AS method, 0 AS eventId FROM item_template WHERE startquest <> 0'; + $query['Item'] = 'SELECT 3 AS type, entry AS typeId, startquest AS questId, 1 AS method, 0 AS eventId FROM item_template WHERE startquest <> 0'; - // always rebuild this table from scratch - // or how would i know what to fetch specifically - DB::Aowow()->query('TRUNCATE TABLE ?_quests_startend'); + DB::Aowow()->query('TRUNCATE ?_quests_startend'); - foreach ($query as $q) + foreach ($query as $n => $q) { + CLI::write(' - ' . $n . ' start/end-points', CLI::LOG_BLANK, true, true); + $data = DB::World()->select($q); foreach ($data as $d) DB::Aowow()->query('INSERT INTO ?_quests_startend (?#) VALUES (?a) ON DUPLICATE KEY UPDATE method = method | VALUES(method), eventId = IF(eventId = 0, VALUES(eventId), eventId)', array_keys($d), array_values($d)); diff --git a/setup/tools/sqlgen/races.func.php b/setup/tools/sqlgen/races.ss.php similarity index 59% rename from setup/tools/sqlgen/races.func.php rename to setup/tools/sqlgen/races.ss.php index e299d8e4c..fe5d3dbc3 100644 --- a/setup/tools/sqlgen/races.func.php +++ b/setup/tools/sqlgen/races.ss.php @@ -7,29 +7,24 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrCustomData; // import custom data from DB - protected $command = 'races'; + protected $info = array( + 'races' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: PlayerRace from dbc.'] + ); protected $dbcSourceFiles = ['chrraces', 'charbaseinfo']; public function generate(array $ids = []) : bool { - /**********/ - /* Basics */ - /**********/ - - $baseQuery = ' - REPLACE INTO - ?_races - SELECT - id, 0, flags, 0, factionId, 0, 0, baseLanguage, IF(side = 2, 0, side + 1), fileString, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, expansion - FROM - dbc_chrraces'; - - DB::Aowow()->query($baseQuery); + DB::Aowow()->query('TRUNCATE ?_races'); + DB::Aowow()->query( + 'INSERT INTO ?_races + SELECT id, 0, flags, 0, factionId, 0, 0, baseLanguage, IF(side = 2, 0, side + 1), fileString, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, expansion + FROM dbc_chrraces' + ); // add classMask DB::Aowow()->query('UPDATE ?_races r JOIN (SELECT BIT_OR(1 << (classId - 1)) as classMask, raceId FROM dbc_charbaseinfo GROUP BY raceId) cbi ON cbi.raceId = r.id SET r.classMask = cbi.classMask'); diff --git a/setup/tools/sqlgen/scalingstatdistribution.func.php b/setup/tools/sqlgen/scalingstatdistribution.ss.php similarity index 81% rename from setup/tools/sqlgen/scalingstatdistribution.func.php rename to setup/tools/sqlgen/scalingstatdistribution.ss.php index f8be3f309..5d1f94af3 100644 --- a/setup/tools/sqlgen/scalingstatdistribution.func.php +++ b/setup/tools/sqlgen/scalingstatdistribution.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/scalingstatvalues.func.php b/setup/tools/sqlgen/scalingstatvalues.ss.php similarity index 80% rename from setup/tools/sqlgen/scalingstatvalues.func.php rename to setup/tools/sqlgen/scalingstatvalues.ss.php index 0b1fb5829..bf809eb87 100644 --- a/setup/tools/sqlgen/scalingstatvalues.func.php +++ b/setup/tools/sqlgen/scalingstatvalues.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/shapeshiftforms.func.php b/setup/tools/sqlgen/shapeshiftforms.func.php deleted file mode 100644 index 245ace2db..000000000 --- a/setup/tools/sqlgen/shapeshiftforms.func.php +++ /dev/null @@ -1,36 +0,0 @@ -query(' - REPLACE INTO - ?_shapeshiftforms - SELECT - id, flags, creatureType, - displayIdA, displayIdH, - spellId1, spellId2, spellId3, spellId4, spellId5, spellId6, spellId7, spellId8, - IF(name_loc0 = "", IF(name_loc2 = "", IF(name_loc3 = "", IF(name_loc6 = "", IF(name_loc8 = "", "???", name_loc8), name_loc6), name_loc3), name_loc2), name_loc0) - FROM - dbc_spellshapeshiftform' - ); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/shapeshiftforms.ss.php b/setup/tools/sqlgen/shapeshiftforms.ss.php new file mode 100644 index 000000000..9602128f3 --- /dev/null +++ b/setup/tools/sqlgen/shapeshiftforms.ss.php @@ -0,0 +1,35 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Spell from dbc.'] + ); + + protected $dbcSourceFiles = ['spellshapeshiftform']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_shapeshiftforms'); + DB::Aowow()->query( + 'INSERT INTO ?_shapeshiftforms + SELECT id, flags, creatureType, displayIdA, displayIdH, + spellId1, spellId2, spellId3, spellId4, spellId5, spellId6, spellId7, spellId8, + IF(name_loc0 = "", IF(name_loc2 = "", IF(name_loc3 = "", IF(name_loc4 = "", IF(name_loc6 = "", IF(name_loc8 = "", "???", name_loc8), name_loc6), name_loc4), name_loc3), name_loc2), name_loc0) + FROM dbc_spellshapeshiftform' + ); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/skillline.func.php b/setup/tools/sqlgen/skillline.ss.php similarity index 59% rename from setup/tools/sqlgen/skillline.func.php rename to setup/tools/sqlgen/skillline.ss.php index e1511d069..4f1781461 100644 --- a/setup/tools/sqlgen/skillline.func.php +++ b/setup/tools/sqlgen/skillline.ss.php @@ -7,26 +7,25 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrCustomData; // import custom data from DB - protected $command = 'skillline'; + protected $info = array( + 'skillline' => [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Skill from dbc.'] + ); - protected $tblDependencyAowow = ['icons']; protected $dbcSourceFiles = ['skillline', 'spell', 'skilllineability']; + protected $setupAfter = [['icons'], []]; public function generate(array $ids = []) : bool { - $baseQuery = ' - REPLACE INTO - ?_skillline - SELECT - id, categoryId, 0, categoryId, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, description_loc0, description_loc2, description_loc3, description_loc4, description_loc6, description_loc8, 0, iconId, 0, 0, "" - FROM - dbc_skillline'; - - DB::Aowow()->query($baseQuery); + DB::Aowow()->query('TRUNCATE ?_skillline'); + DB::Aowow()->query( + 'INSERT INTO ?_skillline + SELECT id, categoryId, 0, categoryId, name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, description_loc0, description_loc2, description_loc3, description_loc4, description_loc6, description_loc8, 0, iconId, 0, 0, "" + FROM dbc_skillline' + ); // categorization DB::Aowow()->query('UPDATE ?_skillline SET typeCat = -5 WHERE id = 777 OR (categoryId = 9 AND id NOT IN (356, 129, 185, 142, 155))'); @@ -39,22 +38,19 @@ public function generate(array $ids = []) : bool // apply icons DB::Aowow()->query('UPDATE ?_skillline sl, ?_icons ic, dbc_spellicon si SET sl.iconId = ic.id WHERE sl.iconIdBak = si.id AND ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1))'); - DB::Aowow()->query(' - UPDATE - ?_skillline sl, - dbc_spell s, - dbc_skilllineability sla, - ?_icons ic, - dbc_spellicon si - SET - sl.iconId = ic.id - WHERE - (s.effect1Id IN (25, 26, 40) OR s.effect2Id = 60) AND - ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) AND - s.iconId = si.id AND - sla.spellId = s.id AND - sl.id = sla.skillLineId - '); + DB::Aowow()->query( + 'UPDATE ?_skillline sl, + dbc_spell s, + dbc_skilllineability sla, + ?_icons ic, + dbc_spellicon si + SET sl.iconId = ic.id + WHERE (s.effect1Id IN (25, 26, 40) OR s.effect2Id = 60) AND + ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) AND + s.iconId = si.id AND + sla.spellId = s.id AND + sl.id = sla.skillLineId' + ); DB::Aowow()->query('UPDATE ?_skillline sl, ?_icons ic SET sl.iconId = ic.id WHERE ic.name = ? AND sl.id = ?d', 'inv_misc_pelt_wolf_01', 393); DB::Aowow()->query('UPDATE ?_skillline sl, ?_icons ic SET sl.iconId = ic.id WHERE ic.name = ? AND sl.id = ?d', 'inv_misc_key_03', 633); diff --git a/setup/tools/sqlgen/skilllineability.func.php b/setup/tools/sqlgen/skilllineability.ss.php similarity index 79% rename from setup/tools/sqlgen/skilllineability.func.php rename to setup/tools/sqlgen/skilllineability.ss.php index eebdb0157..562d2f121 100644 --- a/setup/tools/sqlgen/skilllineability.func.php +++ b/setup/tools/sqlgen/skilllineability.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup('sql', new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/soundemitter.func.php b/setup/tools/sqlgen/soundemitter.ss.php similarity index 79% rename from setup/tools/sqlgen/soundemitter.func.php rename to setup/tools/sqlgen/soundemitter.ss.php index 5a551d406..9e33f5e75 100644 --- a/setup/tools/sqlgen/soundemitter.func.php +++ b/setup/tools/sqlgen/soundemitter.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup('sql', new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/sounds.func.php b/setup/tools/sqlgen/sounds.func.php deleted file mode 100644 index e4e9a27f9..000000000 --- a/setup/tools/sqlgen/sounds.func.php +++ /dev/null @@ -1,453 +0,0 @@ - AreaTable.dbc/id - $worldStateZoneSoundFix = array( - 18153 => 2119, - 18154 => 2119, - 47321 => 4273, // The Spark of Imagination - 43600 => 4273, // The Celestial Planetarium - 47478 => 4273 // The Prison of Yogg-Saron - ); - - - /***********/ - /* M A I N */ - /***********/ - - CLI::write(' - sounds main data'); - - // file extraction and conversion manually - // moving files in build step. data here is purely structural - // reality check ... thats probably gigabytes worth of sound.. only growing in size with every locale added on top (RedRocketSite didn't do it. Should i then?) - - // .wav => audio/ogg; codecs="vorbis" - // .mp3 => audio/mpeg - - $query = ' - SELECT id AS `id`, `type` AS `cat`, `name`, 0 AS cuFlags, - `file1` AS soundFile1, `file2` AS soundFile2, `file3` AS soundFile3, `file4` AS soundFile4, `file5` AS soundFile5, - `file6` AS soundFile6, `file7` AS soundFile7, `file8` AS soundFile8, `file9` AS soundFile9, `file10` AS soundFile10, - path, flags - FROM dbc_soundentries - WHERE id > ?d LIMIT ?d - '; - - DB::Aowow()->query('TRUNCATE ?_sounds'); - DB::Aowow()->query('TRUNCATE ?_sounds_files'); - - $lastMax = 0; - $soundFileIdx = 0; - $soundIndex = []; - $j = 0; - while ($sounds = DB::Aowow()->select($query, $lastMax, SqlGen::$sqlBatchSize)) - { - $newMax = max(array_column($sounds, 'id')); - - CLI::write(' * batch #' . ++$j . ' (' . count($sounds) . ')', CLI::LOG_BLANK, true, true); - - $lastMax = $newMax; - - $groupSets = []; - foreach ($sounds as $s) - { - /* attention! - - one sound can be used in 20 or more locations but may appear as multiple files, - because of different cases, path being attached to file and other shenanigans - - build a usable path and create full index to compensate - 25.6k raw files => expect ~21k filtered files - */ - - $fileSets = []; - $hasDupes = false; - for ($i = 1; $i < 11; $i++) - { - $nicePath = CLI::nicePath($s['soundFile'.$i], $s['path']); - if ($s['soundFile'.$i] && array_key_exists($nicePath, $soundIndex)) - { - $s['soundFile'.$i] = $soundIndex[$nicePath]; - $hasDupes = true; - continue; - } - - // convert to something web friendly => ogg - if (stristr($s['soundFile'.$i], '.wav')) - { - $soundIndex[$nicePath] = ++$soundFileIdx; - - $fileSets[] = array( - $soundFileIdx, - $s['soundFile'.$i], - $s['path'], - SOUND_TYPE_OGG - ); - $s['soundFile'.$i] = $soundFileIdx; - } - // mp3 .. keep as is - else if (stristr($s['soundFile'.$i], '.mp3')) - { - $soundIndex[$nicePath] = ++$soundFileIdx; - - $fileSets[] = array( - $soundFileIdx, - $s['soundFile'.$i], - $s['path'], - SOUND_TYPE_MP3 - ); - $s['soundFile'.$i] = $soundFileIdx; - } - // i call bullshit - else if ($s['soundFile'.$i]) - { - CLI::write(' - sound group #'.$s['id'].' "'.$s['name'].'" has invalid sound file "'.$s['soundFile'.$i].'" on index '.$i.'! Skipping...', CLI::LOG_WARN); - $s['soundFile'.$i] = null; - } - // empty case - else - $s['soundFile'.$i] = null; - } - - if (!$fileSets && !$hasDupes) - { - CLI::write(' - sound group #'.$s['id'].' "'.$s['name'].'" contains no sound files! Skipping...', CLI::LOG_WARN); - continue; - } - else if ($fileSets) - DB::Aowow()->query('INSERT INTO ?_sounds_files VALUES (?a)', array_values($fileSets)); - - unset($s['path']); - - $groupSets[] = array_values($s); - } - - DB::Aowow()->query('REPLACE INTO ?_sounds VALUES (?a)', array_values($groupSets)); - } - - - /******************/ - /* VocalUI Sounds */ - /******************/ - - CLI::write(' - linking to race'); - - DB::Aowow()->query('TRUNCATE ?_races_sounds'); // just to silence expected duplicate key errors - DB::Aowow()->query('INSERT INTO ?_races_sounds SELECT `raceId`, `soundIdMale`, 1 FROM dbc_vocaluisounds WHERE `soundIdMale` <> `soundIdFemale` AND `soundIdMale` > 0 ON DUPLICATE KEY UPDATE `soundId` = `soundId`'); - DB::Aowow()->query('INSERT INTO ?_races_sounds SELECT `raceId`, `soundIdFemale`, 2 FROM dbc_vocaluisounds WHERE `soundIdMale` <> `soundIdFemale` AND `soundIdFemale` > 0 ON DUPLICATE KEY UPDATE `soundId` = `soundId`'); - - // ps: im too dumb to union this - - - /***************/ - /* Emote Sound */ - /***************/ - - CLI::write(' - linking to emotes'); - - DB::Aowow()->query('TRUNCATE ?_emotes_sounds'); // just to silence expected duplicate key errors - DB::Aowow()->query('INSERT INTO ?_emotes_sounds SELECT `emotesTextId`, `raceId`, `gender` + 1, `soundId` FROM dbc_emotestextsound ON DUPLICATE KEY UPDATE `emoteId` = `emoteId`'); - - - /*******************/ - /* Creature Sounds */ - /*******************/ - - CLI::write(' - linking to creatures'); - - // currently ommitting: - // * footsteps (matrix of: creature + terrain + humidity) - // * fidget2 through 5 - // * customattack2 through 3 - // in case of conflicting data CreatureDisplayInfo overrides CreatureModelData (seems to be more specialized (Thral > MaleOrc / Maiden > FemaleTitan)) - - DB::Aowow()->query('TRUNCATE ?_creature_sounds'); - DB::Aowow()->query(' - INSERT INTO - ?_creature_sounds (`id`, `greeting`, `farewell`, `angry`, `exertion`, `exertioncritical`, `injury`, `injurycritical`, `death`, `stun`, `stand`, `aggro`, `wingflap`, `wingglide`, `alert`, `fidget`, `customattack`, `loop`, `jumpstart`, `jumpend`, `petattack`, `petorder`, `petdismiss`, `birth`, `spellcast`, `submerge`, `submerged`) - SELECT - cdi.id, - GREATEST(IFNULL(ns.greetSoundId, 0), 0), - GREATEST(IFNULL(ns.byeSoundId, 0), 0), - GREATEST(IFNULL(ns.angrySoundId, 0), 0), - GREATEST(IF(csdA.exertion, csdA.exertion, IFNULL(csdB.exertion, 0)), 0), - GREATEST(IF(csdA.exertionCritical, csdA.exertionCritical, IFNULL(csdB.exertionCritical, 0)), 0), - GREATEST(IF(csdA.injury, csdA.injury, IFNULL(csdB.injury, 0)), 0), - GREATEST(IF(csdA.injuryCritical, csdA.injuryCritical, IFNULL(csdB.injuryCritical, 0)), 0), - GREATEST(IF(csdA.death, csdA.death, IFNULL(csdB.death, 0)), 0), - GREATEST(IF(csdA.stun, csdA.stun, IFNULL(csdB.stun, 0)), 0), - GREATEST(IF(csdA.stand, csdA.stand, IFNULL(csdB.stand, 0)), 0), - GREATEST(IF(csdA.aggro, csdA.aggro, IFNULL(csdB.aggro, 0)), 0), - GREATEST(IF(csdA.wingFlap, csdA.wingFlap, IFNULL(csdB.wingFlap, 0)), 0), - GREATEST(IF(csdA.wingGlide, csdA.wingGlide, IFNULL(csdB.wingGlide, 0)), 0), - GREATEST(IF(csdA.alert, csdA.alert, IFNULL(csdB.alert, 0)), 0), - GREATEST(IF(csdA.fidget, csdA.fidget, IFNULL(csdB.fidget, 0)), 0), - GREATEST(IF(csdA.customAttack, csdA.customAttack, IFNULL(csdB.customAttack, 0)), 0), - GREATEST(IF(csdA.loop, csdA.loop, IFNULL(csdB.loop, 0)), 0), - GREATEST(IF(csdA.jumpStart, csdA.jumpStart, IFNULL(csdB.jumpStart, 0)), 0), - GREATEST(IF(csdA.jumpEnd, csdA.jumpEnd, IFNULL(csdB.jumpEnd, 0)), 0), - GREATEST(IF(csdA.petAttack, csdA.petAttack, IFNULL(csdB.petAttack, 0)), 0), - GREATEST(IF(csdA.petOrder, csdA.petOrder, IFNULL(csdB.petOrder, 0)), 0), - GREATEST(IF(csdA.petDismiss, csdA.petDismiss, IFNULL(csdB.petDismiss, 0)), 0), - GREATEST(IF(csdA.birth, csdA.birth, IFNULL(csdB.birth, 0)), 0), - GREATEST(IF(csdA.spellcast, csdA.spellcast, IFNULL(csdB.spellcast, 0)), 0), - GREATEST(IF(csdA.submerge, csdA.submerge, IFNULL(csdB.submerge, 0)), 0), - GREATEST(IF(csdA.submerged, csdA.submerged, IFNULL(csdB.submerged, 0)), 0) - FROM - dbc_creaturedisplayinfo cdi - LEFT JOIN - dbc_creaturemodeldata cmd ON cmd.id = cdi.modelId - LEFT JOIN - dbc_creaturesounddata csdA ON cdi.creatureSoundId = csdA.id - LEFT JOIN - dbc_creaturesounddata csdB ON cmd.creatureSoundId = csdB.id - LEFT JOIN - dbc_npcsounds ns ON cdi.npcSoundId = ns.id - '); - - - /****************/ - /* Spell Sounds */ - /****************/ - - CLI::write(' - linking to spells'); - - // issues: (probably because of 335-data) - // * animate is probably wrong - // * missile and impactarea not in js - // * ready, castertargeting, casterstate and targetstate not in dbc - - DB::Aowow()->query('TRUNCATE ?_spell_sounds'); - DB::Aowow()->query(' - INSERT INTO - ?_spell_sounds (`id`, `precast`, `cast`, `impact`, `state`, `statedone`, `channel`, `missile`, `animation`, `casterimpact`, `targetimpact`, `missiletargeting`, `instantarea`, `impactarea`, `persistentarea`) - SELECT - sv.id, - GREATEST(IFNULL(svk1.soundId, 0), 0), - GREATEST(IFNULL(svk2.soundId, 0), 0), - GREATEST(IFNULL(svk3.soundId, 0), 0), - GREATEST(IFNULL(svk4.soundId, 0), 0), - GREATEST(IFNULL(svk5.soundId, 0), 0), - GREATEST(IFNULL(svk6.soundId, 0), 0), - GREATEST(missileSoundId, 0), - GREATEST(animationSoundId, 0), - GREATEST(IFNULL(svk7.soundId, 0), 0), - GREATEST(IFNULL(svk8.soundId, 0), 0), - GREATEST(IFNULL(svk9.soundId, 0), 0), - GREATEST(IFNULL(svk10.soundId, 0), 0), - GREATEST(IFNULL(svk11.soundId, 0), 0), - GREATEST(IFNULL(svk12.soundId, 0), 0) - FROM - dbc_spellvisual sv - LEFT JOIN - dbc_spellvisualkit svk1 ON svk1.id = sv.precastKitId - LEFT JOIN - dbc_spellvisualkit svk2 ON svk2.id = sv.castKitId - LEFT JOIN - dbc_spellvisualkit svk3 ON svk3.id = sv.impactKitId - LEFT JOIN - dbc_spellvisualkit svk4 ON svk4.id = sv.stateKitId - LEFT JOIN - dbc_spellvisualkit svk5 ON svk5.id = sv.statedoneKitId - LEFT JOIN - dbc_spellvisualkit svk6 ON svk6.id = sv.channelKitId - LEFT JOIN - dbc_spellvisualkit svk7 ON svk7.id = sv.casterImpactKitId - LEFT JOIN - dbc_spellvisualkit svk8 ON svk8.id = sv.targetImpactKitId - LEFT JOIN - dbc_spellvisualkit svk9 ON svk9.id = sv.missileTargetingKitId - LEFT JOIN - dbc_spellvisualkit svk10 ON svk10.id = sv.instantAreaKitId - LEFT JOIN - dbc_spellvisualkit svk11 ON svk11.id = sv.impactAreaKitId - LEFT JOIN - dbc_spellvisualkit svk12 ON svk12.id = sv.persistentAreaKitId - '); - - - /***************/ - /* Zone Sounds */ - /***************/ - - CLI::write(' - linking to zones'); - - // omiting data from WMOAreaTable, as its at the moment impossible to link to actual zones - - DB::Aowow()->query('TRUNCATE ?_zones_sounds'); - DB::Aowow()->query(' - INSERT INTO - ?_zones_sounds (id, ambienceDay, ambienceNight, musicDay, musicNight, intro, worldStateId, worldStateValue) - SELECT - a.id, - IFNULL(sa1.soundIdDay, 0), - IFNULL(sa1.soundIdNight, 0), - IFNULL(zm1.soundIdDay, 0), - IFNULL(zm1.soundIdNight, 0), - IFNULL(zimt1.soundId, 0), - 0, - 0 - FROM - dbc_areatable a - LEFT JOIN - dbc_soundambience sa1 ON sa1.id = a.soundAmbience - LEFT JOIN - dbc_zonemusic zm1 ON zm1.id = a.zoneMusic - LEFT JOIN - dbc_zoneintromusictable zimt1 ON zimt1.id = a.zoneIntroMusic - WHERE - a.soundAmbience > 0 OR a.zoneMusic > 0 OR a.zoneIntroMusic - UNION - SELECT - IF(wszs.areaId, wszs.areaId, wszs.wmoAreaId), - IFNULL(sa2.soundIdDay, 0), - IFNULL(sa2.soundIdNight, 0), - IFNULL(zm2.soundIdDay, 0), - IFNULL(zm2.soundIdNight, 0), - IFNULL(zimt2.soundId, 0), - wszs.stateId, - wszs.value - FROM - dbc_worldstatezonesounds wszs - LEFT JOIN - dbc_soundambience sa2 ON sa2.id = wszs.soundAmbienceId - LEFT JOIN - dbc_zonemusic zm2 ON zm2.id = wszs.zoneMusicId - LEFT JOIN - dbc_zoneintromusictable zimt2 ON zimt2.id = wszs.zoneIntroMusicId - WHERE - wszs.zoneMusicId > 0 AND (wszs.areaId OR wszs.wmoAreaId IN (?a)) - ', array_keys($worldStateZoneSoundFix)); - - // apply post-fix - foreach ($worldStateZoneSoundFix as $old => $new) - DB::Aowow()->query('UPDATE ?_zones_sounds SET id = ?d WHERE id = ?d', $new, $old); - - - /***************/ - /* Item Sounds */ - /***************/ - - CLI::write(' - linking to items'); - - DB::Aowow()->query(' - UPDATE - ?_items i - LEFT JOIN - dbc_itemdisplayinfo idi ON - idi.id = i.displayId - LEFT JOIN - dbc_itemgroupsounds igs ON - igs.id = idi.groupSoundId - LEFT JOIN - dbc_material m ON - m.id = i.material - SET - i.spellVisualId = IFNULL(idi.spellVisualId, 0), - i.pickUpSoundId = IFNULL(igs.pickUpSoundId, 0), - i.dropDownSoundId = IFNULL(igs.dropDownSoundId, 0), - i.sheatheSoundId = IFNULL(m.sheatheSoundId, 0), - i.unsheatheSoundId = IFNULL(m.unsheatheSoundId, 0) - '); - - DB::Aowow()->query('TRUNCATE ?_items_sounds'); - - $fields = ['hit', 'crit']; - foreach ($fields as $f) - { - for ($i = 1; $i <= 10; $i++) - { - DB::Aowow()->query(' - INSERT INTO - ?_items_sounds - SELECT - ?#, - (1 << wis.subClass) - FROM - dbc_weaponimpactsounds wis - WHERE - ?# > 0 - ON DUPLICATE KEY UPDATE - subClassMask = subClassMask | (1 << wis.subClass) - ', $f.$i, $f.$i); - } - } - - DB::Aowow()->query(' - INSERT INTO - ?_items_sounds - SELECT - wss.soundId, - (1 << isc.subClass) - FROM - dbc_itemsubclass isc - JOIN - dbc_weaponswingsounds2 wss ON - wss.weaponSize = isc.weaponSize - WHERE - isc.class = 2 - ON DUPLICATE KEY UPDATE - subClassMask = subClassMask | (1 << isc.subClass) - '); - - - /************************/ - /* Screen Effect Sounds */ - /************************/ - - CLI::write(' - linking to screen effects'); - - DB::Aowow()->query('TRUNCATE ?_screeneffect_sounds'); - DB::Aowow()->query(' - INSERT INTO - ?_screeneffect_sounds - SELECT - se.id, se.name, IFNULL(sa.soundIdDay, 0), IFNULL(sa.soundIdNight, 0), IFNULL(zm.soundIdDay, 0), IFNULL(zm.soundIdNight, 0) - FROM - dbc_screeneffect se - LEFT JOIN - dbc_soundambience sa ON se.soundAmbienceId = sa.id - LEFT JOIN - dbc_zonemusic zm ON se.zoneMusicId = zm.id - '); - - $this->reapplyCCFlags('sounds', Type::SOUND); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/sounds.ss.php b/setup/tools/sqlgen/sounds.ss.php new file mode 100644 index 000000000..89e482480 --- /dev/null +++ b/setup/tools/sqlgen/sounds.ss.php @@ -0,0 +1,385 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Sound from dbc.'] + ); + + protected $dbcSourceFiles = array( + // base emotes race + 'soundentries', 'emotestextsound', 'vocaluisounds', + // creatures + 'npcsounds', 'creaturesounddata', 'creaturedisplayinfo', 'creaturemodeldata', + // spells + 'spell', 'spellvisual', 'spellvisualkit', 'screeneffect', + // zones + 'soundambience', 'zonemusic', 'zoneintromusictable', 'worldstatezonesounds', 'areatable', + // items + 'material', 'itemgroupsounds', 'itemdisplayinfo', 'weaponimpactsounds', 'itemsubclass', 'weaponswingsounds2' /*, 'sheathesoundlookups' data is redundant with material..? */ + ); + + public function generate(array $ids = []) : bool + { + /* + okay, here's the thing. WMOAreaTable.dbc references WMO-files to get its position in the world (AreTable) and has sparse information on the related AreaTables themself. + Though it has sets for ZoneAmbience, ZoneMusic and ZoneIntroMusic, these can't be linked for this very reason and are omitted for now. + content: e.g. Tavern Music + */ + + // WMOAreaTable.dbc/id => AreaTable.dbc/id + $worldStateZoneSoundFix = array( + 18153 => 2119, + 18154 => 2119, + 47321 => 4273, // The Spark of Imagination + 43600 => 4273, // The Celestial Planetarium + 47478 => 4273 // The Prison of Yogg-Saron + ); + + + /***********/ + /* M A I N */ + /***********/ + + CLI::write('[sound] - sounds main data'); + + // file extraction and conversion manually + // moving files in build step. data here is purely structural + // reality check ... thats probably gigabytes worth of sound.. only growing in size with every locale added on top (RedRocketSite didn't do it. Should i then?) + + // .wav => audio/ogg; codecs="vorbis" + // .mp3 => audio/mpeg + + $query = + 'SELECT `id` AS `id`, `type` AS `cat`, `name`, 0 AS `cuFlags`, + `file1` AS `soundFile1`, `file2` AS `soundFile2`, `file3` AS `soundFile3`, `file4` AS `soundFile4`, `file5` AS `soundFile5`, + `file6` AS `soundFile6`, `file7` AS `soundFile7`, `file8` AS `soundFile8`, `file9` AS `soundFile9`, `file10` AS `soundFile10`, + `path`, `flags` + FROM dbc_soundentries + LIMIT ?d, ?d'; + + DB::Aowow()->query('TRUNCATE ?_sounds'); + DB::Aowow()->query('TRUNCATE ?_sounds_files'); + + $soundFileIdx = 0; + $soundIndex = []; + $j = 0; + while ($sounds = DB::Aowow()->select($query, $j * CLISetup::SQL_BATCH, CLISetup::SQL_BATCH)) + { + CLI::write('[sound] * batch #' . ++$j . ' (' . count($sounds) . ')', CLI::LOG_BLANK, true, true); + + $groupSets = []; + foreach ($sounds as $s) + { + /* attention! + + one sound can be used in 20 or more locations but may appear as multiple files, + because of different cases, path being attached to file and other shenanigans + + build a usable path and create full index to compensate + 25.6k raw files => expect ~21k filtered files + */ + + $fileSets = []; + $hasDupes = false; + for ($i = 1; $i < 11; $i++) + { + $nicePath = CLI::nicePath($s['soundFile'.$i], $s['path']); + if ($s['soundFile'.$i] && array_key_exists($nicePath, $soundIndex)) + { + $s['soundFile'.$i] = $soundIndex[$nicePath]; + $hasDupes = true; + continue; + } + + // convert to something web friendly => ogg + if (stristr($s['soundFile'.$i], '.wav')) + { + $soundIndex[$nicePath] = ++$soundFileIdx; + + $fileSets[] = [$soundFileIdx, $s['soundFile'.$i], $s['path'], SOUND_TYPE_OGG]; + $s['soundFile'.$i] = $soundFileIdx; + } + // mp3 .. keep as is + else if (stristr($s['soundFile'.$i], '.mp3')) + { + $soundIndex[$nicePath] = ++$soundFileIdx; + + $fileSets[] = [$soundFileIdx, $s['soundFile'.$i], $s['path'], SOUND_TYPE_MP3]; + $s['soundFile'.$i] = $soundFileIdx; + } + // i call bullshit + else if ($s['soundFile'.$i]) + { + // todo (low) - file extension may be implied by SoundType, but only two entries (12292, 13401) are affected by this, sooo... eh? + CLI::write('[sound] Group '.str_pad('['.$s['id'].']', 7).' '.CLI::bold($s['name']).' has invalid sound file '.CLI::bold($s['soundFile'.$i]).' on index '.$i.'! Skipping...', CLI::LOG_WARN); + $s['soundFile'.$i] = null; + } + // empty case + else + $s['soundFile'.$i] = null; + } + + if (!$fileSets && !$hasDupes) + { + CLI::write('[sound] Group '.str_pad('['.$s['id'].']', 7).' '.CLI::bold($s['name']).' contains no sound files! Skipping...', CLI::LOG_WARN); + continue; + } + else if ($fileSets) + DB::Aowow()->query('INSERT INTO ?_sounds_files VALUES (?a)', array_values($fileSets)); + + unset($s['path']); + + $groupSets[] = array_values($s); + } + + DB::Aowow()->query('INSERT INTO ?_sounds VALUES (?a)', array_values($groupSets)); + } + + + /******************/ + /* VocalUI Sounds */ + /******************/ + + CLI::write('[sound] - linking to race'); + + DB::Aowow()->query('TRUNCATE ?_races_sounds'); + DB::Aowow()->query( + 'INSERT IGNORE INTO ?_races_sounds + SELECT `raceId`, `soundIdMale`, 1 FROM dbc_vocaluisounds WHERE `soundIdMale` <> `soundIdFemale` AND `soundIdMale` > 0 UNION + SELECT `raceId`, `soundIdFemale`, 2 FROM dbc_vocaluisounds WHERE `soundIdMale` <> `soundIdFemale` AND `soundIdFemale` > 0' + ); + + + /***************/ + /* Emote Sound */ + /***************/ + + CLI::write('[sound] - linking to emotes'); + + DB::Aowow()->query('TRUNCATE ?_emotes_sounds'); + DB::Aowow()->query('INSERT IGNORE INTO ?_emotes_sounds SELECT `emotesTextId`, `raceId`, `gender` + 1, `soundId` FROM dbc_emotestextsound'); + + + /*******************/ + /* Creature Sounds */ + /*******************/ + + CLI::write('[sound] - linking to creatures'); + + // currently ommitting: + // * footsteps (matrix of: creature + terrain + humidity) + // * fidget2 through 5 + // * customattack2 through 3 + // in case of conflicting data CreatureDisplayInfo overrides CreatureModelData (seems to be more specialized (Thral > MaleOrc / Maiden > FemaleTitan)) + + DB::Aowow()->query('TRUNCATE ?_creature_sounds'); + DB::Aowow()->query( + 'INSERT INTO ?_creature_sounds + SELECT cdi.`id`, + GREATEST(IFNULL(ns.`greetSoundId`, 0), 0), + GREATEST(IFNULL(ns.`byeSoundId`, 0), 0), + GREATEST(IFNULL(ns.`angrySoundId`, 0), 0), + GREATEST(IF(csdA.`exertion`, csdA.`exertion`, IFNULL(csdB.`exertion`, 0)), 0), + GREATEST(IF(csdA.`exertionCritical`, csdA.`exertionCritical`, IFNULL(csdB.`exertionCritical`, 0)), 0), + GREATEST(IF(csdA.`injury`, csdA.`injury`, IFNULL(csdB.`injury`, 0)), 0), + GREATEST(IF(csdA.`injuryCritical`, csdA.`injuryCritical`, IFNULL(csdB.`injuryCritical`, 0)), 0), + GREATEST(IF(csdA.`death`, csdA.`death`, IFNULL(csdB.`death`, 0)), 0), + GREATEST(IF(csdA.`stun`, csdA.`stun`, IFNULL(csdB.`stun`, 0)), 0), + GREATEST(IF(csdA.`stand`, csdA.`stand`, IFNULL(csdB.`stand`, 0)), 0), + 0, -- footsteps + GREATEST(IF(csdA.`aggro`, csdA.`aggro`, IFNULL(csdB.`aggro`, 0)), 0), + GREATEST(IF(csdA.`wingFlap`, csdA.`wingFlap`, IFNULL(csdB.`wingFlap`, 0)), 0), + GREATEST(IF(csdA.`wingGlide`, csdA.`wingGlide`, IFNULL(csdB.`wingGlide`, 0)), 0), + GREATEST(IF(csdA.`alert`, csdA.`alert`, IFNULL(csdB.`alert`, 0)), 0), + GREATEST(IF(csdA.`fidget`, csdA.`fidget`, IFNULL(csdB.`fidget`, 0)), 0), + GREATEST(IF(csdA.`customAttack`, csdA.`customAttack`, IFNULL(csdB.`customAttack`, 0)), 0), + GREATEST(IF(csdA.`loop`, csdA.`loop`, IFNULL(csdB.`loop`, 0)), 0), + GREATEST(IF(csdA.`jumpStart`, csdA.`jumpStart`, IFNULL(csdB.`jumpStart`, 0)), 0), + GREATEST(IF(csdA.`jumpEnd`, csdA.`jumpEnd`, IFNULL(csdB.`jumpEnd`, 0)), 0), + GREATEST(IF(csdA.`petAttack`, csdA.`petAttack`, IFNULL(csdB.`petAttack`, 0)), 0), + GREATEST(IF(csdA.`petOrder`, csdA.`petOrder`, IFNULL(csdB.`petOrder`, 0)), 0), + GREATEST(IF(csdA.`petDismiss`, csdA.`petDismiss`, IFNULL(csdB.`petDismiss`, 0)), 0), + GREATEST(IF(csdA.`birth`, csdA.`birth`, IFNULL(csdB.`birth`, 0)), 0), + GREATEST(IF(csdA.`spellcast`, csdA.`spellcast`, IFNULL(csdB.`spellcast`, 0)), 0), + GREATEST(IF(csdA.`submerge`, csdA.`submerge`, IFNULL(csdB.`submerge`, 0)), 0), + GREATEST(IF(csdA.`submerged`, csdA.`submerged`, IFNULL(csdB.`submerged`, 0)), 0), + 0, -- transform + 0 -- transformanimated + FROM dbc_creaturedisplayinfo cdi + LEFT JOIN dbc_creaturemodeldata cmd ON cmd.`id` = cdi.`modelId` + LEFT JOIN dbc_creaturesounddata csdA ON csdA.`id` = cdi.`creatureSoundId` + LEFT JOIN dbc_creaturesounddata csdB ON csdB.`id` = cmd.`creatureSoundId` + LEFT JOIN dbc_npcsounds ns ON ns.`id` = cdi.`npcSoundId`' + ); + + + /****************/ + /* Spell Sounds */ + /****************/ + + CLI::write('[sound] - linking to spells'); + + // issues: (probably because of 335-data) + // * animate is probably wrong + // * missile and impactarea not in js + // * ready, castertargeting, casterstate and targetstate not in dbc + + DB::Aowow()->query('TRUNCATE ?_spell_sounds'); + DB::Aowow()->query( + 'INSERT INTO ?_spell_sounds + SELECT sv.`id`, + GREATEST(`animationSoundId`, 0), + 0, -- ready + GREATEST(IFNULL(svk1.`soundId`, 0), 0), + GREATEST(IFNULL(svk2.`soundId`, 0), 0), + GREATEST(IFNULL(svk3.`soundId`, 0), 0), + GREATEST(IFNULL(svk4.`soundId`, 0), 0), + GREATEST(IFNULL(svk5.`soundId`, 0), 0), + GREATEST(IFNULL(svk6.`soundId`, 0), 0), + GREATEST(IFNULL(svk7.`soundId`, 0), 0), + GREATEST(IFNULL(svk8.`soundId`, 0), 0), + 0, -- castertargeting + GREATEST(IFNULL(svk9.`soundId`, 0), 0), + GREATEST(IFNULL(svk10.`soundId`, 0), 0), + GREATEST(IFNULL(svk12.`soundId`, 0), 0), + 0, -- casterstate + 0, -- targetstate + GREATEST(`missileSoundId`, 0), + GREATEST(IFNULL(svk11.`soundId`, 0), 0) + FROM dbc_spellvisual sv + LEFT JOIN dbc_spellvisualkit svk1 ON svk1.`id` = sv.`precastKitId` + LEFT JOIN dbc_spellvisualkit svk2 ON svk2.`id` = sv.`castKitId` + LEFT JOIN dbc_spellvisualkit svk3 ON svk3.`id` = sv.`impactKitId` + LEFT JOIN dbc_spellvisualkit svk4 ON svk4.`id` = sv.`stateKitId` + LEFT JOIN dbc_spellvisualkit svk5 ON svk5.`id` = sv.`statedoneKitId` + LEFT JOIN dbc_spellvisualkit svk6 ON svk6.`id` = sv.`channelKitId` + LEFT JOIN dbc_spellvisualkit svk7 ON svk7.`id` = sv.`casterImpactKitId` + LEFT JOIN dbc_spellvisualkit svk8 ON svk8.`id` = sv.`targetImpactKitId` + LEFT JOIN dbc_spellvisualkit svk9 ON svk9.`id` = sv.`missileTargetingKitId` + LEFT JOIN dbc_spellvisualkit svk10 ON svk10.`id` = sv.`instantAreaKitId` + LEFT JOIN dbc_spellvisualkit svk11 ON svk11.`id` = sv.`impactAreaKitId` + LEFT JOIN dbc_spellvisualkit svk12 ON svk12.`id` = sv.`persistentAreaKitId`' + ); + + + /***************/ + /* Zone Sounds */ + /***************/ + + CLI::write('[sound] - linking to zones'); + + // omiting data from WMOAreaTable, as its at the moment impossible to link to actual zones + + DB::Aowow()->query('TRUNCATE ?_zones_sounds'); + DB::Aowow()->query( + 'INSERT INTO ?_zones_sounds (`id`, `ambienceDay`, `ambienceNight`, `musicDay`, `musicNight`, `intro`, `worldStateId`, `worldStateValue`) + SELECT a.`id`, + IFNULL(sa1.`soundIdDay`, 0), + IFNULL(sa1.`soundIdNight`, 0), + IFNULL(zm1.`soundIdDay`, 0), + IFNULL(zm1.`soundIdNight`, 0), + IFNULL(zimt1.`soundId`, 0), + 0 AS worldStateId, + 0 AS worldStateValue + FROM dbc_areatable a + LEFT JOIN dbc_soundambience sa1 ON sa1.`id` = a.`soundAmbience` + LEFT JOIN dbc_zonemusic zm1 ON zm1.`id` = a.`zoneMusic` + LEFT JOIN dbc_zoneintromusictable zimt1 ON zimt1.`id` = a.`zoneIntroMusic` + WHERE a.`soundAmbience` > 0 OR a.`zoneMusic` > 0 OR a.`zoneIntroMusic` + UNION + SELECT IF(wszs.`areaId`, wszs.`areaId`, wszs.`wmoAreaId`), + IFNULL(sa2.`soundIdDay`, 0), + IFNULL(sa2.`soundIdNight`, 0), + IFNULL(zm2.`soundIdDay`, 0), + IFNULL(zm2.`soundIdNight`, 0), + IFNULL(zimt2.`soundId`, 0), + wszs.`stateId`, + wszs.`value` + FROM dbc_worldstatezonesounds wszs + LEFT JOIN dbc_soundambience sa2 ON sa2.`id` = wszs.`soundAmbienceId` + LEFT JOIN dbc_zonemusic zm2 ON zm2.`id` = wszs.`zoneMusicId` + LEFT JOIN dbc_zoneintromusictable zimt2 ON zimt2.`id` = wszs.`zoneIntroMusicId` + WHERE wszs.`zoneMusicId` > 0 AND (wszs.`areaId` OR wszs.`wmoAreaId` IN (?a))', + array_keys($worldStateZoneSoundFix) + ); + + // apply post-fix + foreach ($worldStateZoneSoundFix as $old => $new) + DB::Aowow()->query('UPDATE ?_zones_sounds SET `id` = ?d WHERE `id` = ?d', $new, $old); + + + /***************/ + /* Item Sounds */ + /***************/ + + CLI::write('[sound] - linking to items'); + + DB::Aowow()->query( + 'UPDATE ?_items i + LEFT JOIN dbc_itemdisplayinfo idi ON idi.`id` = i.`displayId` + LEFT JOIN dbc_itemgroupsounds igs ON igs.`id` = idi.`groupSoundId` + LEFT JOIN dbc_material m ON m.`id` = i.`material` + SET i.`spellVisualId` = IFNULL(idi.`spellVisualId`, 0), + i.`pickUpSoundId` = IFNULL(igs.`pickUpSoundId`, 0), + i.`dropDownSoundId` = IFNULL(igs.`dropDownSoundId`, 0), + i.`sheatheSoundId` = IFNULL(m.`sheatheSoundId`, 0), + i.`unsheatheSoundId` = IFNULL(m.`unsheatheSoundId`, 0)' + ); + + DB::Aowow()->query('TRUNCATE ?_items_sounds'); + + $fields = ['hit', 'crit']; + foreach ($fields as $f) + for ($i = 1; $i <= 10; $i++) + DB::Aowow()->query( + 'INSERT INTO ?_items_sounds + SELECT ?#, (1 << wis.`subClass`) + FROM dbc_weaponimpactsounds wis + WHERE ?# > 0 + ON DUPLICATE KEY UPDATE `subClassMask` = `subClassMask` | (1 << wis.`subClass`)', + $f.$i, $f.$i + ); + + DB::Aowow()->query( + 'INSERT INTO ?_items_sounds + SELECT wss.`soundId`, (1 << isc.`subClass`) + FROM dbc_itemsubclass isc + JOIN dbc_weaponswingsounds2 wss ON wss.`weaponSize` = isc.`weaponSize` + WHERE isc.`class` = 2 + ON DUPLICATE KEY UPDATE `subClassMask` = `subClassMask` | (1 << isc.`subClass`)' + ); + + + /************************/ + /* Screen Effect Sounds */ + /************************/ + + CLI::write('[sound] - linking to screen effects'); + + DB::Aowow()->query('TRUNCATE ?_screeneffect_sounds'); + DB::Aowow()->query( + 'INSERT INTO ?_screeneffect_sounds + SELECT se.`id`, se.`name`, IFNULL(sa.`soundIdDay`, 0), IFNULL(sa.`soundIdNight`, 0), IFNULL(zm.`soundIdDay`, 0), IFNULL(zm.`soundIdNight`, 0) + FROM dbc_screeneffect se + LEFT JOIN dbc_soundambience sa ON se.`soundAmbienceId` = sa.`id` + LEFT JOIN dbc_zonemusic zm ON se.`zoneMusicId` = zm.`id`' + ); + + + $this->reapplyCCFlags('sounds', Type::SOUND); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/source.func.php b/setup/tools/sqlgen/source.ss.php similarity index 50% rename from setup/tools/sqlgen/source.func.php rename to setup/tools/sqlgen/source.ss.php index b819f1827..ea027f6c6 100644 --- a/setup/tools/sqlgen/source.func.php +++ b/setup/tools/sqlgen/source.ss.php @@ -7,13 +7,15 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'source'; + protected $info = array( + 'source' => [[], CLISetup::ARGV_PARAM, 'Compiles source data for type: Item, Spell & Titles from dbc and world db.'] + ); - protected $tblDependancyAowow = ['spell', 'achievement', 'items', 'itemset', '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', 'npc_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']; + protected $dbcSourceFiles = ['charstartoutfit', 'talent', 'spell', 'skilllineability', 'itemextendedcost', 'lock']; + protected $worldDependency = ['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', '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 $setupAfter = [['spell', 'achievement', 'items', 'itemset', 'spawns', 'creature', 'zones', 'titles'], []]; private $srcBuffer = []; private $refLoot = []; @@ -35,7 +37,7 @@ public function generate(array $ids = []) : bool ); // todo: do the same for GOs - CLI::write(' - resolving ref-loot tree', CLI::LOG_BLANK, true, true); + CLI::write('[source] - 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 @@ -72,7 +74,7 @@ public function generate(array $ids = []) : bool /* Item & inherited Spells */ /***************************/ - CLI::write(' - Items & Spells [inherited]'); + CLI::write('[source] - Items & Spells [inherited]'); # also everything from items that teach spells, is src of spell $this->itemCrafted(); # 1: Crafted # @@ -96,7 +98,7 @@ public function generate(array $ids = []) : bool /* Spell */ /*********/ - CLI::write(' - Spells [original]'); + CLI::write('[source] - Spells [original]'); $this->spellQuest(); # 4: Quest # $this->spellTrainer(); # 6: Trainer # @@ -108,7 +110,7 @@ public function generate(array $ids = []) : bool /* Titles */ /**********/ - CLI::write(' - Titles'); + CLI::write('[source] - Titles'); $this->titleQuest(); # 4: Quest # $this->titleAchievement(); # 12: Achievement # @@ -118,7 +120,7 @@ public function generate(array $ids = []) : bool /* Itemsets */ /************/ - CLI::write(' - Itemsets'); + CLI::write('[source] - Itemsets'); $this->itemset(); # Meta category .. inherit from items # @@ -139,7 +141,7 @@ public function generate(array $ids = []) : bool $rows[$j][5] |= SRC_FLAG_COMMON; if ($t->update()) - CLI::write(' - Inserting... (['.$type.'] '.$j.' / '.$sum.')', CLI::LOG_BLANK, true, true); + CLI::write('[source] - Inserting... (['.$type.'] '.$j.' / '.$sum.')', CLI::LOG_BLANK, true, true); if (!($j % 300)) $this->insert($rows); @@ -149,16 +151,16 @@ public function generate(array $ids = []) : bool $this->insert($rows); } - CLI::write(' - Inserting... (done)'); + CLI::write('[source] - Inserting... (done)'); // 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); + 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); return true; } - 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 + private function pushBuffer(int $type, int $typeId, int $srcId, int $srcBit = 1, int $mType = 0, int $mTypeId = 0, ?int $mZoneId = null, int $mMask = 0x0, int $qty = 1) : void { if (!isset($this->srcBuffer[$type])) $this->srcBuffer[$type] = []; @@ -209,11 +211,11 @@ private function taughtSpell(array $item) : int return 0; // wotlk system - if (in_array($item['spellid_1'], LEARN_SPELLS) && $item['spelltrigger_1'] == 0 && $item['spellid_2'] > 0 && $item['spelltrigger_2'] == 6) + if (in_array($item['spellid_1'], LEARN_SPELLS) && $item['spelltrigger_1'] == SPELL_TRIGGER_USE && $item['spellid_2'] > 0 && $item['spelltrigger_2'] == SPELL_TRIGGER_LEARN) return $item['spellid_2']; // deprecated system - if (!in_array($item['spellid_1'], LEARN_SPELLS) && $item['spellid_1'] > 0 && $item['spelltrigger_1'] == 0) + if (!in_array($item['spellid_1'], LEARN_SPELLS) && $item['spelltrigger_1'] == SPELL_TRIGGER_USE && $item['spellid_1'] > 0) return $item['spellid_1']; return 0; @@ -221,22 +223,22 @@ private function taughtSpell(array $item) : int private function itemCrafted() : void { - CLI::write(' * #1 Crafted', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #1 Crafted', CLI::LOG_BLANK, true, true); $itemSpells = DB::Aowow()->selectCol( - 'SELECT effect1CreateItemId AS ARRAY_KEY, s.id + '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) + 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] + array_merge(SKILLS_TRADE_PRIMARY, [SKILL_FIRST_AID, SKILL_COOKING, SKILL_FISHING]) ); // assume unique craft spells per item - $perfectItems = DB::World()->selectCol('SELECT perfectItemType AS ARRAY_KEY, spellId AS spell FROM skill_perfect_item_template'); + $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)); + $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)) @@ -251,19 +253,19 @@ private function itemDrop() : void $objectOT = []; $itemOT = []; - CLI::write(' * #2 Drop [NPC]', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #2 Drop [NPC]', CLI::LOG_BLANK, true, true); $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 + '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' + 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`' ); - $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'))); + $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) { @@ -306,7 +308,7 @@ private function itemDrop() : void } else if ($roi < 0) { - CLI::write(' - unresolved ref-loot ref: ' . $roi, CLI::LOG_WARN); + CLI::write('[source] - unresolved ref-loot ref: ' . $roi, CLI::LOG_WARN); continue; } @@ -316,19 +318,20 @@ private function itemDrop() : void $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); + CLI::write('[source] * #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 + '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)') + 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` = ?d AND gt.`data1` > 0 AND gt.`data0` NOT IN (?a) + GROUP BY `refOrItem`, gt.`entry`', + OBJECT_CHEST, + DB::Aowow()->selectCol('SELECT `id` FROM dbc_lock WHERE `properties1` IN (?a)', [LOCK_PROPERTY_HERBALISM, LOCK_PROPERTY_MINING]) ); - $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')); + $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) @@ -358,15 +361,16 @@ private function itemDrop() : void $this->pushBuffer(Type::ITEM, $roi, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']); } - CLI::write(' * #2 Drop [Item]', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #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 + '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' + 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` & ?d + GROUP BY ARRAY_KEY', + ITEM_FLAG_OPENABLE ); foreach ($itemLoot as $roi => $l) @@ -392,25 +396,25 @@ private function itemDrop() : void $this->pushBuffer(Type::ITEM, $roi, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']); } - 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); + 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); } private function itemPvP() : void { - CLI::write(' * #3 PvP', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #3 PvP', CLI::LOG_BLANK, true, true); $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) + 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'; + '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`'; foreach ($subSrcByXCost as $subSrc => $xCost) { @@ -426,26 +430,36 @@ private function itemPvP() : void private function itemQuest() : void { - CLI::write(' * #4 Quest', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #4 Quest', CLI::LOG_BLANK, true, true); $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' + '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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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`', + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH ); - - $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'))); + $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) { @@ -459,16 +473,17 @@ private function itemQuest() : void } $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 + '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` & ?d AND !(qt.`AllowableRaces` & ?d), ?d, IF(qt.`AllowableRaces` & ?d AND !(qt.`AllowableRaces` & ?d), ?d, ?d))) 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 - '); + 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', + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH + ); - $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'))); + $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) { @@ -490,28 +505,28 @@ private function itemQuest() : void } if ($_ = $this->taughtSpell($l)) - $this->pushBuffer(Type::SPELL, $_, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? $l['zone']); + $this->pushBuffer(Type::SPELL, $_, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? Game::$questSortFix[$l['zone']] ?? $l['zone']); $itemOT[] = $roi; - $this->pushBuffer(Type::ITEM, $roi, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? $l['zone']); + $this->pushBuffer(Type::ITEM, $roi, SRC_QUEST, $l['side'], $l['qty'] > 1 ? 0 : Type::QUEST, $l['entry'], $areaParent[$l['zone']] ?? Game::$questSortFix[$l['zone']] ?? $l['zone']); } } private function itemVendor() : void { - CLI::write(' * #5 Vendor', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #5 Vendor', CLI::LOG_BLANK, true, true); - $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); + $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', + '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 ); - $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')); + $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) { @@ -524,39 +539,38 @@ private function itemVendor() : void private function itemStarter() : void { - CLI::write(' * #10 Starter', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #10 Starter', CLI::LOG_BLANK, true, true); - if ($pcii = DB::World()->selectCol('SELECT DISTINCT itemid FROM playercreateinfo_item')) + 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++) - if ($cso = DB::Aowow()->selectCol('SELECT item?d FROM dbc_charstartoutfit WHERE item?d > 0', $i, $i)) + if ($cso = DB::Aowow()->selectCol('SELECT ?# FROM dbc_charstartoutfit WHERE ?# > 0', 'item'.$i, 'item'.$i)) foreach ($cso as $item) $this->pushBuffer(Type::ITEM, $item, SRC_STARTER); } private function itemAchievement() : void { - CLI::write(' * #12 Achievement', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #12 Achievement', CLI::LOG_BLANK, true, true); - $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 + $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 + 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); + CLI::write('[source] itemAchievement() - Reward items are unexpectedly empty.', CLI::LOG_WARN); else { - $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)); + $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)) @@ -577,15 +591,15 @@ private function itemAchievement() : void private function itemDisenchantment() : void { - CLI::write(' * #15 Disenchanted', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #15 Disenchanted', CLI::LOG_BLANK, true, true); $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 + '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' + 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 $l) @@ -614,24 +628,25 @@ private function itemDisenchantment() : void private function itemFishing() : void { - CLI::write(' * #16 Fished', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #16 Fished', CLI::LOG_BLANK, true, true); $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' + '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` = ?d AND gt.`data1` > 0) src + LEFT JOIN item_template it ON src.`itemOrRef` > 0 AND src.`itemOrRef` = it.`entry` + GROUP BY `refOrItem`, src.`entry`', + OBJECT_FISHINGHOLE ); if (!$fishLoot) { - CLI::write(' SourceGen: itemFishing() - fishing_loot_template empty and gameobject_template contained no fishing nodes (type:25).', CLI::LOG_WARN); + CLI::write('[source] 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'))); + $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) { @@ -663,26 +678,26 @@ private function itemFishing() : void private function itemGathering() : void { - CLI::write(' * #17 Gathered', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #17 Gathered', CLI::LOG_BLANK, true, true); $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', + '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` = ?d 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, DB::Aowow()->selectCol('SELECT id FROM dbc_lock WHERE properties1 = 2') + Type::OBJECT, OBJECT_CHEST, DB::Aowow()->selectCol('SELECT `id` FROM dbc_lock WHERE `properties1` = ?d', LOCK_PROPERTY_HERBALISM) ); if (!$herbLoot) { - 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); + CLI::write('[source] 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')); + $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) { @@ -716,14 +731,14 @@ private function itemGathering() : void private function itemMilling() : void { - CLI::write(' * #18 Milled', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #18 Milled', CLI::LOG_BLANK, true, true); $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 + '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' + 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 ($millLoot as $l) @@ -752,26 +767,26 @@ private function itemMilling() : void private function itemMining() : void { - CLI::write(' * #19 Mined', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #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', + '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` = ?d 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') + Type::OBJECT, OBJECT_CHEST, DB::Aowow()->selectCol('SELECT `id` FROM dbc_lock WHERE `properties1` = ?d', LOCK_PROPERTY_MINING) ); 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); + CLI::write('[source] itemMining() - skinning_loot_template/creature_template contained no loot/mining-lootable creature and gameobject_template contained no nodes (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($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')); + $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')); foreach ($mineLoot as $l) { @@ -805,14 +820,14 @@ private function itemMining() : void private function itemProspecting() : void { - CLI::write(' * #20 Prospected', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #20 Prospected', CLI::LOG_BLANK, true, true); $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 + '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' + 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) @@ -841,22 +856,22 @@ private function itemProspecting() : void private function itemPickpocketing() : void { - CLI::write(' * #21 Pickpocket', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #21 Pickpocket', CLI::LOG_BLANK, true, true); $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' + '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`' ); if (!$theftLoot) { - CLI::write(' SourceGen: itemPickpocketing() - pickpocketing_loot_template/creature_template contained no loot/generous creature.', CLI::LOG_WARN); + CLI::write('[source] 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'))); + $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) { @@ -890,23 +905,23 @@ private function itemPickpocketing() : void private function itemSalvaging() : void { - CLI::write(' * #22 Salvaged', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #22 Salvaged', CLI::LOG_BLANK, true, true); $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', + '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 ); if (!$salvageLoot) { - CLI::write(' SourceGen: itemSalvaging() - skinning_loot_template/creature_template contained no loot/salvageable creature.', CLI::LOG_WARN); + CLI::write('[source] 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'))); + $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) { @@ -940,23 +955,23 @@ private function itemSalvaging() : void private function itemSkinning() : void { - CLI::write(' * #23 Skinned', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #23 Skinned', CLI::LOG_BLANK, true, true); $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) + '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_SPECIALLOOT ); if (!$skinLoot) { - CLI::write(' SourceGen: itemSkinning() - skinning_loot_template/creature_template contained no loot/skinable beast.', CLI::LOG_WARN); + CLI::write('[source] 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'))); + $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) { @@ -990,23 +1005,27 @@ private function itemSkinning() : void private function spellQuest() : void { - CLI::write(' * #4 Quest', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #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, 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' + '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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) 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`', + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH, + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH ); if (!$quests) { - CLI::write(' SourceGen: spellQuest() - quest_template contained no spell rewards or grants.', CLI::LOG_WARN); + CLI::write('[source] spellQuest() - quest_template contained no spell rewards or grants.', 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'))); - $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)); + $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` = ?d OR `effect2Id` = ?d OR `effect3Id` = ?d)', + array_keys($quests), SPELL_EFFECT_LEARN_SPELL, SPELL_EFFECT_LEARN_SPELL, SPELL_EFFECT_LEARN_SPELL + ); foreach ($qSpells as $sId => $spell) for ($i = 1; $i <= 3; $i++) @@ -1016,19 +1035,18 @@ private function spellQuest() : void 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'); + CLI::write('[source] * #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); + CLI::write('[source] spelltrainer() - trainer_spell contained no spell or creature_default_trainer no trainer.', 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)); + $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) @@ -1056,29 +1074,30 @@ private function spellTrainer() : void private function spellDiscovery() : void { - CLI::write(' * #7 Discovery', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #7 Discovery', CLI::LOG_BLANK, true, true); // 61756: Northrend Inscription Research (FAST QA VERSION); - if ($disco = DB::World()->selectCol('SELECT spellId FROM skill_discovery_template WHERE reqSpell <> ?d', 61756)) + 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); + CLI::write('[source] * #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 + '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))' + JOIN dbc_spell s ON s.`id` = t.`rank1` + WHERE t.`rank2` < 1 AND (t.`talentSpell` = 1 OR (s.`effect1Id` = ?d OR s.`effect2Id` = ?d OR s.`effect3Id` = ?d))', + SPELL_EFFECT_LEARN_SPELL, SPELL_EFFECT_LEARN_SPELL, SPELL_EFFECT_LEARN_SPELL ); $n = 0; while ($tSpells) { - CLI::write(' - '.++$n.'. pass', CLI::LOG_BLANK, true, true); + CLI::write('[source] - '.++$n.'. pass', CLI::LOG_BLANK, true, true); $recurse = []; foreach ($tSpells as $tId => $spell) @@ -1097,7 +1116,7 @@ private function spellTalent() : void 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)); + $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)); } } @@ -1107,31 +1126,32 @@ private function spellStarter() : void 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); + CLI::write('[source] * #10 Starter', CLI::LOG_BLANK, true, true); - $pcis = DB::World()->selectCol('SELECT DISTINCT skill FROM playercreateinfo_skills'); - $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); + $pcis = DB::World()->selectCol('SELECT DISTINCT `skill` FROM playercreateinfo_skills'); + $subSkills = DB::Aowow()->selectCol('SELECT `spellId` FROM dbc_skilllineability WHERE {(`skillLineId` IN (?a) AND `acquireMethod` = 2) OR} (`acquireMethod` = 1 AND (`reqSkillLevel` = 1 OR `skillLineId` = ?d)) GROUP BY `spellId`', $pcis ?: DBSIMPLE_SKIP, SKILL_FIRST_AID); foreach ($subSkills as $s) $this->pushBuffer(Type::SPELL, $s, SRC_STARTER); } private function titleQuest() : void { - CLI::write(' * #4 Quest', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #4 Quest', CLI::LOG_BLANK, true, true); $quests = DB::World()->select( - 'SELECT RewardTitle AS ARRAY_KEY, id AS 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' + 'SELECT `RewardTitle` AS ARRAY_KEY, `ID` AS `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` & ?d AND !(`AllowableRaces` & ?d), ?d, IF(`AllowableRaces` & ?d AND !(`AllowableRaces` & ?d), ?d, ?d)) AS `side`, GREATEST(`QuestSortID`, 0) AS `zone` FROM quest_template WHERE `RewardTitle` > 0) q + GROUP BY `RewardTitle`', + RACE_MASK_HORDE, RACE_MASK_ALLIANCE, SIDE_HORDE, RACE_MASK_ALLIANCE, RACE_MASK_HORDE, SIDE_ALLIANCE, SIDE_BOTH ); if (!$quests) { - CLI::write(' SourceGen: titleQuest() - quest_template contained no title rewards.', CLI::LOG_WARN); + CLI::write('[source] 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'))); + $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']] ?? Game::$questSortFix[$q['zone']] ?? $q['zone']); @@ -1139,13 +1159,13 @@ private function titleQuest() : void private function titleAchievement() : void { - CLI::write(' * #12 Achievement', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #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' + '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) @@ -1159,7 +1179,7 @@ private function titleAchievement() : void private function titleCustomString() : void { - CLI::write(' * #13 cuStrings', CLI::LOG_BLANK, true, true); + CLI::write('[source] * #13 cuStrings', CLI::LOG_BLANK, true, true); foreach (Lang::game('pvpSources') as $src => $__) $this->pushBuffer(Type::TITLE, $src, SRC_CUSTOM_STRING, $src); @@ -1169,7 +1189,7 @@ private function itemset() : void { // every item in ?_itemset needs a source. if so merge fields. if not it's not available. - $sets = DB::Aowow()->select('SELECT id AS ARRAY_KEY, contentGroup, item1, item2, item3, item4, item5, item6, item7, item8, item9, item10 FROM ?_itemset'); + $sets = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `contentGroup`, `item1`, `item2`, `item3`, `item4`, `item5`, `item6`, `item7`, `item8`, `item9`, `item10` FROM ?_itemset'); $metaSrc = []; foreach ($sets as $id => $set) diff --git a/setup/tools/sqlgen/spawns.func.php b/setup/tools/sqlgen/spawns.ss.php similarity index 55% rename from setup/tools/sqlgen/spawns.func.php rename to setup/tools/sqlgen/spawns.ss.php index 66c128322..16c9978c4 100644 --- a/setup/tools/sqlgen/spawns.func.php +++ b/setup/tools/sqlgen/spawns.ss.php @@ -9,41 +9,44 @@ // requires https://github.com/TrinityCore/TrinityCore/commit/f989c7182c4cc30f1d0ffdc566c7624a5e108a2f to have been used at least once -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'spawns'; // and waypoints + protected $info = array( + 'spawns' => [[], CLISetup::ARGV_PARAM, 'Compiles spawns/waypoints for type: GObject, NPC, Sound & Areatrigger from dbc and world db.'] + ); - protected $tblDependencyTC = ['creature', 'creature_addon', 'gameobject', 'gameobject_template', 'vehicle_accessory', 'vehicle_accessory_template', 'script_waypoint', 'waypoints', 'waypoint_data']; - protected $dbcSourceFiles = ['worldmaparea', 'map', 'dungeonmap', 'taxipathnode', 'soundemitters', 'areatrigger', 'areatable']; + protected $dbcSourceFiles = ['worldmaparea', 'map', 'taxipathnode', 'soundemitters', 'areatrigger', 'areatable']; + protected $worldDependency = ['creature', 'creature_addon', 'gameobject', 'gameobject_template', 'vehicle_accessory', 'vehicle_accessory_template', 'script_waypoint', 'waypoints', 'waypoint_data']; + protected $setupAfter = [['dungeonmap', 'worldmaparea', 'zones'], ['img-maps']]; private $querys = array( - 1 => ['SELECT c.guid, 1 AS "type", c.id AS typeId, c.spawntimesecs AS respawn, c.phaseMask, c.zoneId AS areaId, c.map, IFNULL(ca.path_id, 0) AS pathId, c.position_y AS `posX`, c.position_x AS `posY` ' . + 1 => ['SELECT c.`guid`, 1 AS `type`, c.`id` AS `typeId`, c.`spawntimesecs` AS `respawn`, c.`phaseMask`, c.`zoneId` AS `areaId`, c.`map`, IFNULL(ca.`path_id`, 0) AS `pathId`, c.`position_y` AS `posX`, c.`position_x` AS `posY` ' . 'FROM creature c LEFT JOIN creature_addon ca ON ca.guid = c.guid', - 'creature spawns', Type::NPC], + '`creature` spawns', Type::NPC], - 2 => ['SELECT c.guid, 2 AS "type", c.id AS typeId, c.spawntimesecs AS respawn, c.phaseMask, c.zoneId AS areaId, c.map, 0 as pathId, c.position_y AS `posX`, c.position_x AS `posY` ' . + 2 => ['SELECT c.`guid`, 2 AS `type`, c.`id` AS `typeId`, c.`spawntimesecs` AS `respawn`, c.`phaseMask`, c.`zoneId` AS `areaId`, c.`map`, 0 AS `pathId`, c.`position_y` AS `posX`, c.`position_x` AS `posY` ' . 'FROM gameobject c', - 'gameobject spawns', Type::OBJECT], + '`gameobject` spawns', Type::OBJECT], - 3 => ['SELECT id AS "guid", 19 AS "type", soundId AS typeId, 0 AS respawn, 0 AS phaseMask, 0 AS areaId, mapId AS "map", 0 AS pathId, posX, posY ' . + 3 => ['SELECT `id` AS `guid`, 19 AS `type`, `soundId` AS `typeId`, 0 AS `respawn`, 0 AS `phaseMask`, 0 AS `areaId`, `mapId` AS `map`, 0 AS `pathId`, `posX`, `posY` ' . 'FROM dbc_soundemitters', - 'sound emitter spawns', Type::SOUND], + 'SoundEmitters.dbc spawns', Type::SOUND], - 4 => ['SELECT id AS "guid", 503 AS "type", id AS typeId, 0 AS respawn, 0 AS phaseMask, 0 AS areaId, mapId AS "map", 0 AS pathId, posX, posY ' . + 4 => ['SELECT `id` AS `guid`, 503 AS `type`, `id` AS `typeId`, 0 AS `respawn`, 0 AS `phaseMask`, 0 AS `areaId`, `mapId` AS `map`, 0 AS `pathId`, `posX`, `posY` ' . 'FROM dbc_areatrigger', - 'areatrigger spawns', Type::AREATRIGGER], + 'AreaTrigger.dbc spawns', Type::AREATRIGGER], - 5 => ['SELECT c.guid, w.entry AS "npcOrPath", w.pointId AS "point", c.zoneId AS areaId, c.map, w.waittime AS "wait", w.location_y AS `posX`, w.location_x AS `posY` ' . - 'FROM creature c JOIN script_waypoint w ON c.id = w.entry', - 'script_waypoint table', Type::NPC], + 5 => ['SELECT c.`guid`, w.`entry` AS `npcOrPath`, w.`pointId` AS `point`, c.`zoneId` AS `areaId`, c.`map`, w.`waittime` AS `wait`, w.`location_y` AS `posX`, w.`location_x` AS `posY` ' . + 'FROM creature c JOIN script_waypoint w ON c.`id` = w.`entry`', + '`script_waypoint`', Type::NPC], - 6 => ['SELECT c.guid, w.entry AS "npcOrPath", w.pointId AS "point", c.zoneId AS areaId, c.map, 0 AS "wait", w.position_y AS `posX`, w.position_x AS `posY` ' . - 'FROM creature c JOIN waypoints w ON c.id = w.entry', - 'waypoints table', Type::NPC], + 6 => ['SELECT c.`guid`, w.`entry` AS `npcOrPath`, w.`pointId` AS `point`, c.`zoneId` AS `areaId`, c.`map`, 0 AS `wait`, w.`position_y` AS `posX`, w.`position_x` AS `posY` ' . + 'FROM creature c JOIN waypoints w ON c.`id` = w.`entry`', + '`waypoints`', Type::NPC], - 7 => ['SELECT c.guid, -w.id AS "npcOrPath", w.point, c.zoneId AS areaId, c.map, w.delay AS "wait", w.position_y AS `posX`, w.position_x AS `posY` ' . - 'FROM creature c JOIN creature_addon ca ON ca.guid = c.guid JOIN waypoint_data w ON w.id = ca.path_id WHERE ca.path_id <> 0', - 'waypoint_data table', Type::NPC] + 7 => ['SELECT c.`guid`, -w.`id` AS `npcOrPath`, w.`point`, c.`zoneId` AS `areaId`, c.`map`, w.`delay` AS `wait`, w.`position_y` AS `posX`, w.`position_x` AS `posY` ' . + 'FROM creature c JOIN creature_addon ca ON ca.`guid` = c.`guid` JOIN waypoint_data w ON w.`id` = ca.`path_id` WHERE ca.`path_id` <> 0', + '`waypoint_data`', Type::NPC] ); public function generate(array $ids = []) : bool @@ -60,16 +63,16 @@ public function generate(array $ids = []) : bool /* offsets for transports */ /**************************/ - $transports = DB::World()->selectCol('SELECT Data0 AS pathId, Data6 AS ARRAY_KEY FROM gameobject_template WHERE type = 15 AND Data6 <> 0'); + $transports = DB::World()->selectCol('SELECT `data0` AS `pathId`, `data6` AS ARRAY_KEY FROM gameobject_template WHERE `type` = ?d AND `data6` <> 0', OBJECT_MO_TRANSPORT); foreach ($transports as &$t) - $t = DB::Aowow()->selectRow('SELECT posX, posY, mapId FROM dbc_taxipathnode tpn WHERE tpn.pathId = ?d AND nodeIdx = 0', $t); + $t = DB::Aowow()->selectRow('SELECT `posX`, `posY`, `mapId` FROM dbc_taxipathnode tpn WHERE tpn.`pathId` = ?d AND `nodeIdx` = 0', $t); /*********************/ /* get override data */ /*********************/ - $overrideData = DB::Aowow()->select('SELECT `type` AS ARRAY_KEY, `typeGuid` AS ARRAY_KEY2, areaId, floor FROM ?_spawns_override'); + $overrideData = DB::Aowow()->select('SELECT `type` AS ARRAY_KEY, `typeGuid` AS ARRAY_KEY2, `areaId`, `floor` FROM ?_spawns_override'); /**************/ @@ -78,8 +81,7 @@ public function generate(array $ids = []) : bool foreach ($this->querys as $idx => $q) { - $intv = 0.5; - $time = microtime(true); + $time = new Timer(500); $sum = 0; if ($idx == 3 || $idx == 4) @@ -87,22 +89,14 @@ public function generate(array $ids = []) : bool else $queryResult = DB::World()->select($q[0]); - $qryProgress = array_combine(array_keys($this->querys), array_column($this->querys, 1)); - array_walk($qryProgress, function (&$v, $k) use ($idx) { if ($idx == $k) $v = CLI::bold('<'.$v.'>'); }); - $qryProgress = ' ['.implode(', ', $qryProgress).']'; - $qryTotal = count($queryResult); $qtLen = strlen($qryTotal); $doneGUID = 0; foreach ($queryResult as $spawn) { $sum++; - $newTime = microtime(true); - if ($newTime > $time + $intv) - { - CLI::write(sprintf(' * %'.$qtLen.'d / %d (%4.1f%%)', $sum, $qryTotal, round(100 * $sum / $qryTotal, 1)) . $qryProgress, CLI::LOG_BLANK, true, true); - $time = $newTime; - } + if ($time->update()) + CLI::write(' * '.$idx.'/'.count($this->querys).': '. CLI::bold($q[1]).' - '.sprintf('%'.$qtLen.'d / %d (%4.1f%%)', $sum, $qryTotal, round(100 * $sum / $qryTotal, 1)), CLI::LOG_BLANK, true, true); // npc/object is on a transport -> apply offsets to path of transport // note, that the coordinates are mixed up .. again @@ -122,8 +116,8 @@ public function generate(array $ids = []) : bool $floor = $overrideData[$q[2]][$spawn['guid']]['floor']; if ($doneGUID != $spawn['guid']) { - CLI::write('GUID '.$spawn['guid'].' was manually moved [A:'.$spawn['areaId'].' => '.$area.'; F: '.$floor.']', CLI::LOG_INFO); - $time = 0; // force refresh progress + CLI::write('[spawns] '.str_pad('['.$spawn['guid'].']', 9).' manually moved to [A:'.$spawn['areaId'].' => '.$area.'; F: '.$floor.']', CLI::LOG_INFO); + $time->reset(); $doneGUID = $spawn['guid']; // do not spam on waypoints } } @@ -134,19 +128,19 @@ public function generate(array $ids = []) : bool if (!$points && !in_array($spawn['map'], [0, 1, 530, 571]) && $idx < 5) { // use server calculated areaId if possible - $area = $area ?: DB::Aowow()->selectCell('SELECT id FROM aowow_zones WHERE mapId = ?d AND parentArea = 0 AND (cuFlags & ?d) = 0', $spawn['map'], CUSTOM_EXCLUDE_FOR_LISTVIEW); + $area = $area ?: DB::Aowow()->selectCell('SELECT `id` FROM ?_zones WHERE `mapId` = ?d AND `parentArea` = 0 AND (`cuFlags` & ?d) = 0', $spawn['map'], CUSTOM_EXCLUDE_FOR_LISTVIEW); if (!$area) { - CLI::write('tried to default GUID '.$spawn['guid'].' to instanced area by mapId, but returned empty [M:'.$spawn['map'].']', CLI::LOG_WARN); - $time = 0; // force refresh progress + CLI::write('[spawns] '.str_pad('['.$spawn['guid'].']', 9).' could not be defaulted to instanced area by mapId [M:'.$spawn['map'].']', CLI::LOG_WARN); + $time->reset(); continue; } $final = ['areaId' => $area, 'posX' => 0, 'posY' => 0, 'floor' => 0]; } else if (!$points) // still impossible (there are areas that are intentionally off the map (e.g. the isles south of tanaris)) { - CLI::write('GUID '.$spawn['guid'].($idx < 5 ? '' : ' on path/point '.$spawn['npcOrPath'].'/'.$spawn['point']).' could not be matched to displayable area [A:'.$area.'; X:'.$spawn['posY'].'; Y:'.$spawn['posX'].']', CLI::LOG_WARN); - $time = 0; // force refresh progress + CLI::write('[spawns] '.str_pad('['.$spawn['guid'].']', 9).' '.($idx < 5 ? '' : 'with path/point ['.$spawn['npcOrPath'].'; '.$spawn['point']).'] could not be matched to displayable area [A:'.$area.'; X:'.$spawn['posY'].'; Y:'.$spawn['posX'].']', CLI::LOG_WARN); + $time->reset(); continue; } else // if areaId is set, area was determined by TC .. we're fine .. mostly @@ -181,6 +175,7 @@ public function generate(array $ids = []) : bool 'posY' => $final['posY'] ); + // REPLACE: because there is bogus data where one path may be assigned to multiple npcs DB::Aowow()->query('REPLACE INTO ?_creature_waypoints (?#) VALUES (?a)', array_keys($set), array_values($set)); } } @@ -192,9 +187,10 @@ public function generate(array $ids = []) : bool /*****************************/ // get vehicle template accessories - $accessories = DB::World()->select(' - SELECT vta.accessory_entry AS typeId, c.guid, vta.entry, count(1) AS nSeats FROM vehicle_template_accessory vta LEFT JOIN creature c ON c.id = vta.entry GROUP BY accessory_entry, c.guid UNION - SELECT va.accessory_entry AS typeId, va.guid, 0 AS entry, count(1) AS nSeats FROM vehicle_accessory va GROUP BY accessory_entry, va.guid'); + $accessories = DB::World()->select( + 'SELECT vta.`accessory_entry` AS `typeId`, c.`guid`, vta.`entry`, COUNT(1) AS `nSeats` FROM vehicle_template_accessory vta LEFT JOIN creature c ON c.`id` = vta.`entry` GROUP BY `accessory_entry`, c.`guid` UNION + SELECT va.`accessory_entry` AS `typeId`, va.`guid`, 0 AS `entry`, COUNT(1) AS `nSeats` FROM vehicle_accessory va GROUP BY `accessory_entry`, va.`guid`' + ); // accessories may also be vehicles (e.g. "Kor'kron Infiltrator" is seated on "Kor'kron Suppression Turret" is seated on "Kor'kron Troop Transport") // so we will retry finding a spawned vehicle if none were found on the previous pass and a change occured @@ -208,18 +204,17 @@ public function generate(array $ids = []) : bool { $vehicles = []; if ($data['guid']) // vehicle already spawned - $vehicles = DB::Aowow()->select('SELECT s.areaId, s.posX, s.posY, s.floor FROM ?_spawns s WHERE s.guid = ?d AND s.type = ?d', $data['guid'], Type::NPC); + $vehicles = DB::Aowow()->select('SELECT s.`areaId`, s.`posX`, s.`posY`, s.`floor` FROM ?_spawns s WHERE s.`guid` = ?d AND s.`type` = ?d', $data['guid'], Type::NPC); else if ($data['entry']) // vehicle on unspawned vehicle action - $vehicles = DB::Aowow()->select('SELECT s.areaId, s.posX, s.posY, s.floor FROM ?_spawns s WHERE s.typeId = ?d AND s.type = ?d', $data['entry'], Type::NPC); + $vehicles = DB::Aowow()->select('SELECT s.`areaId`, s.`posX`, s.`posY`, s.`floor` FROM ?_spawns s WHERE s.`typeId` = ?d AND s.`type` = ?d', $data['entry'], Type::NPC); if ($vehicles) { $matches++; foreach ($vehicles as $v) // if there is more than one vehicle, its probably due to overlapping zones for ($i = 0; $i < $data['nSeats']; $i++) - DB::Aowow()->query(' - REPLACE INTO ?_spawns (`guid`, `type`, `typeId`, `respawn`, `spawnMask`, `phaseMask`, `areaId`, `floor`, `posX`, `posY`, `pathId`) VALUES - (?d, ?d, ?d, 0, 0, 1, ?d, ?d, ?f, ?f, 0)', --$vGuid, Type::NPC, $data['typeId'], $v['areaId'], $v['floor'], $v['posX'], $v['posY']); + DB::Aowow()->query('INSERT INTO ?_spawns (`guid`, `type`, `typeId`, `respawn`, `spawnMask`, `phaseMask`, `areaId`, `floor`, `posX`, `posY`, `pathId`) VALUES (?d, ?d, ?d, 0, 0, 1, ?d, ?d, ?f, ?f, 0)', + --$vGuid, Type::NPC, $data['typeId'], $v['areaId'], $v['floor'], $v['posX'], $v['posY']); unset($accessories[$idx]); } @@ -228,14 +223,14 @@ public function generate(array $ids = []) : bool CLI::write(' * assigned '.$matches.' accessories on '.++$n.'. pass on vehicle accessories', CLI::LOG_BLANK, true, true); } if ($accessories) - CLI::write(count($accessories).' accessories could not be fitted onto a spawned vehicle.', CLI::LOG_WARN); + CLI::write('[spawns] - '.count($accessories).' accessories could not be fitted onto a spawned vehicle.', CLI::LOG_WARN); /********************************/ /* restrict difficulty displays */ /********************************/ - DB::Aowow()->query('UPDATE ?_spawns s, dbc_worldmaparea wma, dbc_map m SET s.spawnMask = 0 WHERE s.areaId = wma.areaId AND wma.mapId = m.id AND m.areaType IN (0, 3, 4)'); + DB::Aowow()->query('UPDATE ?_spawns s, dbc_worldmaparea wma, dbc_map m SET s.`spawnMask` = 0 WHERE s.`areaId` = wma.`areaId` AND wma.`mapId` = m.`id` AND m.`areaType` IN (0, 3, 4)'); return true; } diff --git a/setup/tools/sqlgen/spell.func.php b/setup/tools/sqlgen/spell.func.php deleted file mode 100644 index b16650071..000000000 --- a/setup/tools/sqlgen/spell.func.php +++ /dev/null @@ -1,756 +0,0 @@ - ?d - LIMIT - ?d'; - - $baseQuery = ' - SELECT - s.id, - category, - dispelType, - mechanic, - attributes0, attributes1, attributes2, attributes3, attributes4, attributes5, attributes6, attributes7, - 0 AS cuFlags, - 0 AS typeCat, - stanceMask, stanceMaskNot, - targets, - spellFocus, - GREATEST(IFNULL(sct.baseTime, 0), 0) / 1000 AS castTime, - recoveryTime, recoveryTimeCategory, - startRecoveryTime, startRecoveryCategory, - procChance, procCharges, - 0 AS procCustom, 0 AS procCooldown, - maxLevel, baseLevel, spellLevel, 0 AS talentLevel, - IF (sd.baseTime <> -1, ABS(sd.baseTime), -1) AS duration, - IF (powerDisplayId, -powerDisplayId, powerType) AS powerType, - powerCost, - powerCostPerLevel, - powerCostPercent, - powerPerSecond, - powerPerSecondPerLevel, - IFNULL (src.runicPowerGain, 0) AS powerGainRunicPower, - IF (src.id IS NULL, 0, (src.costFrost << 8) | (src.costUnholy << 4) | src.costBlood) AS powerCostRunes, - rangeId, - stackAmount, - tool1, tool2, - toolCategory1, toolCategory2, - reagent1, reagent2, reagent3, reagent4, reagent5, reagent6, reagent7, reagent8, - reagentCount1, reagentCount2, reagentCount3, reagentCount4, reagentCount5, reagentCount6, reagentCount7, reagentCount8, - equippedItemClass, - equippedItemSubClassMask, - equippedItemInventoryTypeMask, - effect1Id, effect2Id, effect3Id, - effect1DieSides, effect2DieSides, effect3DieSides, - effect1RealPointsPerLevel, effect2RealPointsPerLevel, effect3RealPointsPerLevel, - effect1BasePoints, effect2BasePoints, effect3BasePoints, - effect1Mechanic, effect2Mechanic, effect3Mechanic, - effect1ImplicitTargetA, effect2ImplicitTargetA, effect3ImplicitTargetA, - effect1ImplicitTargetB, effect2ImplicitTargetB, effect3ImplicitTargetB, - IFNULL (sr1.radiusMin, 0) AS effect1RadiusMin, IFNULL (sr1.radiusMax, 0) AS effect1RadiusMax, - IFNULL (sr2.radiusMin, 0) AS effect2RadiusMin, IFNULL (sr2.radiusMax, 0) AS effect2RadiusMax, - IFNULL (sr3.radiusMin, 0) AS effect3RadiusMin, IFNULL (sr3.radiusMax, 0) AS effect3RadiusMax, - effect1AuraId, effect2AuraId, effect3AuraId, - effect1Periode, effect2Periode, effect3Periode, - effect1ValueMultiplier, effect2ValueMultiplier, effect3ValueMultiplier, - effect1ChainTarget, effect2ChainTarget, effect3ChainTarget, - effect1CreateItemId, effect2CreateItemId, effect3CreateItemId, - effect1MiscValue, effect2MiscValue, effect3MiscValue, - effect1MiscValueB, effect2MiscValueB, effect3MiscValueB, - effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell, - effect1PointsPerComboPoint, effect2PointsPerComboPoint, effect3PointsPerComboPoint, - effect1SpellClassMaskA, effect2SpellClassMaskA, effect3SpellClassMaskA, - effect1SpellClassMaskB, effect2SpellClassMaskB, effect3SpellClassMaskB, - effect1SpellClassMaskC, effect2SpellClassMaskC, effect3SpellClassMaskC, - effect1DamageMultiplier, effect2DamageMultiplier, effect3DamageMultiplier, - effect1BonusMultiplier, effect2BonusMultiplier, effect3BonusMultiplier, - 0 AS iconId, iconId AS iconIdBak, 0 AS iconIdAlt, - 0 AS rankId, spellVisualId1, - name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, - rank_loc0, rank_loc2, rank_loc3, rank_loc4, rank_loc6, rank_loc8, - description_loc0, description_loc2, description_loc3, description_loc4, description_loc6, description_loc8, - buff_loc0, buff_loc2, buff_loc3, buff_loc4, buff_loc6, buff_loc8, - maxTargetLevel, - spellFamilyId, - spellFamilyFlags1, spellFamilyFlags2, spellFamilyFlags3, - maxAffectedTargets, - damageClass, - 0 AS skillLine1, - 0 AS skillLine2OrMask, - 0 AS reqRaceMask, - 0 AS reqClassMask, - 0 AS reqSpellId, - 0 AS reqSkillLevel, - 0 AS learnedAt, - 0 AS skillLevelGrey, - 0 AS skillLevelYellow, - schoolMask, - spellDescriptionVariable, - 0 AS trainingCost - FROM - dbc_spell s - LEFT JOIN - dbc_spellcasttimes sct ON s.castTimeId = sct.id - LEFT JOIN - dbc_spellrunecost src ON s.runeCostId = src.id - LEFT JOIN - dbc_spellduration sd ON s.durationId = sd.id - LEFT JOIN - dbc_spellradius sr1 ON s.effect1RadiusId = sr1.id - LEFT JOIN - dbc_spellradius sr2 ON s.effect2RadiusId = sr2.id - LEFT JOIN - dbc_spellradius sr3 ON s.effect3RadiusId = sr3.id - WHERE - s.id > ?d - LIMIT - ?d'; - - $serverside = []; - - // merge serverside spells into dbc_spell (should not affect other scripts) - $lastMax = 0; - CLI::write(' - merging serverside spells into spell.dbc'); - while ($spells = DB::World()->select($ssQuery, $lastMax, SqlGen::$sqlBatchSize)) - { - $newMax = max(array_column($spells, 'id')); - - CLI::write(' * sets '.($lastMax + 1).' - '.$newMax, CLI::LOG_BLANK, true, true); - - $lastMax = $newMax; - - foreach ($spells as $id => $spell) - { - $serverside[] = $id; - DB::Aowow()->query('REPLACE INTO dbc_spell VALUES (?a)', array_values($spell)); - } - } - - // merge everything into aowow_spell - $lastMax = 0; - CLI::write(' - filling aowow_spell'); - while ($spells = DB::Aowow()->select($baseQuery, $lastMax, SqlGen::$sqlBatchSize)) - { - $newMax = max(array_column($spells, 'id')); - - CLI::write(' * sets '.($lastMax + 1).' - '.$newMax, CLI::LOG_BLANK, true, true); - - $lastMax = $newMax; - - foreach ($spells as $spell) - DB::Aowow()->query('REPLACE INTO ?_spell VALUES (?a)', array_values($spell)); - } - - // apply flag: CUSTOM_SERVERSIDE - if ($serverside) - DB::Aowow()->query('UPDATE ?_spell SET cuFlags = cuFlags | ?d WHERE id IN (?a)', CUSTOM_SERVERSIDE, $serverside); - - // apply flag: CUSTOM_DISABLED [0xD: players (0x1), pets (0x4), general (0x8); only generally disabled spells] - if ($disables = DB::World()->selectCol('SELECT entry FROM disables WHERE sourceType = 0 AND params_0 = "" AND params_1 = "" AND flags & 0xD')) - DB::Aowow()->query('UPDATE ?_spell SET cuFlags = cuFlags | ?d WHERE id IN (?a)', CUSTOM_DISABLED, $disables); - - // apply spell ranks (can't use skilllineability.dbc, as it does not contain ranks for non-player/pet spells) - $ranks = DB::World()->selectCol('SELECT first_spell_id AS ARRAY_KEY, spell_id AS ARRAY_KEY2, `rank` FROM spell_ranks'); - foreach ($ranks as $firstSpell => $sets) - { - // apply flag: SPELL_CU_FIRST_RANK - DB::Aowow()->query('UPDATE ?_spell SET cuFlags = cuFlags | ?d WHERE id = ?d', SPELL_CU_FIRST_RANK, $firstSpell); - - foreach ($sets as $spell => $rank) - DB::Aowow()->query('UPDATE ?_spell SET rankNo = ?d WHERE id = ?d', $rank, $spell); - - // apply flag: SPELL_CU_LAST_RANK - end($sets); - DB::Aowow()->query('UPDATE ?_spell SET cuFlags = cuFlags | ?d WHERE id = ?d', SPELL_CU_LAST_RANK, key($sets)); - } - - - /*************************************/ - /* merge SkillLineAbility into Spell */ - /*************************************/ - - /* acquireMethod - ABILITY_LEARNED_ON_GET_PROFESSION_SKILL = 1, learnedAt = 1 - ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL = 2 not used for now - */ - - CLI::write(' - linking with skilllineability'); - - $results = DB::Aowow()->select('SELECT spellId AS ARRAY_KEY, id AS ARRAY_KEY2, skillLineId, reqRaceMask, reqClassMask, reqSkillLevel, acquireMethod, skillLevelGrey, skillLevelYellow FROM dbc_skilllineability sla'); - foreach ($results as $spellId => $sets) - { - $names = array_keys(current($sets)); - $lines = []; - $trainer = false; - $update = array( - 'skillLine1' => 0, - 'skillLine2OrMask' => 0, - 'reqRaceMask' => 0, - 'reqClassMask' => 0, - 'reqSkillLevel' => 0, - 'skillLevelGrey' => 0, - 'skillLevelYellow' => 0 - ); - - foreach ($sets as $set) - { - $i = 0; - while (isset($names[$i])) - { - $field = $set[$names[$i]]; - switch ($names[$i]) - { - case 'acquireMethod': - if ($field == 1) - $trainer = true; - break; - case 'skillLineId': // array - if (!in_array($field, $lines)) - $lines[] = $field; - break; - case 'reqRaceMask': // mask - case 'reqClassMask': - if (((int)$update[$names[$i]] & (int)$field) != $field) - (int)$update[$names[$i]] |= (int)$field; - break; - case 'reqSkillLevel': // max - case 'skillLevelYellow': - case 'skillLevelGrey': - if ($update[$names[$i]] < $field) - $update[$names[$i]] = $field; - break; - } - $i++; - } - } - - if ($trainer) - DB::Aowow()->query('UPDATE ?_spell SET learnedAt = 1 WHERE id = ?d', $spellId); - - // check skillLineId against mask - switch (count($lines)) - { - case 2: - $update['skillLine2OrMask'] = $lines[1]; - case 1: - $update['skillLine1'] = $lines[0]; - break; - default: - for ($i = -count(Game::$skillLineMask); $i < 0; $i++) - { - foreach (Game::$skillLineMask[$i] as $k => $pair) - { - if (in_array($pair[1], $lines)) - { - $update['skillLine1'] = $i; - $update['skillLine2OrMask'] |= 1 << $k; - } - } - } - } - - DB::Aowow()->query('UPDATE ?_spell SET ?a WHERE id = ?d', $update, $spellId); - } - - // fill learnedAt, trainingCost from trainer - if ($trainer = DB::World()->select('SELECT SpellID AS ARRAY_KEY, MIN(ReqSkillRank) AS reqSkill, MIN(MoneyCost) AS cost, ReqAbility1 AS reqSpellId, COUNT(*) AS count FROM trainer_spell GROUP BY SpellID')) - { - $spells = DB::Aowow()->select('SELECT id AS ARRAY_KEY, effect1Id, effect2Id, effect3Id, effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell FROM dbc_spell WHERE id IN (?a)', array_keys($trainer)); - $links = []; - - // todo (med): this skips some spells (e.g. riding) - foreach ($trainer as $spell => $tData) - { - if (!isset($spells[$spell])) - continue; - - $triggered = false; - $effects = $spells[$spell]; - - for ($i = 1; $i <= 3; $i++) - { - if ($effects['effect'.$i.'Id'] != SPELL_EFFECT_LEARN_SPELL) - continue; - - $triggered = true; - - $l = &$links[$effects['effect'.$i.'TriggerSpell']]; - - if (!isset($l)) - $l = [$tData['reqSkill'], $tData['cost'], $tData['reqSpellId']]; - - if ($tData['reqSkill'] < $l[0]) - $l[0] = $tData['reqSkill']; - - if ($tData['cost'] < $l[1]) - $l[1] = $tData['cost']; - } - - if (!$triggered) - { - $l = &$links[$spell]; - - if (!isset($l)) - $l = [$tData['reqSkill'], $tData['cost'], $tData['reqSpellId']]; - - if ($tData['reqSkill'] < $l[0]) - $l[0] = $tData['reqSkill']; - - if ($tData['cost'] < $l[1]) - $l[1] = $tData['cost']; - } - } - - foreach ($links as $spell => $link) - DB::Aowow()->query("UPDATE ?_spell s SET s.learnedAt = ?d, s.trainingCost = ?d WHERE s.id = ?d", $link[0], $link[1], $spell); - } - - // fill learnedAt from recipe-items - $recipes = DB::World()->selectCol('SELECT IF(spelltrigger_2 = 6, spellid_2, spellid_1) AS ARRAY_KEY, MIN(RequiredSkillRank) FROM item_template WHERE `class` = 9 AND spelltrigger_1 <> 1 AND RequiredSkillRank > 0 GROUP BY ARRAY_KEY'); - foreach ($recipes as $spell => $reqSkill) - DB::Aowow()->query('UPDATE ?_spell SET learnedAt = IF(learnedAt = 0 OR learnedAt > ?d, ?d, learnedAt) WHERE id = ?d', $reqSkill, $reqSkill, $spell); - - // fill learnedAt from Discovery - // 61756: Northrend Inscription Research (FAST QA VERSION); - // 64323: Book of Glyph Mastery (todo: get reqSkill from item [425]) - // 28571 - 28576: $element Protection Potion (todo: get reqSkill from teaching spell [360]) - $discovery = DB::World()->selectCol(' - SELECT spellId AS ARRAY_KEY, - IF(reqSpell = ?d, ?d, - IF(reqSpell BETWEEN ?d AND ?d, ?d, - IF(reqSkillValue, reqSkillValue, 1))) - FROM skill_discovery_template WHERE reqSpell NOT IN (?a)', 64323, 425, 28571, 28576, 360, [61756]); - foreach ($discovery as $spell => $reqSkill) - DB::Aowow()->query('UPDATE ?_spell SET learnedAt = ?d WHERE id = ?d', $reqSkill, $spell); - - // calc reqSkill for gethering-passives (herbing, mining, skinning) (on second thought .. it is set in skilllineability >.<) - $sets = DB::World()->selectCol('SELECT spell_id AS ARRAY_KEY, `rank` * 75 AS reqSkill FROM spell_ranks WHERE first_spell_id IN (?a)', [55428, 53120, 53125]); - foreach ($sets as $spell => $reqSkill) - DB::Aowow()->query('UPDATE ?_spell SET learnedAt = ?d WHERE id = ?d', $reqSkill, $spell); - - - /******************/ - /* talent related */ - /******************/ - - CLI::write(' - linking with talent'); - - for ($i = 1; $i < 6; $i++) - { - // classMask - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t, dbc_talenttab tt SET s.reqClassMask = tt.classMask WHERE tt.creatureFamilyMask = 0 AND tt.id = t.tabId AND t.rank?d = s.id', $i); - // talentLevel - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t, dbc_talenttab tt SET s.talentLevel = (t.row * 5) + 10 + (?d * 1) WHERE tt.id = t.tabId AND tt.creatureFamilyMask = 0 AND t.rank?d = s.id', $i - 1, $i); - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t, dbc_talenttab tt SET s.talentLevel = (t.row * 12) + 20 + (?d * 4) WHERE tt.id = t.tabId AND tt.creatureFamilyMask <> 0 AND t.rank?d = s.id', $i - 1, $i); - } - - // passive talent - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.cuFlags = s.cuFlags | ?d WHERE t.talentSpell = 0 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3 OR s.id = t.rank4 OR s.id = t.rank5)', SPELL_CU_TALENT); - - // spell taught by talent - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.cuFlags = s.cuFlags | ?d WHERE t.talentSpell = 1 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3 OR s.id = t.rank4 OR s.id = t.rank5)', SPELL_CU_TALENTSPELL); - - - /*********/ - /* Other */ - /*********/ - - CLI::write(' - misc fixups & icons'); - - // FU [FixUps] - DB::Aowow()->query('UPDATE ?_spell SET reqRaceMask = ?d WHERE skillLine1 = ?d', RACE_DRAENEI, 760); // Draenai Racials - DB::Aowow()->query('UPDATE ?_spell SET reqRaceMask = ?d WHERE skillLine1 = ?d', RACE_BLOODELF, 756); // Bloodelf Racials - DB::Aowow()->query('UPDATE ?_spell SET reqClassMask = ?d WHERE id = ?d', CLASS_MAGE, 30449); // Mage - Spellsteal - - // triggered by spell - DB::Aowow()->query(' - UPDATE - ?_spell a - JOIN ( - SELECT effect1TriggerSpell as id FROM ?_spell WHERE effect1Id NOT IN (36, 57, 133) AND effect1TriggerSpell <> 0 UNION - SELECT effect2TriggerSpell as id FROM ?_spell WHERE effect2Id NOT IN (36, 57, 133) AND effect2TriggerSpell <> 0 UNION - SELECT effect3TriggerSpell as id FROM ?_spell WHERE effect3Id NOT IN (36, 57, 133) AND effect3TriggerSpell <> 0 - ) as b - SET - cuFlags = cuFlags | ?d - WHERE a.id = b.id', - SPELL_CU_TRIGGERED); - - // altIcons and quality for craftSpells - $itemSpells = DB::Aowow()->selectCol(' - SELECT s.id AS ARRAY_KEY, effect1CreateItemId - FROM dbc_spell s - LEFT JOIN dbc_talent t1 ON t1.rank1 = s.id - LEFT JOIN dbc_talent t2 ON t2.rank2 = s.id - LEFT JOIN dbc_talent t3 ON t3.rank3 = s.id - WHERE effect1CreateItemId > 0 AND (effect1Id in (?a) OR effect1AuraId in (?a)) AND t1.id IS NULL AND t2.id IS NULL AND t3.id IS NULL - UNION - SELECT s.id AS ARRAY_KEY, effect2CreateItemId - FROM dbc_spell s - LEFT JOIN dbc_talent t1 ON t1.rank1 = s.id - LEFT JOIN dbc_talent t2 ON t2.rank2 = s.id - LEFT JOIN dbc_talent t3 ON t3.rank3 = s.id - WHERE effect2CreateItemId > 0 AND (effect2Id in (?a) OR effect2AuraId in (?a)) AND t1.id IS NULL AND t2.id IS NULL AND t3.id IS NULL - UNION - SELECT s.id AS ARRAY_KEY, effect3CreateItemId - FROM dbc_spell s - LEFT JOIN dbc_talent t1 ON t1.rank1 = s.id - LEFT JOIN dbc_talent t2 ON t2.rank2 = s.id - LEFT JOIN dbc_talent t3 ON t3.rank3 = s.id - WHERE effect3CreateItemId > 0 AND (effect3Id in (?a) OR effect3AuraId in (?a)) AND t1.id IS NULL AND t2.id IS NULL AND t3.id IS NULL - ', - SpellList::EFFECTS_ITEM_CREATE, SpellList::AURAS_ITEM_CREATE, - SpellList::EFFECTS_ITEM_CREATE, SpellList::AURAS_ITEM_CREATE, - SpellList::EFFECTS_ITEM_CREATE, SpellList::AURAS_ITEM_CREATE); - - $itemInfo = DB::World()->select('SELECT entry AS ARRAY_KEY, displayId AS d, Quality AS q FROM item_template WHERE entry IN (?a)', $itemSpells); - foreach ($itemSpells as $sId => $itemId) - if (isset($itemInfo[$itemId])) - DB::Aowow()->query('UPDATE ?_spell s, ?_icons ic, dbc_spellicon si SET s.iconIdAlt = ?d, s.cuFlags = s.cuFlags | ?d WHERE s.iconIdBak = si.id AND ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) AND s.id = ?d', $itemInfo[$itemId]['d'], ((7 - $itemInfo[$itemId]['q']) << 8), $sId); - - $itemReqs = DB::World()->selectCol('SELECT entry AS ARRAY_KEY, requiredSpell FROM item_template WHERE requiredSpell NOT IN (?a)', [0, 34090, 34091]); // not riding - foreach ($itemReqs AS $itemId => $req) - DB::Aowow()->query('UPDATE ?_spell SET reqSpellId = ?d WHERE skillLine1 IN (?a) AND effect1CreateItemId = ?d', $req, [SKILL_BLACKSMITHING, SKILL_LEATHERWORKING, SKILL_TAILORING, SKILL_ENGINEERING], $itemId); - - // setting icons - DB::Aowow()->query('UPDATE ?_spell s, ?_icons ic, dbc_spellicon si SET s.iconId = ic.id WHERE s.iconIdBak = si.id AND ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1))'); - - // hide internal stuff from listviews - // QA*; *DND*; square brackets anything; *(NYI)*; *(TEST)* - // cant catch raw: NYI (uNYIelding); PH (PHasing) - DB::Aowow()->query('UPDATE ?_spell SET cuFlags = cuFlags | ?d WHERE name_loc0 LIKE "QA%" OR name_loc0 LIKE "%DND%" OR name_loc0 LIKE "%[%" OR name_loc0 LIKE "%(NYI)%" OR name_loc0 LIKE "%(TEST)%"', CUSTOM_EXCLUDE_FOR_LISTVIEW); - - - /**************/ - /* Categories */ - /**************/ - - CLI::write(' - applying categories'); - - // player talents (-2) - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -2 WHERE t.tabId NOT IN (409, 410, 411) AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3 OR s.id = t.rank4 OR s.id = t.rank5)'); - - // pet spells (-3) - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -3 WHERE (s.cuFlags & 0x3) = 0 AND s.skillline1 IN (?a)', - array_merge( - array_column(Game::$skillLineMask[-1], 1), // hunter pets - array_column(Game::$skillLineMask[-2], 1), // warlock pets - [270, 782], // hunter generic, DK - Ghoul - [-1, -2] // super categories - ) - ); - - // racials (-4) - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -4 WHERE s.skillLine1 IN (101, 124, 125, 126, 220, 733, 753, 754, 756, 760)'); - - // mounts (-5) - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -5 WHERE s.effect1AuraId = 78 AND (s.skillLine1 IN (354, 594, 772, 777) OR (s.skillLine1 > 0 AND s.skillLine2OrMask = 777))'); - - // companions (-6) - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -6 WHERE s.skillLine1 = 778'); - - // pet talents (-7) - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -7, s.cuFlags = s.cuFlags | 0x10 WHERE t.tabId = 409 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3)'); - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -7, s.cuFlags = s.cuFlags | 0x08 WHERE t.tabId = 410 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3)'); - DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -7, s.cuFlags = s.cuFlags | 0x20 WHERE t.tabId = 411 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3)'); - - // internal (-9) by faaaaaar not complete - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -9 WHERE s.skillLine1 = 769'); - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -9 WHERE s.typeCat = 0 AND s.cuFlags = 0 AND ( - s.name_loc0 LIKE "%qa%" OR - s.name_loc0 LIKE "%debug%" OR - s.name_loc0 LIKE "%internal%" OR - s.name_loc0 LIKE "%(NYI)%" OR - s.name_loc0 LIKE "%(TEST)%" OR - s.name_loc0 LIKE "%(OLD)%")' - ); - - // proficiencies (-11) - DB::Aowow()->query('UPDATE ?_spell s, dbc_skillline sl SET s.typeCat = -11 WHERE s.skillLine1 = sl.id AND sl.categoryId IN (6, 8, 10)'); - - // glyphs (-13) - DB::Aowow()->query('UPDATE ?_spell s, dbc_glyphproperties gp SET s.cuFlags = s.cuFlags | IF(gp.typeFlags, ?d, ?d), s.typeCat = -13 WHERE gp.typeFlags IN (0, 1) AND gp.id = s.effect1MiscValue AND s.effect1Id = 74', SPELL_CU_GLYPH_MINOR, SPELL_CU_GLYPH_MAJOR); - $glyphs = DB::World()->selectCol('SELECT it.spellid_1 AS ARRAY_KEY, it.AllowableClass FROM item_template it WHERE it.class = 16'); - foreach ($glyphs as $spell => $classMask) - DB::Aowow()->query('UPDATE ?_spell s, dbc_glyphproperties gp SET s.reqClassMask = ?d WHERE gp.typeFlags IN (0, 1) AND gp.id = s.effect1MiscValue AND s.effect1Id = 74 AND s.id = ?d', $classMask, $spell); - - // class Spells (7) - DB::Aowow()->query('UPDATE ?_spell s, dbc_skillline sl SET s.typeCat = 7 WHERE s.typeCat = 0 AND s.skillLine1 = sl.id AND sl.categoryId = 7'); - - // hide some internal/unused stuffs - DB::Aowow()->query('UPDATE ?_spell s SET s.cuFlags = ?d WHERE s.typeCat = 7 AND ( - s.name_loc0 LIKE "%passive%" OR s.name_loc0 LIKE "%effect%" OR s.name_loc0 LIKE "%improved%" OR s.name_loc0 LIKE "%prototype%" OR -- can probably be extended - (s.id NOT IN (47241, 59879, 59671) AND s.baseLevel <= 1 AND s.reqclassMask = 0) OR -- can probably still be extended - (s.SpellFamilyId = 15 AND s.SpellFamilyFlags1 & 0x2000 AND s.SpellDescriptionVariableId <> 84) OR -- DK: Skill Coil - (s.SpellFamilyId = 10 AND s.SpellFamilyFlags2 & 0x1000000 AND s.attributes1 = 0) OR -- Paladin: Bacon of Light hmm.. Bacon.... :] - (s.SpellFamilyId = 6 AND s.SpellFamilyFlags3 & 0x4000) OR -- Priest: Lolwell Renew - (s.SpellFamilyId = 6 AND s.SpellFamilyFlags1 & 0x8000000 AND s.rank_loc0 <> "") OR -- Priest: Bling Bling - (s.SpellFamilyId = 8 AND s.attributes0 = 0x50 AND s.attributes1 & 0x400) OR -- Rogue: Intuition (dropped Talent..? looks nice though) - (s.SpellfamilyId = 11 AND s.SpellFamilyFlags1 & 3 AND s.attributes1 = 1024) OR -- Shaman: Lightning Overload procs - (s.attributes0 = 0x20000000 AND s.attributes3 = 0x10000000) -- Master Demonologist (FamilyId = 0) - )', CUSTOM_EXCLUDE_FOR_LISTVIEW); - - foreach ([1, 2, 3, 4, 5, 6, 7, 8, 9, 11] as $classId) - DB::Aowow()->query(' - UPDATE - ?_spell s, - dbc_skillline sl, - dbc_skillraceclassinfo srci - SET - s.reqClassMask = srci.classMask - WHERE - s.typeCat IN (-2, 7) AND - (s.attributes0 & 0x80) = 0 AND - s.skillLine1 = srci.skillLine AND - sl.categoryId = 7 AND - srci.skillline <> 769 AND - srci.skillline = sl.id AND - srci.flags & 0x90 AND - srci.classMask & ?d', - 1 << ($classId - 1) - ); - - // secondary Skills (9) - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = 9 WHERE s.typeCat = 0 AND (s.skillLine1 IN (?a) OR (s.skillLine1 > 0 AND s.skillLine2OrMask IN (?a)))', SKILLS_TRADE_SECONDARY, SKILLS_TRADE_SECONDARY); - - // primary Skills (11) - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = 11 WHERE s.typeCat = 0 AND s.skillLine1 IN (?a)', SKILLS_TRADE_PRIMARY); - - // npc spells (-8) (run as last! .. missing from npc_scripts? "enum Spells { \s+(\w\d_)+\s+=\s(\d+) }" and "#define SPELL_(\d\w_)+\s+(\d+)") // RAID_MODE(1, 2[, 3, 4]) - macro still not considered - $world = DB::World()->selectCol(' - SELECT ss.action_param1 FROM smart_scripts ss WHERE ss.action_type IN (11, 75, 85, 86) UNION - SELECT cts.Spell FROM creature_template_spell cts' - ); - - $auras = DB::World()->selectCol('SELECT `entry` AS ARRAY_KEY, cta.auras FROM creature_template_addon cta WHERE auras <> ""'); - foreach ($auras as $e => $aur) - { - // people keep trying to seperate auras with commas - $a = preg_replace('/[^\d ]/', ' ', $aur, -1, $nErrors); - if ($nErrors > 0) - CLI::write('creature_template_addon entry #' . CLI::bold($e) . ' has invalid chars in auras string "'. CLI::bold($aur).'"', CLI::LOG_WARN); - - $world = array_merge($world, array_filter(explode(' ', $a))); - } - - DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -8 WHERE s.typeCat = 0 AND s.id IN (?a)', $world); - - - /**********/ - /* Glyphs */ - /**********/ - - CLI::write(' - fixing glyph data'); - - // glyphSpell => affectedSpell - $glyphAffects = array( - 63959 => 50842, // Pestilence - 58723 => 55090, // Scourge Strike - 58721 => 46584, // Raise Dead - 58711 => 52375, // Death Coil - 54857 => 33876, // Mangle (Cat) - 56881 => 13165, // Aspect of the Hawk - 56598 => 27101, // Conjure Mana Gem (Rank 5) - 63871 => 1038, // Hand of Salvation - 55003 => 53407, // Judgement of Justice - 63873 => 47788, // Guardian Spirit - 58258 => 2983, // Sprint - 55535 => 52127, // Water Shield - 55558 => 16190, // Mana Tide Totem - 56302 => 697, // Summon Voidwalker - 56299 => 712, // Summon Succubus - 58272 => 126, // Summon Eye of Kilrogg - 56292 => 688, // Summon Imp - 56286 => 691, // Summon Felhunter - 56285 => 30146, // Summon Felguard - 58275 => 29893, // Ritual of Souls - 63941 => 1454, // Life Tap - 56289 => 5699, // Create Healthstone - 56297 => 693, // Create Soulstone - 58271 => 1120, // Drain Soul - 58281 => 34428, // Victory Rush - 58397 => 23922, // Shield Slam - 63949 => 50720 // Vigilance - ); - - $queryIcons = ' - SELECT s.id, s.name_loc0, s.skillLine1 as skill, ic.id as icon, s.typeCat * s.typeCat AS prio - FROM ?_spell s - LEFT JOIN dbc_spellicon si ON s.iconIdBak = si.id - LEFT JOIN ?_icons ic ON ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) - WHERE [WHERE] AND (s.cuFlags & ?d) = 0 AND s.typeCat IN (0, 7, -2) -- not triggered; class spells first, talents second, unk last - ORDER BY prio DESC - '; - - $effects = DB::Aowow()->select(' - SELECT - s2.id AS ARRAY_KEY, - s1.id, - s1.name_loc0, - s1.spellFamilyId, - s1.spellFamilyFlags1, s1.spellFamilyFlags2, s1.spellFamilyFlags3, - s1.effect1Id, s1.effect2Id, s1.effect3Id, - s1.effect1SpellClassMaskA, s1.effect1SpellClassMaskB, s1.effect1SpellClassMaskC, - s1.effect2SpellClassMaskA, s1.effect2SpellClassMaskB, s1.effect2SpellClassMaskC, - s1.effect3SpellClassMaskA, s1.effect3SpellClassMaskB, s1.effect3SpellClassMaskC - FROM - dbc_glyphproperties gp - JOIN - ?_spell s1 ON s1.id = gp.spellId - JOIN - ?_spell s2 ON s2.effect1MiscValue = gp.id AND s2.effect1Id = 74 - WHERE - gp.typeFlags IN (0, 1) -- AND s2.id In (58271, 56297, 56289, 63941, 58275) - '); - - foreach ($effects as $applyId => $glyphEffect) - { - $l = [null, 'A', 'B', 'C']; - $i = 0; - $icons = []; - $fam = $glyphEffect['spellFamilyId']; - - // first: manuall replace - if ($applyId == 57144) // has no skillLine.. :/ - { - DB::Aowow()->query('UPDATE ?_spell s, ?_icons ic SET s.skillLine1 = ?d, s.iconIdAlt = ic.id WHERE s.id = ?d AND ic.name = ?', 253, 57144, 'ability_poisonsting'); - continue; - } - - // second: search by name and family equality - if (!$icons) - { - $search = !empty($glyphAffects[$applyId]) ? $glyphAffects[$applyId] : str_replace('Glyph of ', '', $glyphEffect['name_loc0']); - if (is_int($search)) - $where = "?d AND s.id = ?d"; - else - $where = "s.SpellFamilyId = ?d AND s.name_loc0 LIKE ?"; - - $qry = str_replace('[WHERE]', $where, $queryIcons); - $icons = DB::Aowow()->selectRow($qry, $fam ?: 1, $search, SPELL_CU_TRIGGERED); - } - - // third: match by SpellFamily affect mask - while (empty($icons) && $i < 3) - { - $i++; - $m1 = $glyphEffect['effect1SpellClassMask'.$l[$i]]; - $m2 = $glyphEffect['effect2SpellClassMask'.$l[$i]]; - $m3 = $glyphEffect['effect3SpellClassMask'.$l[$i]]; - - if ($glyphEffect['effect'.$i.'Id'] != 6 || (!$m1 && !$m2 && !$m3)) - continue; - - $where = "s.SpellFamilyId = ?d AND (s.SpellFamilyFlags1 & ?d OR s.SpellFamilyFlags2 & ?d OR s.SpellFamilyFlags3 & ?d)"; - - $icons = DB::Aowow()->selectRow(str_replace('[WHERE]', $where, $queryIcons), $fam, $m1, $m2, $m3, SPELL_CU_TRIGGERED); - } - - if ($icons) - DB::Aowow()->query('UPDATE ?_spell s SET s.skillLine1 = ?d, s.iconIdAlt = ?d WHERE s.id = ?d', $icons['skill'], $icons['icon'], $applyId); - else - CLI::write('could not match '.$glyphEffect['name_loc0'].' ('.$glyphEffect['id'].') with affected spells', CLI::LOG_WARN); - } - - $this->reapplyCCFlags('spell', Type::SPELL); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/spell.ss.php b/setup/tools/sqlgen/spell.ss.php new file mode 100644 index 000000000..713fa632f --- /dev/null +++ b/setup/tools/sqlgen/spell.ss.php @@ -0,0 +1,732 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Spell from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['spell', 'spellradius', 'spellduration', 'spellrunecost', 'spellcasttimes', 'skillline', 'skilllineability', 'skillraceclassinfo', 'talent', 'talenttab', 'glyphproperties', 'spellicon', 'itemdisplayinfo']; + protected $worldDependency = ['item_template', 'creature_template', 'creature_template_addon', 'creature_template_spell', 'smart_scripts', 'trainer_spell', 'disables', 'spell_ranks', 'spell_dbc', 'skill_discovery_template']; + protected $setupAfter = [['icons', 'spellrange'], []]; // spellrange required to use SpellList + + public function generate(array $ids = []) : bool + { + $ssQuery = 'SELECT id AS ARRAY_KEY, + id, + 0 AS category, + Dispel, + Mechanic, + Attributes, AttributesEx, AttributesEx2, AttributesEx3, AttributesEx4, AttributesEx5, AttributesEx6, AttributesEx7, + Stances, StancesNot, + Targets, + 0 AS spellFocus, + CastingTimeIndex, + 0 AS recoveryTime, 0 AS recoveryTimeCategory, + ProcChance, ProcCharges, + MaxLevel, BaseLevel, SpellLevel, + DurationIndex, + 0 AS powerType, + 0 AS powerCost, + 0 AS powerCostPerLevel, + 0 AS powerPerSecond, + 0 AS powerPerSecondPerLevel, + RangeIndex, + StackAmount, + 0 AS tool1, 0 AS tool2, + 0 AS reagent1, 0 AS reagent2, 0 AS reagent3, 0 AS reagent4, 0 AS reagent5, 0 AS reagent6, 0 AS reagent7, 0 AS reagent8, + 0 AS reagentCount1, 0 AS reagentCount2, 0 AS reagentCount3, 0 AS reagentCount4, 0 AS reagentCount5, 0 AS reagentCount6, 0 AS reagentCount7, 0 AS reagentCount8, + EquippedItemClass, + EquippedItemSubClassMask, + EquippedItemInventoryTypeMask, + Effect1, Effect2, Effect3, + EffectDieSides1, EffectDieSides2, EffectDieSides3, + EffectRealPointsPerLevel1, EffectRealPointsPerLevel2, EffectRealPointsPerLevel3, + EffectBasePoints1, EffectBasePoints2, EffectBasePoints3, + EffectMechanic1, EffectMechanic2, EffectMechanic3, + EffectImplicitTargetA1, EffectImplicitTargetA2, EffectImplicitTargetA3, + EffectImplicitTargetB1, EffectImplicitTargetB2, EffectImplicitTargetB3, + EffectRadiusIndex1, EffectRadiusIndex2, EffectRadiusIndex3, + EffectApplyAuraName1, EffectApplyAuraName2, EffectApplyAuraName3, + EffectAmplitude1, EffectAmplitude2, EffectAmplitude3, + EffectMultipleValue1, EffectMultipleValue2, EffectMultipleValue3, + 0 AS effect1ChainTarget, 0 AS effect2ChainTarget, 0 AS effect3ChainTarget, + EffectItemType1, EffectItemType2, EffectItemType3, + EffectMiscValue1, EffectMiscValue2, EffectMiscValue3, + EffectMiscValueB1, EffectMiscValueB2, EffectMiscValueB3, + EffectTriggerSpell1, EffectTriggerSpell2, EffectTriggerSpell3, + 0 AS effect1PointsPerComboPoint, 0 AS effect2PointsPerComboPoint, 0 AS effect3PointsPerComboPoint, + EffectSpellClassMaskA1, EffectSpellClassMaskA2, EffectSpellClassMaskA3, + EffectSpellClassMaskB1, EffectSpellClassMaskB2, EffectSpellClassMaskB3, + EffectSpellClassMaskC1, EffectSpellClassMaskC2, EffectSpellClassMaskC3, + 0 AS iconId, 0 AS iconIdAlt, + 0 AS rankId, 0 AS spellVisualId1, + CONCAT("Serverside - ",SpellName) AS name_loc0,CONCAT("Serverside - ",SpellName) AS name_loc2,CONCAT("Serverside - ",SpellName) AS name_loc3,CONCAT("Serverside - ",SpellName) AS name_loc4,CONCAT("Serverside - ",SpellName) AS name_loc6,CONCAT("Serverside - ",SpellName) AS name_loc8, + "" AS rank_loc0, "" AS rank_loc2, "" AS rank_loc3, "" AS rank_loc4, "" AS rank_loc6, "" AS rank_loc8, + "" AS description_loc0, "" AS description_loc2, "" AS description_loc3, "" AS description_loc4, "" AS description_loc6, "" AS description_loc8, + "" AS buff_loc0, "" AS buff_loc2, "" AS buff_loc3, "" AS buff_loc4, "" AS buff_loc6, "" AS buff_loc8, + 0 AS powerCostPercent, + 0 AS startRecoveryCategory, + 0 AS startRecoveryTime, + MaxTargetLevel, + SpellFamilyName, + SpellFamilyFlags1, + SpellFamilyFlags2, + SpellFamilyFlags3, + MaxAffectedTargets, + DmgClass, + DmgMultiplier1, DmgMultiplier2, DmgMultiplier3, + 0 AS toolCategory1, 0 AS toolCategory2, + SchoolMask, + 0 AS runeCostId, + 0 AS powerDisplayId, + 0 AS effect1BonusMultiplier, 0 AS effect2BonusMultiplier, 0 AS effect3BonusMultiplier, + 0 AS spellDescriptionVariable, + 0 AS spellDifficulty + FROM spell_dbc + WHERE id > ?d + LIMIT ?d'; + + $baseQry = 'SELECT s.id, + category, + dispelType, + mechanic, + attributes0, attributes1, attributes2, attributes3, attributes4, attributes5, attributes6, attributes7, + 0 AS cuFlags, + 0 AS typeCat, + stanceMask, stanceMaskNot, + targets, + spellFocus, + GREATEST(IFNULL(sct.baseTime, 0), 0) / 1000 AS castTime, + recoveryTime, recoveryTimeCategory, + startRecoveryTime, startRecoveryCategory, + procChance, IF(procCharges > 255, 0, procCharges), + 0 AS procCustom, 0 AS procCooldown, + maxLevel, baseLevel, spellLevel, 0 AS talentLevel, + IF (sd.baseTime <> -1, ABS(sd.baseTime), -1) AS duration, + IF (powerDisplayId, -powerDisplayId, powerType) AS powerType, + powerCost, + powerCostPerLevel, + powerCostPercent, + powerPerSecond, + powerPerSecondPerLevel, + IFNULL (src.runicPowerGain, 0) AS powerGainRunicPower, + IF (src.id IS NULL, 0, (src.costFrost << 8) | (src.costUnholy << 4) | src.costBlood) AS powerCostRunes, + rangeId, + stackAmount, + tool1, tool2, + toolCategory1, toolCategory2, + GREATEST(reagent1, 0), GREATEST(reagent2, 0), GREATEST(reagent3, 0), GREATEST(reagent4, 0), GREATEST(reagent5, 0), GREATEST(reagent6, 0), GREATEST(reagent7, 0), GREATEST(reagent8, 0), + reagentCount1, reagentCount2, reagentCount3, reagentCount4, reagentCount5, reagentCount6, reagentCount7, reagentCount8, + equippedItemClass, + equippedItemSubClassMask, + equippedItemInventoryTypeMask, + effect1Id, effect2Id, effect3Id, + effect1DieSides, effect2DieSides, effect3DieSides, + effect1RealPointsPerLevel, effect2RealPointsPerLevel, effect3RealPointsPerLevel, + effect1BasePoints, effect2BasePoints, effect3BasePoints, + effect1Mechanic, effect2Mechanic, effect3Mechanic, + effect1ImplicitTargetA, effect2ImplicitTargetA, effect3ImplicitTargetA, + effect1ImplicitTargetB, effect2ImplicitTargetB, effect3ImplicitTargetB, + IFNULL (sr1.radiusMin, 0) AS effect1RadiusMin, IFNULL (sr1.radiusMax, 0) AS effect1RadiusMax, + IFNULL (sr2.radiusMin, 0) AS effect2RadiusMin, IFNULL (sr2.radiusMax, 0) AS effect2RadiusMax, + IFNULL (sr3.radiusMin, 0) AS effect3RadiusMin, IFNULL (sr3.radiusMax, 0) AS effect3RadiusMax, + effect1AuraId, effect2AuraId, effect3AuraId, + effect1Periode, effect2Periode, effect3Periode, + effect1ValueMultiplier, effect2ValueMultiplier, effect3ValueMultiplier, + effect1ChainTarget, effect2ChainTarget, effect3ChainTarget, + GREATEST(effect1CreateItemId, 0), GREATEST(effect2CreateItemId, 0), GREATEST(effect3CreateItemId, 0), + effect1MiscValue, effect2MiscValue, effect3MiscValue, + effect1MiscValueB, effect2MiscValueB, effect3MiscValueB, + effect1TriggerSpell, effect2TriggerSpell, effect3TriggerSpell, + effect1PointsPerComboPoint, effect2PointsPerComboPoint, effect3PointsPerComboPoint, + effect1SpellClassMaskA, effect2SpellClassMaskA, effect3SpellClassMaskA, + effect1SpellClassMaskB, effect2SpellClassMaskB, effect3SpellClassMaskB, + effect1SpellClassMaskC, effect2SpellClassMaskC, effect3SpellClassMaskC, + effect1DamageMultiplier, effect2DamageMultiplier, effect3DamageMultiplier, + effect1BonusMultiplier, effect2BonusMultiplier, effect3BonusMultiplier, + 0 AS iconId, iconId AS iconIdBak, 0 AS iconIdAlt, + 0 AS rankId, spellVisualId1, + name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8, + rank_loc0, rank_loc2, rank_loc3, rank_loc4, rank_loc6, rank_loc8, + description_loc0, description_loc2, description_loc3, description_loc4, description_loc6, description_loc8, + buff_loc0, buff_loc2, buff_loc3, buff_loc4, buff_loc6, buff_loc8, + maxTargetLevel, + spellFamilyId, + spellFamilyFlags1, spellFamilyFlags2, spellFamilyFlags3, + maxAffectedTargets, + damageClass, + 0 AS skillLine1, + 0 AS skillLine2OrMask, + 0 AS reqRaceMask, + 0 AS reqClassMask, + 0 AS reqSpellId, + 0 AS reqSkillLevel, + 0 AS learnedAt, + 0 AS skillLevelGrey, + 0 AS skillLevelYellow, + schoolMask, + GREATEST(spellDescriptionVariable, 0), + 0 AS trainingCost + FROM tmp_spell s + LEFT JOIN dbc_spellcasttimes sct ON s.castTimeId = sct.id + LEFT JOIN dbc_spellrunecost src ON s.runeCostId = src.id + LEFT JOIN dbc_spellduration sd ON s.durationId = sd.id + LEFT JOIN dbc_spellradius sr1 ON s.effect1RadiusId = sr1.id + LEFT JOIN dbc_spellradius sr2 ON s.effect2RadiusId = sr2.id + LEFT JOIN dbc_spellradius sr3 ON s.effect3RadiusId = sr3.id + WHERE s.id > ?d + LIMIT ?d'; + + $serverside = []; + + DB::Aowow()->query('TRUNCATE ?_spell'); + + if (!(new DBC('spell', ['temporary' => true, 'tableName' => 'tmp_spell']))->readFile()) + { + CLI::write('[spell] could not create temprary spell table to merge spell_dbc into', CLI::LOG_ERROR); + return false; + } + + // merge serverside spells into tmp_spell + $lastMax = 0; + CLI::write('[spell] - merging serverside spells into spell.dbc'); + while ($spells = DB::World()->select($ssQuery, $lastMax, CLISetup::SQL_BATCH)) + { + $newMax = max(array_column($spells, 'id')); + + CLI::write(' * sets '.($lastMax + 1).' - '.$newMax, CLI::LOG_BLANK, true, true); + + $lastMax = $newMax; + + foreach ($spells as $id => $spell) + { + $serverside[] = $id; + DB::Aowow()->query('INSERT INTO tmp_spell VALUES (?a)', array_values($spell)); + } + } + + // merge everything into aowow_spell + $lastMax = 0; + CLI::write('[spell] - filling aowow_spell'); + while ($spells = DB::Aowow()->select($baseQry, $lastMax, CLISetup::SQL_BATCH)) + { + $newMax = max(array_column($spells, 'id')); + + CLI::write(' * sets '.($lastMax + 1).' - '.$newMax, CLI::LOG_BLANK, true, true); + + $lastMax = $newMax; + + foreach ($spells as $spell) + DB::Aowow()->query('INSERT INTO ?_spell VALUES (?a)', array_values($spell)); + } + + // apply flag: CUSTOM_SERVERSIDE + if ($serverside) + DB::Aowow()->query('UPDATE ?_spell SET `cuFlags` = `cuFlags` | ?d WHERE `id` IN (?a)', CUSTOM_SERVERSIDE, $serverside); + + // apply flag: CUSTOM_DISABLED [0xD: players (0x1), pets (0x4), general (0x8); only generally disabled spells] + if ($disables = DB::World()->selectCol('SELECT `entry` FROM disables WHERE `sourceType` = 0 AND `params_0` = "" AND `params_1` = "" AND `flags` & 0xD')) + DB::Aowow()->query('UPDATE ?_spell SET `cuFlags` = `cuFlags` | ?d WHERE `id` IN (?a)', CUSTOM_DISABLED, $disables); + + // apply spell ranks (can't use skilllineability.dbc, as it does not contain ranks for non-player/pet spells) + $ranks = DB::World()->selectCol('SELECT `first_spell_id` AS ARRAY_KEY, `spell_id` AS ARRAY_KEY2, `rank` FROM spell_ranks'); + foreach ($ranks as $firstSpell => $sets) + { + // apply flag: SPELL_CU_FIRST_RANK + DB::Aowow()->query('UPDATE ?_spell SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', SPELL_CU_FIRST_RANK, $firstSpell); + + foreach ($sets as $spell => $rank) + DB::Aowow()->query('UPDATE ?_spell SET `rankNo` = ?d WHERE `id` = ?d', $rank, $spell); + + // apply flag: SPELL_CU_LAST_RANK + end($sets); + DB::Aowow()->query('UPDATE ?_spell SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', SPELL_CU_LAST_RANK, key($sets)); + } + + + /*************************************/ + /* merge SkillLineAbility into Spell */ + /*************************************/ + + /* acquireMethod + ABILITY_LEARNED_ON_GET_PROFESSION_SKILL = 1, learnedAt = 1 + ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL = 2 not used for now + */ + + CLI::write('[spell] - linking with skilllineability'); + + $results = DB::Aowow()->select('SELECT `spellId` AS ARRAY_KEY, `id` AS ARRAY_KEY2, `skillLineId`, `reqRaceMask`, `reqClassMask`, `reqSkillLevel`, `acquireMethod`, `skillLevelGrey`, `skillLevelYellow` FROM dbc_skilllineability sla'); + foreach ($results as $spellId => $sets) + { + $names = array_keys(current($sets)); + $lines = []; + $trainer = false; + $update = array( + 'skillLine1' => 0, + 'skillLine2OrMask' => 0, + 'reqRaceMask' => 0, + 'reqClassMask' => 0, + 'reqSkillLevel' => 0, + 'skillLevelGrey' => 0, + 'skillLevelYellow' => 0 + ); + + foreach ($sets as $set) + { + $i = 0; + while (isset($names[$i])) + { + $field = $set[$names[$i]]; + switch ($names[$i]) + { + case 'acquireMethod': + if ($field == 1) + $trainer = true; + break; + case 'skillLineId': // array + if (!in_array($field, $lines)) + $lines[] = $field; + break; + case 'reqRaceMask': // mask + case 'reqClassMask': + if (((int)$update[$names[$i]] & (int)$field) != $field) + (int)$update[$names[$i]] |= (int)$field; + break; + case 'reqSkillLevel': // max + case 'skillLevelYellow': + case 'skillLevelGrey': + if ($update[$names[$i]] < $field) + $update[$names[$i]] = $field; + break; + } + $i++; + } + } + + if ($trainer) + DB::Aowow()->query('UPDATE ?_spell SET `learnedAt` = 1 WHERE `id` = ?d', $spellId); + + // check skillLineId against mask + switch (count($lines)) + { + case 2: + $update['skillLine2OrMask'] = $lines[1]; + case 1: + $update['skillLine1'] = $lines[0]; + break; + default: + for ($i = -count(Game::$skillLineMask); $i < 0; $i++) + { + foreach (Game::$skillLineMask[$i] as $k => $pair) + { + if (in_array($pair[1], $lines)) + { + $update['skillLine1'] = $i; + $update['skillLine2OrMask'] |= 1 << $k; + } + } + } + } + + DB::Aowow()->query('UPDATE ?_spell SET ?a WHERE `id` = ?d', $update, $spellId); + } + + // fill learnedAt, trainingCost from trainer + if ($trainer = DB::World()->select('SELECT `spellID` AS ARRAY_KEY, MIN(`ReqSkillRank`) AS `reqSkill`, MIN(`MoneyCost`) AS `cost`, `ReqAbility1` AS `reqSpellId`, COUNT(*) AS `count` FROM trainer_spell GROUP BY `SpellID`')) + { + $spells = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `effect1Id`, `effect2Id`, `effect3Id`, `effect1TriggerSpell`, `effect2TriggerSpell`, `effect3TriggerSpell` FROM tmp_spell WHERE `id` IN (?a)', array_keys($trainer)); + $links = []; + + // todo (med): this skips some spells (e.g. riding) + foreach ($trainer as $spell => $tData) + { + if (!isset($spells[$spell])) + continue; + + $triggered = false; + $effects = $spells[$spell]; + + for ($i = 1; $i <= 3; $i++) + { + if ($effects['effect'.$i.'Id'] != SPELL_EFFECT_LEARN_SPELL) + continue; + + $triggered = true; + + $l = &$links[$effects['effect'.$i.'TriggerSpell']]; + + if (!isset($l)) + $l = [$tData['reqSkill'], $tData['cost'], $tData['reqSpellId']]; + + if ($tData['reqSkill'] < $l[0]) + $l[0] = $tData['reqSkill']; + + if ($tData['cost'] < $l[1]) + $l[1] = $tData['cost']; + } + + if (!$triggered) + { + $l = &$links[$spell]; + + if (!isset($l)) + $l = [$tData['reqSkill'], $tData['cost'], $tData['reqSpellId']]; + + if ($tData['reqSkill'] < $l[0]) + $l[0] = $tData['reqSkill']; + + if ($tData['cost'] < $l[1]) + $l[1] = $tData['cost']; + } + } + + foreach ($links as $spell => $link) + DB::Aowow()->query("UPDATE ?_spell s SET s.`learnedAt` = ?d, s.`trainingCost` = ?d WHERE s.`id` = ?d", $link[0], $link[1], $spell); + } + + // fill learnedAt from recipe-items + $recipes = DB::World()->selectCol('SELECT IF(`spelltrigger_2` = ?d, `spellid_2`, `spellid_1`) AS ARRAY_KEY, MIN(`RequiredSkillRank`) FROM item_template WHERE `class` = ?d AND `spelltrigger_1` <> ?d AND `RequiredSkillRank` > 0 GROUP BY ARRAY_KEY', + SPELL_TRIGGER_LEARN, ITEM_CLASS_RECIPE, SPELL_TRIGGER_EQUIP); + foreach ($recipes as $spell => $reqSkill) + DB::Aowow()->query('UPDATE ?_spell SET `learnedAt` = IF(`learnedAt` = 0 OR `learnedAt` > ?d, ?d, `learnedAt`) WHERE `id` = ?d', $reqSkill, $reqSkill, $spell); + + // fill learnedAt from Discovery + // 61756: Northrend Inscription Research (FAST QA VERSION); + // 64323: Book of Glyph Mastery (todo: get reqSkill from item [425]) + // 28571 - 28576: $element Protection Potion (todo: get reqSkill from teaching spell [360]) + $discovery = DB::World()->selectCol( + 'SELECT `spellId` AS ARRAY_KEY, + IF(`reqSpell` = ?d, ?d, + IF(`reqSpell` BETWEEN ?d AND ?d, ?d, + IF(`reqSkillValue`, `reqSkillValue`, 1))) + FROM skill_discovery_template + WHERE `reqSpell` NOT IN (?a)', + 64323, 425, 28571, 28576, 360, [61756] + ); + foreach ($discovery as $spell => $reqSkill) + DB::Aowow()->query('UPDATE ?_spell SET `learnedAt` = ?d WHERE `id` = ?d', $reqSkill, $spell); + + // calc reqSkill for gathering-passives (herbing, mining, skinning) (on second thought .. it is set in skilllineability >.<) + $sets = DB::World()->selectCol('SELECT `spell_id` AS ARRAY_KEY, `rank` * 75 AS `reqSkill` FROM spell_ranks WHERE `first_spell_id` IN (?a)', [55428, 53120, 53125]); + foreach ($sets as $spell => $reqSkill) + DB::Aowow()->query('UPDATE ?_spell SET `learnedAt` = ?d WHERE `id` = ?d', $reqSkill, $spell); + + + /******************/ + /* talent related */ + /******************/ + + CLI::write('[spell] - linking with talent'); + + for ($i = 1; $i < 6; $i++) + { + // classMask + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t, dbc_talenttab tt SET s.`reqClassMask` = tt.`classMask` WHERE tt.`creatureFamilyMask` = 0 AND tt.`id` = t.`tabId` AND t.?# = s.`id`', 'rank'.$i); + // talentLevel + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t, dbc_talenttab tt SET s.`talentLevel` = (t.`row` * 5) + 10 + (?d * 1) WHERE tt.`id` = t.`tabId` AND tt.`creatureFamilyMask` = 0 AND t.?# = s.`id`', $i - 1, 'rank'.$i); + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t, dbc_talenttab tt SET s.`talentLevel` = (t.`row` * 12) + 20 + (?d * 4) WHERE tt.`id` = t.`tabId` AND tt.`creatureFamilyMask` <> 0 AND t.?# = s.`id`', $i - 1, 'rank'.$i); + } + // passive talent + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.`cuFlags` = s.`cuFlags` | ?d WHERE t.`talentSpell` = 0 AND (s.`id` = t.`rank1` OR s.`id` = t.`rank2` OR s.`id` = t.`rank3` OR s.`id` = t.`rank4` OR s.`id` = t.`rank5`)', SPELL_CU_TALENT); + + // spell taught by talent + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.`cuFlags` = s.`cuFlags` | ?d WHERE t.`talentSpell` = 1 AND (s.`id` = t.`rank1` OR s.`id` = t.`rank2` OR s.`id` = t.`rank3` OR s.`id` = t.`rank4` OR s.`id` = t.`rank5`)', SPELL_CU_TALENTSPELL); + + + /*********/ + /* Other */ + /*********/ + + CLI::write('[spell] - misc fixups & icons'); + + // FU [FixUps] + DB::Aowow()->query('UPDATE ?_spell SET `reqRaceMask` = ?d WHERE `skillLine1` = ?d', RACE_DRAENEI, 760); // Draenai Racials + DB::Aowow()->query('UPDATE ?_spell SET `reqRaceMask` = ?d WHERE `skillLine1` = ?d', RACE_BLOODELF, 756); // Bloodelf Racials + DB::Aowow()->query('UPDATE ?_spell SET `reqClassMask` = ?d WHERE `id` = ?d', CLASS_MAGE, 30449); // Mage - Spellsteal + + // triggered by spell + DB::Aowow()->query( + 'UPDATE ?_spell a + JOIN ( SELECT effect1TriggerSpell as id FROM ?_spell WHERE effect1Id NOT IN (36, 57, 133) AND effect1TriggerSpell <> 0 UNION + SELECT effect2TriggerSpell as id FROM ?_spell WHERE effect2Id NOT IN (36, 57, 133) AND effect2TriggerSpell <> 0 UNION + SELECT effect3TriggerSpell as id FROM ?_spell WHERE effect3Id NOT IN (36, 57, 133) AND effect3TriggerSpell <> 0 ) as b + SET cuFlags = cuFlags | ?d + WHERE a.id = b.id', + SPELL_CU_TRIGGERED); + + // altIcons and quality for craftSpells + $itemSpells = DB::Aowow()->selectCol( + 'SELECT s.id AS ARRAY_KEY, effect1CreateItemId + FROM tmp_spell s + LEFT JOIN dbc_talent t1 ON t1.rank1 = s.id + LEFT JOIN dbc_talent t2 ON t2.rank2 = s.id + LEFT JOIN dbc_talent t3 ON t3.rank3 = s.id + WHERE effect1CreateItemId > 0 AND (effect1Id in (?a) OR effect1AuraId in (?a)) AND t1.id IS NULL AND t2.id IS NULL AND t3.id IS NULL + UNION + SELECT s.id AS ARRAY_KEY, effect2CreateItemId + FROM tmp_spell s + LEFT JOIN dbc_talent t1 ON t1.rank1 = s.id + LEFT JOIN dbc_talent t2 ON t2.rank2 = s.id + LEFT JOIN dbc_talent t3 ON t3.rank3 = s.id + WHERE effect2CreateItemId > 0 AND (effect2Id in (?a) OR effect2AuraId in (?a)) AND t1.id IS NULL AND t2.id IS NULL AND t3.id IS NULL + UNION + SELECT s.id AS ARRAY_KEY, effect3CreateItemId + FROM tmp_spell s + LEFT JOIN dbc_talent t1 ON t1.rank1 = s.id + LEFT JOIN dbc_talent t2 ON t2.rank2 = s.id + LEFT JOIN dbc_talent t3 ON t3.rank3 = s.id + WHERE effect3CreateItemId > 0 AND (effect3Id in (?a) OR effect3AuraId in (?a)) AND t1.id IS NULL AND t2.id IS NULL AND t3.id IS NULL', + SpellList::EFFECTS_ITEM_CREATE, SpellList::AURAS_ITEM_CREATE, + SpellList::EFFECTS_ITEM_CREATE, SpellList::AURAS_ITEM_CREATE, + SpellList::EFFECTS_ITEM_CREATE, SpellList::AURAS_ITEM_CREATE); + + $itemInfo = DB::World()->select('SELECT entry AS ARRAY_KEY, displayId AS d, Quality AS q FROM item_template WHERE entry IN (?a)', $itemSpells); + foreach ($itemSpells as $sId => $itemId) + if (isset($itemInfo[$itemId])) + DB::Aowow()->query('UPDATE ?_spell s, ?_icons ic, dbc_itemdisplayinfo idi SET s.iconIdAlt = ic.id, s.cuFlags = s.cuFlags | ?d WHERE ic.name = LOWER(idi.inventoryIcon1) AND idi.id = ?d AND s.id = ?d', ((7 - $itemInfo[$itemId]['q']) << 8), $itemInfo[$itemId]['d'], $sId); + + $itemReqs = DB::World()->selectCol('SELECT entry AS ARRAY_KEY, requiredSpell FROM item_template WHERE requiredSpell NOT IN (?a)', [0, 34090, 34091]); // not riding + foreach ($itemReqs AS $itemId => $req) + DB::Aowow()->query('UPDATE ?_spell SET reqSpellId = ?d WHERE skillLine1 IN (?a) AND effect1CreateItemId = ?d', $req, [SKILL_BLACKSMITHING, SKILL_LEATHERWORKING, SKILL_TAILORING, SKILL_ENGINEERING], $itemId); + + // setting icons + DB::Aowow()->query('UPDATE ?_spell s, ?_icons ic, dbc_spellicon si SET s.iconId = ic.id WHERE s.iconIdBak = si.id AND ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1))'); + + // hide internal stuff from listviews + // QA*; *DND*; square brackets anything; *(NYI)*; *(TEST)* + // cant catch raw: NYI (uNYIelding); PH (PHasing) + DB::Aowow()->query('UPDATE ?_spell SET cuFlags = cuFlags | ?d WHERE name_loc0 LIKE "QA%" OR name_loc0 LIKE "%DND%" OR name_loc0 LIKE "%[%" OR name_loc0 LIKE "%(NYI)%" OR name_loc0 LIKE "%(TEST)%"', CUSTOM_EXCLUDE_FOR_LISTVIEW); + + + /**************/ + /* Categories */ + /**************/ + + CLI::write('[spell] - applying categories'); + + // player talents (-2) + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -2 WHERE t.tabId NOT IN (409, 410, 411) AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3 OR s.id = t.rank4 OR s.id = t.rank5)'); + + // pet spells (-3) + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -3 WHERE (s.cuFlags & 0x3) = 0 AND s.skillline1 IN (?a)', + array_merge( + array_column(Game::$skillLineMask[-1], 1), // hunter pets + array_column(Game::$skillLineMask[-2], 1), // warlock pets + [270, 782], // hunter generic, DK - Ghoul + [-1, -2] // super categories + ) + ); + + // racials (-4) + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -4 WHERE s.skillLine1 IN (101, 124, 125, 126, 220, 733, 753, 754, 756, 760)'); + + // mounts (-5) + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -5 WHERE s.effect1AuraId = 78 AND (s.skillLine1 IN (354, 594, 772, 777) OR (s.skillLine1 > 0 AND s.skillLine2OrMask = 777))'); + + // companions (-6) + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -6 WHERE s.skillLine1 = 778'); + + // pet talents (-7) + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -7, s.cuFlags = s.cuFlags | 0x10 WHERE t.tabId = 409 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3)'); + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -7, s.cuFlags = s.cuFlags | 0x08 WHERE t.tabId = 410 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3)'); + DB::Aowow()->query('UPDATE ?_spell s, dbc_talent t SET s.typeCat = -7, s.cuFlags = s.cuFlags | 0x20 WHERE t.tabId = 411 AND (s.id = t.rank1 OR s.id = t.rank2 OR s.id = t.rank3)'); + + // internal (-9) by faaaaaar not complete + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -9 WHERE s.skillLine1 = 769'); + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -9 WHERE s.typeCat = 0 AND s.cuFlags = 0 AND ( + s.name_loc0 LIKE "%qa%" OR + s.name_loc0 LIKE "%debug%" OR + s.name_loc0 LIKE "%internal%" OR + s.name_loc0 LIKE "%(NYI)%" OR + s.name_loc0 LIKE "%(TEST)%" OR + s.name_loc0 LIKE "%(OLD)%")' + ); + + // proficiencies (-11) + DB::Aowow()->query('UPDATE ?_spell s, dbc_skillline sl SET s.typeCat = -11 WHERE s.skillLine1 = sl.id AND sl.categoryId IN (6, 8, 10)'); + + // glyphs (-13) + DB::Aowow()->query('UPDATE ?_spell s, dbc_glyphproperties gp SET s.cuFlags = s.cuFlags | IF(gp.typeFlags, ?d, ?d), s.typeCat = -13 WHERE gp.typeFlags IN (0, 1) AND gp.id = s.effect1MiscValue AND s.effect1Id = 74', SPELL_CU_GLYPH_MINOR, SPELL_CU_GLYPH_MAJOR); + $glyphs = DB::World()->selectCol('SELECT it.spellid_1 AS ARRAY_KEY, it.AllowableClass FROM item_template it WHERE it.class = 16'); + foreach ($glyphs as $spell => $classMask) + DB::Aowow()->query('UPDATE ?_spell s, dbc_glyphproperties gp SET s.reqClassMask = ?d WHERE gp.typeFlags IN (0, 1) AND gp.id = s.effect1MiscValue AND s.effect1Id = 74 AND s.id = ?d', $classMask, $spell); + + // class Spells (7) + DB::Aowow()->query('UPDATE ?_spell s, dbc_skillline sl SET s.typeCat = 7 WHERE s.typeCat = 0 AND s.skillLine1 = sl.id AND sl.categoryId = 7'); + + // hide some internal/unused stuffs + DB::Aowow()->query('UPDATE ?_spell s SET s.cuFlags = ?d WHERE s.typeCat = 7 AND ( + s.name_loc0 LIKE "%passive%" OR s.name_loc0 LIKE "%effect%" OR s.name_loc0 LIKE "%improved%" OR s.name_loc0 LIKE "%prototype%" OR -- can probably be extended + (s.id NOT IN (47241, 59879, 59671) AND s.baseLevel <= 1 AND s.reqclassMask = 0) OR -- can probably still be extended + (s.SpellFamilyId = 15 AND s.SpellFamilyFlags1 & 0x2000 AND s.SpellDescriptionVariableId <> 84) OR -- DK: Skill Coil + (s.SpellFamilyId = 10 AND s.SpellFamilyFlags2 & 0x1000000 AND s.attributes1 = 0) OR -- Paladin: Bacon of Light hmm.. Bacon.... :] + (s.SpellFamilyId = 6 AND s.SpellFamilyFlags3 & 0x4000) OR -- Priest: Lolwell Renew + (s.SpellFamilyId = 6 AND s.SpellFamilyFlags1 & 0x8000000 AND s.rank_loc0 <> "") OR -- Priest: Bling Bling + (s.SpellFamilyId = 8 AND s.attributes0 = 0x50 AND s.attributes1 & 0x400) OR -- Rogue: Intuition (dropped Talent..? looks nice though) + (s.SpellfamilyId = 11 AND s.SpellFamilyFlags1 & 3 AND s.attributes1 = 1024) OR -- Shaman: Lightning Overload procs + (s.attributes0 = 0x20000000 AND s.attributes3 = 0x10000000) -- Master Demonologist (FamilyId = 0) + )', CUSTOM_EXCLUDE_FOR_LISTVIEW); + + for ($i = 0; (1 << $i) < CLASS_MASK_ALL; $i++) + if ((1 << $i) & CLASS_MASK_ALL) + DB::Aowow()->query( + 'UPDATE ?_spell s, dbc_skillline sl, dbc_skillraceclassinfo srci + SET s.reqClassMask = srci.classMask + WHERE s.typeCat IN (-2, 7) AND (s.attributes0 & 0x80) = 0 AND s.skillLine1 = srci.skillLine AND sl.categoryId = 7 AND + srci.skillline <> 769 AND srci.skillline = sl.id AND srci.flags & 0x90 AND srci.classMask & ?d', + 1 << $i); + + // secondary Skills (9) + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = 9 WHERE s.typeCat = 0 AND (s.skillLine1 IN (?a) OR (s.skillLine1 > 0 AND s.skillLine2OrMask IN (?a)))', SKILLS_TRADE_SECONDARY, SKILLS_TRADE_SECONDARY); + + // primary Skills (11) + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = 11 WHERE s.typeCat = 0 AND s.skillLine1 IN (?a)', SKILLS_TRADE_PRIMARY); + + // npc spells (-8) (run as last! .. missing from npc_scripts? "enum Spells { \s+(\w\d_)+\s+=\s(\d+) }" and "#define SPELL_(\d\w_)+\s+(\d+)") // RAID_MODE(1, 2[, 3, 4]) - macro still not considered + $world = DB::World()->selectCol( + 'SELECT ss.`action_param1` FROM smart_scripts ss WHERE ss.`action_type` IN (?a) UNION + SELECT cts.`Spell` FROM creature_template_spell cts', + [SAI_ACTION_CAST, SAI_ACTION_ADD_AURA, SAI_ACTION_SELF_CAST, SAI_ACTION_CROSS_CAST] + ); + + $auras = DB::World()->selectCol('SELECT `entry` AS ARRAY_KEY, cta.`auras` FROM creature_template_addon cta WHERE `auras` <> ""'); + foreach ($auras as $e => $aur) + { + // people keep trying to seperate auras with commas + $a = preg_replace('/[^\d ]/', ' ', $aur, -1, $nErrors); + if ($nErrors > 0) + CLI::write('[spell] creature_template_addon entry #' . CLI::bold($e) . ' has invalid chars in auras string "'. CLI::bold($aur).'"', CLI::LOG_WARN); + + $world = array_merge($world, array_filter(explode(' ', $a))); + } + + DB::Aowow()->query('UPDATE ?_spell s SET s.typeCat = -8 WHERE s.typeCat = 0 AND s.id IN (?a)', $world); + + + /**********/ + /* Glyphs */ + /**********/ + + CLI::write('[spell] - fixing glyph data'); + + // glyphSpell => affectedSpell + $glyphAffects = array( + 63959 => 50842, // Pestilence + 58723 => 55090, // Scourge Strike + 58721 => 46584, // Raise Dead + 58711 => 52375, // Death Coil + 54857 => 33876, // Mangle (Cat) + 56881 => 13165, // Aspect of the Hawk + 56598 => 27101, // Conjure Mana Gem (Rank 5) + 63871 => 1038, // Hand of Salvation + 55003 => 53407, // Judgement of Justice + 63873 => 47788, // Guardian Spirit + 58258 => 2983, // Sprint + 55535 => 52127, // Water Shield + 55558 => 16190, // Mana Tide Totem + 56302 => 697, // Summon Voidwalker + 56299 => 712, // Summon Succubus + 58272 => 126, // Summon Eye of Kilrogg + 56292 => 688, // Summon Imp + 56286 => 691, // Summon Felhunter + 56285 => 30146, // Summon Felguard + 58275 => 29893, // Ritual of Souls + 63941 => 1454, // Life Tap + 56289 => 5699, // Create Healthstone + 56297 => 693, // Create Soulstone + 58271 => 1120, // Drain Soul + 58281 => 34428, // Victory Rush + 58397 => 23922, // Shield Slam + 63949 => 50720 // Vigilance + ); + + $queryIcons = + 'SELECT s.id, s.name_loc0, s.skillLine1 as skill, ic.id as icon, s.typeCat * s.typeCat AS prio + FROM ?_spell s + LEFT JOIN dbc_spellicon si ON s.iconIdBak = si.id + LEFT JOIN ?_icons ic ON ic.name = LOWER(SUBSTRING_INDEX(si.iconPath, "\\\\", -1)) + WHERE [WHERE] AND (s.cuFlags & ?d) = 0 AND s.typeCat IN (0, 7, -2) -- not triggered; class spells first, talents second, unk last + ORDER BY prio DESC'; + + $effects = DB::Aowow()->select( + 'SELECT s2.id AS ARRAY_KEY, + s1.id, + s1.name_loc0, + s1.spellFamilyId, + s1.spellFamilyFlags1, s1.spellFamilyFlags2, s1.spellFamilyFlags3, + s1.effect1Id, s1.effect2Id, s1.effect3Id, + s1.effect1SpellClassMaskA, s1.effect1SpellClassMaskB, s1.effect1SpellClassMaskC, + s1.effect2SpellClassMaskA, s1.effect2SpellClassMaskB, s1.effect2SpellClassMaskC, + s1.effect3SpellClassMaskA, s1.effect3SpellClassMaskB, s1.effect3SpellClassMaskC + FROM dbc_glyphproperties gp + JOIN ?_spell s1 ON s1.id = gp.spellId + JOIN ?_spell s2 ON s2.effect1MiscValue = gp.id AND s2.effect1Id = 74 + WHERE gp.typeFlags IN (0, 1)' // AND s2.id In (58271, 56297, 56289, 63941, 58275) + ); + + foreach ($effects as $applyId => $glyphEffect) + { + $l = [null, 'A', 'B', 'C']; + $i = 0; + $icons = []; + $fam = $glyphEffect['spellFamilyId']; + + // first: manuall replace + if ($applyId == 57144) // has no skillLine.. :/ + { + DB::Aowow()->query('UPDATE ?_spell s, ?_icons ic SET s.skillLine1 = ?d, s.iconIdAlt = ic.id WHERE s.id = ?d AND ic.name = ?', 253, 57144, 'ability_poisonsting'); + continue; + } + + // second: search by name and family equality + if (!$icons) + { + $search = !empty($glyphAffects[$applyId]) ? $glyphAffects[$applyId] : str_replace('Glyph of ', '', $glyphEffect['name_loc0']); + if (is_int($search)) + $where = "?d AND s.id = ?d"; + else + $where = "s.SpellFamilyId = ?d AND s.name_loc0 LIKE ?"; + + $qry = str_replace('[WHERE]', $where, $queryIcons); + $icons = DB::Aowow()->selectRow($qry, $fam ?: 1, $search, SPELL_CU_TRIGGERED); + } + + // third: match by SpellFamily affect mask + while (empty($icons) && $i < 3) + { + $i++; + $m1 = $glyphEffect['effect1SpellClassMask'.$l[$i]]; + $m2 = $glyphEffect['effect2SpellClassMask'.$l[$i]]; + $m3 = $glyphEffect['effect3SpellClassMask'.$l[$i]]; + + if ($glyphEffect['effect'.$i.'Id'] != 6 || (!$m1 && !$m2 && !$m3)) + continue; + + $where = "s.SpellFamilyId = ?d AND (s.SpellFamilyFlags1 & ?d OR s.SpellFamilyFlags2 & ?d OR s.SpellFamilyFlags3 & ?d)"; + + $icons = DB::Aowow()->selectRow(str_replace('[WHERE]', $where, $queryIcons), $fam, $m1, $m2, $m3, SPELL_CU_TRIGGERED); + } + + if ($icons) + DB::Aowow()->query('UPDATE ?_spell s SET s.skillLine1 = ?d, s.iconIdAlt = ?d WHERE s.id = ?d', $icons['skill'], $icons['icon'], $applyId); + else + CLI::write('[spell] '.str_pad('['.$glyphEffect['id'].']', 8).'could not match '.CLI::bold($glyphEffect['name_loc0']).' with affected spells', CLI::LOG_WARN); + } + + $this->reapplyCCFlags('spell', Type::SPELL); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/spelldifficulty.func.php b/setup/tools/sqlgen/spelldifficulty.ss.php similarity index 73% rename from setup/tools/sqlgen/spelldifficulty.func.php rename to setup/tools/sqlgen/spelldifficulty.ss.php index 8e825f449..5ce69f500 100644 --- a/setup/tools/sqlgen/spelldifficulty.func.php +++ b/setup/tools/sqlgen/spelldifficulty.ss.php @@ -7,16 +7,17 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { - protected $command = 'spelldifficulty'; + protected $info = array( + 'spelldifficulty' => [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Spell from dbc and world db.'] + ); - protected $tblDependencyTC = ['spelldifficulty_dbc']; protected $dbcSourceFiles = ['spelldifficulty']; + protected $worldDependency = ['spelldifficulty_dbc']; public function generate(array $ids = []) : bool { - // has no unique keys.. DB::Aowow()->query('TRUNCATE TABLE ?_spelldifficulty'); DB::Aowow()->query('INSERT INTO ?_spelldifficulty SELECT GREATEST(`normal10`, 0), GREATEST(`normal25`, 0), GREATEST(`heroic10`, 0), GREATEST(`heroic25`, 0) FROM dbc_spelldifficulty'); diff --git a/setup/tools/sqlgen/spellfocusobject.func.php b/setup/tools/sqlgen/spellfocusobject.ss.php similarity index 80% rename from setup/tools/sqlgen/spellfocusobject.func.php rename to setup/tools/sqlgen/spellfocusobject.ss.php index f34f8704c..a6dd55d99 100644 --- a/setup/tools/sqlgen/spellfocusobject.func.php +++ b/setup/tools/sqlgen/spellfocusobject.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/spelloverride.func.php b/setup/tools/sqlgen/spelloverride.ss.php similarity index 80% rename from setup/tools/sqlgen/spelloverride.func.php rename to setup/tools/sqlgen/spelloverride.ss.php index 42ee3461b..5de13b003 100644 --- a/setup/tools/sqlgen/spelloverride.func.php +++ b/setup/tools/sqlgen/spelloverride.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/spellrange.func.php b/setup/tools/sqlgen/spellrange.ss.php similarity index 79% rename from setup/tools/sqlgen/spellrange.func.php rename to setup/tools/sqlgen/spellrange.ss.php index e9a32b29c..3e0bd3f55 100644 --- a/setup/tools/sqlgen/spellrange.func.php +++ b/setup/tools/sqlgen/spellrange.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/spellvariables.func.php b/setup/tools/sqlgen/spellvariables.ss.php similarity index 80% rename from setup/tools/sqlgen/spellvariables.func.php rename to setup/tools/sqlgen/spellvariables.ss.php index 12d45aa32..ff76d6c96 100644 --- a/setup/tools/sqlgen/spellvariables.func.php +++ b/setup/tools/sqlgen/spellvariables.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/talents.func.php b/setup/tools/sqlgen/talents.func.php deleted file mode 100644 index f9940ffee..000000000 --- a/setup/tools/sqlgen/talents.func.php +++ /dev/null @@ -1,44 +0,0 @@ - hunter pets - for ($i = 1; $i < 6; $i++) - DB::Aowow()->query(' - REPLACE INTO - ?_talents - SELECT - t.id, - IF(tt.classMask <> 0, LOG(2, tt.classMask) + 1, 0), - tt.creatureFamilyMask, - IF(tt.creaturefamilyMask <> 0, LOG(2, tt.creaturefamilyMask), tt.tabNumber), - t.row, - t.column, - t.rank?d, - ?d - FROM - dbc_talenttab tt - JOIN - dbc_talent t ON tt.id = t.tabId - WHERE - t.rank?d <> 0 - ', $i, $i, $i); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/talents.ss.php b/setup/tools/sqlgen/talents.ss.php new file mode 100644 index 000000000..49caec0d2 --- /dev/null +++ b/setup/tools/sqlgen/talents.ss.php @@ -0,0 +1,44 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Profile from dbc.'] + ); + + protected $dbcSourceFiles = ['talent', 'talenttab']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_talents'); + + // class: 0 => hunter pets + for ($i = 1; $i < 6; $i++) + DB::Aowow()->query( + 'INSERT INTO ?_talents + SELECT t.id, + IF(tt.classMask <> 0, LOG(2, tt.classMask) + 1, 0), + tt.creatureFamilyMask, + IF(tt.creaturefamilyMask <> 0, LOG(2, tt.creaturefamilyMask), tt.tabNumber), + t.row, + t.column, + t.rank?d, + ?d + FROM dbc_talenttab tt + JOIN dbc_talent t ON tt.id = t.tabId + WHERE t.rank?d <> 0', + $i, $i, $i + ); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/taxi.func.php b/setup/tools/sqlgen/taxi.func.php deleted file mode 100644 index 788a0afed..000000000 --- a/setup/tools/sqlgen/taxi.func.php +++ /dev/null @@ -1,175 +0,0 @@ -query('REPLACE INTO ?_taxipath SELECT tp.id, tp.startNodeId, tp.endNodeId FROM dbc_taxipath tp WHERE tp.startNodeId > 0 AND tp.EndNodeId > 0'); - - // paths are monodirectional and thus exist twice for regular flight travel (which is bidirectional) - $paths = DB::Aowow()->select('SELECT id AS ARRAY_KEY, tp.* FROM ?_taxipath tp'); - foreach ($paths as $i => $p) - { - foreach ($paths as $j => $_) - { - if ($_['startNodeId'] == $p['endNodeId'] AND $_['endNodeId'] == $p['startNodeId']) - { - DB::Aowow()->query('DELETE FROM ?_taxipath WHERE id = ?d', $j); - unset($paths[$j]); - unset($paths[$i]); - break; - } - } - } - - - /*********/ - /* nodes */ - /*********/ - - // all sensible nodes - $fNodes = DB::Aowow()->select( - 'SELECT - tn.id, - tn.mapId, - 100 - ROUND((tn.posY - wma.right) * 100 / (wma.left - wma.right), 1) AS posX, - 100 - ROUND((tn.posX - wma.bottom) * 100 / (wma.top - wma.bottom), 1) AS poxY, - 1 AS type, - 0 AS typeId, - 1 AS reactA, - 1 AS reactH, - tn.name_loc0, tn.name_loc2, tn.name_loc3, tn.name_loc4, tn.name_loc6, tn.name_loc8, - tn.mapId AS origMap, - tn.posX AS origPosX, - tn.posY AS origPosY, - IF (tn.id NOT IN (15, 148, 225, 235) AND - ( - tn.id IN (64, 250) OR - ( - tn.name_loc0 NOT LIKE "%Transport%" AND - tn.name_loc0 NOT LIKE "%Quest%" AND - tn.name_loc0 NOT LIKE "%Start%" AND - tn.name_loc0 NOT LIKE "%End%" - ) - ), 0, 1) AS scripted - FROM - dbc_taxinodes tn - JOIN - dbc_worldmaparea wma ON ( tn.mapId = wma.mapId AND tn.posX BETWEEN wma.bottom AND wma.top AND tn.posY BETWEEN wma.right AND wma.left) - WHERE - wma.areaId = 0 AND - wma.mapId = tn.mapId - UNION - SELECT - tn.id, - wmt.targetMapId, - 100 - ROUND((tn.posY + wmt.offsetY - wma.right) * 100 / (wma.left - wma.right), 1) AS posX, - 100 - ROUND((tn.posX + wmt.offsetX - wma.bottom) * 100 / (wma.top - wma.bottom), 1) AS poxY, - 1 AS type, - 0 AS typeId, - 1 AS reactA, - 1 AS reactH, - tn.name_loc0, tn.name_loc2, tn.name_loc3, tn.name_loc4, tn.name_loc6, tn.name_loc8, - tn.mapId AS origMap, - tn.posX AS origPosX, - tn.posY AS origPosY, - IF (tn.name_loc0 NOT LIKE "%Transport%" AND tn.name_loc0 NOT LIKE "%Quest%" AND tn.name_loc0 NOT LIKE "%Start%" AND tn.name_loc0 NOT LIKE "%End%", - 0, - 1 - ) AS scripted - FROM - dbc_taxinodes tn - JOIN - dbc_worldmaptransforms wmt ON ( tn.mapId = wmt.sourceMapId AND tn.posX BETWEEN wmt.minX AND wmt.maxX AND tn.posY BETWEEN wmt.minY AND wmt.maxY) - JOIN - dbc_worldmaparea wma ON ( wmt.targetMapId = wma.mapId AND tn.posX + wmt.offsetX BETWEEN wma.bottom AND wma.top AND tn.posY + wmt.offsetY BETWEEN wma.right AND wma.left) - WHERE - wma.areaId = 0 AND - wmt.sourcemapId = tn.mapId' - ); - - // all available flightmaster - $fMaster = DB::World()->select( - 'SELECT ct.entry, ct.faction, c.map, c.position_x AS posX, c.position_y AS posY FROM creature_template ct JOIN creature c ON c.id = ct.entry WHERE ct.npcflag & ?d OR c.npcflag & ?d', - NPC_FLAG_FLIGHT_MASTER, NPC_FLAG_FLIGHT_MASTER - ); - - // assign nearest flightmaster to node - foreach ($fNodes as &$n) - { - foreach ($fMaster as &$c) - { - if ($c['map'] != $n['origMap']) - continue; - - $dist = pow($c['posX'] - $n['origPosX'], 2) + pow($c['posY'] - $n['origPosY'], 2); - if ($dist > 1000) - continue; - - if (!isset($n['dist']) || $n['dist'] < $dist) - { - $n['dist'] = $dist; - $n['typeId'] = $c['entry']; - $n['faction'] = $c['faction']; - } - } - } - - unset($n); - - // fetch reactions per faction - $factions = DB::Aowow()->query(' - SELECT - id AS ARRAY_KEY, - IF(enemyFactionId1 = 1 OR enemyFactionId2 = 1 OR enemyFactionId3 = 1 OR enemyFactionId4 = 1 OR hostileMask & 0x3, -1, 1) AS reactA, - IF(enemyFactionId1 = 2 OR enemyFactionId2 = 2 OR enemyFactionId3 = 2 OR enemyFactionId4 = 2 OR hostileMask & 0x5, -1, 1) AS reactH - FROM - dbc_factiontemplate - WHERE - id IN (?a)', - array_column($fNodes, 'faction') - ); - - foreach ($fNodes as $n) - { - // if (empty($n['faction'])) - // { - // CLI::write(' - ['.$n['id'].'] "'.$n['name_loc0'].'" has no NPC assigned ... skipping', CLI::LOG_WARN); - // continue; - // } - - if ($n['scripted'] || empty($n['faction'])) - $n['type'] = $n['typeId'] = 0; - else if (isset($factions[$n['faction']])) - { - $n['reactA'] = $factions[$n['faction']]['reactA']; - $n['reactH'] = $factions[$n['faction']]['reactH']; - } - - unset($n['faction'], $n['origMap'], $n['origPosX'], $n['origPosY'], $n['dist'], $n['scripted']); - - DB::Aowow()->query('REPLACE INTO ?_taxinodes VALUES (?a)', array_values($n)); - } - - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/taxi.ss.php b/setup/tools/sqlgen/taxi.ss.php new file mode 100644 index 000000000..6ac232b41 --- /dev/null +++ b/setup/tools/sqlgen/taxi.ss.php @@ -0,0 +1,147 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: NPC from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['taxipath', 'taxinodes', 'worldmaparea', 'worldmaptransforms', 'factiontemplate']; + protected $worldDependency = ['creature', 'creature_template']; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_taxipath'); + DB::Aowow()->query('TRUNCATE ?_taxinodes'); + + /*********/ + /* paths */ + /*********/ + + DB::Aowow()->query('INSERT INTO ?_taxipath SELECT tp.id, tp.startNodeId, tp.endNodeId FROM dbc_taxipath tp WHERE tp.startNodeId > 0 AND tp.EndNodeId > 0'); + + // paths are monodirectional and thus exist twice for regular flight travel (which is bidirectional) + $paths = DB::Aowow()->select('SELECT id AS ARRAY_KEY, tp.* FROM ?_taxipath tp'); + foreach ($paths as $i => $p) + { + foreach ($paths as $j => $_) + { + if ($_['startNodeId'] != $p['endNodeId'] || $_['endNodeId'] != $p['startNodeId']) + continue; + + DB::Aowow()->query('DELETE FROM ?_taxipath WHERE id = ?d', $j); + unset($paths[$j]); + unset($paths[$i]); + break; + } + } + + + /*********/ + /* nodes */ + /*********/ + + // all sensible nodes + $fNodes = DB::Aowow()->select( + 'SELECT tn.id, tn.mapId, + 100 - ROUND((tn.posY - wma.right) * 100 / (wma.left - wma.right), 1) AS posX, + 100 - ROUND((tn.posX - wma.bottom) * 100 / (wma.top - wma.bottom), 1) AS poxY, + 1 AS `type`, 0 AS `typeId`, 1 AS reactA, 1 AS reactH, + tn.name_loc0, tn.name_loc2, tn.name_loc3, tn.name_loc4, tn.name_loc6, tn.name_loc8, + tn.mapId AS origMap, tn.posX AS origPosX, tn.posY AS origPosY, + IF (tn.id NOT IN (15, 148, 225, 235) AND ( + tn.id IN (64, 250) OR ( + tn.name_loc0 NOT LIKE "%Transport%" AND tn.name_loc0 NOT LIKE "%Quest%" AND + tn.name_loc0 NOT LIKE "%Start%" AND tn.name_loc0 NOT LIKE "%End%" + ) + ), 0, 1) AS scripted + FROM dbc_taxinodes tn + JOIN dbc_worldmaparea wma ON ( tn.mapId = wma.mapId AND tn.posX BETWEEN wma.bottom AND wma.top AND tn.posY BETWEEN wma.right AND wma.left) + WHERE wma.areaId = 0 AND wma.mapId = tn.mapId + UNION + SELECT tn.id, wmt.targetMapId, + 100 - ROUND((tn.posY + wmt.offsetY - wma.right) * 100 / (wma.left - wma.right), 1) AS posX, + 100 - ROUND((tn.posX + wmt.offsetX - wma.bottom) * 100 / (wma.top - wma.bottom), 1) AS poxY, + 1 AS `type`, 0 AS `typeId`, 1 AS reactA, 1 AS reactH, + tn.name_loc0, tn.name_loc2, tn.name_loc3, tn.name_loc4, tn.name_loc6, tn.name_loc8, + tn.mapId AS origMap, tn.posX AS origPosX, tn.posY AS origPosY, + IF (tn.name_loc0 NOT LIKE "%Transport%" AND tn.name_loc0 NOT LIKE "%Quest%" AND tn.name_loc0 NOT LIKE "%Start%" AND tn.name_loc0 NOT LIKE "%End%", + 0, 1 ) AS scripted + FROM dbc_taxinodes tn + JOIN dbc_worldmaptransforms wmt ON ( tn.mapId = wmt.sourceMapId AND tn.posX BETWEEN wmt.minX AND wmt.maxX AND tn.posY BETWEEN wmt.minY AND wmt.maxY) + JOIN dbc_worldmaparea wma ON ( wmt.targetMapId = wma.mapId AND tn.posX + wmt.offsetX BETWEEN wma.bottom AND wma.top AND tn.posY + wmt.offsetY BETWEEN wma.right AND wma.left) + WHERE wma.areaId = 0 AND wmt.sourcemapId = tn.mapId' + ); + + // all available flightmaster + $fMaster = DB::World()->select( + 'SELECT ct.entry, ct.faction, c.map, c.position_x AS posX, c.position_y AS posY FROM creature_template ct JOIN creature c ON c.id = ct.entry WHERE ct.npcflag & ?d OR c.npcflag & ?d', + NPC_FLAG_FLIGHT_MASTER, NPC_FLAG_FLIGHT_MASTER + ); + + // assign nearest flightmaster to node + foreach ($fNodes as &$n) + { + foreach ($fMaster as &$c) + { + if ($c['map'] != $n['origMap']) + continue; + + $dist = pow($c['posX'] - $n['origPosX'], 2) + pow($c['posY'] - $n['origPosY'], 2); + if ($dist > 1000) + continue; + + if (!isset($n['dist']) || $n['dist'] < $dist) + { + $n['dist'] = $dist; + $n['typeId'] = $c['entry']; + $n['faction'] = $c['faction']; + } + } + } + + unset($n); + + // fetch reactions per faction + $factions = DB::Aowow()->query( + 'SELECT id AS ARRAY_KEY, + IF(enemyFactionId1 = 1 OR enemyFactionId2 = 1 OR enemyFactionId3 = 1 OR enemyFactionId4 = 1 OR hostileMask & 0x3, -1, 1) AS reactA, + IF(enemyFactionId1 = 2 OR enemyFactionId2 = 2 OR enemyFactionId3 = 2 OR enemyFactionId4 = 2 OR hostileMask & 0x5, -1, 1) AS reactH + FROM dbc_factiontemplate + WHERE id IN (?a)', + array_column($fNodes, 'faction')); + + foreach ($fNodes as $n) + { + // if (empty($n['faction'])) + // { + // CLI::write(' - ['.$n['id'].'] "'.$n['name_loc0'].'" has no NPC assigned ... skipping', CLI::LOG_WARN); + // continue; + // } + + if ($n['scripted'] || empty($n['faction'])) + $n['type'] = $n['typeId'] = 0; + else if (isset($factions[$n['faction']])) + { + $n['reactA'] = $factions[$n['faction']]['reactA']; + $n['reactH'] = $factions[$n['faction']]['reactH']; + } + + unset($n['faction'], $n['origMap'], $n['origPosX'], $n['origPosY'], $n['dist'], $n['scripted']); + + DB::Aowow()->query('INSERT INTO ?_taxinodes VALUES (?a)', array_values($n)); + } + + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/titles.func.php b/setup/tools/sqlgen/titles.func.php deleted file mode 100644 index 76dd28c9a..000000000 --- a/setup/tools/sqlgen/titles.func.php +++ /dev/null @@ -1,103 +0,0 @@ - 201, - 138 => 201, - 124 => 324, - 135 => 423, - 155 => 181, - 133 => 372, - 74 => 327, - 75 => 341, - 76 => 341, - 134 => 141, - 168 => 404 - ); - - public function generate(array $ids = []) : bool - { - $questQuery = ' - SELECT - qt.RewardTitle AS ARRAY_KEY, - qt.AllowableRaces, - IFNULL(ge.eventEntry, 0) AS eventEntry - FROM - quest_template qt - LEFT JOIN - game_event_seasonal_questrelation sq ON sq.questId = qt.ID - LEFT JOIN - game_event ge ON ge.eventEntry = sq.eventEntry - WHERE - qt.RewardTitle <> 0'; - - DB::Aowow()->query('REPLACE INTO ?_titles SELECT id, 0, 0, 0, 0, 0, 0, 0, bitIdx, male_loc0, male_loc2, male_loc3, male_loc4, male_loc6, male_loc8, female_loc0, female_loc2, female_loc3, female_loc4, female_loc6, female_loc8 FROM dbc_chartitles'); - - // hide unused titles - DB::Aowow()->query('UPDATE ?_titles SET cuFlags = ?d WHERE id BETWEEN 85 AND 123 AND id NOT IN (113, 120, 121, 122)', CUSTOM_EXCLUDE_FOR_LISTVIEW); - - // set expansion - DB::Aowow()->query('UPDATE ?_titles SET expansion = 2 WHERE id >= 72 AND id <> 80'); - DB::Aowow()->query('UPDATE ?_titles SET expansion = 1 WHERE id >= 42 AND id <> 46 AND expansion = 0'); - - // set category - DB::Aowow()->query('UPDATE ?_titles SET category = 1 WHERE id <= 28 OR id IN (42, 43, 44, 45, 47, 48, 62, 71, 72, 80, 82, 126, 127, 128, 157, 163, 167, 169, 177)'); - DB::Aowow()->query('UPDATE ?_titles SET category = 5 WHERE id BETWEEN 96 AND 109 OR id IN (83, 84)'); - DB::Aowow()->query('UPDATE ?_titles SET category = 2 WHERE id BETWEEN 144 AND 156 OR id IN (63, 77, 79, 113, 123, 130, 131, 132, 176)'); - DB::Aowow()->query('UPDATE ?_titles SET category = 6 WHERE id IN (46, 74, 75, 76, 124, 133, 134, 135, 137, 138, 155, 168)'); - DB::Aowow()->query('UPDATE ?_titles SET category = 4 WHERE id IN (81, 125)'); - DB::Aowow()->query('UPDATE ?_titles SET category = 3 WHERE id IN (53, 64, 120, 121, 122, 129, 139, 140, 141, 142) OR (id >= 158 AND category = 0)'); - - // update event - if ($assoc = DB::World()->selectCol('SELECT holiday AS ARRAY_KEY, eventEntry FROM game_event WHERE holiday IN (?a)', array_values($this->titleHoliday))) - foreach ($this->titleHoliday as $tId => $hId) - if (!empty($assoc[$hId])) - DB::Aowow()->query('UPDATE ?_titles SET eventId = ?d WHERE id = ?d', $assoc[$hId], $tId); - - // update side - $questInfo = DB::World()->select($questQuery); - $sideUpd = DB::World()->selectCol('SELECT IF(TitleA, TitleA, TitleH) AS ARRAY_KEY, BIT_OR(IF(TitleA, 1, 2)) AS side FROM achievement_reward WHERE (TitleA <> 0 AND TitleH = 0) OR (TitleH <> 0 AND TitleA = 0) GROUP BY ARRAY_KEY HAVING side <> 3'); - foreach ($questInfo as $tId => $data) - { - if ($data['eventEntry']) - DB::Aowow()->query('UPDATE ?_titles SET eventId = ?d WHERE id = ?d', $data['eventEntry'], $tId); - - $side = Game::sideByRaceMask($data['AllowableRaces']); - if ($side == 3) - continue; - - if (!isset($sideUpd[$tId])) - $sideUpd[$tId] = $side; - else - $sideUpd[$tId] |= $side; - } - foreach ($sideUpd as $tId => $side) - if ($side != 3) - DB::Aowow()->query("UPDATE ?_titles SET side = ?d WHERE id = ?d", $side, $tId); - - // update side - sourceless titles (maintain query order) - DB::Aowow()->query('UPDATE ?_titles SET side = 2 WHERE id <= 28 OR id IN (118, 119, 116, 117, 110, 127)'); - DB::Aowow()->query('UPDATE ?_titles SET side = 1 WHERE id <= 14 OR id IN (111, 115, 112, 114, 126)'); - - $this->reapplyCCFlags('titles', Type::TITLE); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/titles.ss.php b/setup/tools/sqlgen/titles.ss.php new file mode 100644 index 000000000..eed458a71 --- /dev/null +++ b/setup/tools/sqlgen/titles.ss.php @@ -0,0 +1,100 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles data for type: Title from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['chartitles']; + protected $worldDependency = ['quest_template', 'game_event_seasonal_questrelation', 'game_event', 'achievement_reward']; + + private $titleHoliday = array( + 137 => 201, + 138 => 201, + 124 => 324, + 135 => 423, + 155 => 181, + 133 => 372, + 74 => 327, + 75 => 341, + 76 => 341, + 134 => 141, + 168 => 404 + ); + + public function generate(array $ids = []) : bool + { + $questQuery = + 'SELECT qt.`RewardTitle` AS ARRAY_KEY, qt.`AllowableRaces`, IFNULL(ge.`eventEntry`, 0) AS `eventEntry` + FROM quest_template qt + LEFT JOIN game_event_seasonal_questrelation sq ON sq.`questId` = qt.`Id` + LEFT JOIN game_event ge ON ge.`eventEntry` = sq.`eventEntry` + WHERE qt.`RewardTitle` <> 0'; + + DB::Aowow()->query('TRUNCATE ?_titles'); + DB::Aowow()->query('INSERT INTO ?_titles SELECT `id`, 0, 0, 0, 0, 0, 0, 0, `bitIdx`, `male_loc0`, `male_loc2`, `male_loc3`, `male_loc4`, `male_loc6`, `male_loc8`, `female_loc0`, `female_loc2`, `female_loc3`, `female_loc4`, `female_loc6`, `female_loc8` FROM dbc_chartitles'); + + // hide unused titles + DB::Aowow()->query('UPDATE ?_titles SET `cuFlags` = ?d WHERE `id` BETWEEN 85 AND 123 AND `id` NOT IN (113, 120, 121, 122)', CUSTOM_EXCLUDE_FOR_LISTVIEW); + + // set expansion + DB::Aowow()->query('UPDATE ?_titles SET `expansion` = 2 WHERE `id` >= 72 AND `id` <> 80'); + DB::Aowow()->query('UPDATE ?_titles SET `expansion` = 1 WHERE `id` >= 42 AND `id` <> 46 AND `expansion` = 0'); + + // set category + DB::Aowow()->query('UPDATE ?_titles SET `category` = 1 WHERE `id` <= 28 OR `id` IN (42, 43, 44, 45, 47, 48, 62, 71, 72, 80, 82, 126, 127, 128, 157, 163, 167, 169, 177)'); + DB::Aowow()->query('UPDATE ?_titles SET `category` = 5 WHERE `id` BETWEEN 96 AND 109 OR `id` IN (83, 84)'); + DB::Aowow()->query('UPDATE ?_titles SET `category` = 2 WHERE `id` BETWEEN 144 AND 156 OR `id` IN (63, 77, 79, 113, 123, 130, 131, 132, 176)'); + DB::Aowow()->query('UPDATE ?_titles SET `category` = 6 WHERE `id` IN (46, 74, 75, 76, 124, 133, 134, 135, 137, 138, 155, 168)'); + DB::Aowow()->query('UPDATE ?_titles SET `category` = 4 WHERE `id` IN (81, 125)'); + DB::Aowow()->query('UPDATE ?_titles SET `category` = 3 WHERE `id` IN (53, 64, 120, 121, 122, 129, 139, 140, 141, 142) OR (`id` >= 158 AND `category` = 0)'); + + // update event + if ($assoc = DB::World()->selectCol('SELECT `holiday` AS ARRAY_KEY, `eventEntry` FROM game_event WHERE `holiday` IN (?a)', array_values($this->titleHoliday))) + foreach ($this->titleHoliday as $tId => $hId) + if (!empty($assoc[$hId])) + DB::Aowow()->query('UPDATE ?_titles SET `eventId` = ?d WHERE `id` = ?d', $assoc[$hId], $tId); + + // update side + $questInfo = DB::World()->select($questQuery); + $sideUpd = DB::World()->selectCol('SELECT IF(`TitleA`, `TitleA`, `TitleH`) AS ARRAY_KEY, BIT_OR(IF(`TitleA`, ?d, ?d)) AS `side` FROM achievement_reward WHERE (`TitleA` <> 0 AND `TitleH` = 0) OR (`TitleH` <> 0 AND `TitleA` = 0) GROUP BY ARRAY_KEY HAVING `side` <> ?d', + SIDE_ALLIANCE, SIDE_HORDE, SIDE_BOTH); + foreach ($questInfo as $tId => $data) + { + if ($data['eventEntry']) + DB::Aowow()->query('UPDATE ?_titles SET `eventId` = ?d WHERE `id` = ?d', $data['eventEntry'], $tId); + + $side = Game::sideByRaceMask($data['AllowableRaces']); + if ($side == SIDE_BOTH) + continue; + + if (!isset($sideUpd[$tId])) + $sideUpd[$tId] = $side; + else + $sideUpd[$tId] |= $side; + } + foreach ($sideUpd as $tId => $side) + if ($side != SIDE_BOTH) + DB::Aowow()->query("UPDATE ?_titles SET `side` = ?d WHERE `id` = ?d", $side, $tId); + + // update side - sourceless titles (maintain query order) + DB::Aowow()->query('UPDATE ?_titles SET `side` = ?d WHERE `id` <= 28 OR `id` IN (118, 119, 116, 117, 110, 127)', SIDE_HORDE); + DB::Aowow()->query('UPDATE ?_titles SET `side` = ?d WHERE `id` <= 14 OR `id` IN (111, 115, 112, 114, 126)', SIDE_ALLIANCE); + + $this->reapplyCCFlags('titles', Type::TITLE); + + return true; + } +}); + +?> diff --git a/setup/tools/sqlgen/totemcategory.func.php b/setup/tools/sqlgen/totemcategory.ss.php similarity index 79% rename from setup/tools/sqlgen/totemcategory.func.php rename to setup/tools/sqlgen/totemcategory.ss.php index edb30f511..e867089b2 100644 --- a/setup/tools/sqlgen/totemcategory.func.php +++ b/setup/tools/sqlgen/totemcategory.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup("sql", new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/worldmaparea.func.php b/setup/tools/sqlgen/worldmaparea.ss.php similarity index 79% rename from setup/tools/sqlgen/worldmaparea.func.php rename to setup/tools/sqlgen/worldmaparea.ss.php index ca74dda20..0a15073fa 100644 --- a/setup/tools/sqlgen/worldmaparea.func.php +++ b/setup/tools/sqlgen/worldmaparea.ss.php @@ -7,7 +7,7 @@ die('not in cli mode'); -SqlGen::register(new class extends SetupScript +CLISetup::registerSetup('sql', new class extends SetupScript { use TrDBCcopy; diff --git a/setup/tools/sqlgen/zones.func.php b/setup/tools/sqlgen/zones.func.php deleted file mode 100644 index 54707b1d5..000000000 --- a/setup/tools/sqlgen/zones.func.php +++ /dev/null @@ -1,224 +0,0 @@ -query('SELECT - a.id, - IFNULL(wmt.targetMapId, m.id) AS map, - m.id AS mapBak, - a.areaTable AS parentArea, - IFNULL(wmt.targetMapId, - IF(m.areaType = 1, 2, - IF(m.areaType = 2, 3, - IF(m.areaType = 4, 9, - IF(m.isBG = 1, 6, - IF(m.id = 571, 10, - IF(m.id = 530, 8, m.id))))))) AS category, - a.flags, - IF(a.mapId IN (13, 25, 37, 42, 169) OR - (a.mapId IN (0, 1, 530, 571) AND wma.id IS NULL) OR - a.areaTable <> 0 OR - (a.soundAmbience = 0 AND a.mapId IN (0, 1, 530, 571)), ?d, 0) AS cuFlags, - IF(a.flags & 0x01000000, 5, -- g_zone_territories - IF(m.isBG = 1, 4, - IF(m.areaType = 4, 4, - IF(a.flags & 0x00000800, 3, - IF(a.factionGroupMask = 6, 2, - IF(a.factionGroupMask > 0, LOG2(a.factionGroupMask) - 1, 2)))))) AS faction, - m.expansion, - IF(m.areaType = 0, 0, -- g_zone_instancetypes - IF(m.isBG = 1, 4, - IF(m.areaType = 4, 6, - IF(md.modeMask & 0xC, 8, - IF(md.minPl = 10 AND md.maxPL = 25, 7, - IF(m.areaType = 2, 3, - IF(m.areaType = 1 AND md.modeMask & 0x2, 5, 2))))))) AS `type`, - IF (md.minPl = 10 AND md.maxPl = 25, -2, - IFNULL(bm.maxPlayers, IFNULL(md.maxPl, m.maxPlayers))) AS maxPlayer, - 0 AS `itemLevelN`, -- - 0 AS `itemLevelH`, - 0 AS `levelReq`, - IFNULL(lfgIni.levelLFG, 0) AS `levelReqLFG`, - 0 AS `levelHeroic`, - IF(a.flags & 0x8, 1, - IFNULL(bm.minLevel, - IFNULL(lfgIni.levelMin, - IFNULL(lfgOpen.levelMin, 0)))) AS `levelMin`, - IF(a.flags & 0x8, ?d, - IFNULL(bm.maxLevel, - IFNULL(lfgIni.levelMax, - IFNULL(lfgOpen.levelMax, 0)))) AS `levelMax`, - "" AS `attunementsN`, - "" AS `attunementsH`, - m.parentMapId, -- IFNULL(pa.areaId, 0), - m.parentX, -- IFNULL(pa.posX, 0), - m.parentY, -- IFNULL(pa.posY, 0), - IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc0, m.name_loc0), - IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc2, m.name_loc2), - IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc3, m.name_loc3), - IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc4, m.name_loc4), - IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc6, m.name_loc6), - IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc8, m.name_loc8) - FROM - dbc_areatable a - JOIN - dbc_map m ON m.id = IF(a.id = 2159, 249, a.mapId) -- Zone: Onyxias Lair is linked to the wrong map - LEFT JOIN ( - SELECT mapId, BIT_OR(1 << difficulty) AS modeMask, MIN(nPlayer) AS minPl, MAX(nPlayer) AS maxPl FROM dbc_mapdifficulty GROUP BY mapId - ) md ON md.mapId = m.id - LEFT JOIN - dbc_lfgdungeons lfgOpen ON a.mapId IN (0, 1, 530, 571) AND a.name_loc0 LIKE CONCAT("%", lfgOpen.name_loc0) AND lfgOpen.type = 4 - LEFT JOIN ( - SELECT - mapId, - MIN(IF(targetLevelMin, targetLevelMin, levelMin)) AS levelMin, - MAX(IF(targetLevelMax, targetLevelMax, targetLevel)) AS levelMax, - MIN(IF(levelMin, levelMin, targetLevel)) AS levelLFG - FROM - dbc_lfgdungeons - WHERE - type NOT IN (4, 6) AND - groupId <> 11 - GROUP BY - mapId - ) lfgIni ON lfgIni.mapId = m.id - LEFT JOIN - dbc_battlemasterlist bm ON bm.mapId = a.mapId AND bm.moreMapId < 0 - LEFT JOIN - dbc_worldmaparea wma ON wma.areaId = a.id - LEFT JOIN - dbc_worldmaptransforms wmt ON - wmt.targetMapId <> wmt.sourceMapId AND - wma.mapId = wmt.sourceMapId AND - wma.left < wmt.maxY AND - wma.right > wmt.minY AND - wma.top < wmt.maxX AND - wma.bottom > wmt.minX - ', CUSTOM_EXCLUDE_FOR_LISTVIEW, MAX_LEVEL); - - foreach ($baseData as &$bd) - { - // usually parent = -1 means no parent but some maps have this touple set to 0 - if (!$bd['parentMapId'] && !$bd['parentX'] && !$bd['parentY']) - continue; - - if ($gPos = Game::worldPosToZonePos($bd['parentMapId'], $bd['parentY'], $bd['parentX'])) - { - $pos = Game::checkCoords($gPos); - $bd['parentMapId'] = $pos['areaId'] ?? $gPos[0]['areaId']; - $bd['parentX'] = $pos['posX'] ?? $gPos[0]['posX']; - $bd['parentY'] = $pos['posY'] ?? $gPos[0]['posY']; - continue; - } - - $bd['parentMapId'] = 0; - } - - DB::Aowow()->query('REPLACE INTO ?_zones VALUES (?a)', $baseData); - - // get requirements from world.access_requirement - $zoneReq = DB::World()->select(' - SELECT - mapId AS ARRAY_KEY, - MIN(level_min) AS reqLevel, - MAX(IF(difficulty > 0, level_min, 0)) AS heroicLevel, - MAX(IF(difficulty = 0, item_level, 0)) AS reqItemLevelN, - MAX(IF(difficulty > 0, item_level, 0)) AS reqItemLevelH, - CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty = 0 AND item, item, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty = 0 AND item2 AND item2 <> item, item2, NULL) SEPARATOR " ")) AS reqItemN, - CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty > 0 AND item, item, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty > 0 AND item2 AND item2 <> item, item2, NULL) SEPARATOR " ")) AS reqItemH, - CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty = 0 AND quest_done_A, quest_done_A, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty = 0 AND quest_done_H AND quest_done_H <> quest_done_A, quest_done_H, NULL) SEPARATOR " ")) AS reqQuestN, - CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty > 0 AND quest_done_A, quest_done_A, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty > 0 AND quest_done_H AND quest_done_H <> quest_done_A, quest_done_H, NULL) SEPARATOR " ")) AS reqQuestH, - CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty = 0 AND completed_achievement, completed_achievement, NULL) SEPARATOR " ")) AS reqAchievementN, - CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty > 0 AND completed_achievement, completed_achievement, NULL) SEPARATOR " ")) AS reqAchievementH - FROM - access_requirement - GROUP BY - mapId - '); - - $heroics = DB::Aowow()->selectCol('SELECT DISTINCT mapId FROM aowow_zones WHERE type IN (5, 8)'); - - foreach ($zoneReq as $mapId => $req) - { - $update = ['levelReq' => $req['reqLevel']]; - $aN = $aH = []; - - if ($req['heroicLevel'] && in_array($mapId, $heroics)) - $update['levelHeroic'] = $req['heroicLevel']; - - if ($req['reqItemLevelN']) - $update['itemLevelReqN'] = $req['reqItemLevelN']; - - if ($req['reqItemLevelH'] && $req['reqItemLevelH'] > $req['reqItemLevelN']) - $update['itemLevelReqH'] = $req['reqItemLevelH']; - - if ($req['reqItemN'] && ($entries = explode(' ', $req['reqItemN']))) - foreach ($entries as $_) - $aN[Type::ITEM][] = $_; - - if ($req['reqItemH'] && ($entries = explode(' ', $req['reqItemH']))) - if ($entries = array_diff($entries, $aN[Type::ITEM] ?? [])) - foreach ($entries as $_) - $aH[Type::ITEM][] = $_; - - if ($req['reqQuestN'] && ($entries = explode(' ', $req['reqQuestN']))) - foreach ($entries as $_) - $aN[Type::QUEST][] = $_; - - if ($req['reqQuestH'] && ($entries = explode(' ', $req['reqQuestH']))) - if ($entries = array_diff($entries, $aN[Type::QUEST] ?? [])) - foreach ($entries as $_) - $aH[Type::QUEST][] = $_; - - if ($req['reqAchievementN'] && ($entries = explode(' ', $req['reqAchievementN']))) - foreach ($entries as $_) - $aN[Type::ACHIEVEMENT][] = $_; - - if ($req['reqAchievementH'] && ($entries = explode(' ', $req['reqAchievementH']))) - if ($entries = array_diff($entries, $aN[Type::ACHIEVEMENT] ?? [])) - foreach ($entries as $_) - $aH[Type::ACHIEVEMENT][] = $_; - - if ($aN) - { - foreach ($aN as $type => $entries) - $aN[$type] = $type.':'.implode(' '.$type.':', $entries); - - $update['attunementsN'] = implode(' ', $aN); - } - - if ($aH) - { - foreach ($aH as $type => $entries) - $aH[$type] = $type.':'.implode(' '.$type.':', $entries); - - $update['attunementsH'] = implode(' ', $aH); - } - - DB::Aowow()->query('UPDATE ?_zones SET ?a WHERE mapId = ?d', $update, $mapId); - } - - $this->reapplyCCFlags('zones', Type::ZONE); - - return true; - } -}); - -?> diff --git a/setup/tools/sqlgen/zones.ss.php b/setup/tools/sqlgen/zones.ss.php new file mode 100644 index 000000000..010b1d599 --- /dev/null +++ b/setup/tools/sqlgen/zones.ss.php @@ -0,0 +1,185 @@ + [[], CLISetup::ARGV_PARAM, 'Compiles supplemental data for type: Zone from dbc and world db.'] + ); + + protected $dbcSourceFiles = ['worldmaptransforms', 'worldmaparea', 'map', 'mapdifficulty', 'areatable', 'lfgdungeons', 'battlemasterlist']; + protected $worldDependency = ['access_requirement']; + protected $setupAfter = [['dungeonmap', 'worldmaparea'], []]; + + public function generate(array $ids = []) : bool + { + DB::Aowow()->query('TRUNCATE ?_zones'); + + $baseData = DB::Aowow()->query( + 'SELECT a.id, + IFNULL(wmt.targetMapId, m.id) AS map, + m.id AS mapBak, + a.areaTable AS parentArea, + IFNULL(wmt.targetMapId, IF(m.areaType = 1, 2, IF(m.areaType = 2, 3, IF(m.areaType = 4, 9, IF(m.isBG = 1, 6, IF(m.id = 571, 10, IF(m.id = 530, 8, m.id))))))) AS category, + a.flags, + IF(a.mapId IN (13, 25, 37, 42, 169) OR (a.mapId IN (0, 1, 530, 571) AND wma.id IS NULL) OR a.areaTable <> 0 OR (a.soundAmbience = 0 AND a.mapId IN (0, 1, 530, 571)), ?d, 0) AS cuFlags, + IF(a.flags & 0x01000000, 5, IF(m.isBG = 1, 4, IF(m.areaType = 4, 4, IF(a.flags & 0x00000800, 3, IF(a.factionGroupMask = 6, 2, IF(a.factionGroupMask > 0, LOG2(a.factionGroupMask) - 1, 2)))))) AS faction, -- g_zone_territories + m.expansion, + IF(m.areaType = 0, 0, IF(m.isBG = 1, 4, IF(m.areaType = 4, 6, IF(md.modeMask & 0xC, 8, IF(md.minPl = 10 AND md.maxPL = 25, 7, IF(m.areaType = 2, 3, IF(m.areaType = 1 AND md.modeMask & 0x2, 5, 2))))))) AS `type`, -- g_zone_instancetypes + IF (md.minPl = 10 AND md.maxPl = 25, -2, IFNULL(bm.maxPlayers, IFNULL(md.maxPl, m.maxPlayers))) AS maxPlayer, + 0 AS `itemLevelN`, + 0 AS `itemLevelH`, + 0 AS `levelReq`, + IFNULL(lfgIni.levelLFG, 0) AS `levelReqLFG`, + 0 AS `levelHeroic`, + IF(a.flags & 0x8, 1, IFNULL(bm.minLevel, IFNULL(lfgIni.levelMin, IFNULL(lfgOpen.levelMin, 0)))) AS `levelMin`, + IF(a.flags & 0x8, ?d, IFNULL(bm.maxLevel, IFNULL(lfgIni.levelMax, IFNULL(lfgOpen.levelMax, 0)))) AS `levelMax`, + "" AS `attunementsN`, + "" AS `attunementsH`, + m.parentMapId, -- IFNULL(pa.areaId, 0), + m.parentX, -- IFNULL(pa.posX, 0), + m.parentY, -- IFNULL(pa.posY, 0), + IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc0, m.name_loc0), + IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc2, m.name_loc2), + IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc3, m.name_loc3), + IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc4, m.name_loc4), + IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc6, m.name_loc6), + IF(wma.id IS NULL OR m.areaType = 0 OR a.mapId IN (269, 560) OR a.areaTable, a.name_loc8, m.name_loc8) + FROM dbc_areatable a + JOIN dbc_map m ON m.id = IF(a.id = 2159, 249, a.mapId) -- Zone: Onyxias Lair is linked to the wrong map + LEFT JOIN (SELECT mapId, BIT_OR(1 << difficulty) AS modeMask, MIN(nPlayer) AS minPl, MAX(nPlayer) AS maxPl FROM dbc_mapdifficulty GROUP BY mapId) md ON md.mapId = m.id + LEFT JOIN dbc_lfgdungeons lfgOpen ON a.mapId IN (0, 1, 530, 571) AND a.name_loc0 LIKE CONCAT("%", lfgOpen.name_loc0) AND lfgOpen.type = 4 + LEFT JOIN (SELECT mapId, + MIN(IF(targetLevelMin, targetLevelMin, levelMin)) AS levelMin, + MAX(IF(targetLevelMax, targetLevelMax, targetLevel)) AS levelMax, + MIN(IF(levelMin, levelMin, targetLevel)) AS levelLFG + FROM dbc_lfgdungeons + WHERE type NOT IN (4, 6) AND groupId <> 11 + GROUP BY mapId) lfgIni ON lfgIni.mapId = m.id + LEFT JOIN dbc_battlemasterlist bm ON bm.mapId = a.mapId AND bm.moreMapId < 0 + LEFT JOIN dbc_worldmaparea wma ON wma.areaId = a.id + LEFT JOIN dbc_worldmaptransforms wmt ON wmt.targetMapId <> wmt.sourceMapId AND wma.mapId = wmt.sourceMapId AND + wma.left < wmt.maxY AND wma.right > wmt.minY AND + wma.top < wmt.maxX AND wma.bottom > wmt.minX', + CUSTOM_EXCLUDE_FOR_LISTVIEW, MAX_LEVEL + ); + + foreach ($baseData as &$bd) + { + // usually parent = -1 means no parent but some maps have this touple set to 0 + if (!$bd['parentMapId'] && !$bd['parentX'] && !$bd['parentY']) + continue; + + if ($gPos = Game::worldPosToZonePos($bd['parentMapId'], $bd['parentY'], $bd['parentX'])) + { + $pos = Game::checkCoords($gPos); + $bd['parentMapId'] = $pos['areaId'] ?? $gPos[0]['areaId']; + $bd['parentX'] = $pos['posX'] ?? $gPos[0]['posX']; + $bd['parentY'] = $pos['posY'] ?? $gPos[0]['posY']; + continue; + } + + $bd['parentMapId'] = 0; + } + + DB::Aowow()->query('INSERT INTO ?_zones VALUES (?a)', $baseData); + + // get requirements from world.access_requirement + $zoneReq = DB::World()->select( + 'SELECT mapId AS ARRAY_KEY, + MIN(level_min) AS reqLevel, + MAX(IF(difficulty > 0, level_min, 0)) AS heroicLevel, + MAX(IF(difficulty = 0, item_level, 0)) AS reqItemLevelN, + MAX(IF(difficulty > 0, item_level, 0)) AS reqItemLevelH, + CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty = 0 AND item, item, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty = 0 AND item2 AND item2 <> item, item2, NULL) SEPARATOR " ")) AS reqItemN, + CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty > 0 AND item, item, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty > 0 AND item2 AND item2 <> item, item2, NULL) SEPARATOR " ")) AS reqItemH, + CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty = 0 AND quest_done_A, quest_done_A, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty = 0 AND quest_done_H AND quest_done_H <> quest_done_A, quest_done_H, NULL) SEPARATOR " ")) AS reqQuestN, + CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty > 0 AND quest_done_A, quest_done_A, NULL) SEPARATOR " "), GROUP_CONCAT(IF(difficulty > 0 AND quest_done_H AND quest_done_H <> quest_done_A, quest_done_H, NULL) SEPARATOR " ")) AS reqQuestH, + CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty = 0 AND completed_achievement, completed_achievement, NULL) SEPARATOR " ")) AS reqAchievementN, + CONCAT_WS(" ", GROUP_CONCAT(IF(difficulty > 0 AND completed_achievement, completed_achievement, NULL) SEPARATOR " ")) AS reqAchievementH + FROM access_requirement + GROUP BY mapId' + ); + + $heroics = DB::Aowow()->selectCol('SELECT DISTINCT mapId FROM ?_zones WHERE type IN (5, 8)'); + + foreach ($zoneReq as $mapId => $req) + { + $update = ['levelReq' => $req['reqLevel']]; + $aN = $aH = []; + + if ($req['heroicLevel'] && in_array($mapId, $heroics)) + $update['levelHeroic'] = $req['heroicLevel']; + + if ($req['reqItemLevelN']) + $update['itemLevelReqN'] = $req['reqItemLevelN']; + + if ($req['reqItemLevelH'] && $req['reqItemLevelH'] > $req['reqItemLevelN']) + $update['itemLevelReqH'] = $req['reqItemLevelH']; + + if ($req['reqItemN'] && ($entries = explode(' ', $req['reqItemN']))) + foreach ($entries as $_) + $aN[Type::ITEM][] = $_; + + if ($req['reqItemH'] && ($entries = explode(' ', $req['reqItemH']))) + if ($entries = array_diff($entries, $aN[Type::ITEM] ?? [])) + foreach ($entries as $_) + $aH[Type::ITEM][] = $_; + + if ($req['reqQuestN'] && ($entries = explode(' ', $req['reqQuestN']))) + foreach ($entries as $_) + $aN[Type::QUEST][] = $_; + + if ($req['reqQuestH'] && ($entries = explode(' ', $req['reqQuestH']))) + if ($entries = array_diff($entries, $aN[Type::QUEST] ?? [])) + foreach ($entries as $_) + $aH[Type::QUEST][] = $_; + + if ($req['reqAchievementN'] && ($entries = explode(' ', $req['reqAchievementN']))) + foreach ($entries as $_) + $aN[Type::ACHIEVEMENT][] = $_; + + if ($req['reqAchievementH'] && ($entries = explode(' ', $req['reqAchievementH']))) + if ($entries = array_diff($entries, $aN[Type::ACHIEVEMENT] ?? [])) + foreach ($entries as $_) + $aH[Type::ACHIEVEMENT][] = $_; + + if ($aN) + { + foreach ($aN as $type => $entries) + $aN[$type] = $type.':'.implode(' '.$type.':', $entries); + + $update['attunementsN'] = implode(' ', $aN); + } + + if ($aH) + { + foreach ($aH as $type => $entries) + $aH[$type] = $type.':'.implode(' '.$type.':', $entries); + + $update['attunementsH'] = implode(' ', $aH); + } + + DB::Aowow()->query('UPDATE ?_zones SET ?a WHERE mapId = ?d', $update, $mapId); + } + + $this->reapplyCCFlags('zones', Type::ZONE); + + return true; + } +}); + +?> diff --git a/setup/tools/utilityScript.class.php b/setup/tools/utilityScript.class.php new file mode 100644 index 000000000..f77354ec8 --- /dev/null +++ b/setup/tools/utilityScript.class.php @@ -0,0 +1,85 @@ +inited) + return false; + + // link to my subscripts + foreach (CLISetup::getSubScripts($usName) as $cmd => [, $scriptRef]) + $this->generators[$cmd] = $scriptRef; + + return true; + } +} + +abstract class UtilityScript +{ + public $argvOpts = []; + public $argvFlags = 0x0; + public $optGroup = -1; + public $childArgs = []; + public $followupFn = ''; + + public const COMMAND = ''; + public const DESCRIPTION = ''; + public const APPENDIX = ''; + public const PROMPT = ''; + public const NOTE_START = ''; + public const NOTE_ERROR = ''; + public const NOTE_END_OK = ''; + public const NOTE_END_FAIL = ''; + + public const REQUIRED_DB = []; + + public const USE_CLI_ARGS = false; + public const LOCK_SITE = CLISetup::LOCK_OFF; + + /* + actual UtilityScript functionality + $args[4] + variable use parameters for passing data to followup UtilityScripts + return + script success + + */ + abstract public function run(&$args) : bool; + + /* + implement help output here. + return + true - Help has been provided. Do not process further. + false - Fall back to help of parent container. + */ + public function writeCLIHelp() : bool + { + return false; + } + + /* + implement tests for script success here. + $error + list of error messages to display + return + test success + */ + public function test(?array &$error = []) : bool + { + return true; + } +} + +?> diff --git a/static/js/locale_dede.js b/static/js/locale_dede.js index e00e27eb4..6a0ac8eb9 100644 --- a/static/js/locale_dede.js +++ b/static/js/locale_dede.js @@ -2133,44 +2133,7 @@ var g_zones = { 4987: 'Das Rubinsanktum' }; -var g_zone_areas = { - 206: ['Vorbereitung der Norndir', 'Aufstieg der Drachenschinder', 'Tyrs Terrasse'], - 209: ['Der Hof', 'Speisesaal', 'Die Verwaiste Höhle', 'Das Tiefere Observatorium', 'Das Obere Observatorium', 'Lord Godfreys Kammer', 'Der Wehrgang'], - 719: ['Der Teich von Ask\'Ar', 'Mondschreinsanktum', 'Der Vergessene Teich'], - 721: ['Die Halle der Zahnräder', 'Der Schlafsaal', 'Startrampe', 'Tüftlerhof'], - 796: ['Friedhof', 'Bibliothek', 'Waffenkammer', 'Kathedrale'], - 1196: ['Untere Spitze', 'Obere Spitze'], - 1337: ['Halle der Bewahrer', 'Khaz\'goroths Sitz'], - 1581: ['Die Todesminen', 'Eiserne Bucht'], - 1583: ['Tazz\'Alaor', 'Listspinnertunnel', 'Hordemar', 'Schwarzfausthalle', 'Drachenspitzhalle', 'Der Krähenhorst', 'Schwarzfelsstadion'], - 1584: ['Gefängnisblock', 'Die Schattenschmiede'], - 2017: ['Kreuzzüglerplatz', 'Der Spießrutenlauf'], - 2057: ['Das Reliquiarium', 'Kammer der Beschwörung', 'Das Arbeitszimmer des Direktors', 'Familiengruft der Barovs'], - 2100: ['Höhlen von Maraudon', 'Zaetars Grab'], - 2557: ['Gordokhallen', 'Hauptstadtgärten', 'Hof der Hochgeborenen', 'Das Gefängnis von Immol\'thar', 'Wucherborkenviertel', 'Der Schrein von Eldretharr'], - 2677: ['Garnison des Drachenmals', 'Hallen des Zwists', 'Die Blutroten Labore', 'Nefarians Unterschlupf'], - 3428: ['Untergrund des Schwarmbaus', 'Die Tempeltore', 'Höhle von C\'Thun'], - 3457: ['Bedienstetenunterkünfte', 'Obere Nobelställe', 'Der Bankettsaal', 'Die Gästezimmer', 'Balkon des Opernsaals', 'Die Terrasse des Meisters', 'Untere Eingestürzte Treppe', 'Obere Eingestürzte Treppe', 'Die Menagerie', 'Bibliothek des Wächters', 'Das Warenlager', 'Obere Bibliothek', 'Die Himmelswacht', 'Halle der Spiele', 'Medivhs Gemächer', 'Die Energiekammer', 'Netherraum'], - 3790: ['Hallen des Jenseits', 'Brücke der Seelen'], - 3791: ['Sethekkversteck', 'Hallen der Trauer'], - 3959: ['Ausbildungsgelände der Illidari', 'Kanäle von Karabor', 'Zuflucht der Schatten', 'Hallen der Pein', 'Blutschattens Wacht', 'Hof der Irdischen Gelüste', 'Kommandoraum', 'Tempelspitze'], - 3456: ['Das Konstruktviertel', 'Das Arachnidenviertel', 'Das Militärviertel', 'Das Seuchenviertel', 'Übersicht', 'Frostwyrmhort'], - 3715: ['Die Dampfkammer', 'Die Kühlteiche'], - 3848: ['Stasisblock: Trion', 'Stasisblock: Maximus', 'Eindämmungskern'], - 3849: ['Die Mechanar', 'Berechnungskammer'], - 4075: ['Sonnenbrunnenplateau', 'Schrein der Finsternis'], - 4100: ['Außerhalb von Stratholme', 'Stratholme'], - 4131: ['Zuflucht des Großmagisters', 'Beobachtungsplatz'], - 4196: ['Die Vorhallen von Drak\'Tharon', 'Aussichtspunkt von Drak\'Tharon'], - 4228: ['Band der Varianz', 'Band der Akzeleration', 'Band der Transmutation', 'Band der Angleichung'], - 4272: ['Die unnachgiebige Garnison', 'Straße der Schöpfer'], - 4273: ['Der große Vorstoß', 'Die Vorkammer von Ulduar', 'Das innere Sanktum von Ulduar', 'Das Gefängnis von Yogg-Saron', 'Der Funke der Imagination', 'Das Gedankenauge'], - 4277: ['Die Brutgrube', 'Hadronox\' Hort', 'Das vergoldete Tor'], - 4395: ['Dalaran', 'Die Schattenseite'], - 4494: ['Ahn\'kahet', '2. Stockwerk'], - 4722: ['Kolosseum der Kreuzfahrer', 'Die eisigen Tiefen'], - 4812: ['Die untere Zitadelle', 'Das Schädelbollwerk', 'Dom des Todbringers', 'Hort der Frostkönigin', 'Der obere Bereich', 'Königliche Quartiere', 'Der Frostthron', 'Frostgram'] -}; +var g_zone_areas = {}; var g_zone_categories = { 0: 'Östliche Königreiche', diff --git a/static/js/locale_enus.js b/static/js/locale_enus.js index 57763466c..bcc213c26 100644 --- a/static/js/locale_enus.js +++ b/static/js/locale_enus.js @@ -2181,44 +2181,7 @@ var g_zones = { 4987: 'The Ruby Sanctum' }; -var g_zone_areas = { - 206: ['Norndir Preparation', 'Dragonflayer Ascent', 'Tyr\'s Terrace'], - 209: ['The Courtyard', 'Dining Hall', 'The Vacant Den', 'Lower Observatory', 'Upper Observatory', 'Lord Godfrey\'s Chamber', 'The Wall Walk'], - 719: ['The Pool of Ask\'Ar', 'Moonshrine Sanctum', 'The Forgotten Pool'], - 721: ['The Hall of Gears', 'The Dormitory', 'Launch Bay', 'Tinkers\' Court'], - 796: ['Graveyard', 'Library', 'Armory', 'Cathedral'], - 1196: ['Lower Pinnacle', 'Upper Pinnacle'], - 1337: ['Hall of the Keepers', 'Khaz\'Goroth\'s Seat'], - 1581: ['The Deadmines', 'Ironclad Cove'], - 1583: ['Tazz\'Alaor', 'Skitterweb Tunnels', 'Hordemar City', 'Hall of Blackhand', 'Dragonspire Hall', 'The Rookery', 'Blackrock Stadium'], - 1584: ['Detention Block', 'Shadowforge City'], - 2017: ['Crusader\'s Square', 'The Gauntlet'], - 2057: ['The Reliquary', 'Chamber of Summoning', 'The Headmaster\'s Study', 'Barov Family Vault'], - 2100: ['Caverns of Maraudon', 'Zaetar\'s Grave'], - 2557: ['Gordok Commons', 'Capital Gardens', 'Court of the Highborne', 'Prison of Immol\'Thar', 'Warpwood Quarter', 'The Shrine of Eldretharr'], - 2677: ['Dragonmaw Garrison', 'Halls of Strife', 'Crimson Laboratories', 'Nefarian\'s Lair'], - 3428: ['The Hive Undergrounds', 'The Temple Gates', 'Vault of C\'Thun'], - 3456: ['The Construct Quarter', 'The Arachnid Quarter', 'The Military Quarter', 'The Plague Quarter', 'Overview', 'Frostwyrm Lair'], - 3457: ['Servant\'s Quarters', 'Upper Livery Stables', 'The Banquet Hall', 'The Guest Chambers', 'Opera Hall Balcony', 'Master\'s Terrace', 'Lower Broken Stair', 'Upper Broken Stair', 'The Menagerie', 'Guardian\'s Library', 'The Repository', 'Upper Library', 'The Celestial Watch', 'Gamesman\'s Hall', 'Medivh\'s Chambers', 'The Power Station', 'Netherspace'], - 3715: ['The Steamvault', 'The Cooling Pools'], - 3790: ['Halls of the Hereafter', 'Bridge of Souls'], - 3791: ['Veil Sethekk', 'Halls of Mourning'], - 3848: ['Stasis Block: Trion', 'Stasis Block: Maximus', 'Containment Core'], - 3849: ['The Mechanar', 'Calculation Chamber'], - 3959: ['Illidari Training Grounds', 'Karabor Sewers', 'Sanctuary of Shadows', 'Halls of Anguish', 'Gorefiend\'s Vigil', 'Den of Mortal Delights', 'Chamber of Command', 'Temple Summit'], - 4075: ['Sunwell Plateau', 'Shrine of the Eclipse'], - 4100: ['Outside Stratholme', 'Stratholme City'], - 4131: ['Grand Magister\'s Asylum', 'Observation Grounds'], - 4196: ['The Vestibules of Drak\'Tharon', 'Drak\'Tharon Overlook'], - 4228: ['Band of Variance', 'Band of Acceleration', 'Band of Transmutation', 'Band of Alignment'], - 4272: ['Unyielding Garrison', 'Walk of the Makers'], - 4273: ['The Grand Approach', 'The Antechamber of Ulduar', 'The Inner Sanctum of Ulduar', 'The Prison of Yogg-Saron', 'The Spark of Imagination', 'The Mind\'s Eye'], - 4277: ['The Brood Pit', 'Hadronox\'s Lair', 'The Gilded Gate'], - 4395: ['Dalaran City', 'The Underbelly'], - 4494: ['Ahn\'Kahet', 'Level 2'], - 4722: ['Crusaders\' Coliseum', 'The Icy Depths'], - 4812: ['The Lower Citadel', 'The Rampart of Skulls', 'Deathbringer\'s Rise', 'The Frost Queen\'s Lair', 'The Upper Reaches', 'Royal Quarters', 'The Frozen Throne', 'Frostmourne'] -}; +var g_zone_areas = {}; var g_zone_categories = { 0: 'Eastern Kingdoms', diff --git a/static/js/locale_eses.js b/static/js/locale_eses.js index e89538ce8..6a93401ce 100644 --- a/static/js/locale_eses.js +++ b/static/js/locale_eses.js @@ -2133,44 +2133,7 @@ var g_zones = { 4987: 'El Sagrario Rubí' }; -var g_zone_areas = { - 206: ['Preparación Norndir', 'Ascenso de los Desuelladragones', 'Bancal de Tyr'], - 209: ['El Patio', 'Comedor', 'El Cubil Vacío', 'Observatorio inferior', 'Observatorio superior', 'Cámara de Lord Godfrey', 'El Camino de la Muralla'], - 719: ['La Alberca de Ask\'ar', 'Sagrario Lunar', 'Las Charcas del Olvido'], - 721: ['La Sala de Máquinas', 'Los Dormitorios', 'Aeropuerto', 'Cámara Manitas'], - 796: ['Cementerio', 'Biblioteca', 'Arsenal', 'Catedral'], - 1196: ['Pináculo inferior', 'Pináculo superior'], - 1337: ['Sala de los Guardianes', 'Trono de Khaz\'goroth'], - 1581: ['Las Minas de la Muerte', 'Cala del Acorazado'], - 1583: ['Tazz\'Alaor', 'Túneles de Arácnidas', 'Ciudad Hordemar', 'Sala de Puño Negro', 'Sala Dracopico', 'El Grajero', 'Estadio de Roca Negra'], - 1584: ['Bloque de Detención', 'Ciudad Forjatiniebla'], - 2017: ['Plaza de los Cruzados', 'El Guantelete'], - 2057: ['El Relicario', 'Cámara de la Invocación', 'Sala Rectoral', '[Barov Family Vault]'], - 2100: ['Cavernas de Maraudon', 'Tumba de Zaetar'], - 2557: ['Ágora de Gordok', 'Jardines de la Capital', 'Corte de los Altonato', 'Prisión de Immol\'thar', 'Barrio Alabeo', 'Santuario de Eldretharr'], - 2677: ['Cuartel Faucedraco', 'Salas de los Conflictos', 'Laboratorios Carmesí', 'Guarida de Nefarian'], - 3428: ['El Subterráneo de la Colmena', 'Las Puertas del Templo', 'Cámara de C\'Thun'], - 3456: ['El arrabal de los ensamblajes', 'El arrabal arácnido', 'El arrabal militar', 'El arrabal de la peste', 'La Necrópolis inferior', 'La Necrópolis superior'], - 3457: ['Alcobas de los Sirvientes', 'Caballerizas superiores', 'La Sala de Banquetes', 'Los Aposentos de los Invitados', 'Balcón de la Sala de la Ópera', 'El Bancal del Maestro', 'La Escalera Quebrada inferior', 'La Escalera Quebrada superior', 'La Sala de las Fieras', 'Biblioteca del Guardián', 'El Repositorio', 'La Biblioteca superior', 'El Mirador Celestial', 'Sala del Tablero', 'Estancias de Medivh', 'La Central Eléctrica', 'Espacio Abisal'], - 3715: ['La Cámara de Vapor', 'Las Charcas Refrescantes'], - 3790: ['Salas del Más Allá', 'Puente de las Almas'], - 3791: ['Velo Sethekk', 'Salas del Luto'], - 3848: ['Bloque de Estasis: Trion', 'Bloque de Estasis: Maximus', 'Pabellón de Aislamiento'], - 3849: ['El Mechanar', 'Estancias de Calculación'], - 3959: ['Campo de entrenamiento Illidari', 'Cloacas de Karabor', 'Santuario de las Sombras', 'Salas de Angustia', 'Vigilia de Sanguino', 'Guarida de los Placeres Mortales', 'Cámara de Mando', 'Cima del Templo'], - 4075: ['Meseta de La Fuente del Sol', 'Santuario del Eclipse'], - 4100: ['El Camino a Stratholme', 'Stratholme'], - 4131: ['Asilo del Gran Magister', 'Sector de Observación'], - 4196: ['El vestíbulo de Drak\'Tharon', 'Centinela de Drak\'Tharon'], - 4228: ['Sortija de discrepancia', 'Sortija de Aceleración', 'Sortija de transmutación', 'Sortija de alineación'], - 4272: ['El Cuartel Implacable', 'Camino de los Creadores'], - 4273: ['El Gran Acceso', 'La Antecámara de Ulduar', 'El Sagrario Interior de Ulduar', 'La Prisión de Yogg-Saron', 'La Chispa de la Imaginación', 'El Ojo de la Mente'], - 4277: ['El Foso del Linaje', 'Guarida de Hadronox', 'La Puerta dorada'], - 4395: ['Ciudad de Dalaran', 'Los Bajos Fondos'], - 4494: ['Ahn\'kahet', 'Nivel 2'], - 4722: ['El Coliseo Argenta', 'Las profundidades heladas'], - 4812: ['La ciudadela inferior', 'La Muralla de las Calaveras', 'Ascenso del Libramorte', 'La guarida de la Reina de Escarcha', 'Los Confines superiores', 'Cuarteles Reales', 'El Trono Helado', 'Agonía de Escarcha'] -}; +var g_zone_areas = {}; var g_zone_categories = { 0: 'Reinos del Este', diff --git a/static/js/locale_frfr.js b/static/js/locale_frfr.js index 2de41571d..4c3c5edf2 100644 --- a/static/js/locale_frfr.js +++ b/static/js/locale_frfr.js @@ -2133,44 +2133,7 @@ var g_zones = { 4987: 'Le sanctum Rubis' }; -var g_zone_areas = { - 206: ['Préparation de Norndir', 'Ascension d\'Écorche-dragon', 'Terrasse de Tyr'], - 209: ['La cour', 'Salle à manger', 'Antre Vacant', 'Observatoire Inférieur', 'Observatoire Supérieur', 'Chambre du seigneur Godfrey', 'Le chemin de ronde'], - 719: ['Le Bassin d\'Ask\'ar', 'Sanctuaire d’Écrin-de-Lune', 'Les bassins Oubliés'], - 721: ['Le Hall des engrenages', 'Le dortoir', 'Baie de lancement', 'Cour du Bricoleur'], - 796: ['Cimetière', 'La Bibliothèque', 'Armurerie', 'Cathédrale'], - 1196: ['Pinnacle inférieur', 'Pinacle Supérieur'], - 1337: ['Hall des Gardiens', 'Siège de Khaz\'goroth'], - 1581: ['Les Mortemines', 'Crique du Cuirassé'], - 1583: ['Tazz\'Alaor', 'Tunnels de Toile-grouillante', 'Cité d\'Hordemar', 'Hall de Main-noire', 'Hall de la Flèche des dragons', 'La colonie', 'Stade Rochenoire'], - 1584: ['Le mitard', 'Ville des Ombreforges'], - 2017: ['Place des Croisés', 'Le Défi'], - 2057: ['Le Reliquaire', 'Chambre d\'invocation', 'Bureau du proviseur', '[Barov Family Vault]'], - 2100: ['Cavernes de Maraudon', 'Tombe de Zaetar'], - 2557: ['Communs gordok', 'Grands jardins', 'Cours des Bien-nés', 'Prison d\'Immol\'Thar', 'Quartier de Crochebois', 'Le sanctuaire d\'Eldretharr'], - 2677: ['Garnison des Gueules-de-dragon', 'Halls des conflits', 'Laboratoires Cramoisis', 'Antre de Nefarian'], - 3428: ['Les souterrains de la ruche', 'Portes du Temple', 'Caveau de C\'Thun'], - 3456: ['Le quartier des Assemblages', 'Le Quartier des Arachnide', 'Le Quartier Militaire', 'Le quartier de la Peste', 'La Nécropole Inférieure', 'La Nécropole Supérieure'], - 3457: ['Quartiers des serviteurs', 'Écuries supérieures', 'La salle de banquet', 'Les Appartements des hôtes', 'Balcon de l’Opéra', 'Terrasse du maître', 'Partie inférieure de l’Escalier brisé', 'Partie supérieure de l’Escalier brisé', 'La Ménagerie', 'Bibliothèque du Gardien', 'Le Dépôt', 'Bibliothèque supérieure', 'Le Guet céleste', 'Hall du Flambeur', 'Appartements de Medivh', 'Centrale électrique', 'Néantespace'], - 3715: ['Le caveau de la Vapeur', 'Les bassins de refroidissement'], - 3790: ['Les salles de l’Après-vie', 'Le pont des âmes'], - 3791: ['Voile Sethekk', 'Les salles du Deuil'], - 3848: ['Bloc de stase : Trion', 'Bloc de stase : Maximus', 'Cœur de confinement'], - 3849: ['Le Méchanar', 'Chambre des Calculs'], - 3959: ['Terrain d\'entraînement Illidari', 'Égouts de Karabor', 'Sanctuaire des ombres', 'Les salles de l’Angoisse', 'Veillée de Fielsang', 'Tanière des délices mortels', 'Chambre de commandement', 'Sommet du temple'], - 4075: ['Plateau du Puits de soleil', 'Sanctuaire de l’eclipse'], - 4100: ['La Route de Stratholme', 'Stratholme'], - 4131: ['Asile du grand magistère', 'Terrain d’observation'], - 4196: ['Le Vestibule de Drak\'Tharon', 'Surplombe de Drak\'Tharon'], - 4228: ['Bande d\'Écart', 'Bande d\'Accélération', 'Bande de Transmutation', 'Bande d\'Alignement'], - 4272: ['La garnison inflexible', 'Promenade des Faiseurs'], - 4273: ['Le Grand abord', 'L\'antichambre d\'Ulduar', 'Le sanctum intérieur d\'Ulduar', 'La prison de Yogg-Saron', 'L\'Étincelle d\'imagination', 'La Vue de l\'esprit'], - 4277: ['La Fosse des couvées', 'Antre d\'Hadronox', 'La Porte de la Daurade'], - 4395: ['Dalaran', 'Les Entrailles'], - 4494: ['Ahn\'kahet', 'Plancher 2'], - 4722: ['L\'colisée d\'Argent', 'Les Profondeurs Glacées'], - 4812: ['La Citadelle Inférieure', 'Le Rempart des Cranes', 'Ascension de Porte-mort', 'Le repaire de la reine du Givre', 'Les étages supérieurs', 'Quartiers Royaux', 'Le Trône Gelé', 'Deuillegivre'] -}; +var g_zone_areas = {}; var g_zone_categories = { 0: 'Royaumes de l\'est', diff --git a/static/js/locale_ruru.js b/static/js/locale_ruru.js index 6e1e303aa..db97c28ff 100644 --- a/static/js/locale_ruru.js +++ b/static/js/locale_ruru.js @@ -2133,44 +2133,7 @@ var g_zones = { 4987: 'Рубиновое святилище' }; -var g_zone_areas = { - 206: ['Подготовка Норндира', 'Подъем Укротителей драконов', 'Терраса Тира'], - 209: ['Внутренний двор', 'Обеденный зал', 'Свободная берлога', 'Нижняя обсерватория', 'Верхняя обсерватория', 'Палата лорда Годфри', 'Крепостной вал'], - 719: ['Пруд Аск\'ара', 'Алтарь святилища Луны', 'Забытый пруд'], - 721: ['Машинный зал', 'Спальни', 'Пусковая установка', 'Двор Механиков'], - 796: ['Кладбище', 'Библиотека', 'Оружейная', 'Собор'], - 1196: ['Подножие', 'Вершина'], - 1337: ['Зал Хранителей', 'Трон Каз\'горота'], - 1581: ['Мертвые копи', 'Потайная бухта'], - 1583: ['Тазз\'Алаор', 'Паучий лабиринт', 'Ордамар', 'Зал Чернорука', 'Зал Драконов', 'Гнездовье', 'Стадион Черной горы'], - 1584: ['Тюремный блок', 'Тенегорн'], - 2017: ['Площадь рыцарей', 'Улица Испытаний'], - 2057: ['Хранилище реликвий', 'Чертог Призыва', 'Кабинет ректора', '[Barov Family Vault]'], - 2100: ['Пещеры Мародона', 'Могила Зейтара'], - 2557: ['Палаты Гордока', 'Центральный сад', 'Двор высокорожденных', 'Тюрьма Бессмер\'тера', 'Квартал Криводревов', 'Святилище Элдретарра'], - 2677: ['Гарнизон Драконьей Пасти', 'Залы Раздора', 'Багровые лаборатории', 'Логово Нефариана'], - 3428: ['Подземелье улья', 'Ворота храма', 'Обитель К\'Туна'], - 3456: ['Квартал Мерзости', 'Паучий квартал', 'Военный квартал', 'Чумной квартал', 'Нижний некрополь', 'Верхний некрополь'], - 3457: ['Комнаты cлуг', 'Cтойла', 'Пиршественный зал', 'Гостевые комнаты', 'Балкон в опере', 'Терраса Мастера', 'Низ разрушенной лестницы', 'Верх разрушенной лестницы', 'Галерея', 'Библиотека Стража', 'Хранилище', 'Верхний ярус библиотеки', 'Обсерватория', 'Игровой зал', 'Покои Медива', 'Энергетический блок', 'Пустомарь'], - 3715: ['Паровое подземелье', 'Охладительные резервуары'], - 3790: ['Потусторонние залы', 'Мост Душ'], - 3791: ['Гнездовье Сетекк', 'Залы Плача'], - 3848: ['Изоляционная камера: Трион', 'Изоляционная камера: Максимус', 'Ядро Сдерживания'], - 3849: ['Механар', 'Комната Вычислений'], - 3959: ['Черный храм', 'Канализация', 'Святилище Теней', 'Залы Страданий', 'Пост Кровожада', 'Приют Земных Наслаждений', 'Чертог Власти', 'Храмовая вершина'], - 4075: ['Плато Солнечного Колодца', 'Святилище Затмения'], - 4100: ['Дорога к Стратхольму', 'Стратхольм'], - 4131: ['Пристанище Великого Магистра', 'Обзорная площадка'], - 4196: ['Залы крепости Драк\'Тарон', 'Дозорное укрепление Драк\'Тарона'], - 4228: ['Кольцо отклонения', 'Кольцо ускорения', 'Кольцо трансмутации', 'Кольцо управления'], - 4272: ['Стойкий гарнизон', 'Галерея Творцов'], - 4273: ['Большой переход', 'Вестибюль Ульдуара', 'Внутреннее святилище Ульдуара', 'Темница Йогг-Сарона', 'Искра Воображения', 'Око разума'], - 4277: ['Родовая яма', 'Логово Хадронокса', 'Золоченые врата'], - 4395: ['Даларан', 'Клоака'], - 4494: ['Ан\'кахет', 'Уровень 2'], - 4722: ['Колизей Серебряного Авангарда', 'Ледяные глубины'], - 4812: ['Нижний ярус', 'Черепной вал', 'Подъем Смертоносного', 'Логово Королевы Льда', 'Верхний ярус', 'Королевские палаты', 'Ледяной Трон', 'Ледяная Скорбь'] -}; +var g_zone_areas = {}; var g_zone_categories = { 0: 'Восточные королевства', diff --git a/static/js/locale_zhcn.js b/static/js/locale_zhcn.js index fa55c9559..ea5d9e48d 100644 --- a/static/js/locale_zhcn.js +++ b/static/js/locale_zhcn.js @@ -2180,44 +2180,7 @@ var g_zones = { 4987: '红玉圣殿' }; -var g_zone_areas = { - 206: ['诺迪尔备战区', '掠龙氏族高台', '提尔之台'], - 209: ['庭院', '饭厅', '空巢', '下层瞭望台', '上层瞭望台', '高弗雷勋爵的大厅', '城墙走道'], - 719: ['阿斯卡之池', '月神圣地密室', '遗忘之池'], - 721: ['齿轮大厅', '宿舍', '发射台', '工匠议会'], - 796: ['墓园', '图书馆', '军械库', '教堂'], - 1196: ['尖塔下层', '尖塔上层'], - 1337: ['守护者大厅', '卡兹格罗斯之座'], - 1581: ['死亡矿井', '铁甲湾'], - 1583: ['塔萨洛尔', '蛛网隧道', '霍德玛尔城', '黑手大厅', '龙塔大厅', '孵化间', '黑石竞技场'], - 1584: ['禁闭室', '暗炉城'], - 2017: ['十字军广场', '街巷'], - 2057: ['遗骨之穴', '召唤大厅', '书房上层', '院长的书房'], - 2100: ['玛拉顿的洞穴', '扎尔塔之墓'], - 2557: ['戈多克议会', '中心花园', '上层精灵庭院', '伊莫塔尔的牢笼', '扭木广场', '艾德雷斯神殿'], - 2677: ['龙喉兵营', '征战大厅', '血色实验室', '奈法利安的巢穴'], - 3428: ['地下虫巢', '神殿大门', '克苏恩地穴'], - 3456: ['构造区', '蜘蛛区', '军事区', '瘟疫区', '大墓地下层', '大墓地上层'], - 3457: ['仆役宿舍', '上层马厩', '宴会厅', '会客间', '歌剧院楼座', '主宰的露台', '下层断阶', '上层断阶', '展览馆', '守护者的图书馆', '储藏室', '上层图书馆', '观星大厅', '象棋大厅', '麦迪文的房间', '能量站', '虚空异界'], - 3715: ['蒸汽地窟', '冷却池'], - 3790: ['转生大厅', '灵魂之桥'], - 3791: ['塞泰克鸦巢', '哀悼大厅'], - 3848: ['静止隔间:特雷奥', '静止隔间:玛克希姆', '密封核心'], - 3849: ['能源舰', '计算密室'], - 3959: ['伊利达雷训练场', '卡拉波下水道', '暗影圣殿', '苦痛大厅', '血魔之厅', '欢愉之园', '命令大厅', '神殿之巅'], - 4075: ['太阳之井高地', '日蚀神殿'], - 4100: ['斯坦索姆外围', '斯坦索姆城'], - 4131: ['大魔导师的圣堂', '观测台'], - 4196: ['达克萨隆前庭', '达克萨隆悬崖'], - 4228: ['突变之环', '加速之环', '转化之环', '校准之环'], - 4272: ['坚韧军营', '造物者步道'], - 4273: ['壮阔大道', '奥杜尔的前厅', '奥杜尔的内部圣殿', '尤格-萨隆的监狱', '思想火花', '心灵之眼'], - 4277: ['孵化深渊', '哈多诺克斯之巢', '镀金之门'], - 4395: ['达拉然城', '达拉然下水道'], - 4494: ['安卡赫特', '被亵渎的祭坛'], - 4722: ['银色演武场', '寒冰深渊'], - 4812: ['堡垒下层', '颅骨之墙', '死亡使者之台', '冰霜女王的巢穴', '上层区域', '皇家区', '冰封王座', '霜之哀伤'] -}; +var g_zone_areas = {}; var g_zone_categories = { 0: "东部王国",