From 3fd25ca889850ad08549f533749049a16547de98 Mon Sep 17 00:00:00 2001 From: Sarjuuk Date: Thu, 22 Mar 2018 16:48:43 +0100 Subject: [PATCH] Profiler/Backend * added core functions nessecary for profiler --- .htaccess | 4 + includes/ajaxHandler/account.class.php | 44 +- includes/ajaxHandler/arenateam.class.php | 82 +++ includes/ajaxHandler/filter.class.php | 9 + includes/ajaxHandler/guild.class.php | 82 +++ includes/ajaxHandler/profile.class.php | 727 +++++++++++++++----- includes/basetype.class.php | 40 +- includes/defines.php | 43 ++ includes/kernel.php | 17 +- includes/profiler.class.php | 828 +++++++++++++++++++++++ includes/types/arenateam.class.php | 346 ++++++++++ includes/types/guild.class.php | 307 +++++++++ includes/types/item.class.php | 2 +- includes/types/profile.class.php | 790 ++++++++++++++++----- includes/types/spell.class.php | 215 +++++- includes/user.class.php | 90 +-- includes/utilities.php | 144 ++-- localization/lang.class.php | 15 +- pages/achievement.php | 2 +- pages/admin.php | 4 +- pages/genericPage.class.php | 84 ++- pages/item.php | 2 +- prQueue | 116 ++++ setup/db_structure.sql | 242 ++++++- setup/tools/fileGen.class.php | 17 +- setup/tools/filegen/profiler.func.php | 165 +++-- setup/tools/filegen/realmMenu.func.php | 13 +- setup/tools/filegen/realms.func.php | 8 +- setup/tools/filegen/statistics.func.php | 96 ++- setup/tools/filegen/talentCalc.func.php | 84 +-- setup/updates/1521735363_01.sql | 371 ++++++++++ setup/updates/1521735363_02.sql | 16 + 32 files changed, 4386 insertions(+), 619 deletions(-) create mode 100644 includes/ajaxHandler/arenateam.class.php create mode 100644 includes/ajaxHandler/guild.class.php create mode 100644 includes/profiler.class.php create mode 100644 includes/types/arenateam.class.php create mode 100644 includes/types/guild.class.php create mode 100755 prQueue create mode 100644 setup/updates/1521735363_01.sql create mode 100644 setup/updates/1521735363_02.sql diff --git a/.htaccess b/.htaccess index c0d5b592..0c4ad8e7 100644 --- a/.htaccess +++ b/.htaccess @@ -10,6 +10,10 @@ Order Deny,Allow ForceType application/x-httpd-php + + ForceType application/x-httpd-php + + # Block view of some folders Options -Indexes DirectoryIndex index.php diff --git a/includes/ajaxHandler/account.class.php b/includes/ajaxHandler/account.class.php index 1bc47257..ae25b4f8 100644 --- a/includes/ajaxHandler/account.class.php +++ b/includes/ajaxHandler/account.class.php @@ -7,12 +7,15 @@ class AjaxAccount extends AjaxHandler { protected $validParams = ['exclude', 'weightscales']; protected $_post = array( - // 'groups' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']], + 'groups' => [FILTER_SANITIZE_NUMBER_INT, null], 'save' => [FILTER_SANITIZE_NUMBER_INT, null], 'delete' => [FILTER_SANITIZE_NUMBER_INT, null], 'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']], 'name' => [FILTER_CALLBACK, ['options' => 'AjaxAccount::checkName']], 'scale' => [FILTER_CALLBACK, ['options' => 'AjaxAccount::checkScale']], + 'reset' => [FILTER_SANITIZE_NUMBER_INT, null], + 'mode' => [FILTER_SANITIZE_NUMBER_INT, null], + 'type' => [FILTER_SANITIZE_NUMBER_INT, null], ); protected $_get = array( 'locale' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkLocale']] @@ -37,10 +40,41 @@ class AjaxAccount extends AjaxHandler protected function handleExclude() { - // profiler completion exclude handler - // $this->_post['groups'] = bitMask of excludeGroupIds when using .. excludeGroups .. duh - // should probably occur in g_user.excludegroups (dont forget to also set g_users.settings = {}) - return ''; + if (!User::$id) + return; + + if ($this->_post['mode'] == 1) // directly set exludes + { + $type = $this->_post['type']; + $ids = $this->_post['id']; + + if (!isset(Util::$typeStrings[$type]) || empty($ids)) + return; + + // ready for some bullshit? here it comes! + // we don't get signaled whether an id should be added to or removed from either includes or excludes + // so we throw everything into one table and toggle the mode if its already in here + + $includes = DB::Aowow()->selectCol('SELECT typeId FROM ?_profiler_excludes WHERE type = ?d AND typeId IN (?a)', $type, $ids); + + foreach ($ids as $typeId) + DB::Aowow()->query('INSERT INTO ?_account_excludes (`userId`, `type`, `typeId`, `mode`) VALUES (?a) ON DUPLICATE KEY UPDATE mode = (mode ^ 0x3)', array( + User::$id, $type, $typeId, in_array($includes, $typeId) ? 2 : 1 + )); + + return; + } + else if ($this->_post['reset'] == 1) // defaults to unavailable + { + $mask = PR_EXCLUDE_GROUP_UNAVAILABLE; + DB::Aowow()->query('DELETE FROM ?_account_excludes WHERE userId = ?d', User::$id); + } + else // clamp to real groups + $mask = $this->_post['groups'] & PR_EXCLUDE_GROUP_ANY; + + DB::Aowow()->query('UPDATE ?_account SET excludeGroups = ?d WHERE id = ?d', $mask, User::$id); + + return; } protected function handleWeightscales() diff --git a/includes/ajaxHandler/arenateam.class.php b/includes/ajaxHandler/arenateam.class.php new file mode 100644 index 00000000..fdffe473 --- /dev/null +++ b/includes/ajaxHandler/arenateam.class.php @@ -0,0 +1,82 @@ + [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']], + 'profile' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']], + ); + + public function __construct(array $params) + { + parent::__construct($params); + + if (!$this->params) + return; + + switch ($this->params[0]) + { + case 'resync': + $this->handler = 'handleResync'; + break; + case 'status': + $this->handler = 'handleStatus'; + break; + } + } + + /* params + id: + user: [optional, not used] + profile: [optional, also get related chars] + return: 1 + */ + protected function handleResync() + { + if ($teams = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_arena_team WHERE id IN (?a)', $this->_get['id'])) + foreach ($teams as $t) + Profiler::scheduleResync(TYPE_ARENA_TEAM, $t['realm'], $t['realmGUID']); + + if ($this->_get['profile']) + if ($chars = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_profiles p JOIN ?_profiler_arena_team_member atm ON atm.profileId = p.id WHERE atm.arenaTeamId IN (?a)', $this->_get['id'])) + foreach ($chars as $c) + Profiler::scheduleResync(TYPE_PROFILE, $c['realm'], $c['realmGUID']); + + return '1'; + } + + /* params + id: + return + + [ + nQueueProcesses, + [statusCode, timeToRefresh, curQueuePos, errorCode, nResyncTries], + [] + ... + ] + + not all fields are required, if zero they are omitted + statusCode: + 0: end the request + 1: waiting + 2: working... + 3: ready; click to view + 4: error / retry + errorCode: + 0: unk error + 1: char does not exist + 2: armory gone + */ + protected function handleStatus() + { + $response = Profiler::resyncStatus(TYPE_ARENA_TEAM, $this->_get['id']); + return Util::toJSON($response); + } +} + +?> diff --git a/includes/ajaxHandler/filter.class.php b/includes/ajaxHandler/filter.class.php index f89d7cd0..f01152bc 100644 --- a/includes/ajaxHandler/filter.class.php +++ b/includes/ajaxHandler/filter.class.php @@ -62,6 +62,15 @@ class AjaxFilter extends AjaxHandler case 'spells': $this->filter = (new SpellListFilter(true, $opts)); break; + case 'profiles': + $this->filter = (new ProfileListFilter(true, $opts)); + break; + case 'guilds': + $this->filter = (new GuildListFilter(true, $opts)); + break; + case 'arena-teams': + $this->filter = (new ArenaTeamListFilter(true, $opts)); + break; default: return; } diff --git a/includes/ajaxHandler/guild.class.php b/includes/ajaxHandler/guild.class.php new file mode 100644 index 00000000..f791dab0 --- /dev/null +++ b/includes/ajaxHandler/guild.class.php @@ -0,0 +1,82 @@ + [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']], + 'profile' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']], + ); + + public function __construct(array $params) + { + parent::__construct($params); + + if (!$this->params) + return; + + switch ($this->params[0]) + { + case 'resync': + $this->handler = 'handleResync'; + break; + case 'status': + $this->handler = 'handleStatus'; + break; + } + } + + /* params + id: + user: [optional, not used] + profile: [optional, also get related chars] + return: 1 + */ + protected function handleResync() + { + if ($guilds = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_guild WHERE id IN (?a)', $this->_get['id'])) + foreach ($guilds as $g) + Profiler::scheduleResync(TYPE_GUILD, $g['realm'], $g['realmGUID']); + + if ($this->_get['profile']) + if ($chars = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_profiles WHERE guild IN (?a)', $this->_get['id'])) + foreach ($chars as $c) + Profiler::scheduleResync(TYPE_PROFILE, $c['realm'], $c['realmGUID']); + + return '1'; + } + + /* params + id: + return + + [ + nQueueProcesses, + [statusCode, timeToRefresh, curQueuePos, errorCode, nResyncTries], + [] + ... + ] + + not all fields are required, if zero they are omitted + statusCode: + 0: end the request + 1: waiting + 2: working... + 3: ready; click to view + 4: error / retry + errorCode: + 0: unk error + 1: char does not exist + 2: armory gone + */ + protected function handleStatus() + { + $response = Profiler::resyncStatus(TYPE_GUILD, $this->_get['id']); + return Util::toJSON($response); + } +} + +?> diff --git a/includes/ajaxHandler/profile.class.php b/includes/ajaxHandler/profile.class.php index 445a7247..50cded7d 100644 --- a/includes/ajaxHandler/profile.class.php +++ b/includes/ajaxHandler/profile.class.php @@ -5,11 +5,39 @@ if (!defined('AOWOW_REVISION')) class AjaxProfile extends AjaxHandler { - protected $validParams = ['link', 'unlink', 'pin', 'unpin', 'public', 'private', 'avatar', 'resync', 'status', 'delete', 'purge', 'summary', 'load']; + private $undo = false; + + protected $validParams = ['link', 'unlink', 'pin', 'unpin', 'public', 'private', 'avatar', 'resync', 'status', 'save', 'delete', 'purge', 'summary', 'load']; protected $_get = array( - 'id' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkId']], - // 'items' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkItems']], - 'size' => [FILTER_SANITIZE_STRING, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH + 'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']], + 'items' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkItemList']], + 'size' => [FILTER_SANITIZE_STRING, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH + 'guild' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']], + 'arena-team' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']], + ); + + protected $_post = array( + 'name' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext']], + 'level' => [FILTER_SANITIZE_NUMBER_INT, null], + 'class' => [FILTER_SANITIZE_NUMBER_INT, null], + 'race' => [FILTER_SANITIZE_NUMBER_INT, null], + 'gender' => [FILTER_SANITIZE_NUMBER_INT, null], + 'nomodel' => [FILTER_SANITIZE_NUMBER_INT, null], + 'talenttree1' => [FILTER_SANITIZE_NUMBER_INT, null], + 'talenttree2' => [FILTER_SANITIZE_NUMBER_INT, null], + 'talenttree3' => [FILTER_SANITIZE_NUMBER_INT, null], + 'activespec' => [FILTER_SANITIZE_NUMBER_INT, null], + 'talentbuild1' => [FILTER_SANITIZE_STRING, 0xC],// FILTER_FLAG_STRIP_LOW | *_HIGH + 'glyphs1' => [FILTER_SANITIZE_STRING, 0xC], + 'talentbuild2' => [FILTER_SANITIZE_STRING, 0xC], + 'glyphs2' => [FILTER_SANITIZE_STRING, 0xC], + 'icon' => [FILTER_SANITIZE_STRING, 0xC], + 'description' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext']], + 'source' => [FILTER_SANITIZE_NUMBER_INT, null], + 'copy' => [FILTER_SANITIZE_NUMBER_INT, null], + 'public' => [FILTER_SANITIZE_NUMBER_INT, null], + 'gearscore' => [FILTER_SANITIZE_NUMBER_INT, null], + 'inv' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkItemString', 'flags' => FILTER_REQUIRE_ARRAY]], ); public function __construct(array $params) @@ -21,24 +49,29 @@ class AjaxProfile extends AjaxHandler switch ($this->params[0]) { - case 'link': case 'unlink': + $this->undo = true; + case 'link': $this->handler = 'handleLink'; // always returns null break; - case 'pin': case 'unpin': + $this->undo = true; + case 'pin': $this->handler = 'handlePin'; // always returns null break; - case 'public': case 'private': + $this->undo = true; + case 'public': $this->handler = 'handlePrivacy'; // always returns null break; case 'avatar': $this->handler = 'handleAvatar'; // sets an image header break; // so it has to die here or another header will be set case 'resync': + $this->handler = 'handleResync'; // always returns "1" + break; case 'status': - $this->handler = 'handleResync'; + $this->handler = 'handleStatus'; // returns status object break; case 'save': $this->handler = 'handleSave'; @@ -57,33 +90,86 @@ class AjaxProfile extends AjaxHandler } } - protected function handleLink($id, $mode) // links char with account + /* params + id: + user: [optional] + return: null + */ + protected function handleLink() // links char with account { - /* params - id: - user: [optional] - return: null - */ + if (!User::$id || empty($this->_get['id'])) + return; + + $uid = User::$id; + if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU)) + $uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user']); + else if ($this->_get['user']) + return; + + if ($this->undo) + DB::Aowow()->query('DELETE FROM ?_account_profiles WHERE accountId = ?d AND profileId IN (?a)', $uid, $this->_get['id']); + else + foreach ($this->_get['id'] as $prId) // only link characters, not custom profiles + if ($prId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE id = ?d AND realm IS NOT NULL', $prId)) + DB::Aowow()->query('INSERT IGNORE INTO ?_account_profiles VALUES (?d, ?d, 0)', $uid, $prId); } - protected function handlePin($id, $mode) // (un)favorite + /* params + id: + user: [optional] + return: null + */ + protected function handlePin() // (un)favorite { - /* params - id: - user: [optional] - return: null - */ + if (!User::$id || empty($this->_get['id'][0])) + return; + + $uid = User::$id; + if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU)) + $uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user']); + else if ($this->_get['user']) + return; + + // since only one character can be pinned at a time we can reset everything + DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags & ?d WHERE accountId = ?d', ~PROFILER_CU_PINNED, $uid); + // and set a single char if nesecary + if (!$this->undo) + DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags | ?d WHERE profileId = ?d AND accountId = ?d', PROFILER_CU_PINNED, $this->_get['id'][0], $uid); } - protected function handlePrivacy($id, $mode) // public visibility + /* params + id: + user: [optional] + return: null + */ + protected function handlePrivacy() // public visibility { - /* params - id: - user: [optional] - return: null - */ + if (!User::$id || empty($this->_get['id'][0])) + return; + + $uid = User::$id; + if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU)) + $uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user']); + else if ($this->_get['user']) + return; + + if ($this->undo) + { + DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags & ?d WHERE profileId IN (?a) AND accountId = ?d', ~PROFILER_CU_PUBLISHED, $this->_get['id'], $uid); + DB::Aowow()->query('UPDATE ?_profiler_profiles SET cuFlags = cuFlags & ?d WHERE id IN (?a) AND user = ?d', ~PROFILER_CU_PUBLISHED, $this->_get['id'], $uid); + } + else + { + DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags | ?d WHERE profileId IN (?a) AND accountId = ?d', PROFILER_CU_PUBLISHED, $this->_get['id'], $uid); + DB::Aowow()->query('UPDATE ?_profiler_profiles SET cuFlags = cuFlags | ?d WHERE id IN (?a) AND user = ?d', PROFILER_CU_PUBLISHED, $this->_get['id'], $uid); + } } + /* params + id: + size: [optional] + return: image-header + */ protected function handleAvatar() // image { // something happened in the last years: those textures do not include tiny icons @@ -125,199 +211,506 @@ class AjaxProfile extends AjaxHandler return; } - protected function handleResync() // resync init and status requests + /* params + id: + user: [optional, not used] + return: 1 + */ + protected function handleResync() { - /* params - id: - user: [optional] - return - null [onOK] - int or str [onError] - */ + if ($chars = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_profiles WHERE id IN (?a)', $this->_get['id'])) + foreach ($chars as $c) + Profiler::scheduleResync(TYPE_PROFILE, $c['realm'], $c['realmGUID']); - if ($this->params[0] == 'resync') - return '1'; - else // $this->params[0] == 'status' - { - /* - not all fields are required, if zero they are omitted - statusCode: - 0: end the request - 1: waiting - 2: working... - 3: ready; click to view - 4: error / retry - errorCode: - 0: unk error - 1: char does not exist - 2: armory gone - - [ - processId, - [StatusCode, timeToRefresh, iCount, errorCode, iNResyncs], - []... - ] - */ - return '[0, [4, 10000, 1, 2]]'; - } + return '1'; } + /* params + id: + return + + [ + nQueueProcesses, + [statusCode, timeToRefresh, curQueuePos, errorCode, nResyncTries], + [] + ... + ] + + not all fields are required, if zero they are omitted + statusCode: + 0: end the request + 1: waiting + 2: working... + 3: ready; click to view + 4: error / retry + errorCode: + 0: unk error + 1: char does not exist + 2: armory gone + */ + protected function handleStatus() + { + // roster resync for this guild was requested -> get char list + if ($this->_get['guild']) + $ids = DB::Aowow()->selectCol('SELECT id FROM ?_profiler_profiles WHERE guild IN (?a)', $this->_get['id']); + else if ($this->_get['arena-team']) + $ids = DB::Aowow()->selectCol('SELECT profileId FROM ?_profiler_arena_team_member WHERE arenaTeamId IN (?a)', $this->_get['id']); + else + $ids = $this->_get['id']; + + $response = Profiler::resyncStatus(TYPE_PROFILE, $ids); + return Util::toJSON($response); + } + + /* params (get)) + id: [0: new profile] + params (post) + [see below] + return: + proileId [onSuccess] + -1 [onError] + */ protected function handleSave() // unKill a profile { - /* params GET - id: - params POST - name, level, class, race, gender, nomodel, talenttree1, talenttree2, talenttree3, activespec, talentbuild1, glyphs1, talentbuild2, glyphs2, gearscore, icon, public [always] - description, source, copy, inv { inventory: array containing itemLinks } [optional] - } - return - int > 0 [profileId, if we came from an armoryProfile create a new one] - int < 0 [onError] - str [onError] - */ + // todo (med): detail check this post-data + $cuProfile = array( + 'user' => User::$id, + // 'userName' => User::$displayName, + 'name' => $this->_post['name'], + 'level' => $this->_post['level'], + 'class' => $this->_post['class'], + 'race' => $this->_post['race'], + 'gender' => $this->_post['gender'], + 'nomodelMask' => $this->_post['nomodel'], + 'talenttree1' => $this->_post['talenttree1'], + 'talenttree2' => $this->_post['talenttree2'], + 'talenttree3' => $this->_post['talenttree3'], + 'talentbuild1' => $this->_post['talentbuild1'], + 'talentbuild2' => $this->_post['talentbuild2'], + 'activespec' => $this->_post['activespec'], + 'glyphs1' => $this->_post['glyphs1'], + 'glyphs2' => $this->_post['glyphs2'], + 'gearscore' => $this->_post['gearscore'], + 'icon' => $this->_post['icon'], + 'cuFlags' => PROFILER_CU_PROFILE | ($this->_post['public'] ? PROFILER_CU_PUBLISHED : 0) + ); - return 'NYI'; + if (strstr($cuProfile['icon'], 'profile=avatar')) // how the profiler is supposed to handle icons is beyond me + $cuProfile['icon'] = ''; + + if ($_ = $this->_post['description']) + $cuProfile['description'] = $_; + + if ($_ = $this->_post['source']) // should i also set sourcename? + $cuProfile['sourceId'] = $_; + + if ($_ = $this->_post['copy']) // gets set to source profileId when "save as" is clicked. Whats the difference to 'source' though? + { + // get character origin info if possible + if ($r = DB::Aowow()->selectCell('SELECT realm FROM ?_profiler_profiles WHERE id = ?d AND realm IS NOT NULL', $_)) + $cuProfile['realm'] = $r; + + $cuProfile['sourceId'] = $_; + } + + if ($cuProfile['sourceId']) + $cuProfile['sourceName'] = DB::Aowow()->selectCell('SELECT name FROM ?_profiler_profiles WHERE id = ?d', $cuProfile['sourceId']); + + $charId = -1; + if ($id = $this->_get['id'][0]) // update + { + if ($charId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE id = ?d', $id)) + DB::Aowow()->query('UPDATE ?_profiler_profiles SET ?a WHERE id = ?d', $cuProfile, $id); + } + else // new + { + $nProfiles = DB::Aowow()->selectCell('SELECT COUNT(*) FROM ?_profiler_profiles WHERE user = ?d AND realmGUID IS NULL', User::$id); + if ($nProfiles < 10 || User::isPremium()) + if ($newId = DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES (?a)', array_keys($cuProfile), array_values($cuProfile))) + $charId = $newId; + } + + // update items + if ($charId != -1) + { + // ok, 'funny' thing: wether an item has en extra prismatic sockel is determined contextual + // either the socket is -1 or it has an itemId in a socket where there shouldn't be one + $keys = ['id', 'slot', 'item', 'subitem', 'permEnchant', 'tempEnchant', 'gem1', 'gem2', 'gem3', 'gem4']; + + // validate Enchantments + $enchIds = array_merge( + array_column($this->_post['inv'], 3), // perm enchantments + array_column($this->_post['inv'], 4) // temp enchantments (not used..?) + ); + $enchs = new EnchantmentList(array(['id', $enchIds])); + + // validate items + $itemIds = array_merge( + array_column($this->_post['inv'], 1), // base item + array_column($this->_post['inv'], 5), // gem slot 1 + array_column($this->_post['inv'], 6), // gem slot 2 + array_column($this->_post['inv'], 7), // gem slot 3 + array_column($this->_post['inv'], 8) // gem slot 4 + ); + + $items = new ItemList(array(['id', $itemIds])); + if (!$items->error) + { + foreach ($this->_post['inv'] as $slot => $itemData) + { + if ($slot + 1 == array_sum($itemData)) // only slot definition set => empty slot + { + DB::Aowow()->query('DELETE FROM ?_profiler_items WHERE id = ?d AND slot = ?d', $charId, $itemData[0]); + continue; + } + + // item does not exist + if (!$items->getEntry($itemData[1])) + continue; + + // sub-item check + if (!$items->getRandEnchantForItem($itemData[1])) + $itemData[2] = 0; + + // item sockets are fubar + $nSockets = $items->json[$itemData[1]]['nsockets']; + $nSockets += in_array($slot, [SLOT_WAIST, SLOT_WRISTS, SLOT_HANDS]) ? 1 : 0; + for ($i = 5; $i < 9; $i++) + if ($itemData[$i] > 0 && (!$items->getEntry($itemData[$i]) || $i >= (5 + $nSockets))) + $itemData[$i] = 0; + + // item enchantments are borked + if ($itemData[3] && !$enchs->getEntry($itemData[3])) + $itemData[3] = 0; + + if ($itemData[4] && !$enchs->getEntry($itemData[4])) + $itemData[4] = 0; + + // looks good + array_unshift($itemData, $charId); + DB::Aowow()->query('REPLACE INTO ?_profiler_items (?#) VALUES (?a)', $keys, $itemData); + } + } + } + + return $charId; } + /* params + id: + return + null + */ protected function handleDelete() // kill a profile { - /* params - id: - return - null - */ + if (!$this->_get['id']) + return; - return 'NYI'; - } - - protected function handlePurge() // removes certain saved information but not the entire character - { - /* params - id: - data: [string, tabName?] - return - null - */ - - return 'NYI'; + // only flag as deleted; only custom profiles + DB::Aowow()->query( + 'UPDATE ?_profiler_profiles SET cuFlags = cuFlags | ?d WHERE id IN (?a) AND cuFlags & ?d {AND user = ?d}', + PROFILER_CU_DELETED, + $this->_get['id'], + PROFILER_CU_PROFILE, + User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU) ? DBSIMPLE_SKIP : User::$id + ); } + /* params + id: profileId + items: string [itemIds.join(':')] + unnamed: unixtime [only to force the browser to reload instead of cache] + return + lots... + */ protected function handleLoad() { - /* params - id: profileId - items: string [itemIds.join(':')] - unnamed: unixtime [only to force the browser to reload instead of cache] - return - lots... - */ - - // titles, achievements, characterData, talents (, pets) + // titles, achievements, characterData, talents, pets // and some onLoad-hook to .. load it registerProfile($data) // everything else goes through data.php .. strangely enough if (!$this->_get['id']) return; - $char = new ProfileList(array(['id', $this->_get['id'][0]])); // or string or whatever + $pBase = DB::Aowow()->selectRow('SELECT pg.name AS guildname, p.* FROM ?_profiler_profiles p LEFT JOIN ?_profiler_guild pg ON pg.id = p.guild WHERE p.id = ?d', $this->_get['id'][0]); + if (!$pBase) + { + trigger_error('Profiler::handleLoad() - called with invalid profileId #'.$this->_get['id'][0], E_USER_WARNING); + return; + } + + if (($pBase['cuFlags'] & PROFILER_CU_DELETED) && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU)) + return; + + + $rData = []; + foreach (Profiler::getRealms() as $rId => $rData) + if ($rId == $pBase['realm']) + break; + + $profile = array( + 'id' => $pBase['id'], + 'source' => $pBase['id'], + 'level' => $pBase['level'], + 'classs' => $pBase['class'], + 'race' => $pBase['race'], + 'faction' => Game::sideByRaceMask(1 << ($pBase['race'] - 1)) - 1, + 'gender' => $pBase['gender'], + 'skincolor' => $pBase['skincolor'], + 'hairstyle' => $pBase['hairstyle'], + 'haircolor' => $pBase['haircolor'], + 'facetype' => $pBase['facetype'], + 'features' => $pBase['features'], + 'title' => $pBase['title'], + 'name' => $pBase['name'], + 'guild' => "$'".$pBase['guildname']."'", + 'published' => !!($pBase['cuFlags'] & PROFILER_CU_PUBLISHED), + 'pinned' => !!($pBase['cuFlags'] & PROFILER_CU_PINNED), + 'nomodel' => $pBase['nomodelMask'], + 'playedtime' => $pBase['playedtime'], + 'lastupdated' => $pBase['lastupdated'] * 1000, + 'talents' => array( + 'builds' => array( // notice the bullshit to prevent the talent-string from becoming a float! NOTICE IT!! + ['talents' => '$"'.$pBase['talentbuild1'].'"', 'glyphs' => $pBase['glyphs1']], + ['talents' => '$"'.$pBase['talentbuild2'].'"', 'glyphs' => $pBase['glyphs2']] + ), + 'active' => $pBase['activespec'] + ), + // set later + 'inventory' => [], + 'bookmarks' => [], // list of userIds who claimed this profile (claiming and owning are two different things) + + // completion lists: [subjectId => amount/timestamp/1] + 'skills' => [], // skillId => [curVal, maxVal] + 'reputation' => [], // factionId => curVal + 'titles' => [], // titleId => 1 + 'spells' => [], // spellId => 1; recipes, vanity pets, mounts + 'achievements' => [], // achievementId => timestamp + 'quests' => [], // questId => 1 + 'achievementpoints' => 0, // max you have + 'statistics' => [], // all raid activity [achievementId => killCount] + 'activity' => [], // recent raid activity [achievementId => 1] (is a subset of statistics) + ); + + if ($pBase['cuFlags'] & PROFILER_CU_PROFILE) + { + // this parameter is _really_ strange .. probably still not doing this right + $profile['source'] = $pBase['realm'] ? $pBase['sourceId'] : 0; + + $profile['sourcename'] = $pBase['sourceName']; + $profile['description'] = $pBase['description']; + $profile['user'] = $pBase['user']; + $profile['username'] = DB::Aowow()->selectCell('SELECT displayName FROM ?_account WHERE id = ?d', $pBase['user']); + } + + // custom profiles inherit this when copied from real char :( + if ($pBase['realm']) + { + $profile['region'] = [$rData['region'], Lang::profiler('regions', $rData['region'])]; + $profile['battlegroup'] = [Profiler::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP]; + $profile['realm'] = [Profiler::urlize($rData['name']), $rData['name']]; + } + + // bookmarks + if ($_ = DB::Aowow()->selectCol('SELECT accountId FROM ?_account_profiles WHERE profileId = ?d', $pBase['id'])) + $profile['bookmarks'] = $_; + + // arena teams - [size(2|3|5) => DisplayName]; DisplayName gets urlized to use as link + if ($at = DB::Aowow()->selectCol('SELECT type AS ARRAY_KEY, name FROM ?_profiler_arena_team at JOIN ?_profiler_arena_team_member atm ON atm.arenaTeamId = at.id WHERE atm.profileId = ?d', $pBase['id'])) + $profile['arenateams'] = $at; + + // pets if hunter fields: [name:name, family:petFamily, npc:npcId, displayId:modelId, talents:talentString] + if ($pets = DB::Aowow()->select('SELECT name, family, npc, displayId, talents FROM ?_profiler_pets WHERE owner = ?d', $pBase['id'])) + $profile['pets'] = $pets; + + // source for custom profiles; profileId => [name, ownerId, iconString(optional)] + if ($customs = DB::Aowow()->select('SELECT id AS ARRAY_KEY, name, user, icon FROM ?_profiler_profiles WHERE sourceId = ?d AND sourceId <> id', $pBase['id'])) + { + foreach ($customs as $id => $cu) + { + if (!$cu['icon']) + unset($cu['icon']); + + $profile['customs'][$id] = array_values($cu); + } + } + + + /* $profile[] + // CUSTOM + 'auras' => [], // custom list of buffs, debuffs [spellId] + + // UNUSED + 'glyphs' => [], // provided list of already known glyphs (post cataclysm feature) + */ + + + $completion = DB::Aowow()->select('SELECT type AS ARRAY_KEY, typeId AS ARRAY_KEY2, cur, max FROM ?_profiler_completion WHERE id = ?d', $pBase['id']); + foreach ($completion as $type => $data) + { + switch ($type) + { + case TYPE_FACTION: // factionId => amount + $profile['reputation'] = array_combine(array_keys($data), array_column($data, 'cur')); + break; + case TYPE_TITLE: + foreach ($data as &$d) + $d = 1; + + $profile['titles'] = $data; + break; + case TYPE_QUEST: + foreach ($data as &$d) + $d = 1; + + $profile['quests'] = $data; + break; + case TYPE_SPELL: + foreach ($data as &$d) + $d = 1; + + $profile['spells'] = $data; + break; + case TYPE_ACHIEVEMENT: + $achievements = array_filter($data, function ($x) { return $x['max'] === null; }); + $statistics = array_filter($data, function ($x) { return $x['max'] !== null; }); + + // achievements + $profile['achievements'] = array_combine(array_keys($achievements), array_column($achievements, 'cur')); + $profile['achievementpoints'] = DB::Aowow()->selectCell('SELECT SUM(points) FROM ?_achievement WHERE id IN (?a)', array_keys($achievements)); + + // raid progression + $activity = array_filter($statistics, function ($x) { return $x['cur'] > (time() - MONTH); }); + foreach ($activity as &$r) + $r = 1; + + // ony .. subtract 10-man from 25-man + + $profile['statistics'] = array_combine(array_keys($statistics), array_column($statistics, 'max')); + $profile['activity'] = $activity; + break; + case TYPE_SKILL: + foreach ($data as &$d) + $d = [$d['cur'], $d['max']]; + + $profile['skills'] = $data; + break; + } + } $buff = ''; - if ($it = array_column($char->getField('inventory'), 0)) + $usedSlots = []; + if ($this->_get['items']) { - $itemz = new ItemList(array(['id', $it, CFG_SQL_LIMIT_NONE])); - $data = $itemz->getListviewData(ITEMINFO_JSON | ITEMINFO_SUBITEMS); - - // get and apply inventory - foreach ($itemz->iterate() as $iId => $__) - $buff .= 'g_items.add('.$iId.', {name_'.User::$localeString.":'".Util::jsEscape($itemz->getField('name', true))."', quality:".$itemz->getField('quality').", icon:'".$itemz->getField('iconString')."', jsonequip:".Util::toJSON($data[$iId])."});\n"; - - $buff .= "\n"; - } - - if ($au = $char->getField('auras')) - { - $auraz = new SpellList(array(['id', $char->getField('auras')], CFG_SQL_LIMIT_NONE)); - $dataz = $auraz->getListviewData(); - $modz = $auraz->getProfilerMods(); - - // get and apply aura-mods - foreach ($dataz as $id => $data) + $phItems = new ItemList(array(['id', $this->_get['items']], ['slot', INVTYPE_NON_EQUIP, '!'])); + if (!$phItems->error) { - $mods = []; - if (!empty($modz[$id])) + $data = $phItems->getListviewData(ITEMINFO_JSON | ITEMINFO_SUBITEMS); + foreach ($phItems->iterate() as $iId => $__) { - foreach ($modz[$id] as $k => $v) + $sl = $phItems->getField('slot'); + foreach (Profiler::$slot2InvType as $slot => $invTypes) { - if (is_array($v)) - $mods[] = $v; - else if ($str = @Game::$itemMods[$k]) - $mods[$str] = $v; + if (in_array($sl, $invTypes) && !in_array($slot, $usedSlots)) + { + // get and apply inventory + $buff .= 'g_items.add('.$iId.', {name_'.User::$localeString.":'".Util::jsEscape($phItems->getField('name', true))."', quality:".$phItems->getField('quality').", icon:'".$phItems->getField('iconString')."', jsonequip:".Util::toJSON($data[$iId])."});\n"; + $profile['inventory'][$slot] = [$iId, 0, 0, 0, 0, 0, 0, 0]; + + $usedSlots[] = $slot; + break; + } } } - - $buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(mb_substr($data['name'], 1))."', icon:'".$data['icon']."', modifier:".Util::toJSON($mods)."});\n"; } - $buff .= "\n"; } - /* depending on progress-achievements - // required by progress in JScript move to handleLoad()? - Util::$pageTemplate->extendGlobalIds(TYPE_NPC, [29120, 31134, 29306, 29311, 23980, 27656, 26861, 26723, 28923, 15991]); - */ + if ($items = DB::Aowow()->select('SELECT * FROM ?_profiler_items WHERE id = ?d', $pBase['id'])) + { + $itemz = new ItemList(array(['id', array_column($items, 'item')], CFG_SQL_LIMIT_NONE)); + if (!$itemz->error) + { + $data = $itemz->getListviewData(ITEMINFO_JSON | ITEMINFO_SUBITEMS); + + foreach ($items as $i) + { + if ($itemz->getEntry($i['item']) && !in_array($i['slot'], $usedSlots)) + { + // get and apply inventory + $buff .= 'g_items.add('.$i['item'].', {name_'.User::$localeString.":'".Util::jsEscape($itemz->getField('name', true))."', quality:".$itemz->getField('quality').", icon:'".$itemz->getField('iconString')."', jsonequip:".Util::toJSON($data[$i['item']])."});\n"; + $profile['inventory'][$i['slot']] = [$i['item'], $i['subItem'], $i['permEnchant'], $i['tempEnchant'], $i['gem1'], $i['gem2'], $i['gem3'], $i['gem4']]; + } + } + } + } + + if ($buff) + $buff .= "\n"; + + + // if ($au = $char->getField('auras')) + // { + // $auraz = new SpellList(array(['id', $char->getField('auras')], CFG_SQL_LIMIT_NONE)); + // $dataz = $auraz->getListviewData(); + // $modz = $auraz->getProfilerMods(); + + // // get and apply aura-mods + // foreach ($dataz as $id => $data) + // { + // $mods = []; + // if (!empty($modz[$id])) + // { + // foreach ($modz[$id] as $k => $v) + // { + // if (is_array($v)) + // $mods[] = $v; + // else if ($str = @Game::$itemMods[$k]) + // $mods[$str] = $v; + // } + // } + + // $buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(mb_substr($data['name'], 1))."', icon:'".$data['icon']."', modifier:".Util::toJSON($mods)."});\n"; + // } + // $buff .= "\n"; + // } + // load available titles - Util::loadStaticFile('p-titles-'.$char->getField('gender'), $buff, true); - - // load available achievements - if (!Util::loadStaticFile('p-achievements', $buff, true)) - { - $buff .= "\n\ng_achievement_catorder = [];"; - $buff .= "\n\ng_achievement_points = [0];"; - } - - // excludes; structure UNK type => [maskBit => [typeIds]] ? - /* - g_user.excludes = [type:[typeIds]] - g_user.includes = [type:[typeIds]] - g_user.excludegroups = groupMask // requires g_user.settings != null - - maskBit are matched against fieldId from excludeGroups - id: 1, label: LANG.dialog_notavail - id: 2, label: LANG.dialog_tcg - id: 4, label: LANG.dialog_collector - id: 8, label: LANG.dialog_promo - id: 16, label: LANG.dialog_nonus - id: 96, label: LANG.dialog_faction - id: 896, label: LANG.dialog_profession - id: 1024, label: LANG.dialog_noexalted - */ - // $buff .= "\n\ng_excludes = {};"; + Util::loadStaticFile('p-titles-'.$pBase['gender'], $buff, true); // add profile to buffer - $buff .= "\n\n\$WowheadProfiler.registerProfile(".Util::toJSON($char->getEntry(2)).");"; // can't use JSON_NUMERIC_CHECK or the talent-string becomes a float + $buff .= "\n\n\$WowheadProfiler.registerProfile(".Util::toJSON($profile).");"; return $buff."\n"; } - protected function checkId($val) + /* params + id: + data: [string, tabName] + return + null + */ + protected function handlePurge() { } // removes completion data (as uploaded by the wowhead client) Just fail silently if someone triggers this manually + + protected function checkItemList($val) { - // expecting id-list - if (preg_match('/\d+(,\d+)*/', $val)) - return array_map('intVal', explode(',', $val)); + // expecting item-list + if (preg_match('/\d+(:\d+)*/', $val)) + return array_map('intval', explode(':', $val)); return null; } - protected function checkItems($val) + protected function checkItemString($val) { // expecting item-list - if (preg_match('/\d+(:\d+)*/', $val)) - return array_map('intVal', explode(': ', $val)); + if (preg_match('/\d+(,\d+)*/', $val)) + return array_map('intval', explode(',', $val)); return null; } } -?> \ No newline at end of file +?> diff --git a/includes/basetype.class.php b/includes/basetype.class.php index d363f485..f5b1acab 100644 --- a/includes/basetype.class.php +++ b/includes/basetype.class.php @@ -222,8 +222,8 @@ abstract class BaseType if (!in_array($k, $prefixes)) unset($this->queryOpts[$k]); - // prepare usage of guids if using multiple DBs - if (count($this->dbNames) > 1) + // prepare usage of guids if using multiple realms (which have non-zoro indizes) + if (key($this->dbNames) != 0) $this->queryBase = preg_replace('/\s([^\s]+)\sAS\sARRAY_KEY/i', ' CONCAT("DB_IDX", ":", \1) AS ARRAY_KEY', $this->queryBase); // insert additional selected fields @@ -278,10 +278,6 @@ abstract class BaseType if (!$this->templates) return; - // assign query results to template - foreach ($rows as $k => $tpl) - $this->templates[$k] = $tpl; - // push first element for instant use $this->reset(); @@ -388,12 +384,8 @@ abstract class BaseType protected function extendQueryOpts($extra) // needs to be called from __construct { - foreach ($extra as $tbl => $sets) { - if (!isset($this->queryOpts[$tbl])) // allow adding only to known tables - continue; - foreach ($sets as $module => $value) { if (!$value || !is_array($value)) @@ -419,7 +411,7 @@ abstract class BaseType break; // additional (arr) case 'j': // join - if (is_array($this->queryOpts[$tbl][$module])) + if (!empty($this->queryOpts[$tbl][$module]) && is_array($this->queryOpts[$tbl][$module])) $this->queryOpts[$tbl][$module][0][] = $value; else $this->queryOpts[$tbl][$module] = $value; @@ -744,6 +736,32 @@ trait spawnHelper } } +trait profilerHelper +{ + public static $type = 0; // arena teams dont actually have one + public static $brickFile = 'profile'; // profile is multipurpose + + private static $subjectGUID = 0; + + public function selectRealms($fi) + { + $this->dbNames = []; + + foreach(Profiler::getRealms() as $idx => $r) + { + if (!empty($fi['sv']) && Profiler::urlize($r['name']) != Profiler::urlize($fi['sv']) && intVal($fi['sv']) != $idx) + continue; + + if (!empty($fi['rg']) && Profiler::urlize($r['region']) != Profiler::urlize($fi['rg'])) + continue; + + $this->dbNames[$idx] = 'Characters'; + } + + return !!$this->dbNames; + } +} + /* roight! just noticed, that the filters on pages originally pointed to ?filter= diff --git a/includes/defines.php b/includes/defines.php index 623a77c0..4f92bbdc 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -28,7 +28,10 @@ define('TYPE_SKILL', 15); define('TYPE_CURRENCY', 17); define('TYPE_SOUND', 19); define('TYPE_ICON', 29); +define('TYPE_PROFILE', 100); // internal types (not published to js) +define('TYPE_GUILD', 101); +define('TYPE_ARENA_TEAM', 102); define('TYPE_USER', 500); define('TYPE_EMOTE', 501); define('TYPE_ENCHANTMENT', 502); @@ -165,6 +168,7 @@ define('BUTTON_FORUM', 5); define('BUTTON_TALENT', 6); define('BUTTON_EQUIP', 7); define('BUTTON_PLAYLIST', 8); +define('BUTTON_RESYNC', 9); // generic filter handler define('FILTER_CR_BOOLEAN', 1); @@ -204,11 +208,17 @@ define('NPCINFO_REP', 0x4); define('ACHIEVEMENTINFO_PROFILE', 0x1); +define('PROFILEINFO_PROFILE', 0x1); +define('PROFILEINFO_CHARACTER', 0x2); +define('PROFILEINFO_GUILD', 0x10); // like &roster +define('PROFILEINFO_ARENA', 0x20); + define('SPAWNINFO_ZONES', 1); // not a mask, mutually exclusive define('SPAWNINFO_SHORT', 2); define('SPAWNINFO_FULL', 3); define('SPAWNINFO_QUEST', 4); + // Community Content define('CC_FLAG_STICKY', 0x1); define('CC_FLAG_DELETED', 0x2); @@ -287,6 +297,12 @@ define('QUEST_CU_SKIP_LOG', 0x10); define('QUEST_CU_AUTO_ACCEPT', 0x20); define('QUEST_CU_PVP_ENABLED', 0x40); +define('PROFILER_CU_PUBLISHED', 0x01); +define('PROFILER_CU_PINNED', 0x02); +define('PROFILER_CU_DELETED', 0x04); +define('PROFILER_CU_PROFILE', 0x08); +define('PROFILER_CU_NEEDS_RESYNC', 0x10); + define('MAX_LEVEL', 80); define('WOW_BUILD', 12340); @@ -869,4 +885,31 @@ define('CND_DISTANCE_TO', 35); // distance to targe define('CND_ALIVE', 36); // target is alive: NULL, NULL, NULL define('CND_HP_VAL', 37); // targets absolute health: amount, operator, NULL define('CND_HP_PCT', 38); // targets relative health: amount, operator, NULL + +// profiler queue interactions +define('PR_QUEUE_STATUS_ENDED', 0); +define('PR_QUEUE_STATUS_WAITING', 1); +define('PR_QUEUE_STATUS_WORKING', 2); +define('PR_QUEUE_STATUS_READY', 3); +define('PR_QUEUE_STATUS_ERROR', 4); +define('PR_QUEUE_ERROR_UNK', 0); +define('PR_QUEUE_ERROR_CHAR', 1); +define('PR_QUEUE_ERROR_ARMORY', 2); + +// profiler completion manager +define('PR_EXCLUDE_GROUP_UNAVAILABLE', 0x001); +define('PR_EXCLUDE_GROUP_TCG', 0x002); +define('PR_EXCLUDE_GROUP_COLLECTORS_EDITION', 0x004); +define('PR_EXCLUDE_GROUP_PROMOTION', 0x008); +define('PR_EXCLUDE_GROUP_WRONG_REGION', 0x010); +define('PR_EXCLUDE_GROUP_REQ_ALLIANCE', 0x020); +define('PR_EXCLUDE_GROUP_REQ_HORDE', 0x040); +define('PR_EXCLUDE_GROUP_OTHER_FACTION', PR_EXCLUDE_GROUP_REQ_ALLIANCE | PR_EXCLUDE_GROUP_REQ_HORDE); +define('PR_EXCLUDE_GROUP_REQ_FISHING', 0x080); +define('PR_EXCLUDE_GROUP_REQ_ENGINEERING', 0x100); +define('PR_EXCLUDE_GROUP_REQ_TAILORING', 0x200); +define('PR_EXCLUDE_GROUP_WRONG_PROFESSION', PR_EXCLUDE_GROUP_REQ_FISHING | PR_EXCLUDE_GROUP_REQ_ENGINEERING | PR_EXCLUDE_GROUP_REQ_TAILORING); +define('PR_EXCLUDE_GROUP_REQ_CANT_BE_EXALTED', 0x400); +define('PR_EXCLUDE_GROUP_ANY', 0x7FF); + ?> diff --git a/includes/kernel.php b/includes/kernel.php index caf0023f..d2f0a283 100644 --- a/includes/kernel.php +++ b/includes/kernel.php @@ -20,6 +20,7 @@ require_once 'includes/defines.php'; require_once 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master) require_once 'includes/utilities.php'; // helper functions require_once 'includes/game.php'; // game related data & functions +require_once 'includes/profiler.class.php'; require_once 'includes/user.class.php'; require_once 'includes/markup.class.php'; // manipulate markup text require_once 'includes/database.class.php'; // wrap DBSimple @@ -43,10 +44,18 @@ spl_autoload_register(function ($class) { { require_once 'includes/basetype.class.php'; - if (file_exists('includes/types/'.strtr($class, ['list' => '']).'.class.php')) - require_once 'includes/types/'.strtr($class, ['list' => '']).'.class.php'; + $cl = strtr($class, ['list' => '']); + if ($cl == 'remoteprofile' || $cl == 'localprofile') + $cl = 'profile'; + if ($cl == 'remotearenateam' || $cl == 'localarenateam') + $cl = 'arenateam'; + if ($cl == 'remoteguild' || $cl == 'localguild') + $cl = 'guild'; + + if (file_exists('includes/types/'.$cl.'.class.php')) + require_once 'includes/types/'.$cl.'.class.php'; else - throw new Exception('could not register type class: '.$class); + throw new Exception('could not register type class: '.$cl); return; } @@ -206,7 +215,7 @@ if (!CLI) die('error: SITE_HOST or STATIC_HOST not configured'); // Setup Session - if (CFG_SESSION_CACHE_DIR && Util::checkOrCreateDirectory(CFG_SESSION_CACHE_DIR)) + if (CFG_SESSION_CACHE_DIR && Util::writeDir(CFG_SESSION_CACHE_DIR)) session_save_path(getcwd().'/'.CFG_SESSION_CACHE_DIR); session_set_cookie_params(15 * YEAR, '/', '', $secure, true); diff --git a/includes/profiler.class.php b/includes/profiler.class.php new file mode 100644 index 00000000..6be872d5 --- /dev/null +++ b/includes/profiler.class.php @@ -0,0 +1,828 @@ + [INVTYPE_HEAD], // head + 2 => [INVTYPE_NECK], // neck + 3 => [INVTYPE_SHOULDERS], // shoulder + 4 => [INVTYPE_BODY], // shirt + 5 => [INVTYPE_CHEST, INVTYPE_ROBE], // chest + 6 => [INVTYPE_WAIST], // waist + 7 => [INVTYPE_LEGS], // legs + 8 => [INVTYPE_FEET], // feet + 9 => [INVTYPE_WRISTS], // wrists + 10 => [INVTYPE_HANDS], // hands + 11 => [INVTYPE_FINGER], // finger1 + 12 => [INVTYPE_FINGER], // finger2 + 13 => [INVTYPE_TRINKET], // trinket1 + 14 => [INVTYPE_TRINKET], // trinket2 + 15 => [INVTYPE_CLOAK], // chest + 16 => [INVTYPE_WEAPONMAINHAND, INVTYPE_WEAPON, INVTYPE_2HWEAPON], // mainhand + 17 => [INVTYPE_WEAPONOFFHAND, INVTYPE_WEAPON, INVTYPE_HOLDABLE, INVTYPE_SHIELD], // offhand + 18 => [INVTYPE_RANGED, INVTYPE_THROWN, INVTYPE_RELIC], // ranged + relic + 19 => [INVTYPE_TABARD], // tabard + ); + + public static $raidProgression = array( // statisticAchievement => relevantCriterium + 1098 => 3271, // Onyxia's Lair 10 + 1756 => 13345, // Onyxia's Lair 25 + 4031 => 12230, 4034 => 12234, 4038 => 12238, 4042 => 12242, 4046 => 12246, // Trial of the Crusader 25 nh + 4029 => 12231, 4035 => 12235, 4039 => 12239, 4043 => 12243, 4047 => 12247, // Trial of the Crusader 25 hc + 4030 => 12229, 4033 => 12233, 4037 => 12237, 4041 => 12241, 4045 => 12245, // Trial of the Crusader 10 hc + 4028 => 12228, 4032 => 12232, 4036 => 12236, 4040 => 12240, 4044 => 12244, // Trial of the Crusader 10 nh + 4642 => 13091, 4656 => 13106, 4661 => 13111, 4664 => 13114, 4667 => 13117, 4670 => 13120, 4673 => 13123, 4676 => 13126, 4679 => 13129, 4682 => 13132, 4685 => 13135, 4688 => 13138, // Icecrown Citadel 25 hc + 4641 => 13092, 4655 => 13105, 4660 => 13109, 4663 => 13112, 4666 => 13115, 4669 => 13118, 4672 => 13121, 4675 => 13124, 4678 => 13127, 4681 => 13130, 4683 => 13133, 4687 => 13136, // Icecrown Citadel 25 nh + 4640 => 13090, 4654 => 13104, 4659 => 13110, 4662 => 13113, 4665 => 13116, 4668 => 13119, 4671 => 13122, 4674 => 13125, 4677 => 13128, 4680 => 13131, 4684 => 13134, 4686 => 13137, // Icecrown Citadel 10 hc + 4639 => 13089, 4643 => 13093, 4644 => 13094, 4645 => 13095, 4646 => 13096, 4647 => 13097, 4648 => 13098, 4649 => 13099, 4650 => 13100, 4651 => 13101, 4652 => 13102, 4653 => 13103, // Icecrown Citadel 10 nh + // 4823 => 13467, // Ruby Sanctum 25 hc + // 4820 => 13465, // Ruby Sanctum 25 nh + // 4822 => 13468, // Ruby Sanctum 10 hc + // 4821 => 13466, // Ruby Sanctum 10 nh + ); + + public static function getBuyoutForItem($itemId) + { + if (!$itemId) + return 0; + + // try, when having filled char-DB at hand + // return DB::Characters()->selectCell('SELECT SUM(a.buyoutprice) / SUM(ii.count) FROM auctionhouse a JOIN item_instance ii ON ii.guid = a.itemguid WHERE ii.itemEntry = ?d', $itemId); + return 0; + } + + public static function queueStart(&$msg = '') + { + $queuePID = self::queueStatus(); + + if ($queuePID) + { + $msg = 'queue already running'; + return true; + } + + if (OS_WIN) // here be gremlins! .. suggested was "start /B php prQueue" as background process. but that closes itself + pclose(popen('start php prQueue --log=cache/profiling.log', 'r')); + else + exec('php prQueue --log=cache/profiling.log > /dev/null 2>/dev/null &'); + + usleep(500000); + if (self::queueStatus()) + return true; + else + { + $msg = 'failed to start queue'; + return false; + } + } + + public static function queueStatus() + { + if (!file_exists(self::PID_FILE)) + return 0; + + $pid = file_get_contents(self::PID_FILE); + $cmd = OS_WIN ? 'tasklist /NH /FO CSV /FI "PID eq %d"' : 'ps --no-headers p %d'; + + exec(sprintf($cmd, $pid), $out); + if ($out && stripos($out[0], $pid) !== false) + return $pid; + + // have pidFile but no process with this pid + self::queueFree(); + return 0; + } + + public static function queueLock($pid) + { + $queuePID = self::queueStatus(); + if ($queuePID && $queuePID != $pid) + { + trigger_error('pSync - another queue with PID #'.$queuePID.' is already running', E_USER_ERROR); + CLI::write('Profiler::queueLock() - another queue with PID #'.$queuePID.' is already runnung', CLI::LOG_ERROR); + return false; + } + + // no queue running; create or overwrite pidFile + $ok = false; + if ($fh = fopen(self::PID_FILE, 'w')) + { + if (fwrite($fh, $pid)) + $ok = true; + + fclose($fh); + } + + return $ok; + } + + public static function queueFree() + { + unlink(self::PID_FILE); + } + + public static function urlize($str, $allowLocales = false, $profile = false) + { + $search = ['<', '>', ' / ', "'"]; + $replace = ['<', '>', '-', '' ]; + $str = str_replace($search, $replace, $str); + + if ($profile) + { + $str = str_replace(['(', ')'], ['', ''], $str); + $accents = array( + "ß" => "ss", + "á" => "a", "ä" => "a", "à" => "a", "â" => "a", + "è" => "e", "ê" => "e", "é" => "e", "ë" => "e", + "í" => "i", "î" => "i", "ì" => "i", "ï" => "i", + "ñ" => "n", + "ò" => "o", "ó" => "o", "ö" => "o", "ô" => "o", + "ú" => "u", "ü" => "u", "û" => "u", "ù" => "u", + "œ" => "oe", + "Á" => "A", "Ä" => "A", "À" => "A", "Â" => "A", + "È" => "E", "Ê" => "E", "É" => "E", "Ë" => "E", + "Í" => "I", "Î" => "I", "Ì" => "I", "Ï" => "I", + "Ñ" => "N", + "Ò" => "O", "Ó" => "O", "Ö" => "O", "Ô" => "O", + "Ú" => "U", "Ü" => "U", "Û" => "U", "Ù" => "U", + "Œ" => "Oe" + ); + $str = strtr($str, $accents); + } + + $str = trim($str); + + if ($allowLocales) + $str = str_replace(' ', '-', $str); + else + $str = preg_replace('/[^a-z0-9]/i', '-', $str); + + $str = str_replace('--', '-', $str); + $str = str_replace('--', '-', $str); + + $str = rtrim($str, '-'); + $str = strtolower($str); + + return $str; + } + + public static function getRealms() + { + if (DB::isConnectable(DB_AUTH) && !self::$realms) + { + self::$realms = DB::Auth()->select('SELECT id AS ARRAY_KEY, name, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0 AND gamebuild = ?d', WOW_BUILD); + foreach (self::$realms as $rId => $rData) + { + if (DB::isConnectable(DB_CHARACTERS . $rId)) + continue; + + // realm in db but no connection info set + unset(self::$realms[$rId]); + } + } + + return self::$realms; + } + + private static function queueInsert($realmId, $guid, $type, $localId) + { + if ($rData = DB::Aowow()->selectRow('SELECT requestTime AS time, status FROM ?_profiler_sync WHERE realm = ?d AND realmGUID = ?d AND `type` = ?d AND typeId = ?d AND status <> ?d', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WORKING)) + { + // not on already scheduled - recalc time and set status to PR_QUEUE_STATUS_WAITING + if ($rData['status'] != PR_QUEUE_STATUS_WAITING) + { + $newTime = CFG_DEBUG ? time() : max($rData['time'] + CFG_PROFILER_RESYNC_DELAY, time()); + DB::Aowow()->query('UPDATE ?_profiler_sync SET requestTime = ?d, status = ?d, errorCode = 0 WHERE realm = ?d AND realmGUID = ?d AND `type` = ?d AND typeId = ?d', $newTime, PR_QUEUE_STATUS_WAITING, $realmId, $guid, $type, $localId); + } + } + else + DB::Aowow()->query('REPLACE INTO ?_profiler_sync (realm, realmGUID, `type`, typeId, requestTime, status, errorCode) VALUES (?d, ?d, ?d, ?d, UNIX_TIMESTAMP(), ?d, 0)', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WAITING); + } + + public static function scheduleResync($type, $realmId, $guid) + { + $newId = 0; + + switch ($type) + { + case TYPE_PROFILE: + if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid)) + self::queueInsert($realmId, $guid, TYPE_PROFILE, $newId); + + break; + case TYPE_GUILD: + if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_guild WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid)) + self::queueInsert($realmId, $guid, TYPE_GUILD, $newId); + + break; + case TYPE_ARENA_TEAM: + if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_arena_team WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid)) + self::queueInsert($realmId, $guid, TYPE_ARENA_TEAM, $newId); + + break; + default: + trigger_error('scheduling resync for unknown type #'.$type.' omiting..', E_USER_WARNING); + return 0; + } + + if (!$newId) + trigger_error('Profiler::scheduleResync() - tried to resync type #'.$type.' guid #'.$guid.' from realm #'.$realmId.' without preloaded data', E_USER_ERROR); + else if (!self::queueStart($msg)) + trigger_error('Profiler::scheduleResync() - '.$msg, E_USER_ERROR); + + return $newId; + } + + public static function resyncStatus($type, array $subjectGUIDs) + { + $response = [CFG_PROFILER_QUEUE ? 2 : 0]; // in theory you could have multiple queues; used as divisor for: (15 / x) + 2 + if (!$subjectGUIDs) + $response[] = [PR_QUEUE_STATUS_ENDED, 0, 0, PR_QUEUE_ERROR_CHAR]; + else + { + // error out all profiles with status WORKING, that are older than 60sec + DB::Aowow()->query('UPDATE ?_profiler_sync SET status = ?d, errorCode = ?d WHERE status = ?d AND requestTime < ?d', PR_QUEUE_STATUS_ERROR, PR_QUEUE_ERROR_UNK, PR_QUEUE_STATUS_WORKING, time() - MINUTE); + + $subjectStatus = DB::Aowow()->select('SELECT typeId AS ARRAY_KEY, status, realm FROM ?_profiler_sync WHERE `type` = ?d AND typeId IN (?a)', $type, $subjectGUIDs); + $queue = DB::Aowow()->selectCol('SELECT CONCAT(type, ":", typeId) FROM ?_profiler_sync WHERE status = ?d AND requestTime < UNIX_TIMESTAMP() ORDER BY requestTime ASC', PR_QUEUE_STATUS_WAITING); + foreach ($subjectGUIDs as $guid) + { + if (empty($subjectStatus[$guid])) // whelp, thats some error.. + $response[] = [PR_QUEUE_STATUS_ERROR, 0, 0, PR_QUEUE_ERROR_UNK]; + else if ($subjectStatus[$guid]['status'] == PR_QUEUE_STATUS_ERROR) + $response[] = [PR_QUEUE_STATUS_ERROR, 0, 0, $subjectStatus[$guid]['errCode']]; + else + $response[] = array( + $subjectStatus[$guid]['status'], + $subjectStatus[$guid]['status'] != PR_QUEUE_STATUS_READY ? CFG_PROFILER_RESYNC_PING : 0, + array_search($type.':'.$guid, $queue) + 1, + 0, + 1 // nResycTries - unsure about this one + ); + } + } + + return $response; + } + + public static function getCharFromRealm($realmId, $charGuid) + { + $char = DB::Characters($realmId)->selectRow('SELECT c.* FROM characters c WHERE c.guid = ?d', $charGuid); + if (!$char) + return false; + + // reminder: this query should not fail: a placeholder entry is created as soon as a char listview is created or profile detail page is called + $profileId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $realmId, $char['guid']); + + CLI::write('fetching char #'.$charGuid.' from realm #'.$realmId); + CLI::write('writing...'); + + + /*************/ + /* equipment */ + /*************/ + + /* enchantment-Indizes + * 0: permEnchant + * 3: tempEnchant + * 6: gem1 + * 9: gem2 + * 12: gem3 + * 15: socketBonus [not used] + * 18: extraSocket [only check existance] + * 21 - 30: randomProp enchantments + */ + + + DB::Aowow()->query('DELETE FROM ?_profiler_items WHERE id = ?d', $profileId); + $items = DB::Characters($realmId)->select('SELECT ci.slot AS ARRAY_KEY, ii.itemEntry, ii.enchantments, ii.randomPropertyId FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid WHERE ci.guid = ?d AND bag = 0 AND slot BETWEEN 0 AND 18', $char['guid']); + + $gemItems = []; + $permEnch = []; + $mhItem = 0; + $ohItem = 0; + + foreach ($items as $slot => $item) + { + $ench = explode(' ', $item['enchantments']); + $gEnch = []; + foreach ([6, 9, 12] as $idx) + if ($ench[$idx]) + $gEnch[$idx] = $ench[$idx]; + + if ($gEnch) + { + $gi = DB::Aowow()->selectCol('SELECT gemEnchantmentId AS ARRAY_KEY, id FROM ?_items WHERE class = 3 AND gemEnchantmentId IN (?a)', $gEnch); + foreach ($gEnch as $eId) + { + if (isset($gemItems[$eId])) + $gemItems[$eId][1]++; + else + $gemItems[$eId] = [$gi[$eId], 1]; + } + } + + if ($slot + 1 == 16) + $mhItem = $item['itemEntry']; + if ($slot + 1 == 17) + $ohItem = $item['itemEntry']; + + if ($ench[0]) + $permEnch[$slot] = $ench[0]; + + $data = array( + 'id' => $profileId, + 'slot' => $slot + 1, + 'item' => $item['itemEntry'], + 'subItem' => $item['randomPropertyId'], + 'permEnchant' => $ench[0], + 'tempEnchant' => $ench[3], + 'extraSocket' => (int)!!$ench[18], + 'gem1' => isset($gemItems[$ench[6]]) ? $gemItems[$ench[6]][0] : 0, + 'gem2' => isset($gemItems[$ench[9]]) ? $gemItems[$ench[9]][0] : 0, + 'gem3' => isset($gemItems[$ench[12]]) ? $gemItems[$ench[12]][0] : 0, + 'gem4' => 0 // serverside items cant have more than 3 sockets. (custom profile thing) + ); + + DB::Aowow()->query('INSERT INTO ?_profiler_items (?#) VALUES (?a)', array_keys($data), array_values($data)); + } + + CLI::write(' ..inventory'); + + + /**************/ + /* basic info */ + /**************/ + + $data = array( + 'realm' => $realmId, + 'realmGUID' => $charGuid, + 'name' => $char['name'], + 'race' => $char['race'], + 'class' => $char['class'], + 'level' => $char['level'], + 'gender' => $char['gender'], + 'skincolor' => $char['playerBytes'] & 0xFF, + 'facetype' => ($char['playerBytes'] >> 8) & 0xFF, // maybe features + 'hairstyle' => ($char['playerBytes'] >> 16) & 0xFF, + 'haircolor' => ($char['playerBytes'] >> 24) & 0xFF, + 'features' => $char['playerBytes2'] & 0xFF, // maybe facetype + 'title' => $char['chosenTitle'] ? DB::Aowow()->selectCell('SELECT id FROM ?_titles WHERE bitIdx = ?d', $char['chosenTitle']) : 0, + 'playedtime' => $char['totaltime'], + 'nomodelMask' => ($char['playerFlags'] & 0x400 ? (1 << SLOT_HEAD) : 0) | ($char['playerFlags'] & 0x800 ? (1 << SLOT_BACK) : 0), + 'talenttree1' => 0, + 'talenttree2' => 0, + 'talenttree3' => 0, + 'talentbuild1' => '', + 'talentbuild2' => '', + 'glyphs1' => '', + 'glyphs2' => '', + 'activespec' => $char['activespec'], + 'guild' => null, + 'guildRank' => null, + 'gearscore' => 0, + 'achievementpoints' => 0 + ); + + + /********************/ + /* talents + glyphs */ + /********************/ + + $t = DB::Characters($realmId)->selectCol('SELECT spec AS ARRAY_KEY, spell AS ARRAY_KEY2, spell FROM character_talent WHERE guid = ?d', $char['guid']); + $g = DB::Characters($realmId)->select('SELECT spec AS ARRAY_KEY, glyph1 AS g1, glyph2 AS g4, glyph3 AS g5, glyph4 AS g2, glyph5 AS g3, glyph6 AS g6 FROM character_glyphs WHERE guid = ?d', $char['guid']); + for ($i = 0; $i < 2; $i++) + { + // talents + for ($j = 0; $j < 3; $j++) + { + $_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell in (?a), rank, 0)) FROM ?_talents WHERE class = ?d AND tab = ?d GROUP BY id ORDER BY row, col ASC', !empty($t[$i]) ? $t[$i] : [0], $char['class'], $j); + $data['talentbuild'.($i + 1)] .= implode('', $_); + if ($char['activespec'] == $i) + $data['talenttree'.($j + 1)] = array_sum($_); + } + + // glyphs + if (isset($g[$i])) + { + $gProps = []; + for ($j = 1; $j <= 6; $j++) + if ($g[$i]['g'.$j]) + $gProps[$j] = $g[$i]['g'.$j]; + + if ($gProps) + if ($gItems = DB::Aowow()->selectCol('SELECT i.id FROM ?_glyphproperties gp JOIN ?_spell s ON s.effect1MiscValue = gp.id AND s.effect1Id = 74 JOIN ?_items i ON i.class = 16 AND i.spellId1 = s.id WHERE gp.id IN (?a)', $gProps)) + $data['glyphs'.($i + 1)] = implode(':', $gItems); + } + } + + $t = array( + 'spent' => [$data['talenttree1'], $data['talenttree2'], $data['talenttree3']], + 'spec' => 0 + ); + if ($t['spent'][0] > $t['spent'][1] && $t['spent'][0] > $t['spent'][2]) + $t['spec'] = 1; + else if ($t['spent'][1] > $t['spent'][0] && $t['spent'][1] > $t['spent'][2]) + $t['spec'] = 2; + else if ($t['spent'][2] > $t['spent'][1] && $t['spent'][2] > $t['spent'][0]) + $t['spec'] = 3; + + // calc gearscore + if ($items) + $data['gearscore'] += (new ItemList(array(['id', array_column($items, 'itemEntry')])))->getScoreTotal($data['class'], $t, $mhItem, $ohItem); + + if ($gemItems) + { + $gemScores = new ItemList(array(['id', array_column($gemItems, 0)])); + foreach ($gemItems as list($itemId, $mult)) + if (isset($gemScores->json[$itemId]['gearscore'])) + $data['gearscore'] += $gemScores->json[$itemId]['gearscore'] * $mult; + } + + if ($permEnch) // fuck this shit .. we are guestimating this! + { + // enchantId => multiple spells => multiple items with varying itemlevels, quality, whatevs + // cant reasonably get to the castItem from enchantId and slot + + $profSpec = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, skillLevel AS "1", skillLine AS "0" FROM ?_itemenchantment WHERE id IN (?a)', $permEnch); + foreach ($permEnch as $eId) + { + if ($x = Util::getEnchantmentScore(0, 0, !!$profSpec[$eId][1], $eId)) + $data['gearscore'] += $x; + else if ($profSpec[$eId][0] != 776) // not runeforging + $data['gearscore'] += 17; // assume high quality enchantment for unknown cases + } + } + + $data['lastupdated'] = time(); + + CLI::write(' ..basic info'); + + + /***************/ + /* hunter pets */ + /***************/ + + if ((1 << ($char['class'] - 1)) == CLASS_HUNTER) + { + DB::Aowow()->query('DELETE FROM ?_profiler_pets WHERE owner = ?d', $profileId); + $pets = DB::Characters($realmId)->select('SELECT id AS ARRAY_KEY, id, entry, modelId, name FROM character_pet WHERE owner = ?d', $charGuid); + foreach ($pets as $petGuid => $petData) + { + $morePet = DB::Aowow()->selectRow('SELECT p.`type`, c.family FROM ?_pet p JOIN ?_creature c ON c.family = p.id WHERE c.id = ?d', $petData['entry']); + $petSpells = DB::Characters($realmId)->selectCol('SELECT spell FROM pet_spell WHERE guid = ?d', $petGuid); + + $_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell in (?a), rank, 0)) FROM ?_talents WHERE class = 0 AND petTypeMask = ?d GROUP BY id ORDER BY row, col ASC', $petSpells ?: [0], 1 << $morePet['type']); + $pet = array( + 'id' => $petGuid, + 'owner' => $profileId, + 'name' => $petData['name'], + 'family' => $morePet['family'], + 'npc' => $petData['entry'], + 'displayId' => $petData['modelId'], + 'talents' => implode('', $_) + ); + + DB::Aowow()->query('INSERT INTO ?_profiler_pets (?#) VALUES (?a)', array_keys($pet), array_values($pet)); + } + + CLI::write(' ..hunter pets'); + } + + + /*******************/ + /* completion data */ + /*******************/ + + DB::Aowow()->query('DELETE FROM ?_profiler_completion WHERE id = ?d', $profileId); + + // done quests + if ($quests = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, quest AS typeId FROM character_queststatus_rewarded WHERE guid = ?d', $profileId, TYPE_QUEST, $char['guid'])) + foreach (Util::createSqlBatchInsert($quests) as $q) + DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$q, array_keys($quests[0])); + + CLI::write(' ..quests'); + + + // known skills (professions only) + $skAllowed = DB::Aowow()->selectCol('SELECT id FROM ?_skillline WHERE typeCat IN (9, 11) AND (cuFlags & ?d) = 0', CUSTOM_EXCLUDE_FOR_LISTVIEW); + $skills = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, skill AS typeId, `value` AS cur, max FROM character_skills WHERE guid = ?d AND skill IN (?a)', $profileId, TYPE_SKILL, $char['guid'], $skAllowed); + + // manually apply racial profession bonuses + foreach ($skills as &$sk) + { + // Blood Elves - Arcane Affinity + if ($sk['typeId'] == 333 && $char['race'] == 10) + { + $sk['cur'] += 10; + $sk['max'] += 10; + } + // Draenei - Gemcutting + if ($sk['typeId'] == 755 && $char['race'] == 11) + { + $sk['cur'] += 5; + $sk['max'] += 5; + } + // Tauren - Cultivation + // Gnomes - Engineering Specialization + if (($sk['typeId'] == 182 && $char['race'] == 6) || + ($sk['typeId'] == 202 && $char['race'] == 7)) + { + $sk['cur'] += 15; + $sk['max'] += 15; + } + } + unset($sk); + + if ($skills) + foreach (Util::createSqlBatchInsert($skills) as $sk) + DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$sk, array_keys($skills[0])); + + CLI::write(' ..professions'); + + + // reputation + if ($reputation = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, faction AS typeId, standing AS cur FROM character_reputation WHERE guid = ?d AND (flags & 0xC) = 0', $profileId, TYPE_FACTION, $char['guid'])) + foreach (Util::createSqlBatchInsert($reputation) as $rep) + DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$rep, array_keys($reputation[0])); + + CLI::write(' ..reputation'); + + + // known titles + $tBlocks = explode(' ', $char['knownTitles']); + $indizes = []; + for ($i = 0; $i < 6; $i++) + for ($j = 0; $j < 32; $j++) + if ($tBlocks[$i] & (1 << $j)) + $indizes[] = $j + ($i * 32); + + if ($indizes) + DB::Aowow()->query('INSERT INTO ?_profiler_completion SELECT ?d, ?d, id, NULL, NULL FROM ?_titles WHERE bitIdx IN (?a)', $profileId, TYPE_TITLE, $indizes); + + CLI::write(' ..titles'); + + + // achievements + if ($achievements = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, achievement AS typeId, date AS cur FROM character_achievement WHERE guid = ?d', $profileId, TYPE_ACHIEVEMENT, $char['guid'])) + { + foreach (Util::createSqlBatchInsert($achievements) as $a) + DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$a, array_keys($achievements[0])); + + $data['achievementpoints'] = DB::Aowow()->selectCell('SELECT SUM(points) FROM ?_achievement WHERE id IN (?a)', array_column($achievements, 'typeId')); + } + + CLI::write(' ..achievements'); + + + // raid progression + if ($progress = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, criteria AS typeId, date AS cur, counter AS `max` FROM character_achievement_progress WHERE guid = ?d AND criteria IN (?a)', $profileId, TYPE_ACHIEVEMENT, $char['guid'], self::$raidProgression)) + { + array_walk($progress, function (&$val) { $val['typeId'] = array_search($val['typeId'], self::$raidProgression); }); + foreach (Util::createSqlBatchInsert($progress) as $p) + DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$p, array_keys($progress[0])); + } + + CLI::write(' ..raid progression'); + + + // known spells + if ($spells = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, spell AS typeId FROM character_spell WHERE guid = ?d AND disabled = 0', $profileId, TYPE_SPELL, $char['guid'])) + foreach (Util::createSqlBatchInsert($spells) as $s) + DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$s, array_keys($spells[0])); + + CLI::write(' ..known spells (vanity pets & mounts)'); + + + /****************/ + /* related data */ + /****************/ + + // guilds + if ($guild = DB::Characters($realmId)->selectRow('SELECT g.name AS name, g.guildid AS id, gm.rank FROM guild_member gm JOIN guild g ON g.guildid = gm.guildid WHERE gm.guid = ?d', $char['guid'])) + { + $guildId = 0; + if (!($guildId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_guild WHERE realm = ?d AND realmGUID = ?d', $realmId, $guild['id']))) + { + $gData = array( // only most basic data + 'realm' => $realmId, + 'realmGUID' => $guild['id'], + 'name' => $guild['name'], + 'nameUrl' => self::urlize($guild['name']), + 'cuFlags' => PROFILER_CU_NEEDS_RESYNC + ); + + $guildId = DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES (?a)', array_keys($gData), array_values($gData)); + } + + $data['guild'] = $guildId; + $data['guildRank'] = $guild['rank']; + } + + + // arena teams + $teams = DB::Characters($realmId)->select('SELECT at.arenaTeamId AS ARRAY_KEY, at.name, at.type, IF(at.captainGuid = atm.guid, 1, 0) AS captain, atm.* FROM arena_team at JOIN arena_team_member atm ON atm.arenaTeamId = at.arenaTeamId WHERE atm.guid = ?d', $char['guid']); + foreach ($teams as $rGuid => $t) + { + $teamId = 0; + if (!($teamId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_arena_team WHERE realm = ?d AND realmGUID = ?d', $realmId, $rGuid))) + { + $team = array( // only most basic data + 'realm' => $realmId, + 'realmGUID' => $rGuid, + 'name' => $t['name'], + 'nameUrl' => self::urlize($t['name']), + 'type' => $t['type'], + 'cuFlags' => PROFILER_CU_NEEDS_RESYNC + ); + + $teamId = DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team (?#) VALUES (?a)', array_keys($team), array_values($team)); + } + + $member = array( + 'arenaTeamId' => $teamId, + 'profileId' => $profileId, + 'captain' => $t['captain'], + 'weekGames' => $t['weekGames'], + 'weekWins' => $t['weekWins'], + 'seasonGames' => $t['seasonGames'], + 'seasonWins' => $t['seasonWins'], + 'personalRating' => $t['personalRating'] + ); + + DB::Aowow()->query('INSERT INTO ?_profiler_arena_team_member (?#) VALUES (?a) ON DUPLICATE KEY UPDATE ?a', array_keys($member), array_values($member), array_slice($member, 2)); + } + + CLI::write(' ..associated arena teams'); + + /*********************/ + /* mark char as done */ + /*********************/ + + if (DB::Aowow()->query('UPDATE ?_profiler_profiles SET ?a WHERE realm = ?d AND realmGUID = ?d', $data, $realmId, $charGuid) !== null) + DB::Aowow()->query('UPDATE ?_profiler_profiles SET cuFlags = cuFlags & ?d WHERE id = ?d', ~PROFILER_CU_NEEDS_RESYNC, $profileId); + + return true; + } + + public static function getGuildFromRealm($realmId, $guildGuid) + { + $guild = DB::Characters($realmId)->selectRow('SELECT guildId, name, createDate, info, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor FROM guild WHERE guildId = ?d', $guildGuid); + if (!$guild) + return false; + + // reminder: this query should not fail: a placeholder entry is created as soon as a team listview is created or team detail page is called + $guildId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_guild WHERE realm = ?d AND realmGUID = ?d', $realmId, $guild['guildId']); + + CLI::write('fetching guild #'.$guildGuid.' from realm #'.$realmId); + CLI::write('writing...'); + + + /**************/ + /* Guild Data */ + /**************/ + + unset($guild['guildId']); + $guild['nameUrl'] = self::urlize($guild['name']); + + DB::Aowow()->query('UPDATE ?_profiler_guild SET ?a WHERE realm = ?d AND realmGUID = ?d', $guild, $realmId, $guildGuid); + + // ranks + DB::Aowow()->query('DELETE FROM ?_profiler_guild_rank WHERE guildId = ?d', $guildId); + if ($ranks = DB::Characters($realmId)->select('SELECT ?d AS guildId, rid AS rank, rname AS name FROM guild_rank WHERE guildid = ?d', $guildId, $guildGuid)) + foreach (Util::createSqlBatchInsert($ranks) as $r) + DB::Aowow()->query('INSERT INTO ?_profiler_guild_rank (?#) VALUES '.$r, array_keys(reset($ranks))); + + CLI::write(' ..guild data'); + + + /***************/ + /* Member Data */ + /***************/ + + $conditions = array( + ['g.guildid', $guildGuid], + ['deleteInfos_Account', null], + ['level', MAX_LEVEL, '<='], // prevents JS errors + [['extra_flags', self::CHAR_GMFLAGS, '&'], 0] // not a staff char + ); + + // this here should all happen within ProfileList + $members = new RemoteProfileList($conditions, ['sv' => $realmId]); + if (!$members->error) + $members->initializeLocalEntries(); + else + return false; + + CLI::write(' ..guild members'); + + + /*********************/ + /* mark guild as done */ + /*********************/ + + DB::Aowow()->query('UPDATE ?_profiler_guild SET cuFlags = cuFlags & ?d WHERE id = ?d', ~PROFILER_CU_NEEDS_RESYNC, $guildId); + + return true; + } + + public static function getArenaTeamFromRealm($realmId, $teamGuid) + { + $team = DB::Characters($realmId)->selectRow('SELECT arenaTeamId, name, type, captainGuid, rating, seasonGames, seasonWins, weekGames, weekWins, rank, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor FROM arena_team WHERE arenaTeamId = ?d', $teamGuid); + if (!$team) + return false; + + // reminder: this query should not fail: a placeholder entry is created as soon as a team listview is created or team detail page is called + $teamId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_arena_team WHERE realm = ?d AND realmGUID = ?d', $realmId, $team['arenaTeamId']); + + CLI::write('fetching arena team #'.$teamGuid.' from realm #'.$realmId); + CLI::write('writing...'); + + + /*************/ + /* Team Data */ + /*************/ + + $captain = $team['captainGuid']; + unset($team['captainGuid']); + unset($team['arenaTeamId']); + $team['nameUrl'] = self::urlize($team['name']); + + DB::Aowow()->query('UPDATE ?_profiler_arena_team SET ?a WHERE realm = ?d AND realmGUID = ?d', $team, $realmId, $teamGuid); + + CLI::write(' ..team data'); + + + /***************/ + /* Member Data */ + /***************/ + + $members = DB::Characters($realmId)->select(' + SELECT + atm.guid AS ARRAY_KEY, atm.arenaTeamId, atm.weekGames, atm.weekWins, atm.seasonGames, atm.seasonWins, atm.personalrating + FROM + arena_team_member atm + JOIN + characters c ON c.guid = atm.guid AND + c.deleteInfos_Account IS NULL AND + c.level <= ?d AND + (c.extra_flags & ?d) = 0 + WHERE + arenaTeamId = ?d', + MAX_LEVEL, + self::CHAR_GMFLAGS, + $teamGuid + ); + + $conditions = array( + ['c.guid', array_keys($members)], + ['deleteInfos_Account', null], + ['level', MAX_LEVEL, '<='], // prevents JS errors + [['extra_flags', self::CHAR_GMFLAGS, '&'], 0] // not a staff char + ); + + $mProfiles = new RemoteProfileList($conditions, ['sv' => $realmId]); + if (!$mProfiles->error) + { + $mProfiles->initializeLocalEntries(); + foreach ($mProfiles->iterate() as $__) + { + + $mGuid = $mProfiles->getField('guid'); + + $members[$mGuid]['arenaTeamId'] = $teamId; + $members[$mGuid]['captain'] = (int)($mGuid == $captain); + $members[$mGuid]['profileId'] = $mProfiles->getField('id'); + } + + DB::Aowow()->query('DELETE FROM ?_profiler_arena_team_member WHERE arenaTeamId = ?d', $teamId); + + foreach (Util::createSqlBatchInsert($members) as $m) + DB::Aowow()->query('INSERT INTO ?_profiler_arena_team_member (?#) VALUES '.$m, array_keys(reset($members))); + + } + else + return false; + + CLI::write(' ..team members'); + + /*********************/ + /* mark team as done */ + /*********************/ + + DB::Aowow()->query('UPDATE ?_profiler_arena_team SET cuFlags = cuFlags & ?d WHERE id = ?d', ~PROFILER_CU_NEEDS_RESYNC, $teamId); + + return true; + } +} + +?> diff --git a/includes/types/arenateam.class.php b/includes/types/arenateam.class.php new file mode 100644 index 00000000..7bb5ebc8 --- /dev/null +++ b/includes/types/arenateam.class.php @@ -0,0 +1,346 @@ +iterate() as $__) + { + $data[$this->id] = array( + 'name' => $this->curTpl['name'], + 'realm' => Profiler::urlize($this->curTpl['realmName']), + 'realmname' => $this->curTpl['realmName'], + // 'battlegroup' => Profiler::urlize($this->curTpl['battlegroup']), // was renamed to subregion somewhere around cata release + // 'battlegroupname' => $this->curTpl['battlegroup'], + 'region' => Profiler::urlize($this->curTpl['region']), + 'faction' => $this->curTpl['faction'], + 'size' => $this->curTpl['type'], + 'rank' => $this->curTpl['rank'], + 'wins' => $this->curTpl['seasonWins'], + 'games' => $this->curTpl['seasonGames'], + 'rating' => $this->curTpl['rating'], + 'members' => $this->curTpl['members'] + ); + } + + return array_values($data); + } + + public function renderTooltip() {} + public function getJSGlobals($addMask = 0) {} +} + + +class ArenaTeamListFilter extends Filter +{ + public $extraOpts = []; + protected $genericFilter = []; + + // fieldId => [checkType, checkValue[, fieldIsArray]] + protected $inputFields = array( + 'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter + 'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter + 'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact + 'si' => [FILTER_V_LIST, [1, 2], false], // side + 'sz' => [FILTER_V_LIST, [2, 3, 5], false], // tema size + 'rg' => [FILTER_V_CALLBACK, 'cbRegionCheck', false], // region + 'sv' => [FILTER_V_CALLBACK, 'cbServerCheck', false], // server + ); + + protected function createSQLForCriterium(&$cr) { } + + protected function createSQLForValues() + { + $parts = []; + $_v = $this->fiData['v']; + + // region (rg), battlegroup (bg) and server (sv) are passed to ArenaTeamList as miscData and handled there + + // name [str] + if (!empty($_v['na'])) + if ($_ = $this->modularizeString(['at.name'], $_v['na'], !empty($_v['ex']) && $_v['ex'] == 'on')) + $parts[] = $_; + + // side [list] + if (!empty($_v['si'])) + { + if ($_v['si'] == 1) + $parts[] = ['c.race', [1, 3, 4, 7, 11]]; + else if ($_v['si'] == 2) + $parts[] = ['c.race', [2, 5, 6, 8, 10]]; + } + + // size [int] + if (!empty($_v['sz'])) + $parts[] = ['at.type', $_v['sz']]; + + return $parts; + } + + protected function cbRegionCheck(&$v) + { + if ($v == 'eu' || $v == 'us') + { + $this->parentCats[0] = $v; // directly redirect onto this region + $v = ''; // remove from filter + + return true; + } + + return false; + } + + protected function cbServerCheck(&$v) + { + foreach (Profiler::getRealms() as $realm) + if ($realm['name'] == $v) + { + $this->parentCats[1] = Profiler::urlize($v);// directly redirect onto this server + $v = ''; // remove from filter + + return true; + } + + return false; + } +} + + +class RemoteArenaTeamList extends ArenaTeamList +{ + protected $queryBase = 'SELECT `at`.*, `at`.`arenaTeamId` AS ARRAY_KEY FROM arena_team at'; + protected $queryOpts = array( + 'at' => [['atm', 'c'], 'g' => 'ARRAY_KEY', 'o' => 'rating DESC'], + 'atm' => ['j' => 'arena_team_member atm ON atm.arenaTeamId = at.arenaTeamId'], + 'c' => ['j' => 'characters c ON c.guid = atm.guid AND c.deleteInfos_Account IS NULL AND c.level <= 80 AND (c.extra_flags & '.Profiler::CHAR_GMFLAGS.') = 0', 's' => ', BIT_OR(IF(c.race IN (1, 3, 4, 7, 11), 1, 2)) - 1 AS faction'] + ); + + private $members = []; + + public function __construct($conditions = [], $miscData = null) + { + // select DB by realm + if (!$this->selectRealms($miscData)) + { + trigger_error('no access to auth-db or table realmlist is empty', E_USER_WARNING); + return; + } + + parent::__construct($conditions, $miscData); + + if ($this->error) + return; + + // ranks in DB are inaccurate. recalculate from rating (fetched as DESC from DB) + foreach ($this->dbNames as $rId => $__) + foreach ([2, 3, 5] as $type) + $this->rankOrder[$rId][$type] = DB::Characters($rId)->selectCol('SELECT arenaTeamId FROM arena_team WHERE `type` = ?d ORDER BY rating DESC', $type); + + reset($this->dbNames); // only use when querying single realm + $realmId = key($this->dbNames); + $realms = Profiler::getRealms(); + $distrib = []; + + // post processing + foreach ($this->iterate() as $guid => &$curTpl) + { + // battlegroup + $curTpl['battlegroup'] = CFG_BATTLEGROUP; + + // realm, rank + $r = explode(':', $guid); + if (!empty($realms[$r[0]])) + { + $curTpl['realm'] = $r[0]; + $curTpl['realmName'] = $realms[$r[0]]['name']; + $curTpl['region'] = $realms[$r[0]]['region']; + $curTpl['rank'] = array_search($curTpl['arenaTeamId'], $this->rankOrder[$r[0]][$curTpl['type']]) + 1; + } + else + { + trigger_error('arena team "'.$curTpl['name'].'" belongs to nonexistant realm #'.$r, E_USER_WARNING); + unset($this->templates[$guid]); + continue; + } + + // team members + $this->members[$r[0]][$r[1]] = $r[1]; + + // equalize distribution + if (empty($distrib[$curTpl['realm']])) + $distrib[$curTpl['realm']] = 1; + else + $distrib[$curTpl['realm']]++; + } + + // get team members + foreach ($this->members as $realmId => &$teams) + $teams = DB::Characters($realmId)->select(' + SELECT + at.arenaTeamId AS ARRAY_KEY, c.guid AS ARRAY_KEY2, c.name AS "0", c.class AS "1", IF(at.captainguid = c.guid, 1, 0) AS "2" + FROM + arena_team at + JOIN + arena_team_member atm ON atm.arenaTeamId = at.arenaTeamId JOIN characters c ON c.guid = atm.guid + WHERE + at.arenaTeamId IN (?a) AND + c.deleteInfos_Account IS NULL AND + c.level <= ?d AND + (c.extra_flags & ?d) = 0', + $teams, + MAX_LEVEL, + Profiler::CHAR_GMFLAGS + ); + + // equalize subject distribution across realms + $limit = CFG_SQL_LIMIT_DEFAULT; + foreach ($conditions as $c) + if (is_int($c)) + $limit = $c; + + $total = array_sum($distrib); + foreach ($distrib as &$d) + $d = ceil($limit * $d / $total); + + foreach ($this->iterate() as $guid => &$curTpl) + { + if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0) + { + unset($this->templates[$guid]); + continue; + } + + $r = explode(':', $guid); + if (isset($this->members[$r[0]][$r[1]])) + $curTpl['members'] = array_values($this->members[$r[0]][$r[1]]); // [name, classId, isCaptain] + + $distrib[$curTpl['realm']]--; + $limit--; + } + } + + public function initializeLocalEntries() + { + $profiles = []; + // init members for tooltips + foreach ($this->members as $realmId => $teams) + { + $gladiators = []; + foreach ($teams as $team) + $gladiators = array_merge($gladiators, array_keys($team)); + + $profiles[$realmId] = new RemoteProfileList(array(['c.guid', $gladiators], CFG_SQL_LIMIT_NONE), ['sv' => $realmId]); + + if (!$profiles[$realmId]->error) + $profiles[$realmId]->initializeLocalEntries(); + } + + $data = []; + foreach ($this->iterate() as $guid => $__) + { + $data[$guid] = array( + 'realm' => $this->getField('realm'), + 'realmGUID' => $this->getField('arenaTeamId'), + 'name' => $this->getField('name'), + 'nameUrl' => Profiler::urlize($this->getField('name')), + 'type' => $this->getField('type'), + 'rating' => $this->getField('rating'), + 'cuFlags' => PROFILER_CU_NEEDS_RESYNC + ); + } + + // basic arena team data + foreach (Util::createSqlBatchInsert($data) as $ins) + DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team (?#) VALUES '.$ins, array_keys(reset($data))); + + // merge back local ids + $localIds = DB::Aowow()->selectCol( + 'SELECT CONCAT(realm, ":", realmGUID) AS ARRAY_KEY, id FROM ?_profiler_arena_team WHERE realm IN (?a) AND realmGUID IN (?a)', + array_column($data, 'realm'), + array_column($data, 'realmGUID') + ); + + foreach ($this->iterate() as $guid => &$_curTpl) + if (isset($localIds[$guid])) + $_curTpl['id'] = $localIds[$guid]; + + + // profiler_arena_team_member requires profiles and arena teams to be filled + foreach ($this->members as $realmId => $teams) + { + if (empty($profiles[$realmId])) + continue; + + $memberData = []; + foreach ($teams as $teamId => $team) + foreach ($team as $memberId => $member) + $memberData[] = array( + 'arenaTeamId' => $localIds[$realmId.':'.$teamId], + 'profileId' => $profiles[$realmId]->getEntry($realmId.':'.$memberId)['id'], + 'captain' => $member[2] + ); + + foreach (Util::createSqlBatchInsert($memberData) as $ins) + DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team_member (?#) VALUES '.$ins, array_keys(reset($memberData))); + } + } +} + + +class LocalArenaTeamList extends ArenaTeamList +{ + protected $queryBase = 'SELECT at.*, at.id AS ARRAY_KEY FROM ?_profiler_arena_team at'; + + public function __construct($conditions = [], $miscData = null) + { + parent::__construct($conditions, $miscData); + + if ($this->error) + return; + + $realms = Profiler::getRealms(); + + // post processing + $members = DB::Aowow()->selectCol('SELECT *, arenaTeamId AS ARRAY_KEY, profileId AS ARRAY_KEY2 FROM ?_profiler_arena_team_member WHERE arenaTeamId IN (?a)', $this->getFoundIDs()); + + foreach ($this->iterate() as $id => &$curTpl) + { + if ($curTpl['realm'] && !isset($realms[$curTpl['realm']])) + continue; + + if (isset($realms[$curTpl['realm']])) + { + $curTpl['realmName'] = $realms[$curTpl['realm']]['name']; + $curTpl['region'] = $realms[$curTpl['realm']]['region']; + } + + // battlegroup + $curTpl['battlegroup'] = CFG_BATTLEGROUP; + + $curTpl['members'] = $members[$id]; + } + } + + public function getProfileUrl() + { + $url = '?arena-team='; + + return $url.implode('.', array( + Profiler::urlize($this->getField('region')), + Profiler::urlize($this->getField('realmName')), + Profiler::urlize($this->getField('name')) + )); + } +} + + +?> diff --git a/includes/types/guild.class.php b/includes/types/guild.class.php new file mode 100644 index 00000000..c6136261 --- /dev/null +++ b/includes/types/guild.class.php @@ -0,0 +1,307 @@ +getGuildScores(); + + $data = []; + foreach ($this->iterate() as $__) + { + $data[$this->id] = array( + 'name' => "$'".$this->curTpl['name']."'", // MUST be a string + 'members' => $this->curTpl['members'], + 'faction' => $this->curTpl['faction'], + 'achievementpoints' => $this->getField('achievementpoints'), + 'gearscore' => $this->getField('gearscore'), + 'realm' => Profiler::urlize($this->curTpl['realmName']), + 'realmname' => $this->curTpl['realmName'], + // 'battlegroup' => Profiler::urlize($this->curTpl['battlegroup']), // was renamed to subregion somewhere around cata release + // 'battlegroupname' => $this->curTpl['battlegroup'], + 'region' => Profiler::urlize($this->curTpl['region']) + ); + } + + return array_values($data); + } + + private function getGuildScores() + { + /* + Guild gear scores and achievement points are derived using a weighted average of all of the known characters in that guild. + Guilds with at least 25 level 80 players receive full benefit of the top 25 characters' gear scores, while guilds with at least 10 level 80 characters receive a slight penalty, + at least 1 level 80 a moderate penalty, and no level 80 characters a severe penalty. [...] + Instead of being based on level, achievement point averages are based around 1,500 points, but the same penalties apply. + */ + $guilds = array_column($this->templates, 'id'); + if (!$guilds) + return; + + $stats = DB::Aowow()->select('SELECT guild AS ARRAY_KEY, id AS ARRAY_KEY2, level, gearscore, achievementpoints, IF(cuFlags & ?d, 0, 1) AS synced FROM ?_profiler_profiles WHERE guild IN (?a) ORDER BY gearscore DESC', PROFILER_CU_NEEDS_RESYNC, $guilds); + foreach ($this->iterate() as &$_curTpl) + { + $id = $_curTpl['id']; + if (empty($stats[$id])) + continue; + + $guildStats = array_filter($stats[$id], function ($x) { return $x['synced']; } ); + if (!$guildStats) + continue; + + $nMaxLevel = count(array_filter($stats[$id], function ($x) { return $x['level'] >= MAX_LEVEL; } )); + $levelMod = 1.0; + + if ($nMaxLevel < 25) + $levelMod = 0.85; + if ($nMaxLevel < 10) + $levelMod = 0.66; + if ($nMaxLevel < 1) + $levelMod = 0.20; + + $totalGS = $totalAP = $nMembers = 0; + foreach ($guildStats as $gs) + { + $totalGS += $gs['gearscore'] * $levelMod * min($gs['level'], MAX_LEVEL) / MAX_LEVEL; + $totalAP += $gs['achievementpoints'] * $levelMod * min($gs['achievementpoints'], 1500) / 1500; + $nMembers += min($gs['level'], MAX_LEVEL) / MAX_LEVEL; + } + + $_curTpl['gearscore'] = intval($totalGS / $nMembers); + $_curTpl['achievementpoints'] = intval($totalAP / $nMembers); + } + } + + public function renderTooltip() {} + public function getJSGlobals($addMask = 0) {} +} + + +class GuildListFilter extends Filter +{ + public $extraOpts = []; + protected $genericFilter = []; + + // fieldId => [checkType, checkValue[, fieldIsArray]] + protected $inputFields = array( + 'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter + 'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter + 'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact + 'si' => [FILTER_V_LIST, [1, 2], false], // side + 'rg' => [FILTER_V_CALLBACK, 'cbRegionCheck', false], // region + 'sv' => [FILTER_V_CALLBACK, 'cbServerCheck', false], // server + ); + + protected function createSQLForCriterium(&$cr) { } + + protected function createSQLForValues() + { + $parts = []; + $_v = $this->fiData['v']; + + // region (rg), battlegroup (bg) and server (sv) are passed to GuildList as miscData and handled there + + // name [str] + if (!empty($_v['na'])) + if ($_ = $this->modularizeString(['g.name'], $_v['na'], !empty($_v['ex']) && $_v['ex'] == 'on')) + $parts[] = $_; + + // side [list] + if (!empty($_v['si'])) + { + if ($_v['si'] == 1) + $parts[] = ['c.race', [1, 3, 4, 7, 11]]; + else if ($_v['si'] == 2) + $parts[] = ['c.race', [2, 5, 6, 8, 10]]; + } + + return $parts; + } + + protected function cbRegionCheck(&$v) + { + if ($v == 'eu' || $v == 'us') + { + $this->parentCats[0] = $v; // directly redirect onto this region + $v = ''; // remove from filter + + return true; + } + + return false; + } + + protected function cbServerCheck(&$v) + { + foreach (Profiler::getRealms() as $realm) + if ($realm['name'] == $v) + { + $this->parentCats[1] = Profiler::urlize($v);// directly redirect onto this server + $v = ''; // remove from filter + + return true; + } + + return false; + } +} + + +class RemoteGuildList extends GuildList +{ + protected $queryBase = 'SELECT `g`.*, `g`.`guildid` AS ARRAY_KEY FROM guild g'; + protected $queryOpts = array( + 'g' => [['gm', 'c'], 'g' => 'ARRAY_KEY'], + 'gm' => ['j' => 'guild_member gm ON gm.guildid = g.guildid', 's' => ', COUNT(1) AS members'], + 'c' => ['j' => 'characters c ON c.guid = gm.guid', 's' => ', BIT_OR(IF(c.race IN (1, 3, 4, 7, 11), 1, 2)) - 1 AS faction'] + ); + + public function __construct($conditions = [], $miscData = null) + { + // select DB by realm + if (!$this->selectRealms($miscData)) + { + trigger_error('no access to auth-db or table realmlist is empty', E_USER_WARNING); + return; + } + + parent::__construct($conditions, $miscData); + + if ($this->error) + return; + + reset($this->dbNames); // only use when querying single realm + $realmId = key($this->dbNames); + $realms = Profiler::getRealms(); + $distrib = []; + + // post processing + foreach ($this->iterate() as $guid => &$curTpl) + { + // battlegroup + $curTpl['battlegroup'] = CFG_BATTLEGROUP; + + $r = explode(':', $guid)[0]; + if (!empty($realms[$r])) + { + $curTpl['realm'] = $r; + $curTpl['realmName'] = $realms[$r]['name']; + $curTpl['region'] = $realms[$r]['region']; + } + else + { + trigger_error('character "'.$curTpl['name'].'" belongs to nonexistant realm #'.$r, E_USER_WARNING); + unset($this->templates[$guid]); + continue; + } + + // equalize distribution + if (empty($distrib[$curTpl['realm']])) + $distrib[$curTpl['realm']] = 1; + else + $distrib[$curTpl['realm']]++; + } + + $limit = CFG_SQL_LIMIT_DEFAULT; + foreach ($conditions as $c) + if (is_int($c)) + $limit = $c; + + $total = array_sum($distrib); + foreach ($distrib as &$d) + $d = ceil($limit * $d / $total); + + foreach ($this->iterate() as $guid => &$curTpl) + { + if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0) + { + unset($this->templates[$guid]); + continue; + } + + $distrib[$curTpl['realm']]--; + $limit--; + } + } + + public function initializeLocalEntries() + { + $data = []; + foreach ($this->iterate() as $guid => $__) + { + $data[$guid] = array( + 'realm' => $this->getField('realm'), + 'realmGUID' => $this->getField('guildid'), + 'name' => $this->getField('name'), + 'nameUrl' => Profiler::urlize($this->getField('name')), + 'cuFlags' => PROFILER_CU_NEEDS_RESYNC + ); + } + + // basic guild data + foreach (Util::createSqlBatchInsert($data) as $ins) + DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES '.$ins, array_keys(reset($data))); + + // merge back local ids + $localIds = DB::Aowow()->selectCol( + 'SELECT CONCAT(realm, ":", realmGUID) AS ARRAY_KEY, id FROM ?_profiler_guild WHERE realm IN (?a) AND realmGUID IN (?a)', + array_column($data, 'realm'), + array_column($data, 'realmGUID') + ); + + foreach ($this->iterate() as $guid => &$_curTpl) + if (isset($localIds[$guid])) + $_curTpl['id'] = $localIds[$guid]; + } +} + + +class LocalGuildList extends GuildList +{ + protected $queryBase = 'SELECT g.*, g.id AS ARRAY_KEY FROM ?_profiler_guild g'; + + public function __construct($conditions = [], $miscData = null) + { + parent::__construct($conditions, $miscData); + + if ($this->error) + return; + + $realms = Profiler::getRealms(); + + foreach ($this->iterate() as $id => &$curTpl) + { + if ($curTpl['realm'] && !isset($realms[$curTpl['realm']])) + continue; + + if (isset($realms[$curTpl['realm']])) + { + $curTpl['realmName'] = $realms[$curTpl['realm']]['name']; + $curTpl['region'] = $realms[$curTpl['realm']]['region']; + } + + // battlegroup + $curTpl['battlegroup'] = CFG_BATTLEGROUP; + } + } + + public function getProfileUrl() + { + $url = '?guild='; + + return $url.implode('.', array( + Profiler::urlize($this->getField('region')), + Profiler::urlize($this->getField('realmName')), + Profiler::urlize($this->getField('name')) + )); + } +} + + +?> diff --git a/includes/types/item.class.php b/includes/types/item.class.php index 252f54fc..c1d21903 100644 --- a/includes/types/item.class.php +++ b/includes/types/item.class.php @@ -2311,7 +2311,7 @@ class ItemListFilter extends Filter if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1])) return false; - foreach (Util::getRealms() as $rId => $__) + foreach (Profiler::getRealms() as $rId => $__) { // todo: do something sensible.. // // todo (med): get the avgbuyout into the listview diff --git a/includes/types/profile.class.php b/includes/types/profile.class.php index 0730331a..02561231 100644 --- a/includes/types/profile.class.php +++ b/includes/types/profile.class.php @@ -4,145 +4,81 @@ if (!defined('AOWOW_REVISION')) die('illegal access'); -// class CharacterList extends BaseType // new profiler-related parent: ProfilerType?; maybe a trait is enough => use ProfileHelper; -// class GuildList extends BaseType -// class ArenaTeamList extends BaseType class ProfileList extends BaseType { - public static $type = 0; // profiles dont actually have one - public static $brickFile = 'profile'; - public static $dataTable = ''; // doesn't have community content + use profilerHelper, listviewHelper; - protected $queryBase = ''; // SELECT p.*, p.id AS ARRAY_KEY FROM ?_profiles p'; - protected $queryOpts = array( - 'p' => [['pa', 'pg']], - 'pam' => [['?_profiles_arenateam_member pam ON pam.memberId = p.id', true], 's' => ', pam.status'], - 'pa' => ['?_profiles_arenateam pa ON pa.id = pam.teamId', 's' => ', pa.mode, pa.name'], - 'pgm' => [['?_profiles_guid_member pgm ON pgm.memberId = p.Id', true], 's' => ', pgm.rankId'], - 'pg' => ['?_profiles_guild pg ON pg.if = pgm.guildId', 's' => ', pg.name'] - ); - - public function __construct($conditions = [], $miscData = null) - { - $character = array( - 'id' => 2, - 'name' => 'CharName', - 'region' => ['eu', 'Europe'], - 'battlegroup' => ['pure-pwnage', 'Pure Pwnage'], - 'realm' => ['dafuque', 'da\'Fuqúe'], - 'level' => 80, - 'classs' => 11, - 'race' => 6, - 'faction' => 1, // 0:alliance; 1:horde - 'gender' => 1, // 0:male, 1:female - 'skincolor' => 0, // playerbytes % 256 - 'hairstyle' => 0, // (playerbytes >> 16) % 256 - 'haircolor' => 0, // (playerbytes >> 24) % 256 - 'facetype' => 0, // (playerbytes >> 8) % 256 [maybe features] - 'features' => 0, // playerBytes2 % 256 [maybe facetype] - 'source' => 2, // source: used if you create a profile from a genuine character. It inherites region, realm and bGroup - 'sourcename' => 'SourceCharName', // > if these three are false we get a 'genuine' profile [0 for genuine characters..?] - 'user' => 1, // > 'genuine' is the parameter for _isArmoryProfile(allowCustoms) ['' for genuine characters..?] - 'username' => 'TestUser', // > also, if 'source' <> 0, the char-icon is requestet via profile.php?avatar - 'published' => 1, // public / private - 'pinned' => 1, // usable for some utility funcs on site - 'nomodel' => 0x0, // unchecks DisplayOnCharacter by (1 << slotId - 1) - 'title' => 0, // titleId currently in use or null - 'guild' => 'GuildName', // only on chars; id or null - 'description' => 'this is a profile', // only on custom profiles - 'arenateams' => [], // [size(2|3|5) => DisplayName]; DisplayName gets urlized to use as link - 'playedtime' => 0, // exact to the day - 'lastupdated' => 0, // timestamp in ms - 'achievementpoints' => 0, // max you have - 'talents' => array( - 'builds' => array( - ['talents' => '', 'glyphs' => ''], // talents:string of 0-5 points; glyphs: itemIds.join(':') - ), - 'active' => 1 // 1|2 - ), - 'customs' => [], // custom profiles created from this char; profileId => [name, ownerId, iconString(optional)] - 'skills' => [], // skillId => [curVal, maxVal]; can contain anything, should be limited to prim/sec professions - 'inventory' => [], // slotId => [itemId, subItemId, permEnchantId, tempEnchantId, gemItemId1, gemItemId2, gemItemId3, gemItemId4] - 'auras' => [], // custom list of buffs, debuffs [spellId] - - // completion lists: [subjectId => amount/timestamp/1] - 'reputation' => [], // factionId => amount - 'titles' => [], // titleId => 1 - 'spells' => [], // spellId => 1; recipes, pets, mounts - 'achievements' => [], // achievementId => timestamp - 'quests' => [], // questId => 1 - - // UNKNOWN - 'bookmarks' => [2], // UNK pinned or claimed userId => profileIds..? - 'statistics' => [], // UNK all statistics? [achievementId => killCount] - 'activity' => [], // UNK recent achievements? [achievementId => killCount] - 'glyphs' => [], // not really used .. i guess..? - 'pets' => array( // UNK - [], // one array per pet, structure UNK - ), - ); - - // parent::__construct($conditions, $miscData); - @include('datasets/ProfilerExampleChar'); // tmp char data - - $this->templates[2] = $character; - $this->curTpl = $character; - - if ($this->error) - return; - - // post processing - // foreach ($this->iterate() as $_id => &$curTpl) - // { - // } - } - - public function getListviewData() + public function getListviewData($addInfo = 0, array $reqCols = []) { $data = []; foreach ($this->iterate() as $__) { - $tDistrib = $this->getTalentDistribution(); + if ($this->getField('user') && User::$id != $this->getField('user') && !($this->getField('cuFlags') & PROFILER_CU_PUBLISHED)) + continue; + + if (($addInfo & PROFILEINFO_PROFILE) && !$this->isCustom()) + continue; + + if (($addInfo & PROFILEINFO_CHARACTER) && $this->isCustom()) + continue; $data[$this->id] = array( - 'id' => 0, - 'name' => $this->curTpl['name'], - 'achievementpoints' => $this->curTpl['achievementpoints'], - 'guild' => $this->curTpl['guild'], // 0 if none - 'guildRank' => -1, - 'realm' => $this->curTpl['realm'][0], - 'realmname' => $this->curTpl['realm'][1], - 'battlegroup' => $this->curTpl['battlegroup'][0], - 'battlegroupname' => $this->curTpl['battlegroup'][0], - 'region' => $this->curTpl['region'][0], - 'level' => $this->curTpl['level'], - 'race' => $this->curTpl['race'], - 'gender' => $this->curTpl['gender'], - 'classs' => $this->curTpl['classs'], - 'faction' => $this->curTpl['faction'], - 'talenttree1' => $tDistrib[0], - 'talenttree2' => $tDistrib[1], - 'talenttree3' => $tDistrib[2], - 'talentspec' => $this->curTpl['talents']['active'] + 'id' => $this->getField('id'), + 'name' => $this->getField('name'), + 'race' => $this->getField('race'), + 'classs' => $this->getField('class'), + 'gender' => $this->getField('gender'), + 'level' => $this->getField('level'), + 'faction' => (1 << ($this->getField('race') - 1)) & RACE_MASK_ALLIANCE ? 0 : 1, + 'talenttree1' => $this->getField('talenttree1'), + 'talenttree2' => $this->getField('talenttree2'), + 'talenttree3' => $this->getField('talenttree3'), + 'talentspec' => $this->getField('activespec') + 1, // 0 => 1; 1 => 2 + 'achievementpoints' => $this->getField('achievementpoints'), + 'guild' => '$"'.$this->getField('guildname').'"', // force this to be a string + 'guildrank' => $this->getField('guildrank'), + 'realm' => Profiler::urlize($this->getField('realmName')), + 'realmname' => $this->getField('realmName'), + // 'battlegroup' => Profiler::urlize($this->getField('battlegroup')), // was renamed to subregion somewhere around cata release + // 'battlegroupname' => $this->getField('battlegroup'), + 'gearscore' => $this->getField('gearscore') ); - if (!empty($this->curTpl['description'])) - $data[$this->id]['description'] = $this->curTpl['description']; - if (!empty($this->curTpl['icon'])) - $data[$this->id]['icon'] = $this->curTpl['icon']; + // for the lv this determins if the link is profile= or profile=.. + if ($this->isCustom()) + $data[$this->id]['published'] = (int)!!($this->getField('cuFlags') & PROFILER_CU_PUBLISHED); + else + $data[$this->id]['region'] = Profiler::urlize($this->getField('region')); - if ($this->curTpl['cuFlags'] & PROFILE_CU_PUBLISHED) - $data[$this->id]['published'] = 1; + if ($addInfo & PROFILEINFO_ARENA) + { + $data[$this->id]['rating'] = $this->getField('rating'); + $data[$this->id]['captain'] = $this->getField('captain'); + $data[$this->id]['games'] = $this->getField('seasonGames'); + $data[$this->id]['wins'] = $this->getField('seasonWins'); + } - if ($this->curTpl['cuFlags'] & PROFILE_CU_PINNED) + // Filter asked for skills - add them + foreach ($reqCols as $col) + $data[$this->id][$col] = $this->getField($col); + + if ($addInfo & PROFILEINFO_PROFILE) + if ($_ = $this->getField('description')) + $data[$this->id]['description'] = $_; + + if ($addInfo & PROFILEINFO_PROFILE) + if ($_ = $this->getField('icon')) + $data[$this->id]['icon'] = $_; + + if ($this->getField('cuFlags') & PROFILER_CU_PINNED) $data[$this->id]['pinned'] = 1; - if ($this->curTpl['cuFlags'] & PROFILE_CU_DELETED) + if ($this->getField('cuFlags') & PROFILER_CU_DELETED) $data[$this->id]['deleted'] = 1; } - return $data; + return array_values($data); } public function renderTooltip($interactive = false) @@ -150,65 +86,178 @@ class ProfileList extends BaseType if (!$this->curTpl) return []; + $title = ''; + $name = $this->getField('name'); + if ($_ = $this->getField('chosenTitle')) + $title = (new TitleList(array(['bitIdx', $_])))->getField($this->getField('gender') ? 'female' : 'male', true); + + if ($this->isCustom()) + $name .= ' (Custom Profile)'; + else if ($title) + $name = sprintf($title, $name); + $x = ''; - $x .= ''; - if ($g = $this->getField('name')) - $x .= ''; + $x .= ''; + if ($g = $this->getField('guildname')) + $x .= ''; else if ($d = $this->getField('description')) $x .= ''; - $x .= ''; + $x .= ''; $x .= '
'.$this->getField('name').'
<'.$g.'> ('.$this->getField('guildrank').')
'.$name.'
<'.$g.'>
'.$d.'
'.Lang::game('level').' '.$this->getField('level').' '.Lang::game('ra', $this->curTpl['race']).' '.Lang::game('cl', $this->curTpl['classs']).'
'.Lang::game('level').' '.$this->getField('level').' '.Lang::game('ra', $this->getField('race')).' '.Lang::game('cl', $this->getField('class')).'
'; return $x; } - public function getJSGlobals($addMask = 0) {} - - private function getTalentDistribution() + public function getJSGlobals($addMask = 0) { - if (!empty($this->tDistribution)) - $this->tDistribution[$this->curTpl['classId']] = DB::Aowow()->selectCol('SELECT COUNT(t.id) FROM dbc_talent t JOIN dbc_talenttab tt ON t.tabId = tt.id WHERE tt.classMask & ?d GROUP BY tt.id ORDER BY tt.tabNumber ASC', 1 << ($this->curTpl['classId'] - 1)); + $data = []; + $realms = Profiler::getRealms(); - $result = []; - $start = 0; - foreach ($this->tDistribution[$this->curTpl['classId']] as $len) + foreach ($this->iterate() as $id => $__) { - $result[] = array_sum(str_split(substr($this->curTpl['talentString'], $start, $len))); - $start += $len; + if (($addMask & PROFILEINFO_PROFILE) && ($this->getField('cuFlags') & PROFILER_CU_PROFILE)) + { + $profile = array( + 'id' => $this->getField('id'), + 'name' => $this->getField('name'), + 'race' => $this->getField('race'), + 'classs' => $this->getField('class'), + 'level' => $this->getField('level'), + 'gender' => $this->getField('gender') + ); + + if ($_ = $this->getField('icon')) + $profile['icon'] = $_; + + $data[] = $profile; + + continue; + } + + if ($addMask & PROFILEINFO_CHARACTER && !($this->getField('cuFlags') & PROFILER_CU_PROFILE)) + { + if (!isset($realms[$this->getField('realm')])) + continue; + + $data[] = array( + 'id' => $this->getField('id'), + 'name' => $this->getField('name'), + 'realmname' => $realms[$this->getField('realm')]['name'], + 'region' => $realms[$this->getField('realm')]['region'], + 'realm' => Profiler::urlize($realms[$this->getField('realm')]['name']), + 'race' => $this->getField('race'), + 'classs' => $this->getField('class'), + 'level' => $this->getField('level'), + 'gender' => $this->getField('gender'), + 'pinned' => $this->getField('cuFlags') & PROFILER_CU_PINNED ? 1 : 0 + ); + } } - return $result; + return $data; + } + + public function isCustom() + { + return $this->getField('cuFlags') & PROFILER_CU_PROFILE; } } class ProfileListFilter extends Filter { - public $extraOpts = []; + public $useLocalList = false; + public $extraOpts = []; - protected $genericFilter = array( + private $realms = []; + + protected $enums = array( + -1 => array( // arena team sizes + // by name by rating by contrib + 12 => 2, 13 => 2, 14 => 2, + 15 => 3, 16 => 3, 17 => 3, + 18 => 5, 19 => 5, 20 => 5 + ) ); + protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet + 2 => [FILTER_CR_NUMERIC, 'gearscore', NUM_CAST_INT ], // gearscore [num] + 3 => [FILTER_CR_NUMERIC, 'achievementpoints', NUM_CAST_INT ], // achievementpoints [num] + 5 => [FILTER_CR_NUMERIC, 'talenttree1', NUM_CAST_INT ], // talenttree1 [num] + 6 => [FILTER_CR_NUMERIC, 'talenttree2', NUM_CAST_INT ], // talenttree2 [num] + 7 => [FILTER_CR_NUMERIC, 'talenttree3', NUM_CAST_INT ], // talenttree3 [num] + 9 => [FILTER_CR_STRING, 'g.name', ], // guildname + 10 => [FILTER_CR_CALLBACK, 'cbHasGuildRank', null, null], // guildrank + 12 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname2v2 + 15 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname3v3 + 18 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname5v5 + 13 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng2v2 + 16 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng3v3 + 19 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng5v5 + 14 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib2v2 [num] + 17 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib3v3 [num] + 20 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib5v5 [num] + 21 => [FILTER_CR_CALLBACK, 'cbWearsItems', null, null], // wearingitem [str] + 23 => [FILTER_CR_CALLBACK, 'cbCompletedAcv', null, null], // completedachievement + 25 => [FILTER_CR_CALLBACK, 'cbProfession', 171, null], // alchemy [num] + 26 => [FILTER_CR_CALLBACK, 'cbProfession', 164, null], // blacksmithing [num] + 27 => [FILTER_CR_CALLBACK, 'cbProfession', 333, null], // enchanting [num] + 28 => [FILTER_CR_CALLBACK, 'cbProfession', 202, null], // engineering [num] + 29 => [FILTER_CR_CALLBACK, 'cbProfession', 182, null], // herbalism [num] + 30 => [FILTER_CR_CALLBACK, 'cbProfession', 773, null], // inscription [num] + 31 => [FILTER_CR_CALLBACK, 'cbProfession', 755, null], // jewelcrafting [num] + 32 => [FILTER_CR_CALLBACK, 'cbProfession', 165, null], // leatherworking [num] + 33 => [FILTER_CR_CALLBACK, 'cbProfession', 186, null], // mining [num] + 34 => [FILTER_CR_CALLBACK, 'cbProfession', 393, null], // skinning [num] + 35 => [FILTER_CR_CALLBACK, 'cbProfession', 197, null], // tailoring [num] + 36 => [FILTER_CR_CALLBACK, 'cbHasGuild', null, null] // hasguild [yn] + ); + + + // fieldId => [checkType, checkValue[, fieldIsArray]] + protected $inputFields = array( + 'cr' => [FILTER_V_RANGE, [1, 36], true ], // criteria ids + 'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 5000]], true ], // criteria operators + 'crv' => [FILTER_V_REGEX, '/[\p{C};]/ui', true ], // criteria values + 'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter + 'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter + 'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact + 'si' => [FILTER_V_LIST, [1, 2], false], // side + 'ra' => [FILTER_V_LIST, [[1, 8], 10, 11], true ], // race + 'cl' => [FILTER_V_LIST, [[1, 9], 11], true ], // class + 'minle' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // min level + 'maxle' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // max level + 'rg' => [FILTER_V_CALLBACK, 'cbRegionCheck', false], // region + 'sv' => [FILTER_V_CALLBACK, 'cbServerCheck', false], // server + ); + + /* heads up! + a couple of filters are too complex to be run against the characters database + if they are selected, force useage of LocalProfileList + */ + + public function __construct($fromPOST = false, $opts = []) + { + if (!empty($opts['realms'])) + $this->realms = $opts['realms']; + else + $this->realms = array_keys(Profiler::getRealms()); + + parent::__construct($fromPOST, $opts); + + if (!empty($this->fiData['c']['cr'])) + if (array_intersect($this->fiData['c']['cr'], [2, 3, 5, 6, 7, 21])) + $this->useLocalList = true; + } + protected function createSQLForCriterium(&$cr) { if (in_array($cr[0], array_keys($this->genericFilter))) - { if ($genCR = $this->genericCriterion($cr)) return $genCR; - unset($cr); - $this->error = true; - return [1]; - } - - switch ($cr[0]) - { - default: - break; - } - unset($cr); - $this->error = 1; + $this->error = true; return [1]; } @@ -217,13 +266,446 @@ class ProfileListFilter extends Filter $parts = []; $_v = $this->fiData['v']; - // name - if (isset($_v['na'])) - if ($_ = $this->modularizeString(['name_loc'.User::$localeId])) - $parts[] = $_; + // region (rg), battlegroup (bg) and server (sv) are passed to ProflieList as miscData and handled there + + // table key differs between remote and local :< + $k = $this->useLocalList ? 'p' : 'c'; + + // name [str] - the table is case sensitive. Since i down't want to destroy indizes, lets alter the search terms + if (!empty($_v['na'])) + { + $lower = $this->modularizeString([$k.'.name'], Util::lower($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on'); + $proper = $this->modularizeString([$k.'.name'], Util::ucWords($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on'); + + $parts[] = ['OR', $lower, $proper]; + } + + // side [list] + if (!empty($_v['si'])) + { + if ($_v['si'] == 1) + $parts[] = [$k.'.race', [1, 3, 4, 7, 11]]; + else if ($_v['si'] == 2) + $parts[] = [$k.'.race', [2, 5, 6, 8, 10]]; + } + + // race [list] + if (!empty($_v['ra'])) + $parts[] = [$k.'.race', $_v['ra']]; + + // class [list] + if (!empty($_v['cl'])) + $parts[] = [$k.'.class', $_v['cl']]; + + // min level [int] + if (isset($_v['minle'])) + $parts[] = [$k.'.level', $_v['minle'], '>=']; + + // max level [int] + if (isset($_v['maxle'])) + $parts[] = [$k.'.level', $_v['maxle'], '<=']; return $parts; } + + protected function cbRegionCheck(&$v) + { + if ($v == 'eu' || $v == 'us') + { + $this->parentCats[0] = $v; // directly redirect onto this region + $v = ''; // remove from filter + + return true; + } + + return false; + } + + protected function cbServerCheck(&$v) + { + foreach (Profiler::getRealms() as $realm) + if ($realm['name'] == $v) + { + $this->parentCats[1] = Profiler::urlize($v);// directly redirect onto this server + $v = ''; // remove from filter + + return true; + } + + return false; + } + + protected function cbProfession($cr, $skillId) + { + if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1])) + return; + + $k = 'sk_'.Util::createHash(12); + $col = 'skill'.$skillId; + + $this->formData['extraCols'][$skillId] = $col; + + if ($this->useLocalList) + { + $this->extraOpts[$k] = array( + 'j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.TYPE_SKILL.' AND '.$k.'.typeId = '.$skillId.' AND '.$k.'.cur '.$cr[1].' '.$cr[2], true], + 's' => [', '.$k.'.cur AS '.$col] + ); + return [$k.'.typeId', null, '!']; + } + else + { + $this->extraOpts[$k] = array( + 'j' => ['character_skills '.$k.' ON '.$k.'.guid = c.guid AND '.$k.'.skill = '.$skillId.' AND '.$k.'.value '.$cr[1].' '.$cr[2], true], + 's' => [', '.$k.'.value AS '.$col] + ); + return [$k.'.skill', null, '!']; + } + } + + protected function cbCompletedAcv($cr) + { + if (!Util::checkNumeric($cr[2], NUM_CAST_INT)) + return false; + + if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_achievement WHERE id = ?d', $cr[2])) + return false; + + $k = 'acv_'.Util::createHash(12); + + if ($this->useLocalList) + { + $this->extraOpts[$k] = ['j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.TYPE_ACHIEVEMENT.' AND '.$k.'.typeId = '.$cr[2], true]]; + return [$k.'.typeId', null, '!']; + } + else + { + $this->extraOpts[$k] = ['j' => ['character_achievement '.$k.' ON '.$k.'.guid = c.guid AND '.$k.'.achievement = '.$cr[2], true]]; + return [$k.'.achievement', null, '!']; + } + } + + protected function cbWearsItems($cr) + { + if (!Util::checkNumeric($cr[2], NUM_CAST_INT)) + return false; + + if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_items WHERE id = ?d', $cr[2])) + return false; + + $k = 'i_'.Util::createHash(12); + + $this->extraOpts[$k] = ['j' => ['?_profiler_items '.$k.' ON '.$k.'.id = p.id AND '.$k.'.item = '.$cr[2], true]]; + return [$k.'.item', null, '!']; + } + + protected function cbHasGuild($cr) + { + if (!$this->int2Bool($cr[1])) + return false; + + if ($this->useLocalList) + return ['p.guild', null, $cr[1] ? '!' : null]; + else + return ['gm.guildId', null, $cr[1] ? '!' : null]; + } + + protected function cbHasGuildRank($cr) + { + if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1])) + return false; + + if ($this->useLocalList) + return ['p.guildrank', $cr[2], $cr[1]]; + else + return ['gm.rank', $cr[2], $cr[1]]; + } + + protected function cbTeamName($cr) + { + if ($_ = $this->modularizeString(['at.name'], $cr[2])) + return ['AND', ['at.type', $this->enums[-1][$cr[0]]], $_]; + + return false; + } + + protected function cbTeamRating($cr) + { + if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1])) + return false; + + return ['AND', ['at.type', $this->enums[-1][$cr[0]]], ['at.rating', $cr[2], $cr[1]]]; + } } + +class RemoteProfileList extends ProfileList +{ + protected $queryBase = 'SELECT `c`.*, `c`.`guid` AS ARRAY_KEY FROM characters c'; + protected $queryOpts = array( + 'c' => [['gm', 'g', 'ca', 'ct'], 'g' => 'ARRAY_KEY', 'o' => 'level DESC, name ASC'], + 'ca' => ['j' => ['character_achievement ca ON ca.guid = c.guid', true], 's' => ', GROUP_CONCAT(DISTINCT ca.achievement SEPARATOR " ") AS _acvs'], + 'ct' => ['j' => ['character_talent ct ON ct.guid = c.guid AND ct.spec = c.activespec', true], 's' => ', GROUP_CONCAT(DISTINCT ct.spell SEPARATOR " ") AS _talents'], + 'gm' => ['j' => ['guild_member gm ON gm.guid = c.guid', true], 's' => ', gm.rank AS guildrank'], + 'g' => ['j' => ['guild g ON g.guildid = gm.guildid', true], 's' => ', g.guildid AS guild, g.name AS guildname'], + 'atm' => ['j' => ['arena_team_member atm ON atm.guid = c.guid', true], 's' => ', atm.personalRating AS rating'], + 'at' => [['atm'], 'j' => 'arena_team at ON atm.arenaTeamId = at.arenaTeamId', 's' => ', at.name AS arenateam, IF(at.captainGuid = c.guid, 1, 0) AS captain'] + ); + + public function __construct($conditions = [], $miscData = null) + { + // select DB by realm + if (!$this->selectRealms($miscData)) + { + trigger_error('no access to auth-db or table realmlist is empty', E_USER_WARNING); + return; + } + + parent::__construct($conditions, $miscData); + + if ($this->error) + return; + + reset($this->dbNames); // only use when querying single realm + $realmId = key($this->dbNames); + $realms = Profiler::getRealms(); + $acvCache = []; + $talentCache = []; + $atCache = []; + $distrib = null; + $talentData = []; + $limit = CFG_SQL_LIMIT_DEFAULT; + + foreach ($conditions as $c) + if (is_int($c)) + $limit = $c; + + // post processing + foreach ($this->iterate() as $guid => &$curTpl) + { + // battlegroup + $curTpl['battlegroup'] = CFG_BATTLEGROUP; + + // realm + $r = explode(':', $guid)[0]; + if (!empty($realms[$r])) + { + $curTpl['realm'] = $r; + $curTpl['realmName'] = $realms[$r]['name']; + $curTpl['region'] = $realms[$r]['region']; + } + else + { + trigger_error('character "'.$curTpl['name'].'" belongs to nonexistant realm #'.$r, E_USER_WARNING); + unset($this->templates[$guid]); + continue; + } + + // temp id + $curTpl['id'] = 0; + + // achievement points pre + if ($acvs = explode(' ', $curTpl['_acvs'])) + foreach ($acvs as $a) + if ($a && !isset($acvCache[$a])) + $acvCache[$a] = $a; + + // talent points pre + if ($talents = explode(' ', $curTpl['_talents'])) + foreach ($talents as $t) + if ($t && !isset($talentCache[$t])) + $talentCache[$t] = $t; + + // equalize distribution + if ($limit != CFG_SQL_LIMIT_NONE) + { + if (empty($distrib[$curTpl['realm']])) + $distrib[$curTpl['realm']] = 1; + else + $distrib[$curTpl['realm']]++; + } + + $curTpl['cuFlags'] = 0; + } + + if ($talentCache) + $talentData = DB::Aowow()->select('SELECT spell AS ARRAY_KEY, tab, rank FROM ?_talents WHERE spell IN (?a)', $talentCache); + + if ($distrib !== null) + { + $total = array_sum($distrib); + foreach ($distrib as &$d) + $d = ceil($limit * $d / $total); + } + + if ($acvCache) + $acvCache = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, points FROM ?_achievement WHERE id IN (?a)', $acvCache); + + foreach ($this->iterate() as $guid => &$curTpl) + { + if ($distrib !== null) + { + if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0) + { + unset($this->templates[$guid]); + continue; + } + + $distrib[$curTpl['realm']]--; + $limit--; + } + + + $a = explode(' ', $curTpl['_acvs']); + $t = explode(' ', $curTpl['_talents']); + unset($curTpl['_acvs']); + unset($curTpl['_talents']); + + // achievement points post + $curTpl['achievementpoints'] = array_sum(array_intersect_key($acvCache, array_combine($a, $a))); + + // talent points post + $curTpl['talenttree1'] = 0; + $curTpl['talenttree2'] = 0; + $curTpl['talenttree3'] = 0; + foreach ($talentData as $spell => $data) + if (in_array($spell, $t)) + $curTpl['talenttree'.($data['tab'] + 1)] += $data['rank']; + } + } + + public function getListviewData($addInfoMask = 0, array $reqCols = []) + { + $data = parent::getListviewData($addInfoMask, $reqCols); + + // not wanted on server list + foreach ($data as &$d) + unset($d['published']); + + return $data; + } + + public function initializeLocalEntries() + { + $baseData = $guildData = []; + foreach ($this->iterate() as $guid => $__) + { + $baseData[$guid] = array( + 'realm' => $this->getField('realm'), + 'realmGUID' => $this->getField('guid'), + 'name' => $this->getField('name'), + 'race' => $this->getField('race'), + 'class' => $this->getField('class'), + 'level' => $this->getField('level'), + 'gender' => $this->getField('gender'), + 'guild' => $this->getField('guild') ?: null, + 'guildrank' => $this->getField('guild') ? $this->getField('guildrank') : null, + 'cuFlags' => PROFILER_CU_NEEDS_RESYNC + ); + + if ($this->getField('guild')) + $guildData[] = array( + 'realm' => $this->getField('realm'), + 'realmGUID' => $this->getField('guild'), + 'name' => $this->getField('guildname'), + 'nameUrl' => Profiler::urlize($this->getField('guildname')), + 'cuFlags' => PROFILER_CU_NEEDS_RESYNC + ); + } + + // basic guild data (satisfying table constraints) + if ($guildData) + { + foreach (Util::createSqlBatchInsert($guildData) as $ins) + DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES '.$ins, array_keys(reset($guildData))); + + // merge back local ids + $localGuilds = DB::Aowow()->selectCol('SELECT realm AS ARRAY_KEY, realmGUID AS ARRAY_KEY2, id FROM ?_profiler_guild WHERE realm IN (?a) AND realmGUID IN (?a)', + array_column($guildData, 'realm'), array_column($guildData, 'realmGUID') + ); + + foreach ($baseData as &$bd) + if ($bd['guild']) + $bd['guild'] = $localGuilds[$bd['realm']][$bd['guild']]; + } + + // basic char data (enough for tooltips) + if ($baseData) + { + foreach (Util::createSqlBatchInsert($baseData) as $ins) + DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_profiles (?#) VALUES '.$ins, array_keys(reset($baseData))); + + // merge back local ids + $localIds = DB::Aowow()->select( + 'SELECT CONCAT(realm, ":", realmGUID) AS ARRAY_KEY, id, gearscore FROM ?_profiler_profiles WHERE (cuFlags & ?d) = 0 AND realm IN (?a) AND realmGUID IN (?a)', + PROFILER_CU_PROFILE, + array_column($baseData, 'realm'), + array_column($baseData, 'realmGUID') + ); + + foreach ($this->iterate() as $guid => &$_curTpl) + if (isset($localIds[$guid])) + $_curTpl = array_merge($_curTpl, $localIds[$guid]); + } + } +} + + +class LocalProfileList extends ProfileList +{ + protected $queryBase = 'SELECT p.*, p.id AS ARRAY_KEY FROM ?_profiler_profiles p'; + protected $queryOpts = array( + 'p' => [['g'], 'g' => 'p.id'], + 'ap' => ['j' => ['?_account_profiles ap ON ap.profileId = p.id', true], 's' => ', (IFNULL(ap.ExtraFlags, 0) | p.cuFlags) AS cuFlags'], + 'atm' => ['j' => ['?_profiler_arena_team_member atm ON atm.profileId = p.id', true], 's' => ', atm.captain, atm.personalRating AS rating, atm.seasonGames, atm.seasonWins'], + 'at' => [['atm'], 'j' => ['?_profiler_arena_team at ON at.id = atm.arenaTeamId', true], 's' => ', at.type'], + 'g' => ['j' => ['?_profiler_guild g ON g.id = p.guild', true], 's' => ', g.name AS guildname'] + ); + + public function __construct($conditions = [], $miscData = null) + { + parent::__construct($conditions, $miscData); + + if ($this->error) + return; + + $realms = Profiler::getRealms(); + + // post processing + $acvPoints = DB::Aowow()->selectCol('SELECT pc.id AS ARRAY_KEY, SUM(a.points) FROM ?_profiler_completion pc LEFT JOIN ?_achievement a ON a.id = pc.typeId WHERE pc.`type` = ?d AND pc.id IN (?a) GROUP BY pc.id', TYPE_ACHIEVEMENT, $this->getFoundIDs()); + + foreach ($this->iterate() as $id => &$curTpl) + { + if ($curTpl['realm'] && !isset($realms[$curTpl['realm']])) + continue; + + if (isset($realms[$curTpl['realm']])) + { + $curTpl['realmName'] = $realms[$curTpl['realm']]['name']; + $curTpl['region'] = $realms[$curTpl['realm']]['region']; + } + + // battlegroup + $curTpl['battlegroup'] = CFG_BATTLEGROUP; + + $curTpl['achievementpoints'] = isset($acvPoints[$id]) ? $acvPoints[$id] : 0; + } + } + + public function getProfileUrl() + { + $url = '?profile='; + + if ($this->isCustom()) + return $url.$this->getField('id'); + + return $url.implode('.', array( + Profiler::urlize($this->getField('region')), + Profiler::urlize($this->getField('realmName')), + urlencode($this->getField('name')) + )); + } +} + + ?> diff --git a/includes/types/spell.class.php b/includes/types/spell.class.php index 20821201..9bc9cb4a 100644 --- a/includes/types/spell.class.php +++ b/includes/types/spell.class.php @@ -340,33 +340,226 @@ class SpellList extends BaseType public function getProfilerMods() { + // weapon hand check: param: slot, class, subclass, value + $whCheck = '$function() { var j, w = _inventory.getInventory()[%d]; if (!w[0] || !g_items[w[0]]) { return 0; } j = g_items[w[0]].jsonequip; return (j.classs == %d && (%d & (1 << (j.subclass)))) ? %d : 0; }'; + $data = $this->getStatGain(); // flat gains + foreach ($data as $id => &$spellData) + { + foreach ($spellData as $modId => $val) + { + if (!isset(Game::$itemMods[$modId])) + continue; + + if ($modId == ITEM_MOD_EXPERTISE_RATING) // not a rating .. pure expertise + $spellData['exp'] = $val; + else + $spellData[Game::$itemMods[$modId]] = $val; + + unset($spellData[$modId]); + } + + // apply weapon restrictions + $this->getEntry($id); + $class = $this->getField('equippedItemClass'); + $subClass = $this->getField('equippedItemSubClassMask'); + $slot = $subClass & 0x5000C ? 18 : 16; + if ($class != ITEM_CLASS_WEAPON || !$subClass) + continue; + + foreach ($spellData as $json => $pts) + $spellData[$json] = [1, 'functionOf', sprintf($whCheck, $slot, $class, $subClass, $pts)]; + } + + // 4 possible modifiers found + // => [0.15, 'functionOf', ] + // => [0.33, 'percentOf', ] + // => [123, 'add'] + // => ... as from getStatGain() + + $modXByStat = function (&$arr, $stat, $pts) use (&$mv) + { + if ($mv == STAT_STRENGTH) + $arr[$stat ?: 'str'] = [$pts / 100, 'percentOf', 'str']; + else if ($mv == STAT_AGILITY) + $arr[$stat ?: 'agi'] = [$pts / 100, 'percentOf', 'agi']; + else if ($mv == STAT_STAMINA) + $arr[$stat ?: 'sta'] = [$pts / 100, 'percentOf', 'sta']; + else if ($mv == STAT_INTELLECT) + $arr[$stat ?: 'int'] = [$pts / 100, 'percentOf', 'int']; + else if ($mv == STAT_SPIRIT) + $arr[$stat ?: 'spi'] = [$pts / 100, 'percentOf', 'spi']; + }; + + $modXBySchool = function (&$arr, $stat, $val, $mask = null) use (&$mv) + { + if (($mask ?: $mv) & (1 << SPELL_SCHOOL_HOLY)) + $arr['hol'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'hol'.$stat]; + if (($mask ?: $mv) & (1 << SPELL_SCHOOL_FIRE)) + $arr['fir'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fir'.$stat]; + if (($mask ?: $mv) & (1 << SPELL_SCHOOL_NATURE)) + $arr['nat'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'nat'.$stat]; + if (($mask ?: $mv) & (1 << SPELL_SCHOOL_FROST)) + $arr['fro'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fro'.$stat]; + if (($mask ?: $mv) & (1 << SPELL_SCHOOL_SHADOW)) + $arr['sha'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'sha'.$stat]; + if (($mask ?: $mv) & (1 << SPELL_SCHOOL_ARCANE)) + $arr['arc'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'arc'.$stat]; + }; + + $jsonStat = function ($stat) + { + if ($stat == STAT_STRENGTH) + return 'str'; + if ($stat == STAT_AGILITY) + return 'agi'; + if ($stat == STAT_STAMINA) + return 'sta'; + if ($stat == STAT_INTELLECT) + return 'int'; + if ($stat == STAT_SPIRIT) + return 'spi'; + }; foreach ($this->iterate() as $id => $__) { + // Priest: Spirit of Redemption is a spell but also a passive. *yaaayyyy* + if (($this->getField('cuFlags') & SPELL_CU_TALENTSPELL) && $id != 20711) + continue; + + // curious cases of OH MY FUCKING GOD WHY?! + if ($id == 16268) // Shaman - Spirit Weapons (parry is normaly stored in g_statistics) + { + $data[$id]['parrypct'] = [5, 'add']; + continue; + } + + if ($id == 20550) // Tauren - Endurance (dependant on base health) ... if you are looking for something elegant, look away! + { + $data[$id]['health'] = [0.05, 'functionOf', '$function(p) { return g_statistics.combo[p.classs][p.level][5]; }']; + continue; + } + for ($i = 1; $i < 4; $i++) { - $pts = $this->calculateAmountForCurrent($i)[1]; - $mv = $this->curTpl['effect'.$i.'MiscValue']; - $au = $this->curTpl['effect'.$i.'AuraId']; + $pts = $this->calculateAmountForCurrent($i)[1]; + $mv = $this->getField('effect'.$i.'MiscValue'); + $mvB = $this->getField('effect'.$i.'MiscValueB'); + $au = $this->getField('effect'.$i.'AuraId'); + $class = $this->getField('equippedItemClass'); + $subClass = $this->getField('equippedItemSubClassMask'); + /* ISSUE! mods formated like ['' => [, 'percentOf', '']] are applied as multiplier and not as a flat value (that is equal to the percentage, like they should be). So the stats-table won't show the actual deficit */ - switch ($this->curTpl['effect'.$i.'AuraId']) + switch ($au) { - case 101: - $data[$id][] = ['armor' => [$pts / 100, 'percentOf', 'armor']]; + case 101: // Mod Resistance Percent + case 142: // Mod Base Resistance Percent + if ($mv == 1) // Armor only if explicitly specified only affects armor from equippment + $data[$id]['armor'] = [$pts / 100, 'percentOf', ['armor', 0]]; + else if ($mv) + $modXBySchool($data[$id], 'res', $pts); break; - case 13: // damage done flat - // per magic school, omit physical + case 182: // Mod Resistance Of Stat Percent + if ($mv == 1) // Armor only if explicitly specified + $data[$id]['armor'] = [$pts / 100, 'percentOf', $jsonStat($mvB)]; + else if ($mv) + $modXBySchool($data[$id], 'res', [$pts / 100, 'percentOf', $jsonStat($mvB)]); break; - case 30: // mod skill - // diff between character skills and trade skills + case 137: // mod stat percent + if ($mv > -1) // one stat + $modXByStat($data[$id], null, $pts); + else if ($mv < 0) // all stats + for ($iMod = ITEM_MOD_AGILITY; $iMod <= ITEM_MOD_STAMINA; $iMod++) + $data[$id][Game::$itemMods[$iMod]] = [$pts / 100, 'percentOf', Game::$itemMods[$iMod]]; + break; + case 174: // Mod Spell Damage Of Stat Percent + $mv = $mv ?: SPELL_MAGIC_SCHOOLS; + $modXBySchool($data[$id], 'spldmg', [$pts / 100, 'percentOf', $jsonStat($mvB)]); + break; + case 212: // Mod Ranged Attack Power Of Stat Percent + $modXByStat($data[$id], 'rgdatkpwr', $pts); + break; + case 268: // Mod Attack Power Of Stat Percent + $modXByStat($data[$id], 'mleatkpwr', $pts); + break; + case 175: // Mod Spell Healing Of Stat Percent + $modXByStat($data[$id], 'splheal', $pts); + break; + case 219: // Mod Mana Regeneration from Stat + $modXByStat($data[$id], 'manargn', $pts); + break; + case 134: // Mod Mana Regeneration Interrupt + $data[$id]['icmanargn'] = [$pts / 100, 'percentOf', 'oocmanargn']; + break; + case 57: // Mod Spell Crit Chance + case 71: // Mod Spell Crit Chance School + $mv = $mv ?: SPELL_MAGIC_SCHOOLS; + $modXBySchool($data[$id], 'splcritstrkpct', [$pts, 'add']); + if (($mv & SPELL_MAGIC_SCHOOLS) == SPELL_MAGIC_SCHOOLS) + $data[$id]['splcritstrkpct'] = [$pts, 'add']; + break; + case 285: // Mod Attack Power Of Armor + $data[$id]['mleatkpwr'] = [1 / $pts, 'percentOf', 'fullarmor']; + $data[$id]['rgdatkpwr'] = [1 / $pts, 'percentOf', 'fullarmor']; + break; + case 52: // Mod Physical Crit Percent + if ($class < 1 || ($class == ITEM_CLASS_WEAPON && ($subClass & 0x5000C))) + $data[$id]['rgdcritstrkpct'] = [1, 'functionOf', sprintf($whCheck, 18, $class, $subClass, $pts)]; + // $data[$id]['rgdcritstrkpct'] = [$pts, 'add']; + if ($class < 1 || ($class == ITEM_CLASS_WEAPON && ($subClass & 0xA5F3))) + $data[$id]['mlecritstrkpct'] = [1, 'functionOf', sprintf($whCheck, 16, $class, $subClass, $pts)]; + // $data[$id]['mlecritstrkpct'] = [$pts, 'add']; + break; + case 47: // Mod Parry Percent + $data[$id]['parrypct'] = [$pts, 'add']; + break; + case 49: // Mod Dodge Percent + $data[$id]['dodgepct'] = [$pts, 'add']; + break; + case 51: // Mod Block Percent + $data[$id]['blockpct'] = [$pts, 'add']; + break; + case 132: // Mod Increase Energy Percent + if ($mv == POWER_HEALTH) + $data[$id]['health'] = [$pts / 100, 'percentOf', 'health']; + else if ($mv == POWER_ENERGY) + $data[$id]['energy'] = [$pts / 100, 'percentOf', 'energy']; + else if ($mv == POWER_MANA) + $data[$id]['mana'] = [$pts / 100, 'percentOf', 'mana']; + else if ($mv == POWER_RAGE) + $data[$id]['rage'] = [$pts / 100, 'percentOf', 'rage']; + else if ($mv == POWER_RUNIC_POWER) + $data[$id]['runic'] = [$pts / 100, 'percentOf', 'runic']; + break; + case 133: // Mod Increase Health Percent + $data[$id]['health'] = [$pts / 100, 'percentOf', 'health']; + break; + case 150: // Mod Shield Blockvalue Percent + $data[$id]['block'] = [$pts / 100, 'percentOf', 'block']; + break; + case 290: // Mod Crit Percent + $data[$id]['mlecritstrkpct'] = [$pts, 'add']; + $data[$id]['rgdcritstrkpct'] = [$pts, 'add']; + $data[$id]['splcritstrkpct'] = [$pts, 'add']; + break; + case 237: // Mod Spell Damage Of Attack Power + $mv = $mv ?: SPELL_MAGIC_SCHOOLS; + $modXBySchool($data[$id], 'spldmg', [$pts / 100, 'percentOf', 'mleatkpwr']); + break; + case 238: // Mod Spell Healing Of Attack Power + $data[$id]['splheal'] = [$pts / 100, 'percentOf', 'mleatkpwr']; + break; + case 166: // Mod Attack Power Percent [ingmae only melee..?] + $data[$id]['mleatkpwr'] = [$pts / 100, 'percentOf', 'mleatkpwr']; + break; + case 88: // Mod Health Regeneration Percent + $data[$id]['healthrgn'] = [$pts / 100, 'percentOf', 'healthrgn']; break; - case 36: // shapeshift } } } diff --git a/includes/user.class.php b/includes/user.class.php index df821915..9144eaed 100644 --- a/includes/user.class.php +++ b/includes/user.class.php @@ -17,10 +17,12 @@ class User public static $dailyVotes = 0; public static $ip = null; - private static $reputation = 0; - private static $dataKey = ''; - private static $expires = false; - private static $passHash = ''; + private static $reputation = 0; + private static $dataKey = ''; + private static $expires = false; + private static $passHash = ''; + private static $excludeGroups = 1; + private static $profiles = null; public static function init() { @@ -54,7 +56,7 @@ class User return false; $query = DB::Aowow()->SelectRow(' - SELECT a.id, a.passHash, a.displayName, a.locale, a.userGroups, a.userPerms, a.allowExpire, BIT_OR(ab.typeMask) AS bans, IFNULL(SUM(r.amount), 0) as reputation, a.avatar, a.dailyVotes + SELECT a.id, a.passHash, a.displayName, a.locale, a.userGroups, a.userPerms, a.allowExpire, BIT_OR(ab.typeMask) AS bans, IFNULL(SUM(r.amount), 0) as reputation, a.avatar, a.dailyVotes, a.excludeGroups FROM ?_account a LEFT JOIN ?_account_banned ab ON a.id = ab.userId AND ab.end > UNIX_TIMESTAMP() LEFT JOIN ?_account_reputation r ON a.id = r.userId @@ -73,15 +75,26 @@ class User return false; } - self::$id = intval($query['id']); - self::$displayName = $query['displayName']; - self::$passHash = $query['passHash']; - self::$expires = (bool)$query['allowExpire']; - self::$reputation = $query['reputation']; - self::$banStatus = $query['bans']; - self::$groups = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userGroups']); - self::$perms = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userPerms']); - self::$dailyVotes = $query['dailyVotes']; + self::$id = intval($query['id']); + self::$displayName = $query['displayName']; + self::$passHash = $query['passHash']; + self::$expires = (bool)$query['allowExpire']; + self::$reputation = $query['reputation']; + self::$banStatus = $query['bans']; + self::$groups = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userGroups']); + self::$perms = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userPerms']); + self::$dailyVotes = $query['dailyVotes']; + self::$excludeGroups = $query['excludeGroups']; + + $conditions = array( + [['cuFlags', PROFILER_CU_DELETED, '&'], 0], + ['OR', ['user', self::$id], ['ap.accountId', self::$id]] + ); + + if (self::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU)) + array_shift($conditions); + + self::$profiles = (new LocalProfileList($conditions)); if ($query['avatar']) self::$avatar = $query['avatar']; @@ -140,11 +153,11 @@ class User $rawIp = explode(',', $rawIp)[0]; // [ip, proxy1, proxy2] // check IPv4 - if ($ipAddr = filter_var($rawIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_RES_RANGE)) + if ($ipAddr = filter_var($rawIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) break; // check IPv6 - if ($ipAddr = filter_var($rawIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE)) + if ($ipAddr = filter_var($rawIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) break; } } @@ -565,6 +578,11 @@ class User $gUser['downvoteRep'] = CFG_REP_REQ_DOWNVOTE; $gUser['upvoteRep'] = CFG_REP_REQ_UPVOTE; $gUser['characters'] = self::getCharacters(); + $gUser['excludegroups'] = self::$excludeGroups; + $gUser['settings'] = (new StdClass); // profiler requires this to be set; has property premiumborder (NYI) + + if ($_ = self::getProfilerExclusions()) + $gUser = array_merge($gUser, $_); if ($_ = self::getProfiles()) $gUser['profiles'] = $_; @@ -593,36 +611,32 @@ class User return $result; } + public static function getProfilerExclusions() + { + $result = []; + $modes = [1 => 'excludes', 2 => 'includes']; + foreach ($modes as $mode => $field) + if ($ex = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, typeId AS ARRAY_KEY2, typeId FROM ?_account_excludes WHERE mode = ?d AND userId = ?d', $mode, self::$id)) + foreach ($ex as $type => $ids) + $result[$field][$type] = array_values($ids); + + return $result; + } + public static function getCharacters() { - // existing chars on realm(s) - $characters = array( - // array( - // 'id' => 22, - // 'name' => 'Example Char', - // 'realmname' => 'Example Realm', - // 'region' => 'eu', - // 'realm' => 'example-realm', - // 'race' => 6, - // 'classs' => 11, - // 'level' => 80, - // 'gender' => 1, - // 'pinned' => 1 - // ) - ); + if (!self::$profiles) + return []; - return $characters; + return self::$profiles->getJSGlobals(PROFILEINFO_CHARACTER); } public static function getProfiles() { - // chars build in profiler - $profiles = array( - // array('id' => 21, 'name' => 'Example Profile 1', 'race' => 4, 'classs' => 5, 'level' => 72, 'gender' => 1, 'icon' => 'inv_axe_04'), - // array('id' => 23, 'name' => 'Example Profile 2', 'race' => 11, 'classs' => 3, 'level' => 17, 'gender' => 0) - ); + if (!self::$profiles) + return []; - return $profiles; + return self::$profiles->getJSGlobals(PROFILEINFO_PROFILE); } public static function getCookies() diff --git a/includes/utilities.php b/includes/utilities.php index 5911e700..eb1aa37e 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -370,7 +370,7 @@ class Util ); public static $configCats = array( - 'Other', 'Site', 'Caching', 'Account', 'Session', 'Site Reputation', 'Google Analytics' + 'Other', 'Site', 'Caching', 'Account', 'Session', 'Site Reputation', 'Google Analytics', 'Profiler' ); public static $tcEncoding = '0zMcmVokRsaqbdrfwihuGINALpTjnyxtgevElBCDFHJKOPQSUWXYZ123456789'; @@ -413,16 +413,6 @@ class Util return self::formatTime($tDiff * 1000, true); } - public static function getBuyoutForItem($itemId) - { - if (!$itemId) - return 0; - - // try, when having filled char-DB at hand - // return DB::Characters()->selectCell('SELECT SUM(a.buyoutprice) / SUM(ii.count) FROM auctionhouse a JOIN item_instance ii ON ii.guid = a.itemguid WHERE ii.itemEntry = ?d', $itemId); - return 0; - } - public static function formatMoney($qty) { $money = ''; @@ -809,42 +799,6 @@ class Util } } - public static function urlize($str) - { - $search = ['<', '>', ' / ', "'", '(', ')']; - $replace = ['<', '>', '-', '', '', '']; - $str = str_replace($search, $replace, $str); - - $accents = array( - "ß" => "ss", - "á" => "a", "ä" => "a", "à" => "a", "â" => "a", - "è" => "e", "ê" => "e", "é" => "e", "ë" => "e", - "í" => "i", "î" => "i", "ì" => "i", "ï" => "i", - "ñ" => "n", - "ò" => "o", "ó" => "o", "ö" => "o", "ô" => "o", - "ú" => "u", "ü" => "u", "û" => "u", "ù" => "u", - "œ" => "oe", - "Á" => "A", "Ä" => "A", "À" => "A", "Â" => "A", - "È" => "E", "Ê" => "E", "É" => "E", "Ë" => "E", - "Í" => "I", "Î" => "I", "Ì" => "I", "Ï" => "I", - "Ñ" => "N", - "Ò" => "O", "Ó" => "O", "Ö" => "O", "Ô" => "O", - "Ú" => "U", "Ü" => "U", "Û" => "U", "Ù" => "U", - "œ" => "Oe" - ); - $str = strtr($str, $accents); - $str = trim($str); - $str = preg_replace('/[^a-z0-9]/i', '-', $str); - - $str = str_replace('--', '-', $str); - $str = str_replace('--', '-', $str); - - $str = rtrim($str, '-'); - $str = strtolower($str); - - return $str; - } - public static function isValidEmail($email) { return preg_match('/^([a-z0-9._-]+)(\+[a-z0-9._-]+)?(@[a-z0-9.-]+\.[a-z]{2,4})$/i', $email); @@ -1168,38 +1122,86 @@ class Util return $json; } - public static function checkOrCreateDirectory($path) + public static function createSqlBatchInsert(array $data) { - // remove multiple slashes - $path = preg_replace('|/+|', '/', $path); + $nRows = 100; + $nItems = count(reset($data)); + $result = []; + $buff = []; - if (!is_dir($path) && !@mkdir($path, self::FILE_ACCESS, true)) - trigger_error('Could not create directory: '.$path, E_USER_ERROR); - else if (!is_writable($path) && !@chmod($path, self::FILE_ACCESS)) - trigger_error('Cannot write into directory: '.$path, E_USER_ERROR); - else - return true; + if (!count($data)) + return []; - return false; - } - - private static $realms = []; - public static function getRealms() - { - if (DB::isConnectable(DB_AUTH) && !self::$realms) + foreach ($data as $d) { - self::$realms = DB::Auth()->select('SELECT id AS ARRAY_KEY, name, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0 AND gamebuild = ?d', WOW_BUILD); - foreach (self::$realms as $rId => $rData) - { - if (DB::isConnectable(DB_CHARACTERS . $rId)) - continue; + if (count($d) != $nItems) + return []; - unset(self::$realms[$rId]); - trigger_error('Realm #'.$rId.' ('.$rData['name'].') has no connection info set.', E_USER_NOTICE); + $d = array_map(function ($x) { + if ($x === null) + return 'NULL'; + + return DB::Aowow()->escape($x); + }, $d); + + $buff[] = implode(',', $d); + + if (count($buff) >= $nRows) + { + $result[] = '('.implode('),(', $buff).')'; + $buff = []; } } - return self::$realms; + if ($buff) + $result[] = '('.implode('),(', $buff).')'; + + return $result; + } + + /*****************/ + /* file handling */ + /*****************/ + + public static function writeFile($file, $content) + { + $success = false; + if ($handle = @fOpen($file, "w")) + { + if (fWrite($handle, $content)) + $success = true; + else + trigger_error('could not write to file', E_USER_ERROR); + + fClose($handle); + } + else + trigger_error('could not create file', E_USER_ERROR); + + if ($success) + @chmod($file, Util::FILE_ACCESS); + + return $success; + } + + public static function writeDir($dir) + { + // remove multiple slashes + $dir = preg_replace('|/+|', '/', $dir); + + if (is_dir($dir)) + { + if (!is_writable($dir) && !@chmod($dir, Util::FILE_ACCESS)) + trigger_error('cannot write into directory', E_USER_ERROR); + + return is_writable($dir); + } + + if (@mkdir($dir, Util::FILE_ACCESS, true)) + return true; + + trigger_error('could not create directory', E_USER_ERROR); + return false; } diff --git a/localization/lang.class.php b/localization/lang.class.php index 1aff699f..c699079b 100644 --- a/localization/lang.class.php +++ b/localization/lang.class.php @@ -9,6 +9,7 @@ class Lang private static $mail; private static $game; private static $maps; + private static $profiler; private static $screenshot; private static $privileges; @@ -48,6 +49,13 @@ class Lang // *cough* .. reuse-hacks (because copy-pastaing text for 5 locales sucks) self::$item['cat'][2] = [self::$item['cat'][2], self::$spell['weaponSubClass']]; self::$item['cat'][2][1][14] .= ' ('.self::$item['cat'][2][0].')'; + + // not localized .. for whatever reason + self::$profiler['regions'] = array( + 'eu' => "Europe", + 'us' => "US & Oceanic" + ); + self::$main['moreTitles']['privilege'] = self::$privileges['_privileges']; } @@ -252,7 +260,12 @@ class Lang if ($mask & (1 << $k) && $str) $tmp[] = $str; - return implode(', ', $tmp); + if (!$tmp && $class == ITEM_CLASS_ARMOR) + return self::spell('cat', -11, 8); + else if (!$tmp && $class == ITEM_CLASS_WEAPON) + return self::spell('cat', -11, 6); + else + return implode(', ', $tmp); } public static function getStances($stanceMask) diff --git a/pages/achievement.php b/pages/achievement.php index b0f7e746..adb57d02 100644 --- a/pages/achievement.php +++ b/pages/achievement.php @@ -103,7 +103,7 @@ class AchievementPage extends GenericPage if ($this->subject->getField('flags') & 0x100 && DB::isConnectable(DB_AUTH)) { $avlb = []; - foreach (Util::getRealms() AS $rId => $rData) + foreach (Profiler::getRealms() AS $rId => $rData) if (!DB::Characters($rId)->selectCell('SELECT 1 FROM character_achievement WHERE achievement = ?d LIMIT 1', $this->typeId)) $avlb[] = Util::ucWords($rData['name']); diff --git a/pages/admin.php b/pages/admin.php index dbec15c9..5cfc5d38 100644 --- a/pages/admin.php +++ b/pages/admin.php @@ -104,14 +104,14 @@ class AdminPage extends GenericPage $this->lvTabs[] = [null, array( 'data' => $t, 'name' => $n, - 'id' => Util::urlize($n) + 'id' => Profiler::urlize($n) )]; foreach ($miscTab as $n => $t) $this->lvTabs[] = [null, array( 'data' => $t, 'name' => $n, - 'id' => Util::urlize($n) + 'id' => Profiler::urlize($n) )]; } diff --git a/pages/genericPage.class.php b/pages/genericPage.class.php index 3b5cf2ad..99036841 100644 --- a/pages/genericPage.class.php +++ b/pages/genericPage.class.php @@ -73,6 +73,88 @@ trait ListPage } } +trait TrProfiler +{ + protected $region = ''; + protected $realm = ''; + protected $realmId = 0; + protected $battlegroup = ''; // not implemented, since no pserver supports it + protected $subjectName = ''; + protected $subjectGUID = 0; + protected $sumSubjects = 0; + + protected $doResync = null; + + protected function getSubjectFromUrl($str) + { + if (!$str) + return; + + // cat[0] is always region + // cat[1] is realm or bGroup (must be realm if cat[2] is set) + // cat[2] is arena-team, guild or player + $cat = explode('.', $str, 3); + + $cat = array_map('urldecode', $cat); + + if (count($cat) > 3) + return; + + if ($cat[0] !== 'eu' && $cat[0] !== 'us') + return; + + $this->region = $cat[0]; + + // if ($cat[1] == Profiler::urlize(CFG_BATTLEGROUP)) + // $this->battlegroup = CFG_BATTLEGROUP; + if (isset($cat[1])) + { + foreach (Profiler::getRealms() as $rId => $r) + { + if (Profiler::urlize($r['name']) == $cat[1]) + { + $this->realm = $r['name']; + $this->realmId = $rId; + if (isset($cat[2]) && mb_strlen($cat[2]) >= 3) + $this->subjectName = $cat[2]; // cannot reconstruct original name from urlized form; match against special name field + + break; + } + } + } + } + + protected function initialSync() + { + $this->prepareContent(); + + $this->notFound = array( + 'title' => sprintf(Lang::profiler('firstUseTitle'), $this->subjectName, $this->realm), + 'msg' => '' + ); + $this->hasComContent = false; + Util::arraySumByKey($this->mysql, DB::Aowow()->getStatistics(), DB::World()->getStatistics()); + + if (isset($this->tabId)) + $this->pageTemplate['activeTab'] = $this->tabId; + + $this->display('text-page-generic'); + exit(); + } + + protected function generatePath() + { + if ($this->region) + { + $this->path[] = $this->region; + + if ($this->realm) + $this->path[] = Profiler::urlize($this->realm); + // else + // $this->path[] = Profiler::urlize(CFG_BATTLEGROUP); + } + } +} class GenericPage { @@ -148,7 +230,7 @@ class GenericPage if ($pageParam) $this->fullParams .= '='.$pageParam; - if (CFG_CACHE_DIR && Util::checkOrCreateDirectory(CFG_CACHE_DIR)) + if (CFG_CACHE_DIR && Util::writeDir(CFG_CACHE_DIR)) $this->cacheDir = mb_substr(CFG_CACHE_DIR, -1) != '/' ? CFG_CACHE_DIR.'/' : CFG_CACHE_DIR; // force page refresh diff --git a/pages/item.php b/pages/item.php index 9b0c9783..fa65ac21 100644 --- a/pages/item.php +++ b/pages/item.php @@ -261,7 +261,7 @@ class ItemPage extends genericPage // avg auction buyout if (in_array($this->subject->getField('bonding'), [0, 2, 3])) - if ($_ = Util::getBuyoutForItem($this->typeId)) + if ($_ = Profiler::getBuyoutForItem($this->typeId)) $infobox[] = '[tooltip=tooltip_buyoutprice]'.Lang::item('buyout.').'[/tooltip]'.Lang::main('colon').'[money='.$_.']'.$each; // avg money contained diff --git a/prQueue b/prQueue new file mode 100755 index 00000000..4d2c645d --- /dev/null +++ b/prQueue @@ -0,0 +1,116 @@ +query('UPDATE ?_profiler_sync SET status = ?d, errorCode = ?d WHERE realm = ?d AND type = ?d AND typeId = ?d', PR_QUEUE_STATUS_ERROR, PR_QUEUE_ERROR_CHAR, $realmId, $type, $typeId); + trigger_error('prQueue - unknown '.$what.' guid #'.$typeId.' on realm #'.$realmId.' to sync into profiler.', E_USER_WARNING); + CLI::write('unknown '.$what.' guid #'.$typeId.' on realm #'.$realmId.' to sync into profiler.', CLI::LOG_WARN); +}; + + +// if (CFG_PROFILER_QUEUE) - wont work because it is not redefined if changed in config +while (DB::Aowow()->selectCell('SELECT value FROM ?_config WHERE `key` = "profiler_queue"')) +{ + if (($tDiff = (microtime(true) - $tCycle)) < (CFG_PROFILER_QUEUE_DELAY / 1000)) + { + $wait = (CFG_PROFILER_QUEUE_DELAY / 1000) - $tDiff; + CLI::write('sleeping '.Lang::nf($wait, 2).'s..'); + usleep($wait * 1000 * 1000); + } + + $tCycle = microtime(true); + + $row = DB::Aowow()->selectRow('SELECT * FROM ?_profiler_sync WHERE status = ?d ORDER BY requestTime ASC', PR_QUEUE_STATUS_WAITING); + if (!$row) + { + // nothing more to do + CLI::write('profiler queue empty - process halted!', CLI::LOG_INFO); + Profiler::queueFree(); + exit(); + } + // scheduled for future date + if ($row['requestTime'] > time()) + continue; + + if (empty(Profiler::getRealms()[$row['realm']])) + { + DB::Aowow()->query('UPDATE ?_profiler_sync SET status = ?d, errorCode = ?d WHERE realm = ?d AND type = ?d AND typeId = ?d', PR_QUEUE_STATUS_ERROR, PR_QUEUE_ERROR_ARMORY, $row['realm'], $row['type'], $row['typeId']); + CLI::write('realm #'.$row['realm'].' for subject guid '.$row['realmGUID'].' is undefined', CLI::LOG_WARN); + continue; + } + else + DB::Aowow()->query('UPDATE ?_profiler_sync SET status = ?d WHERE requestTime = ?d AND realm = ?d AND type = ?d AND typeId = ?d', PR_QUEUE_STATUS_WORKING, time(), $row['realm'], $row['type'], $row['typeId']); + + switch ($row['type']) + { + case TYPE_PROFILE: + if (!Profiler::getCharFromRealm($row['realm'], $row['realmGUID'])) + { + $error(TYPE_PROFILE, $row['realmGUID'], $row['realm']); + continue 2; + } + + break; + case TYPE_GUILD: + if (!Profiler::getGuildFromRealm($row['realm'], $row['realmGUID'])) + { + $error(TYPE_ARENA_GUILD, $row['realmGUID'], $row['realm']); + continue 2; + } + + break; + case TYPE_ARENA_TEAM: + if (!Profiler::getArenaTeamFromRealm($row['realm'], $row['realmGUID'])) + { + $error(TYPE_ARENA_TEAM, $row['realmGUID'], $row['realm']); + continue 2; + } + + break; + default: + DB::Aowow()->query('DELETE FROM ?_profiler_sync WHERE realm = ?d AND type = ?d AND typeId = ?d', $row['realm'], $row['type'], $row['typeId']); + trigger_error('prQueue - unknown type #'.$row['type'].' to sync into profiler. Removing from queue...', E_USER_ERROR); + CLI::write('unknown type #'.$row['type'].' to sync into profiler. Removing from queue...', CLI::LOG_ERROR); + } + + // mark as ready + DB::Aowow()->query('UPDATE ?_profiler_sync SET status = ?d, errorCode = 0 WHERE realm = ?d AND type = ?d AND typeId = ?d', PR_QUEUE_STATUS_READY, $row['realm'], $row['type'], $row['typeId']); +} + +Profiler::queueFree(); +CLI::write('profiler queue halted!', CLI::LOG_INFO); + +?> diff --git a/setup/db_structure.sql b/setup/db_structure.sql index 16c80b0a..ecd48aeb 100644 --- a/setup/db_structure.sql +++ b/setup/db_structure.sql @@ -42,6 +42,7 @@ CREATE TABLE `aowow_account` ( `avatar` varchar(50) NOT NULL DEFAULT '' COMMENT 'icon-string for internal or id for upload', `title` varchar(50) NOT NULL DEFAULT '' COMMENT 'user can obtain custom titles', `description` text NOT NULL COMMENT 'markdown formated', + `excludeGroups` smallint(5) unsigned NOT NULL DEFAULT '1' COMMENT 'profiler - completion exclude bitmask', `userPerms` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT 'bool isAdmin', `status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT 'flag, see defines', `statusTimer` int(10) unsigned NOT NULL DEFAULT '0', @@ -299,28 +300,6 @@ CREATE TABLE `aowow_articles` ( ) ENGINE=MyISAM DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; --- --- Table structure for table `aowow_characters` --- - -DROP TABLE IF EXISTS `aowow_characters`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `aowow_characters` ( - `id` int(11) NOT NULL, - `name` varchar(50) NOT NULL, - `race` tinyint(3) unsigned NOT NULL, - `class` tinyint(3) unsigned NOT NULL, - `gender` tinyint(3) unsigned NOT NULL, - `level` tinyint(3) unsigned NOT NULL, - `description` varchar(150) NOT NULL, - `iconString` varchar(50) NOT NULL, - `titleId` tinyint(3) unsigned NOT NULL, - `guildId` mediumint(8) unsigned NOT NULL, - `guildRank` tinyint(3) unsigned NOT NULL -) ENGINE=MyISAM DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - -- -- Table structure for table `aowow_classes` -- @@ -2271,6 +2250,7 @@ DROP TABLE IF EXISTS `aowow_talents`; CREATE TABLE `aowow_talents` ( `id` smallint(5) unsigned NOT NULL, `class` tinyint(3) unsigned NOT NULL, + `petTypeMask` tinyint(3) unsigned NOT NULL, `tab` tinyint(3) unsigned NOT NULL, `row` tinyint(3) unsigned NOT NULL, `col` tinyint(3) unsigned NOT NULL, @@ -2625,7 +2605,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_account_weightscales` WRITE; /*!40000 ALTER TABLE `aowow_account_weightscales` DISABLE KEYS */; -INSERT INTO `aowow_account_weightscales` VALUES (1,0,'arms',1,'ability_rogue_eviscerate'),(2,0,'fury',1,'ability_warrior_innerrage'),(3,0,'prot',1,'ability_warrior_defensivestance'),(4,0,'holy',2,'spell_holy_holybolt'),(5,0,'prot',2,'ability_paladin_shieldofthetempl'),(6,0,'retrib',2,'spell_holy_auraoflight'),(7,0,'beast',3,'ability_hunter_beasttaming'),(8,0,'marks',3,'ability_marksmanship'),(9,0,'surv',3,'ability_hunter_swiftstrike'),(10,0,'assas',4,'ability_rogue_eviscerate'),(11,0,'combat',4,'ability_backstab'),(12,0,'subtle',4,'ability_stealth'),(13,0,'disc',5,'spell_holy_wordfortitude'),(14,0,'holy',5,'spell_holy_guardianspirit'),(15,0,'shadow',5,'spell_shadow_shadowwordpain'),(16,0,'blooddps',6,'spell_deathknight_bloodpresence'),(17,0,'frostdps',6,'spell_deathknight_frostpresence'),(18,0,'frosttank',6,'spell_deathknight_frostpresence'),(19,0,'unholydps',6,'spell_deathknight_unholypresence'),(20,0,'elem',7,'spell_nature_lightning'),(21,0,'enhance',7,'spell_nature_lightningshield'),(22,0,'resto',7,'spell_nature_magicimmunity'),(23,0,'arcane',8,'spell_holy_magicalsentry'),(24,0,'fire',8,'spell_fire_firebolt02'),(25,0,'frost',8,'spell_frost_frostbolt02'),(26,0,'afflic',9,'spell_shadow_deathcoil'),(27,0,'demo',9,'spell_shadow_metamorphosis'),(28,0,'destro',9,'spell_shadow_rainoffire'),(29,0,'balance',11,'spell_nature_starfall'),(30,0,'feraltank',11,'ability_racial_bearform'),(31,0,'resto',11,'spell_nature_healingtouch'),(32,0,'feraldps',11,'ability_druid_catform'); +INSERT INTO `aowow_account_weightscales` VALUES (1,0,'arms',1,'ability_rogue_eviscerate'),(2,0,'fury',1,'ability_warrior_innerrage'),(3,0,'prot',1,'ability_warrior_defensivestance'),(4,0,'holy',2,'spell_holy_holybolt'),(5,0,'prot',2,'ability_paladin_shieldofthetemplar'),(6,0,'retrib',2,'spell_holy_auraoflight'),(7,0,'beast',3,'ability_hunter_beasttaming'),(8,0,'marks',3,'ability_marksmanship'),(9,0,'surv',3,'ability_hunter_swiftstrike'),(10,0,'assas',4,'ability_rogue_eviscerate'),(11,0,'combat',4,'ability_backstab'),(12,0,'subtle',4,'ability_stealth'),(13,0,'disc',5,'spell_holy_wordfortitude'),(14,0,'holy',5,'spell_holy_guardianspirit'),(15,0,'shadow',5,'spell_shadow_shadowwordpain'),(16,0,'blooddps',6,'spell_deathknight_bloodpresence'),(17,0,'frostdps',6,'spell_deathknight_frostpresence'),(18,0,'frosttank',6,'spell_deathknight_frostpresence'),(19,0,'unholydps',6,'spell_deathknight_unholypresence'),(20,0,'elem',7,'spell_nature_lightning'),(21,0,'enhance',7,'spell_nature_lightningshield'),(22,0,'resto',7,'spell_nature_magicimmunity'),(23,0,'arcane',8,'spell_holy_magicalsentry'),(24,0,'fire',8,'spell_fire_firebolt02'),(25,0,'frost',8,'spell_frost_frostbolt02'),(26,0,'afflic',9,'spell_shadow_deathcoil'),(27,0,'demo',9,'spell_shadow_metamorphosis'),(28,0,'destro',9,'spell_shadow_rainoffire'),(29,0,'balance',11,'spell_nature_starfall'),(30,0,'feraltank',11,'ability_racial_bearform'),(31,0,'resto',11,'spell_nature_healingtouch'),(32,0,'feraldps',11,'ability_druid_catform'); /*!40000 ALTER TABLE `aowow_account_weightscales` ENABLE KEYS */; UNLOCK TABLES; @@ -2666,7 +2646,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_config` WRITE; /*!40000 ALTER TABLE `aowow_config` DISABLE KEYS */; -INSERT INTO `aowow_config` VALUES ('sql_limit_search','500',1,129,'default: 500 - max results for search'),('sql_limit_default','300',1,129,'default: 300 - max results for listviews'),('sql_limit_quicksearch','10',1,129,'default: 10 - max results for suggestions'),('sql_limit_none','0',1,129,'default: 0 - unlimited results (i wouldn\'t change that mate)'),('ttl_rss','60',1,129,'default: 60 - time to live for RSS (in seconds)'),('name','Aowow Database Viewer (ADV)',1,136,' - website title'),('name_short','Aowow',1,136,' - feed title'),('board_url','http://www.wowhead.com/forums?board=',1,136,' - another halfbaked javascript thing..'),('contact_email','feedback@aowow.org',1,136,' - displayed sender for auth-mails, ect'),('battlegroup','Pure Pwnage',1,136,' - pretend, we belong to a battlegroup to satisfy profiler-related Jscripts'),('debug','0',1,132,'default: 0 - disable cache, enable sql-errors, enable error_reporting'),('maintenance','1',1,132,'default: 0 - display brb gnomes and block access for non-staff'),('user_max_votes','50',1,129,'default: 50 - vote limit per day'),('force_ssl','0',1,132,'default: 0 - enforce SSL, if the server is behind a load balancer'),('locales','333',1,161,'default: 0x14D - allowed locales - 0:English, 2:French, 3:German, 6:Spanish, 8:Russian'),('screenshot_min_size','200',1,129,'default: 200 - minimum dimensions of uploaded screenshots in px (yes, it\'s square)'),('site_host','',1,136,' - points js to executable files'),('static_host','',1,136,' - points js to images & scripts'),('cache_decay','25200',2,129,'default: 60 * 60 * 7 - time to keep cache in seconds'),('cache_mode','1',2,161,'default: 1 - set cache method - 0:filecache, 1:memcached'),('cache_dir','',2,136,'default: cache/template - generated pages are saved here (requires CACHE_MODE: filecache)'),('acc_failed_auth_block','900',3,129,'default: 15 * 60 - how long an account is closed after exceeding FAILED_AUTH_COUNT (in seconds)'),('acc_failed_auth_count','5',3,129,'default: 5 - how often invalid passwords are tolerated'),('acc_allow_register','1',3,132,'default: 1 - allow/disallow account creation (requires AUTH_MODE: aowow)'),('acc_auth_mode','0',3,145,'default: 0 - source to auth against - 0:aowow, 1:TC auth-table, 2:external script'),('acc_create_save_decay','604800',3,129,'default: 604800 - time in wich an unconfirmed account cannot be overwritten by new registrations'),('acc_recovery_decay','300',3,129,'default: 300 - time to recover your account and new recovery requests are blocked'),('session_timeout_delay','3600',4,129,'default: 60 * 60 - non-permanent session times out in time() + X'),('session.gc_maxlifetime','604800',4,200,'default: 7*24*60*60 - lifetime of session data'),('session.gc_probability','1',4,200,'default: 0 - probability to remove session data on garbage collection'),('session.gc_divisor',100,4,200,'default: 100 - probability to remove session data on garbage collection'),('session_cache_dir','',4,136,'default: - php sessions are saved here. Leave empty to use php default directory.'),('rep_req_upvote','125',5,129,'default: 125 - required reputation to upvote comments'),('rep_req_downvote','250',5,129,'default: 250 - required reputation to downvote comments'),('rep_req_comment','75',5,129,'default: 75 - required reputation to write a comment'),('rep_req_reply','75',5,129,'default: 75 - required reputation to write a reply'),('rep_req_supervote','2500',5,129,'default: 2500 - required reputation for double vote effect'),('rep_req_votemore_base','2000',5,129,'default: 2000 - gains more votes past this threshold'),('rep_reward_register','100',5,129,'default: 100 - activated an account'),('rep_reward_upvoted','5',5,129,'default: 5 - comment received upvote'),('rep_reward_downvoted','0',5,129,'default: 0 - comment received downvote'),('rep_reward_good_report','10',5,129,'default: 10 - filed an accepted report'),('rep_reward_bad_report','0',5,129,'default: 0 - filed a rejected report'),('rep_reward_dailyvisit','5',5,129,'default: 5 - daily visit'),('rep_reward_user_warned','-50',5,129,'default: -50 - moderator imposed a warning'),('rep_reward_comment','1',5,129,'default: 1 - created a comment (not a reply) '),('rep_req_premium','25000',5,129,'default: 25000 - required reputation for premium status through reputation'),('rep_reward_upload','10',5,129,'default: 10 - suggested / uploaded video / screenshot was approved'),('rep_reward_article','100',5,129,'default: 100 - submitted an approved article/guide'),('rep_reward_user_suspended','-200',5,129,'default: -200 - moderator revoked rights'),('rep_req_votemore_add','250',5,129,'default: 250 - required reputation per additional vote past threshold'),('serialize_precision','5',0,65,' - some derelict code, probably unused'),('memory_limit','1500M',0,200,'default: 1500M - parsing spell.dbc is quite intense'),('default_charset','UTF-8',0,72,'default: UTF-8'),('analytics_user','',6,136,'default: - enter your GA-user here to track site stats'); +INSERT INTO `aowow_config` VALUES ('sql_limit_search','500',1,129,'default: 500 - max results for search'),('sql_limit_default','300',1,129,'default: 300 - max results for listviews'),('sql_limit_quicksearch','10',1,129,'default: 10 - max results for suggestions'),('sql_limit_none','0',1,129,'default: 0 - unlimited results (i wouldn\'t change that mate)'),('ttl_rss','60',1,129,'default: 60 - time to live for RSS (in seconds)'),('name','Aowow Database Viewer (ADV)',1,136,' - website title'),('name_short','Aowow',1,136,' - feed title'),('board_url','http://www.wowhead.com/forums?board=',1,136,' - another halfbaked javascript thing..'),('contact_email','feedback@aowow.org',1,136,' - displayed sender for auth-mails, ect'),('battlegroup','Pure Pwnage',1,136,' - pretend, we belong to a battlegroup to satisfy profiler-related Jscripts'),('debug','0',1,132,'default: 0 - disable cache, enable sql-errors, enable error_reporting'),('maintenance','1',1,132,'default: 0 - display brb gnomes and block access for non-staff'),('user_max_votes','50',1,129,'default: 50 - vote limit per day'),('force_ssl','0',1,132,'default: 0 - enforce SSL, if the server is behind a load balancer'),('locales','333',1,161,'default: 0x14D - allowed locales - 0:English, 2:French, 3:German, 6:Spanish, 8:Russian'),('screenshot_min_size','200',1,129,'default: 200 - minimum dimensions of uploaded screenshots in px (yes, it\'s square)'),('site_host','',1,136,' - points js to executable files'),('static_host','',1,136,' - points js to images & scripts'),('cache_decay','25200',2,129,'default: 60 * 60 * 7 - time to keep cache in seconds'),('cache_mode','1',2,161,'default: 1 - set cache method - 0:filecache, 1:memcached'),('cache_dir','',2,136,'default: cache/template - generated pages are saved here (requires CACHE_MODE: filecache)'),('acc_failed_auth_block','900',3,129,'default: 15 * 60 - how long an account is closed after exceeding FAILED_AUTH_COUNT (in seconds)'),('acc_failed_auth_count','5',3,129,'default: 5 - how often invalid passwords are tolerated'),('acc_allow_register','1',3,132,'default: 1 - allow/disallow account creation (requires AUTH_MODE: aowow)'),('acc_auth_mode','0',3,145,'default: 0 - source to auth against - 0:aowow, 1:TC auth-table, 2:external script'),('acc_create_save_decay','604800',3,129,'default: 604800 - time in wich an unconfirmed account cannot be overwritten by new registrations'),('acc_recovery_decay','300',3,129,'default: 300 - time to recover your account and new recovery requests are blocked'),('session_timeout_delay','3600',4,129,'default: 60 * 60 - non-permanent session times out in time() + X'),('session.gc_maxlifetime','604800',4,200,'default: 7*24*60*60 - lifetime of session data'),('session.gc_probability','1',4,200,'default: 0 - probability to remove session data on garbage collection'),('session.gc_divisor',100,4,200,'default: 100 - probability to remove session data on garbage collection'),('session_cache_dir','',4,136,'default: - php sessions are saved here. Leave empty to use php default directory.'),('rep_req_upvote','125',5,129,'default: 125 - required reputation to upvote comments'),('rep_req_downvote','250',5,129,'default: 250 - required reputation to downvote comments'),('rep_req_comment','75',5,129,'default: 75 - required reputation to write a comment'),('rep_req_reply','75',5,129,'default: 75 - required reputation to write a reply'),('rep_req_supervote','2500',5,129,'default: 2500 - required reputation for double vote effect'),('rep_req_votemore_base','2000',5,129,'default: 2000 - gains more votes past this threshold'),('rep_reward_register','100',5,129,'default: 100 - activated an account'),('rep_reward_upvoted','5',5,129,'default: 5 - comment received upvote'),('rep_reward_downvoted','0',5,129,'default: 0 - comment received downvote'),('rep_reward_good_report','10',5,129,'default: 10 - filed an accepted report'),('rep_reward_bad_report','0',5,129,'default: 0 - filed a rejected report'),('rep_reward_dailyvisit','5',5,129,'default: 5 - daily visit'),('rep_reward_user_warned','-50',5,129,'default: -50 - moderator imposed a warning'),('rep_reward_comment','1',5,129,'default: 1 - created a comment (not a reply) '),('rep_req_premium','25000',5,129,'default: 25000 - required reputation for premium status through reputation'),('rep_reward_upload','10',5,129,'default: 10 - suggested / uploaded video / screenshot was approved'),('rep_reward_article','100',5,129,'default: 100 - submitted an approved article/guide'),('rep_reward_user_suspended','-200',5,129,'default: -200 - moderator revoked rights'),('rep_req_votemore_add','250',5,129,'default: 250 - required reputation per additional vote past threshold'),('serialize_precision','5',0,65,' - some derelict code, probably unused'),('memory_limit','1500M',0,200,'default: 1500M - parsing spell.dbc is quite intense'),('default_charset','UTF-8',0,72,'default: UTF-8'),('analytics_user','',6,136,'default: - enter your GA-user here to track site stats'),('profiler_queue',0,7,0x84,'default: 0 - enable/disable profiler queue'),('profiler_queue_delay',3000,7,0x81,'default: 3000 - min. delay between queue cycles (in ms)'),('profiler_resync_ping',5000,7,0x81,'default: 5000 - how often the javascript asks for for updates, when queued (in ms)'),('profiler_resync_delay',3600,7,0x81,'default: 1*60*60 - how often a character can be refreshed (in sec)'); /*!40000 ALTER TABLE `aowow_config` ENABLE KEYS */; UNLOCK TABLES; @@ -2676,7 +2656,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_dbversion` WRITE; /*!40000 ALTER TABLE `aowow_dbversion` DISABLE KEYS */; -INSERT INTO `aowow_dbversion` VALUES (1504448041,0,NULL,NULL); +INSERT INTO `aowow_dbversion` VALUES (1521735363,0,NULL,NULL); /*!40000 ALTER TABLE `aowow_dbversion` ENABLE KEYS */; UNLOCK TABLES; @@ -2729,3 +2709,215 @@ UNLOCK TABLES; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2015-10-31 13:22:30 + +SET FOREIGN_KEY_CHECKS=0; + +DROP TABLE IF EXISTS `aowow_profiler_sync`; +CREATE TABLE `aowow_profiler_sync` ( + `realm` tinyint(3) unsigned NOT NULL, + `realmGUID` int(10) unsigned NOT NULL, + `type` smallint(5) unsigned NOT NULL, + `typeId` int(10) unsigned NOT NULL, + `requestTime` int(10) unsigned NOT NULL, + `status` tinyint(3) unsigned NOT NULL, + `errorCode` tinyint(3) unsigned NOT NULL DEFAULT '0', + UNIQUE KEY `realm_realmGUID_type_typeId` (`realm`,`realmGUID`,`type`), + UNIQUE KEY `type_typeId` (`type`,`typeId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_guild`; +CREATE TABLE `aowow_profiler_guild` ( + `id` int(10) unsigned NOT NULL, + `realm` int(10) unsigned NOT NULL DEFAULT '0', + `realmGUID` int(10) unsigned NOT NULL DEFAULT '0', + `cuFlags` int(10) unsigned NOT NULL DEFAULT '0', + `name` varchar(26) NOT NULL DEFAULT '', + `nameUrl` varchar(26) NOT NULL DEFAULT '', + `emblemStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `emblemColor` tinyint(3) unsigned NOT NULL DEFAULT '0', + `borderStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `borderColor` tinyint(3) unsigned NOT NULL DEFAULT '0', + `backgroundColor` tinyint(3) unsigned NOT NULL DEFAULT '0', + `info` varchar(500) NOT NULL DEFAULT '', + `createDate` int(10) unsigned NOT NULL DEFAULT '0', + INDEX `name` (`name`), + INDEX `guild` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_guild_rank`; +CREATE TABLE `aowow_profiler_guild_rank` ( + `guildId` int(10) unsigned NOT NULL DEFAULT '0', + `rank` tinyint(3) unsigned NOT NULL, + `name` varchar(20) NOT NULL DEFAULT '', + PRIMARY KEY (`guildId`,`rank`), + INDEX `rank` (`rank`), + CONSTRAINT `FK_aowow_profiler_guild_rank_aowow_profiler_guild` FOREIGN KEY (`guildId`) REFERENCES `aowow_profiler_guild` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_profiles`; +CREATE TABLE `aowow_profiler_profiles` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `realm` tinyint(3) unsigned DEFAULT NULL, + `realmGUID` int(11) unsigned DEFAULT NULL, + `cuFlags` int(11) unsigned NOT NULL DEFAULT '0', + `sourceId` int(11) unsigned DEFAULT NULL, + `sourceName` varchar(50) DEFAULT NULL, + `copy` int(10) unsigned DEFAULT NULL, + `icon` varchar(50) DEFAULT NULL, + `user` int(11) unsigned DEFAULT NULL, + `name` varchar(50) NOT NULL, + `race` tinyint(3) unsigned NOT NULL, + `class` tinyint(3) unsigned NOT NULL, + `level` tinyint(3) unsigned NOT NULL, + `gender` tinyint(3) unsigned NOT NULL, + `guild` int(10) unsigned NULL, + `guildrank` tinyint(3) unsigned DEFAULT NULL COMMENT '0: guild master', + `skincolor` tinyint(3) unsigned NOT NULL, + `hairstyle` tinyint(3) unsigned NOT NULL, + `haircolor` tinyint(3) unsigned NOT NULL, + `facetype` tinyint(3) unsigned NOT NULL, + `features` tinyint(3) unsigned NOT NULL, + `nomodelMask` int(11) unsigned NOT NULL DEFAULT '0', + `title` tinyint(3) unsigned NOT NULL, + `description` text NULL, + `playedtime` int(11) unsigned NOT NULL, + `gearscore` smallint(5) unsigned NOT NULL, + `achievementpoints` smallint(5) unsigned NOT NULL, + `lastupdated` int(11) NOT NULL, + `talenttree1` tinyint(4) unsigned NOT NULL COMMENT 'points spend in 1st tree', + `talenttree2` tinyint(4) unsigned NOT NULL COMMENT 'points spend in 2nd tree', + `talenttree3` tinyint(4) unsigned NOT NULL COMMENT 'points spend in 3rd tree', + `talentbuild1` varchar(105) NOT NULL, + `talentbuild2` varchar(105) NOT NULL, + `glyphs1` varchar(45) NOT NULL, + `glyphs2` varchar(45) NOT NULL, + `activespec` tinyint(1) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `realm_realmGUID_name` (`realm`,`realmGUID`,`name`), + INDEX `user` (`user`), + INDEX `guild` (`guild`), + CONSTRAINT `FK_aowow_profiler_profiles_aowow_profiler_guild` FOREIGN KEY (`guild`) REFERENCES `aowow_profiler_guild` (`id`) ON UPDATE CASCADE ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_items`; +CREATE TABLE `aowow_profiler_items` ( + `id` int(11) unsigned DEFAULT NULL, + `slot` tinyint(3) unsigned DEFAULT NULL, + `item` mediumint(8) unsigned DEFAULT NULL, + `subItem` smallint(6) DEFAULT NULL, + `permEnchant` mediumint(8) unsigned DEFAULT NULL, + `tempEnchant` mediumint(8) unsigned DEFAULT NULL, + `extraSocket` tinyint(3) unsigned DEFAULT NULL COMMENT 'not used .. the appropriate gem slot is set to -1 instead', + `gem1` mediumint(8) DEFAULT NULL, + `gem2` mediumint(8) DEFAULT NULL, + `gem3` mediumint(8) DEFAULT NULL, + `gem4` mediumint(8) DEFAULT NULL, + UNIQUE KEY `id_slot` (`id`,`slot`), + INDEX `id` (`id`), + INDEX `item` (`item`), + CONSTRAINT `FK_pr_items` FOREIGN KEY (`id`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_arena_team`; +CREATE TABLE `aowow_profiler_arena_team` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `realm` tinyint(3) unsigned NOT NULL, + `realmGUID` int(10) unsigned NOT NULL, + `name` varchar(24) NOT NULL, + `nameUrl` varchar(24) NOT NULL, + `type` tinyint(3) unsigned NOT NULL DEFAULT '0', + `cuFlags` int(11) unsigned NOT NULL, + `rating` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `weekGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `weekWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `rank` int(10) unsigned NOT NULL DEFAULT '0', + `backgroundColor` int(10) unsigned NOT NULL DEFAULT '0', + `emblemStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `emblemColor` int(10) unsigned NOT NULL DEFAULT '0', + `borderStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `borderColor` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE INDEX `realm_realmGUID` (`realm`,`realmGUID`), + INDEX `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_arena_team_member`; +CREATE TABLE `aowow_profiler_arena_team_member` ( + `arenaTeamId` int(10) unsigned NOT NULL DEFAULT '0', + `profileId` int(10) unsigned NOT NULL DEFAULT '0', + `captain` tinyint(1) unsigned NOT NULL DEFAULT '0', + `weekGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `weekWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `personalRating` smallint(5) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`arenaTeamId`,`profileId`), + INDEX `guid` (`profileId`), + CONSTRAINT `FK_aowow_profiler_arena_team_member_aowow_profiler_arena_team` FOREIGN KEY (`arenaTeamId`) REFERENCES `aowow_profiler_arena_team` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_aowow_profiler_arena_team_member_aowow_profiler_profiles` FOREIGN KEY (`profileId`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_completion`; +CREATE TABLE `aowow_profiler_completion` ( + `id` int(11) unsigned NOT NULL, + `type` smallint(6) unsigned NOT NULL, + `typeId` mediumint(9) NOT NULL, + `cur` int(11) DEFAULT NULL, + `max` int(11) DEFAULT NULL, + INDEX `id` (`id`), + INDEX `type` (`type`), + INDEX `typeId` (`typeId`), + CONSTRAINT `FK_pr_completion` FOREIGN KEY (`id`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_pets`; +CREATE TABLE `aowow_profiler_pets` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `owner` int(10) unsigned DEFAULT NULL, + `name` varchar(50) DEFAULT NULL, + `family` tinyint(3) unsigned DEFAULT NULL, + `npc` smallint(5) unsigned DEFAULT NULL, + `displayId` smallint(5) unsigned DEFAULT NULL, + `talents` varchar(20) DEFAULT NULL, + PRIMARY KEY (`id`), + INDEX `owner` (`owner`), + CONSTRAINT `FK_pr_pets` FOREIGN KEY (`owner`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_excludes`; +CREATE TABLE `aowow_profiler_excludes` ( + `type` smallint(5) unsigned NOT NULL, + `typeId` mediumint(8) unsigned NOT NULL, + `groups` smallint(5) unsigned NOT NULL COMMENT 'see exclude group defines', + `comment` varchar(50) NOT NULL COMMENT 'rebuilding profiler files will delete everything without a comment', + PRIMARY KEY (`type`,`typeId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `aowow_profiler_excludes` (`type`,`typeId`,`groups`,`comment`) VALUES (6,459,1,'Gray Wolf'),(6,468,1,'White Stallion'),(6,471,1,'Palamino'),(6,472,1,'Pinto'),(6,578,1,'Black Wolf'),(6,579,1,'Red Wolf'),(6,581,1,'Winter Wolf'),(6,3363,1,'Nether Drake'),(6,6896,1,'Black Ram'),(6,6897,1,'Blue Ram'),(6,8980,1,'Skeletal Horse'),(6,10681,1,'Summon Cockatoo'),(6,10686,1,'Summon Prairie Chicken'),(6,10687,1,'Summon White Plymouth Rock'),(6,10699,1,'Summon Bronze Whelpling'),(6,10700,1,'Summon Faeling'),(6,10701,1,'Summon Dart Frog'),(6,10702,1,'Summon Island Frog'),(6,10705,1,'Summon Eagle Owl'),(6,10708,1,'Summon Snowy Owl'),(6,10710,1,'Summon Cottontail Rabbit'),(6,10712,1,'Summon Spotted Rabbit'),(6,10715,1,'Summon Blue Racer'),(6,10718,1,'Green Water Snake'),(6,10719,1,'Ribbon Snake'),(6,10720,1,'Scarlet Snake'),(6,10721,1,'Summon Elven Wisp'),(6,10795,1,'Ivory Raptor'),(6,10798,1,'Obsidian Raptor'),(6,15648,1,'Corrupted Kitten'),(6,15779,1,'White Mechanostrider Mod B'),(6,15780,1,'Green Mechanostrider'),(6,15781,1,'Steel Mechanostrider'),(6,16055,1,'Black Nightsaber'),(6,16056,1,'Ancient Frostsaber'),(6,16058,1,'Primal Leopard'),(6,16059,1,'Tawny Sabercat'),(6,16060,1,'Golden Sabercat'),(6,16080,1,'Red Wolf'),(6,16081,1,'Winter Wolf'),(6,16082,1,'Palomino'),(6,16083,1,'White Stallion'),(6,16084,1,'Mottled Red Raptor'),(6,17450,1,'Ivory Raptor'),(6,17455,1,'Purple Mechanostrider'),(6,17456,1,'Red and Blue Mechanostrider'),(6,17458,1,'Fluorescent Green Mechanostrider'),(6,17459,1,'Icy Blue Mechanostrider Mod A'),(6,17460,1,'Frost Ram'),(6,17461,1,'Black Ram'),(6,17468,1,'Pet Fish'),(6,17469,1,'Pet Stone'),(6,18363,1,'Riding Kodo'),(6,18991,1,'Green Kodo'),(6,18992,1,'Teal Kodo'),(6,19363,1,'Summon Mechanical Yeti'),(6,23220,1,'Swift Dawnsaber'),(6,23428,1,'Albino Snapjaw'),(6,23429,1,'Loggerhead Snapjaw'),(6,23430,1,'Olive Snapjaw'),(6,23431,1,'Leatherback Snapjaw'),(6,23432,1,'Hawksbill Snapjaw'),(6,23530,16,'Tiny Red Dragon'),(6,23531,16,'Tiny Green Dragon'),(6,24985,1,'Summon Baby Murloc (Blue)'),(6,24986,1,'Summon Baby Murloc (Green)'),(6,24987,1,'Summon Baby Murloc (Orange)'),(6,24988,4,'Lurky'),(6,24989,1,'Summon Baby Murloc (Pink)'),(6,24990,1,'Summon Baby Murloc (Purple)'),(6,25849,1,'Baby Shark'),(6,26067,1,'Summon Mechanical Greench'),(6,26391,1,'Tentacle Call'),(6,28828,1,'Nether Drake'),(6,29059,1,'Naxxramas Deathcharger'),(6,30152,1,'White Tiger Cub'),(6,30156,2,'Hippogryph Hatchling'),(6,30174,2,'Riding Turtle'),(6,32298,4,'Netherwhelp'),(6,32345,1,'Peep the Phoenix Mount'),(6,33050,128,'Magical Crawdad'),(6,33057,1,'Summon Mighty Mr. Pinchy'),(6,33630,1,'Blue Mechanostrider'),(6,34407,1,'Great Elite Elekk'),(6,35157,1,'Summon Spotted Rabbit'),(6,37015,1,'Swift Nether Drake'),(6,40319,16,'Lucky'),(6,40405,16,'Lucky'),(6,43688,1,'Amani War Bear'),(6,43810,1,'Frost Wyrm'),(6,44317,1,'Merciless Nether Drake'),(6,44744,1,'Merciless Nether Drake'),(6,45125,2,'Rocket Chicken'),(6,45174,16,'Golden Pig'),(6,45175,16,'Silver Pig'),(6,45890,1,'Scorchling'),(6,47037,1,'Swift War Elekk'),(6,48406,16,'Essence of Competition'),(6,48408,1,'Essence of Competition'),(6,48954,8,'Swift Zhevra'),(6,49322,8,'Swift Zhevra'),(6,49378,1,'Brewfest Riding Kodo'),(6,50869,1,'Brewfest Kodo'),(6,50870,1,'Brewfest Ram'),(6,51851,1,'Vampiric Batling'),(6,51960,1,'Frost Wyrm Mount'),(6,52615,4,'Frosty'),(6,53082,8,'Mini Tyrael'),(6,53768,1,'Haunted'),(6,54187,1,'Clockwork Rocket Bot'),(6,55068,1,'Mr. Chilly'),(6,58983,8,'Big Blizzard Bear'),(6,59572,1,'Black Polar Bear'),(6,59573,1,'Brown Polar Bear'),(6,59802,1,'Grand Ice Mammoth'),(6,59804,1,'Grand Ice Mammoth'),(6,59976,1,'Black Proto-Drake'),(6,60021,1,'Plagued Proto-Drake'),(6,60136,1,'Grand Caravan Mammoth'),(6,60140,1,'Grand Caravan Mammoth'),(6,61309,512,'Magnificent Flying Carpet'),(6,61442,1,'Swift Mooncloth Carpet'),(6,61444,1,'Swift Shadoweave Carpet'),(6,61446,1,'Swift Spellfire Carpete'),(6,61451,512,'Flying Carpet'),(6,61855,1,'Baby Blizzard Bear'),(6,62048,1,'Black Dragonhawk Mount'),(6,62514,1,'Alarming Clockbot'),(6,63318,8,'Murkimus the Gladiator'),(6,64351,1,'XS-001 Constructor Bot'),(6,64656,1,'Blue Skeletal Warhorse'),(6,64731,128,'Sea Turtle'),(6,65682,1,'Warbot'),(6,65917,2,'Magic Rooster'),(6,66030,8,'Grunty'),(6,66122,1,'Magic Rooster - dummy spell'),(6,66123,1,'Magic Rooster - dummy spell'),(6,66124,1,'Magic Rooster - dummy spell'),(6,66520,1,'Jade Tiger'),(6,66907,1,'Argent Warhorse'),(6,67527,16,'Onyx Panther'),(6,68767,2,'Tuskarr Kite'),(6,68810,2,'Spectral Tiger Cub'),(6,69002,1,'Onyxian Whelpling'),(6,69452,8,'Core Hound Pup'),(6,69535,4,'Gryphon Hatchling'),(6,69536,4,'Wind Rider Cub'),(6,69539,1,'Zipao Tiger'),(6,69541,4,'Pandaren Monk'),(6,69677,4,'Lil\' K.T.'),(6,74856,2,'Blazing Hippogryph'),(6,74918,2,'Wooly White Rhino'),(6,75596,512,'Frosty Flying Carpet'),(6,75613,1,'Celestial Dragon'),(6,75614,16,'Celestial Steed'),(6,75906,4,'Lil\' XT'),(6,75936,1,'Murkimus the Gladiator'),(6,75973,8,'X-53 Touring Rocket'),(6,78381,8,'Mini Thor'),(8,87,1024,'Bloodsail Buccaneers - max rank is honored'),(8,92,1024,'Gelkis Clan Centaur - max rank is friendly'),(8,93,1024,'Magram Clan Centaur - max rank is friendly'); + +DROP TABLE IF EXISTS `aowow_account_profiles`; +CREATE TABLE `aowow_account_profiles` ( + `accountId` INT(10) UNSIGNED NOT NULL, + `profileId` INT(10) UNSIGNED NOT NULL, + `extraFlags` INT(10) UNSIGNED NOT NULL, + UNIQUE INDEX `accountId_profileId` (`accountId`, `profileId`), + INDEX `accountId` (`accountId`), + INDEX `profileId` (`profileId`), + CONSTRAINT `FK_account_id` FOREIGN KEY (`accountId`) REFERENCES `aowow_account` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT `FK_profile_id` FOREIGN KEY (`profileId`) REFERENCES `aowow_profiler_profiles` (`id`) ON UPDATE CASCADE ON DELETE CASCADE +) COLLATE='utf8_general_ci' ENGINE=InnoDB; + +DROP TABLE IF EXISTS `aowow_account_excludes`; +CREATE TABLE `aowow_account_excludes` ( + `userId` INT(11) UNSIGNED NOT NULL, + `type` SMALLINT(5) UNSIGNED NOT NULL, + `typeId` MEDIUMINT(8) UNSIGNED NOT NULL, + `mode` TINYINT(2) UNSIGNED NOT NULL COMMENT '1: exclude; 2: include', + UNIQUE INDEX `userId_type_typeId` (`userId`, `type`, `typeId`), + INDEX `userId` (`userId`), + CONSTRAINT `FK_acc_excludes` FOREIGN KEY (`userId`) REFERENCES `aowow_account` (`id`) ON UPDATE CASCADE ON DELETE CASCADE +) COLLATE='utf8_general_ci' ENGINE=InnoDB; + +SET FOREIGN_KEY_CHECKS=1; diff --git a/setup/tools/fileGen.class.php b/setup/tools/fileGen.class.php index a72651bb..49bbcc68 100644 --- a/setup/tools/fileGen.class.php +++ b/setup/tools/fileGen.class.php @@ -233,11 +233,20 @@ class FileGen 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; + if (fWrite($dest, $content)) + { + CLI::write(sprintf(ERR_NONE, CLI::bold($destPath.$file)), CLI::LOG_OK); + if ($content && $funcOK) + $success = true; + } + else + CLI::write(sprintf(ERR_WRITE_FILE, CLI::bold($destPath.$file)), CLI::LOG_ERROR); + + fClose($dest); + } + else + CLI::write(sprintf(ERR_CREATE_FILE, CLI::bold($destPath.$file)), CLI::LOG_ERROR); } else CLI::write(sprintf(ERR_READ_FILE, CLI::bold(FileGen::$tplPath.$file.'.in')), CLI::LOG_ERROR); diff --git a/setup/tools/filegen/profiler.func.php b/setup/tools/filegen/profiler.func.php index 2a2e1974..171314a5 100644 --- a/setup/tools/filegen/profiler.func.php +++ b/setup/tools/filegen/profiler.func.php @@ -14,11 +14,27 @@ if (!CLI) { $success = true; $scripts = []; + $exclusions = []; + + $exAdd = function ($type, $typeId, $groups, $comment = '') use(&$exclusions) + { + $k = $type.'-'.$typeId; + + if (!isset($exclusions[$k])) + $exclusions[$k] = ['type' => $type, 'typeId' => $typeId, 'groups' => $groups, 'comment' => $comment]; + else + { + $exclusions[$k]['groups'] |= $groups; + if ($comment) + $exclusions[$k]['comment'] .= '; '.$comment; + } + }; + /**********/ /* Quests */ /**********/ - $scripts[] = function() + $scripts[] = function() use ($exAdd) { $success = true; $condition = [ @@ -30,6 +46,23 @@ if (!CLI) ]; $questz = new QuestList($condition); + // get quests for exclusion + foreach ($questz->iterate() as $id => $__) + { + 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) @@ -62,50 +95,10 @@ if (!CLI) return $success; }; - /****************/ - /* Achievements */ - /****************/ - $scripts[] = function() - { - $success = true; - $condition = array( - CFG_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(Util::$localeStrings[$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.'/p-achievements', $buff)) - $success = false; - } - - return $success; - }; - /**********/ /* Titles */ /**********/ - $scripts[] = function() + $scripts[] = function() use ($exAdd) { $success = true; $condition = array( @@ -114,6 +107,11 @@ if (!CLI) ); $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); @@ -142,7 +140,7 @@ if (!CLI) /**********/ /* Mounts */ /**********/ - $scripts[] = function() + $scripts[] = function() use ($exAdd) { $success = true; $condition = array( @@ -177,7 +175,7 @@ if (!CLI) /**************/ /* Companions */ /**************/ - $scripts[] = function() + $scripts[] = function() use ($exAdd) { $success = true; $condition = array( @@ -212,7 +210,7 @@ if (!CLI) /************/ /* Factions */ /************/ - $scripts[] = function() + $scripts[] = function() use ($exAdd) { $success = true; $condition = array( // todo (med): exclude non-gaining reputation-header @@ -244,7 +242,7 @@ if (!CLI) /***********/ /* Recipes */ /***********/ - $scripts[] = function() + $scripts[] = function() use ($exAdd) { // special case: secondary skills are always requested, so put them in one single file (185, 129, 356); it also contains g_skill_order $skills = [171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, [185, 129, 356]]; @@ -303,6 +301,81 @@ if (!CLI) return $success; }; + /****************/ + /* Achievements */ + /****************/ + $scripts[] = function() use ($exAdd) + { + $success = true; + $condition = array( + CFG_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(Util::$localeStrings[$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) + { + $s = count($exclusions); + $i = $n = 0; + CLI::write('applying '.$s.' 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)); + if ($i >= 500) + { + $i = 0; + CLI::write(' * '.$n.' / '.$s.' ('.Lang::nf(100 * $n / $s, 1).'%)'); + } + $i++; + $n++; + } + + // 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)) diff --git a/setup/tools/filegen/realmMenu.func.php b/setup/tools/filegen/realmMenu.func.php index fa96b28d..b415d992 100644 --- a/setup/tools/filegen/realmMenu.func.php +++ b/setup/tools/filegen/realmMenu.func.php @@ -42,21 +42,24 @@ if (!CLI) $subUS = []; $set = 0x0; $menu = [ - ['us', 'US & Oceanic', null,[[Util::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, &$subUS]]], - ['eu', 'Europe', null,[[Util::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, &$subEU]]] + // skip usage of battlegroup + // ['us', Lang::profiler('regions', 'us'), null,[[Profiler::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, &$subUS]]], + // ['eu', Lang::profiler('regions', 'eu'), null,[[Profiler::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, &$subEU]]] + ['us', Lang::profiler('regions', 'us'), null, &$subUS], + ['eu', Lang::profiler('regions', 'eu'), null, &$subEU] ]; - foreach (Util::getRealms() as $row) + foreach (Profiler::getRealms() as $row) { if ($row['region'] == 'eu') { $set |= 0x1; - $subEU[] = [Util::urlize($row['name']), $row['name']]; + $subEU[] = [Profiler::urlize($row['name']), $row['name']]; } else if ($row['region'] == 'us') { $set |= 0x2; - $subUS[] = [Util::urlize($row['name']), $row['name']]; + $subUS[] = [Profiler::urlize($row['name']), $row['name']]; } } diff --git a/setup/tools/filegen/realms.func.php b/setup/tools/filegen/realms.func.php index 3479a226..44453f19 100644 --- a/setup/tools/filegen/realms.func.php +++ b/setup/tools/filegen/realms.func.php @@ -28,12 +28,12 @@ if (!CLI) function realms() { - $realms = Util::getRealms(); + $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_BATTLEGROUP; + // else + // foreach ($realms as &$r) + // $r['battlegroup'] = CFG_BATTLEGROUP; $toFile = "var g_realms = ".Util::toJSON($realms).";"; $file = 'datasets/realms'; diff --git a/setup/tools/filegen/statistics.func.php b/setup/tools/filegen/statistics.func.php index d92b692f..b2ae6b9d 100644 --- a/setup/tools/filegen/statistics.func.php +++ b/setup/tools/filegen/statistics.func.php @@ -32,21 +32,21 @@ if (!CLI) baseParryPct ParryCap baseBlockPct - directMod1 applies mod directly only one class having something worth mentioning: DK - directMod2 applies mod directly so what were they originally used for..? + 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, 0, 0], - 2 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.4943, 88.129021, 5, 47.003525, 5, 0, 0], - 3 => [[-20, 1, 1, 2], [-10, 0, 2, 2], null, 0.9880, -4.0873, 145.560408, 5, 145.560408, 0, 0, 0], - 4 => [[-20, 1, 1, 2], [-10, 0, 1, 1], null, 0.9880, 2.0957, 145.560408, 5, 145.560408, 0, 0, 0], - 5 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.4178, 150.375940, 0, 0.0, 0, 0, 0], - 6 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.6640, 88.129021, 5, 47.003525, 0, 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, 0, 0], - 8 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.6587, 150.375940, 0, 0.0, 0, 0, 0], - 9 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 2.4211, 150.375940, 0, 0.0, 0, 0, 0], - 11 => [[-20, 2, 0, 0], [-10, 0, 1, 0], null, 0.9720, 5.6097, 116.890707, 0, 0.0, 0, 0, 0] + 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) @@ -57,20 +57,43 @@ if (!CLI) $race = function() { - // { str, agi, sta, int, spi, hp, mana, directMod1, directMod2 } - - return array( - 1 => [20, 20, 20, 20, 20, 0, ['spi' => [0.05, 'percentOf', 'spi']]], // The Human Spirit (20598) - 2 => [23, 17, 22, 17, 23, 0, 0], - 3 => [22, 16, 23, 19, 19, 0, 0], - 4 => [17, 25, 19, 20, 20, 0, 0], - 5 => [19, 18, 21, 18, 25, 0, 0], - 6 => [25, 15, 22, 15, 22, 0, ['health' => [0.05, 'functionOf', '$function(p) { return g_statistics.combo[p.classs][p.level][5]; }']]], // Endurance (20550) ... if you are looking for something elegant, look away! - 7 => [15, 23, 19, 24, 20, 0, ['int' => [0.05, 'percentOf', 'int']]], // Expansive Mind (20591) - 8 => [21, 22, 21, 16, 21, 0, ['healthrgn' => [0.1, 'percentOf', 'healthrgn']]], // Regeneration (20555) - 10 => [17, 22, 18, 24, 19, 0, 0], - 11 => [21, 17, 19, 21, 22, 0, 0] // ['mlehitpct' => [1, 'add'], 'splhitpct' => [1, 'add'], 'rgdhitpct' => [1, 'add']] // Heroic Presence (6562, 28878) (not actually shown..?) + // where did i get this data again..? + // { str, agi, sta, int, spi, raceMod1, raceMod2 } + $raceData = array( + 1 => [20, 20, 20, 20, 20, [], []], + 2 => [23, 17, 22, 17, 23, [], []], + 3 => [22, 16, 23, 19, 19, [], []], + 4 => [17, 25, 19, 20, 20, [], []], + 5 => [19, 18, 21, 18, 25, [], []], + 6 => [25, 15, 22, 15, 22, [], []], + 7 => [15, 23, 19, 24, 20, [], []], + 8 => [21, 22, 21, 16, 21, [], []], + 10 => [17, 22, 18, 24, 19, [], []], + 11 => [21, 17, 19, 21, 22, [], []] ); + + $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() @@ -143,12 +166,27 @@ if (!CLI) $skills = function() { - // profession perks (skinning => +crit, mining => +stam) and maybe some others; skillId:{rankNo:someJSON, ..}? - - return []; + // profession perks ... too lazy to formulate a search algorithm for two occurences + return array( + 186 => array( // mining / toughness + 75 => ['sta' => 3], + 150 => ['sta' => 5], + 225 => ['sta' => 7], + 300 => ['sta' => 10], + 375 => ['sta' => 30], + 450 => ['sta' => 60], + ), + 393 => array( // skinning / master of anatomy + 75 => ['critstrkrtng' => 3], + 150 => ['critstrkrtng' => 6], + 225 => ['critstrkrtng' => 9], + 300 => ['critstrkrtng' => 12], + 375 => ['critstrkrtng' => 20], + 450 => ['critstrkrtng' => 40], + ) + ); }; - // todo: x $sub = ['classs', 'race', 'combo', 'level', 'skills']; $out = []; $success = true; diff --git a/setup/tools/filegen/talentCalc.func.php b/setup/tools/filegen/talentCalc.func.php index aa51674b..ee1aee4b 100644 --- a/setup/tools/filegen/talentCalc.func.php +++ b/setup/tools/filegen/talentCalc.func.php @@ -31,7 +31,9 @@ if (!CLI) function talentCalc() { $success = true; - $buildTree = function ($class) use (&$petFamIcons, &$tSpells) + $spellMods = (new SpellList(array(['typeCat', -2], CFG_SQL_LIMIT_NONE)))->getProfilerMods(); + + $buildTree = function ($class) use (&$petFamIcons, &$tSpells, $spellMods) { $petCategories = []; @@ -41,43 +43,44 @@ if (!CLI) $tabs = DB::Aowow()->select('SELECT * FROM dbc_talenttab WHERE classMask = ?d ORDER BY `tabNumber`, `creatureFamilyMask`', $mask); $result = []; - for ($l = 0; $l < count($tabs); $l++) + 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_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[$l]['id']); - $result[$l] = array( - 'n' => Util::localizedString($tabs[$l], 'name'), + $talents = DB::Aowow()->select('SELECT t.id AS tId, t.*, s.name_loc0, s.name_loc2, s.name_loc3, 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[$l]['creatureFamilyMask'], 2); - $result[$l]['icon'] = $petFamIcons[$petFamId]; + $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[$l]['f'] = array_keys($petCategories); + $result[$tabIdx]['f'] = array_keys($petCategories); } // talent dependencies go here $depLinks = []; $tNums = []; - for ($j = 0; $j < count($talents); $j++) + for ($talentIdx = 0; $talentIdx < count($talents); $talentIdx++) { - $tNums[$talents[$j]['tId']] = $j; + $tNums[$talents[$talentIdx]['tId']] = $talentIdx; $d = []; $s = []; - $i = $talents[$j]['tId']; - $n = Util::localizedString($talents[$j], 'name'); - $x = $talents[$j]['column']; - $y = $talents[$j]['row']; + $i = $talents[$talentIdx]['tId']; + $n = Util::localizedString($talents[$talentIdx], 'name'); + $x = $talents[$talentIdx]['column']; + $y = $talents[$talentIdx]['row']; $r = null; $t = []; - $icon = $talents[$j]['iconString']; - $m = $talents[$j]['rank2'] == 0 ? 1 : ( - $talents[$j]['rank3'] == 0 ? 2 : ( - $talents[$j]['rank4'] == 0 ? 3 : ( - $talents[$j]['rank5'] == 0 ? 4 : 5 + $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 ) ) ); @@ -87,34 +90,38 @@ if (!CLI) foreach ($petCategories as $k => $v) { // cant handle 64bit integer .. split - if ($v >= 32 && ((1 << ($v - 32)) & $talents[$j]['petCategory2'])) + if ($v >= 32 && ((1 << ($v - 32)) & $talents[$talentIdx]['petCategory2'])) $f[] = $k; - else if ($v < 32 && ((1 << $v) & $talents[$j]['petCategory1'])) + else if ($v < 32 && ((1 << $v) & $talents[$talentIdx]['petCategory1'])) $f[] = $k; } - for ($k = 0; $k <= ($m - 1); $k++) + for ($itr = 0; $itr <= ($m - 1); $itr++) { - if (!$tSpells->getEntry($talents[$j]['rank'.($k + 1)])) + if (!$tSpells->getEntry($talents[$talentIdx]['rank'.($itr + 1)])) continue; $d[] = $tSpells->parseText()[0]; - $s[] = $talents[$j]['rank'.($k + 1)]; + $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[$j]['talentSpell']) + if ($talents[$talentIdx]['talentSpell']) $t[] = $tSpells->getTalentHeadForCurrent(); } - if ($talents[$j]['reqTalent']) + if ($talents[$talentIdx]['reqTalent']) { // we didn't encounter the required talent yet => create reference - if (!isset($tNums[$talents[$j]['reqTalent']])) - $depLinks[$talents[$j]['reqTalent']] = $j; + if (!isset($tNums[$talents[$talentIdx]['reqTalent']])) + $depLinks[$talents[$talentIdx]['reqTalent']] = $talentIdx; - $r = @[$tNums[$talents[$j]['reqTalent']], $talents[$j]['reqRank'] + 1]; + $r = @[$tNums[$talents[$talentIdx]['reqTalent']], $talents[$talentIdx]['reqRank'] + 1]; } - $result[$l]['t'][$j] = array( + $result[$tabIdx]['t'][$talentIdx] = array( 'i' => $i, 'n' => $n, 'm' => $m, @@ -122,31 +129,32 @@ if (!CLI) 's' => $s, 'x' => $x, 'y' => $y, + 'j' => $j ); if (isset($r)) - $result[$l]['t'][$j]['r'] = $r; + $result[$tabIdx]['t'][$talentIdx]['r'] = $r; if (!empty($t)) - $result[$l]['t'][$j]['t'] = $t; + $result[$tabIdx]['t'][$talentIdx]['t'] = $t; if (!empty($f)) - $result[$l]['t'][$j]['f'] = $f; + $result[$tabIdx]['t'][$talentIdx]['f'] = $f; if ($class) - $result[$l]['t'][$j]['iconname'] = $icon; + $result[$tabIdx]['t'][$talentIdx]['iconname'] = $icon; // If this talent is a reference, add it to the array of talent dependencies - if (isset($depLinks[$talents[$j]['tId']])) + if (isset($depLinks[$talents[$talentIdx]['tId']])) { - $result[$l]['t'][$depLinks[$talents[$j]['tId']]]['r'][0] = $j; - unset($depLinks[$talents[$j]['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[$l]['t'][$dep_link]['r']); + unset($result[$tabIdx]['t'][$dep_link]['r']); } return $result; diff --git a/setup/updates/1521735363_01.sql b/setup/updates/1521735363_01.sql new file mode 100644 index 00000000..3357a58c --- /dev/null +++ b/setup/updates/1521735363_01.sql @@ -0,0 +1,371 @@ +SET FOREIGN_KEY_CHECKS=0; + +DROP TABLE IF EXISTS `aowow_profiler_sync`; +CREATE TABLE `aowow_profiler_sync` ( + `realm` tinyint(3) unsigned NOT NULL, + `realmGUID` int(10) unsigned NOT NULL, + `type` smallint(5) unsigned NOT NULL, + `typeId` int(10) unsigned NOT NULL, + `requestTime` int(10) unsigned NOT NULL, + `status` tinyint(3) unsigned NOT NULL, + `errorCode` tinyint(3) unsigned NOT NULL DEFAULT '0', + UNIQUE KEY `realm_realmGUID_type_typeId` (`realm`,`realmGUID`,`type`), + UNIQUE KEY `type_typeId` (`type`,`typeId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_guild`; +CREATE TABLE `aowow_profiler_guild` ( + `id` int(10) unsigned NOT NULL, + `realm` int(10) unsigned NOT NULL DEFAULT '0', + `realmGUID` int(10) unsigned NOT NULL DEFAULT '0', + `cuFlags` int(10) unsigned NOT NULL DEFAULT '0', + `name` varchar(26) NOT NULL DEFAULT '', + `nameUrl` varchar(26) NOT NULL DEFAULT '', + `emblemStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `emblemColor` tinyint(3) unsigned NOT NULL DEFAULT '0', + `borderStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `borderColor` tinyint(3) unsigned NOT NULL DEFAULT '0', + `backgroundColor` tinyint(3) unsigned NOT NULL DEFAULT '0', + `info` varchar(500) NOT NULL DEFAULT '', + `createDate` int(10) unsigned NOT NULL DEFAULT '0', + INDEX `name` (`name`), + INDEX `guild` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_guild_rank`; +CREATE TABLE `aowow_profiler_guild_rank` ( + `guildId` int(10) unsigned NOT NULL DEFAULT '0', + `rank` tinyint(3) unsigned NOT NULL, + `name` varchar(20) NOT NULL DEFAULT '', + PRIMARY KEY (`guildId`,`rank`), + INDEX `rank` (`rank`), + CONSTRAINT `FK_aowow_profiler_guild_rank_aowow_profiler_guild` FOREIGN KEY (`guildId`) REFERENCES `aowow_profiler_guild` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_profiles`; +CREATE TABLE `aowow_profiler_profiles` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `realm` tinyint(3) unsigned DEFAULT NULL, + `realmGUID` int(11) unsigned DEFAULT NULL, + `cuFlags` int(11) unsigned NOT NULL DEFAULT '0', + `sourceId` int(11) unsigned DEFAULT NULL, + `sourceName` varchar(50) DEFAULT NULL, + `copy` int(10) unsigned DEFAULT NULL, + `icon` varchar(50) DEFAULT NULL, + `user` int(11) unsigned DEFAULT NULL, + `name` varchar(50) NOT NULL, + `race` tinyint(3) unsigned NOT NULL, + `class` tinyint(3) unsigned NOT NULL, + `level` tinyint(3) unsigned NOT NULL, + `gender` tinyint(3) unsigned NOT NULL, + `guild` int(10) unsigned NULL, + `guildrank` tinyint(3) unsigned DEFAULT NULL COMMENT '0: guild master', + `skincolor` tinyint(3) unsigned NOT NULL, + `hairstyle` tinyint(3) unsigned NOT NULL, + `haircolor` tinyint(3) unsigned NOT NULL, + `facetype` tinyint(3) unsigned NOT NULL, + `features` tinyint(3) unsigned NOT NULL, + `nomodelMask` int(11) unsigned NOT NULL DEFAULT '0', + `title` tinyint(3) unsigned NOT NULL, + `description` text NULL, + `playedtime` int(11) unsigned NOT NULL, + `gearscore` smallint(5) unsigned NOT NULL, + `achievementpoints` smallint(5) unsigned NOT NULL, + `lastupdated` int(11) NOT NULL, + `talenttree1` tinyint(4) unsigned NOT NULL COMMENT 'points spend in 1st tree', + `talenttree2` tinyint(4) unsigned NOT NULL COMMENT 'points spend in 2nd tree', + `talenttree3` tinyint(4) unsigned NOT NULL COMMENT 'points spend in 3rd tree', + `talentbuild1` varchar(105) NOT NULL, + `talentbuild2` varchar(105) NOT NULL, + `glyphs1` varchar(45) NOT NULL, + `glyphs2` varchar(45) NOT NULL, + `activespec` tinyint(1) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `realm_realmGUID_name` (`realm`,`realmGUID`,`name`), + INDEX `user` (`user`), + INDEX `guild` (`guild`), + CONSTRAINT `FK_aowow_profiler_profiles_aowow_profiler_guild` FOREIGN KEY (`guild`) REFERENCES `aowow_profiler_guild` (`id`) ON UPDATE CASCADE ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_items`; +CREATE TABLE `aowow_profiler_items` ( + `id` int(11) unsigned DEFAULT NULL, + `slot` tinyint(3) unsigned DEFAULT NULL, + `item` mediumint(8) unsigned DEFAULT NULL, + `subItem` smallint(6) DEFAULT NULL, + `permEnchant` mediumint(8) unsigned DEFAULT NULL, + `tempEnchant` mediumint(8) unsigned DEFAULT NULL, + `extraSocket` tinyint(3) unsigned DEFAULT NULL COMMENT 'not used .. the appropriate gem slot is set to -1 instead', + `gem1` mediumint(8) DEFAULT NULL, + `gem2` mediumint(8) DEFAULT NULL, + `gem3` mediumint(8) DEFAULT NULL, + `gem4` mediumint(8) DEFAULT NULL, + UNIQUE KEY `id_slot` (`id`,`slot`), + INDEX `id` (`id`), + INDEX `item` (`item`), + CONSTRAINT `FK_pr_items` FOREIGN KEY (`id`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_arena_team`; +CREATE TABLE `aowow_profiler_arena_team` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `realm` tinyint(3) unsigned NOT NULL, + `realmGUID` int(10) unsigned NOT NULL, + `name` varchar(24) NOT NULL, + `nameUrl` varchar(24) NOT NULL, + `type` tinyint(3) unsigned NOT NULL DEFAULT '0', + `cuFlags` int(11) unsigned NOT NULL, + `rating` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `weekGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `weekWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `rank` int(10) unsigned NOT NULL DEFAULT '0', + `backgroundColor` int(10) unsigned NOT NULL DEFAULT '0', + `emblemStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `emblemColor` int(10) unsigned NOT NULL DEFAULT '0', + `borderStyle` tinyint(3) unsigned NOT NULL DEFAULT '0', + `borderColor` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE INDEX `realm_realmGUID` (`realm`,`realmGUID`), + INDEX `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_arena_team_member`; +CREATE TABLE `aowow_profiler_arena_team_member` ( + `arenaTeamId` int(10) unsigned NOT NULL DEFAULT '0', + `profileId` int(10) unsigned NOT NULL DEFAULT '0', + `captain` tinyint(1) unsigned NOT NULL DEFAULT '0', + `weekGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `weekWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonGames` smallint(5) unsigned NOT NULL DEFAULT '0', + `seasonWins` smallint(5) unsigned NOT NULL DEFAULT '0', + `personalRating` smallint(5) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`arenaTeamId`,`profileId`), + INDEX `guid` (`profileId`), + CONSTRAINT `FK_aowow_profiler_arena_team_member_aowow_profiler_arena_team` FOREIGN KEY (`arenaTeamId`) REFERENCES `aowow_profiler_arena_team` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_aowow_profiler_arena_team_member_aowow_profiler_profiles` FOREIGN KEY (`profileId`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_completion`; +CREATE TABLE `aowow_profiler_completion` ( + `id` int(11) unsigned NOT NULL, + `type` smallint(6) unsigned NOT NULL, + `typeId` mediumint(9) NOT NULL, + `cur` int(11) DEFAULT NULL, + `max` int(11) DEFAULT NULL, + INDEX `id` (`id`), + INDEX `type` (`type`), + INDEX `typeId` (`typeId`), + CONSTRAINT `FK_pr_completion` FOREIGN KEY (`id`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_pets`; +CREATE TABLE `aowow_profiler_pets` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `owner` int(10) unsigned DEFAULT NULL, + `name` varchar(50) DEFAULT NULL, + `family` tinyint(3) unsigned DEFAULT NULL, + `npc` smallint(5) unsigned DEFAULT NULL, + `displayId` smallint(5) unsigned DEFAULT NULL, + `talents` varchar(20) DEFAULT NULL, + PRIMARY KEY (`id`), + INDEX `owner` (`owner`), + CONSTRAINT `FK_pr_pets` FOREIGN KEY (`owner`) REFERENCES `aowow_profiler_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `aowow_profiler_excludes`; +CREATE TABLE `aowow_profiler_excludes` ( + `type` smallint(5) unsigned NOT NULL, + `typeId` mediumint(8) unsigned NOT NULL, + `groups` smallint(5) unsigned NOT NULL COMMENT 'see exclude group defines', + `comment` varchar(50) NOT NULL COMMENT 'rebuilding profiler files will delete everything without a comment', + PRIMARY KEY (`type`,`typeId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `aowow_profiler_excludes` (`type`, `typeId`, `groups`, `comment`) VALUES + (6, 459, 1, 'Gray Wolf'), + (6, 468, 1, 'White Stallion'), + (6, 471, 1, 'Palamino'), + (6, 472, 1, 'Pinto'), + (6, 578, 1, 'Black Wolf'), + (6, 579, 1, 'Red Wolf'), + (6, 581, 1, 'Winter Wolf'), + (6, 3363, 1, 'Nether Drake'), + (6, 6896, 1, 'Black Ram'), + (6, 6897, 1, 'Blue Ram'), + (6, 8980, 1, 'Skeletal Horse'), + (6, 10681, 1, 'Summon Cockatoo'), + (6, 10686, 1, 'Summon Prairie Chicken'), + (6, 10687, 1, 'Summon White Plymouth Rock'), + (6, 10699, 1, 'Summon Bronze Whelpling'), + (6, 10700, 1, 'Summon Faeling'), + (6, 10701, 1, 'Summon Dart Frog'), + (6, 10702, 1, 'Summon Island Frog'), + (6, 10705, 1, 'Summon Eagle Owl'), + (6, 10708, 1, 'Summon Snowy Owl'), + (6, 10710, 1, 'Summon Cottontail Rabbit'), + (6, 10712, 1, 'Summon Spotted Rabbit'), + (6, 10715, 1, 'Summon Blue Racer'), + (6, 10718, 1, 'Green Water Snake'), + (6, 10719, 1, 'Ribbon Snake'), + (6, 10720, 1, 'Scarlet Snake'), + (6, 10721, 1, 'Summon Elven Wisp'), + (6, 10795, 1, 'Ivory Raptor'), + (6, 10798, 1, 'Obsidian Raptor'), + (6, 15648, 1, 'Corrupted Kitten'), + (6, 15779, 1, 'White Mechanostrider Mod B'), + (6, 15780, 1, 'Green Mechanostrider'), + (6, 15781, 1, 'Steel Mechanostrider'), + (6, 16055, 1, 'Black Nightsaber'), + (6, 16056, 1, 'Ancient Frostsaber'), + (6, 16058, 1, 'Primal Leopard'), + (6, 16059, 1, 'Tawny Sabercat'), + (6, 16060, 1, 'Golden Sabercat'), + (6, 16080, 1, 'Red Wolf'), + (6, 16081, 1, 'Winter Wolf'), + (6, 16082, 1, 'Palomino'), + (6, 16083, 1, 'White Stallion'), + (6, 16084, 1, 'Mottled Red Raptor'), + (6, 17450, 1, 'Ivory Raptor'), + (6, 17455, 1, 'Purple Mechanostrider'), + (6, 17456, 1, 'Red and Blue Mechanostrider'), + (6, 17458, 1, 'Fluorescent Green Mechanostrider'), + (6, 17459, 1, 'Icy Blue Mechanostrider Mod A'), + (6, 17460, 1, 'Frost Ram'), + (6, 17461, 1, 'Black Ram'), + (6, 17468, 1, 'Pet Fish'), + (6, 17469, 1, 'Pet Stone'), + (6, 18363, 1, 'Riding Kodo'), + (6, 18991, 1, 'Green Kodo'), + (6, 18992, 1, 'Teal Kodo'), + (6, 19363, 1, 'Summon Mechanical Yeti'), + (6, 23220, 1, 'Swift Dawnsaber'), + (6, 23428, 1, 'Albino Snapjaw'), + (6, 23429, 1, 'Loggerhead Snapjaw'), + (6, 23430, 1, 'Olive Snapjaw'), + (6, 23431, 1, 'Leatherback Snapjaw'), + (6, 23432, 1, 'Hawksbill Snapjaw'), + (6, 23530, 16, 'Tiny Red Dragon'), + (6, 23531, 16, 'Tiny Green Dragon'), + (6, 24985, 1, 'Summon Baby Murloc (Blue)'), + (6, 24986, 1, 'Summon Baby Murloc (Green)'), + (6, 24987, 1, 'Summon Baby Murloc (Orange)'), + (6, 24988, 4, 'Lurky'), + (6, 24989, 1, 'Summon Baby Murloc (Pink)'), + (6, 24990, 1, 'Summon Baby Murloc (Purple)'), + (6, 25849, 1, 'Baby Shark'), + (6, 26067, 1, 'Summon Mechanical Greench'), + (6, 26391, 1, 'Tentacle Call'), + (6, 28828, 1, 'Nether Drake'), + (6, 29059, 1, 'Naxxramas Deathcharger'), + (6, 30152, 1, 'White Tiger Cub'), + (6, 30156, 2, 'Hippogryph Hatchling'), + (6, 30174, 2, 'Riding Turtle'), + (6, 32298, 4, 'Netherwhelp'), + (6, 32345, 1, 'Peep the Phoenix Mount'), + (6, 33050, 128, 'Magical Crawdad'), + (6, 33057, 1, 'Summon Mighty Mr. Pinchy'), + (6, 33630, 1, 'Blue Mechanostrider'), + (6, 34407, 1, 'Great Elite Elekk'), + (6, 35157, 1, 'Summon Spotted Rabbit'), + (6, 37015, 1, 'Swift Nether Drake'), + (6, 40319, 16, 'Lucky'), + (6, 40405, 16, 'Lucky'), + (6, 43688, 1, 'Amani War Bear'), + (6, 43810, 1, 'Frost Wyrm'), + (6, 44317, 1, 'Merciless Nether Drake'), + (6, 44744, 1, 'Merciless Nether Drake'), + (6, 45125, 2, 'Rocket Chicken'), + (6, 45174, 16, 'Golden Pig'), + (6, 45175, 16, 'Silver Pig'), + (6, 45890, 1, 'Scorchling'), + (6, 47037, 1, 'Swift War Elekk'), + (6, 48406, 16, 'Essence of Competition'), + (6, 48408, 1, 'Essence of Competition'), + (6, 48954, 8, 'Swift Zhevra'), + (6, 49322, 8, 'Swift Zhevra'), + (6, 49378, 1, 'Brewfest Riding Kodo'), + (6, 50869, 1, 'Brewfest Kodo'), + (6, 50870, 1, 'Brewfest Ram'), + (6, 51851, 1, 'Vampiric Batling'), + (6, 51960, 1, 'Frost Wyrm Mount'), + (6, 52615, 4, 'Frosty'), + (6, 53082, 8, 'Mini Tyrael'), + (6, 53768, 1, 'Haunted'), + (6, 54187, 1, 'Clockwork Rocket Bot'), + (6, 55068, 1, 'Mr. Chilly'), + (6, 58983, 8, 'Big Blizzard Bear'), + (6, 59572, 1, 'Black Polar Bear'), + (6, 59573, 1, 'Brown Polar Bear'), + (6, 59802, 1, 'Grand Ice Mammoth'), + (6, 59804, 1, 'Grand Ice Mammoth'), + (6, 59976, 1, 'Black Proto-Drake'), + (6, 60021, 1, 'Plagued Proto-Drake'), + (6, 60136, 1, 'Grand Caravan Mammoth'), + (6, 60140, 1, 'Grand Caravan Mammoth'), + (6, 61309, 512, 'Magnificent Flying Carpet'), + (6, 61442, 1, 'Swift Mooncloth Carpet'), + (6, 61444, 1, 'Swift Shadoweave Carpet'), + (6, 61446, 1, 'Swift Spellfire Carpete'), + (6, 61451, 512, 'Flying Carpet'), + (6, 61855, 1, 'Baby Blizzard Bear'), + (6, 62048, 1, 'Black Dragonhawk Mount'), + (6, 62514, 1, 'Alarming Clockbot'), + (6, 63318, 8, 'Murkimus the Gladiator'), + (6, 64351, 1, 'XS-001 Constructor Bot'), + (6, 64656, 1, 'Blue Skeletal Warhorse'), + (6, 64731, 128, 'Sea Turtle'), + (6, 65682, 1, 'Warbot'), + (6, 65917, 2, 'Magic Rooster'), + (6, 66030, 8, 'Grunty'), + (6, 66122, 1, 'Magic Rooster - dummy spell'), + (6, 66123, 1, 'Magic Rooster - dummy spell'), + (6, 66124, 1, 'Magic Rooster - dummy spell'), + (6, 66520, 1, 'Jade Tiger'), + (6, 66907, 1, 'Argent Warhorse'), + (6, 67527, 16, 'Onyx Panther'), + (6, 68767, 2, 'Tuskarr Kite'), + (6, 68810, 2, 'Spectral Tiger Cub'), + (6, 69002, 1, 'Onyxian Whelpling'), + (6, 69452, 8, 'Core Hound Pup'), + (6, 69535, 4, 'Gryphon Hatchling'), + (6, 69536, 4, 'Wind Rider Cub'), + (6, 69539, 1, 'Zipao Tiger'), + (6, 69541, 4, 'Pandaren Monk'), + (6, 69677, 4, 'Lil\' K.T.'), + (6, 74856, 2, 'Blazing Hippogryph'), + (6, 74918, 2, 'Wooly White Rhino'), + (6, 75596, 512, 'Frosty Flying Carpet'), + (6, 75613, 1, 'Celestial Dragon'), + (6, 75614, 16, 'Celestial Steed'), + (6, 75906, 4, 'Lil\' XT'), + (6, 75936, 1, 'Murkimus the Gladiator'), + (6, 75973, 8, 'X-53 Touring Rocket'), + (6, 78381, 8, 'Mini Thor'), + (8, 87, 1024, 'Bloodsail Buccaneers - max rank is honored'), + (8, 92, 1024, 'Gelkis Clan Centaur - max rank is friendly'), + (8, 93, 1024, 'Magram Clan Centaur - max rank is friendly'); + +CREATE TABLE `aowow_account_profiles` ( + `accountId` INT(10) UNSIGNED NOT NULL, + `profileId` INT(10) UNSIGNED NOT NULL, + `extraFlags` INT(10) UNSIGNED NOT NULL, + UNIQUE INDEX `accountId_profileId` (`accountId`, `profileId`), + INDEX `accountId` (`accountId`), + INDEX `profileId` (`profileId`), + CONSTRAINT `FK_account_id` FOREIGN KEY (`accountId`) REFERENCES `aowow_account` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT `FK_profile_id` FOREIGN KEY (`profileId`) REFERENCES `aowow_profiler_profiles` (`id`) ON UPDATE CASCADE ON DELETE CASCADE +) COLLATE='utf8_general_ci' ENGINE=InnoDB; + +CREATE TABLE `aowow_account_excludes` ( + `userId` INT(11) UNSIGNED NOT NULL, + `type` SMALLINT(5) UNSIGNED NOT NULL, + `typeId` MEDIUMINT(8) UNSIGNED NOT NULL, + `mode` TINYINT(2) UNSIGNED NOT NULL COMMENT '1: exclude; 2: include', + UNIQUE INDEX `userId_type_typeId` (`userId`, `type`, `typeId`), + INDEX `userId` (`userId`), + CONSTRAINT `FK_acc_excludes` FOREIGN KEY (`userId`) REFERENCES `aowow_account` (`id`) ON UPDATE CASCADE ON DELETE CASCADE +) COLLATE='utf8_general_ci' ENGINE=InnoDB; + +SET FOREIGN_KEY_CHECKS=1; diff --git a/setup/updates/1521735363_02.sql b/setup/updates/1521735363_02.sql new file mode 100644 index 00000000..82c3bc46 --- /dev/null +++ b/setup/updates/1521735363_02.sql @@ -0,0 +1,16 @@ +DELETE FROM aowow_config WHERE `key` LIKE 'profiler_%'; +INSERT INTO aowow_config (`key`, `value`, `cat`, `flags`, `comment`) VALUES + ('profiler_queue', 0, 7, 0x84, 'default: 0 - enable/disable profiler queue'), + ('profiler_queue_delay', 3000, 7, 0x81, 'default: 3000 - min. delay between queue cycles (in ms)'), + ('profiler_resync_ping', 5000, 7, 0x81, 'default: 5000 - how often the javascript asks for for updates, when queued (in ms)'), + ('profiler_resync_delay', 1*60*60, 7, 0x81, 'default: 1*60*60 - how often a character can be refreshed (in sec)'); + +DROP TABLE IF EXISTS `aowow_characters`; + +ALTER TABLE `aowow_talents` + ADD COLUMN `petTypeMask` TINYINT(3) UNSIGNED NOT NULL AFTER `class`; + +ALTER TABLE `aowow_account` + ADD COLUMN `excludeGroups` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '1' COMMENT 'profiler - completion exclude bitmask' AFTER `description`; + +UPDATE aowow_dbversion SET `sql` = CONCAT(IFNULL(`sql`, ''), ' talents');