From db1d3ccaceda593ec52b7b34834d243022c0dbe6 Mon Sep 17 00:00:00 2001 From: Sarjuuk Date: Tue, 1 Apr 2025 19:46:19 +0200 Subject: [PATCH] Core/Cleanup * try to give included files a logical structure * move objects from Util and Game to their own files * make non-essential files auto-loaded --- aowow | 7 +- includes/ajaxHandler/admin.class.php | 6 +- .../{ => ajaxHandler}/ajaxHandler.class.php | 0 includes/{config.class.php => cfg.class.php} | 0 .../components/SmartAI/SmartAction.class.php | 2 +- .../communitycontent.class.php} | 0 includes/components/locstring.class.php | 39 + includes/{ => components}/markup.class.php | 0 includes/{ => components}/profiler.class.php | 0 includes/components/report.class.php | 274 ++++++ includes/database.class.php | 2 +- includes/defines.php | 180 +--- includes/game/chrclass.class.php | 79 ++ includes/game/chrrace.class.php | 132 +++ .../chrstatistics.php} | 0 includes/{ => game}/loot.class.php | 0 includes/{game.php => game/misc.php} | 156 ---- includes/game/worldposition.class.php | 160 ++++ includes/kernel.php | 159 ++-- includes/locale.class.php | 32 - includes/setup/cli.class.php | 355 +++++++ includes/setup/timer.class.php | 39 + includes/type.class.php | 228 +++++ includes/{ => types}/basetype.class.php | 2 +- includes/utilities.php | 883 +----------------- pages/genericPage.class.php | 6 +- pages/spell.php | 9 +- pages/zone.php | 2 +- prQueue | 3 +- setup/tools/clisetup/setup.us.php | 2 +- setup/tools/sqlgen/spawns.ss.php | 4 +- template/bricks/footer.tpl.php | 2 +- 32 files changed, 1440 insertions(+), 1323 deletions(-) rename includes/{ => ajaxHandler}/ajaxHandler.class.php (100%) rename includes/{config.class.php => cfg.class.php} (100%) rename includes/{community.class.php => components/communitycontent.class.php} (100%) create mode 100644 includes/components/locstring.class.php rename includes/{ => components}/markup.class.php (100%) rename includes/{ => components}/profiler.class.php (100%) create mode 100644 includes/components/report.class.php create mode 100644 includes/game/chrclass.class.php create mode 100644 includes/game/chrrace.class.php rename includes/{stats.class.php => game/chrstatistics.php} (100%) rename includes/{ => game}/loot.class.php (100%) rename includes/{game.php => game/misc.php} (69%) create mode 100644 includes/game/worldposition.class.php create mode 100644 includes/setup/cli.class.php create mode 100644 includes/setup/timer.class.php create mode 100644 includes/type.class.php rename includes/{ => types}/basetype.class.php (99%) diff --git a/aowow b/aowow index 50715309..428d109f 100755 --- a/aowow +++ b/aowow @@ -6,7 +6,10 @@ if (PHP_SAPI !== 'cli') if (PHP_SAPI === 'cli' && getcwd().DIRECTORY_SEPARATOR.'aowow' != __FILE__) die("this script must be run from the aowow root directory\n"); -require 'includes/kernel.php'; -require 'setup/setup.php'; +require_once 'includes/kernel.php'; +require_once 'includes/setup/cli.class.php'; +require_once 'includes/setup/timer.class.php'; + +require_once 'setup/setup.php'; ?> diff --git a/includes/ajaxHandler/admin.class.php b/includes/ajaxHandler/admin.class.php index 4de03301..7992ead2 100644 --- a/includes/ajaxHandler/admin.class.php +++ b/includes/ajaxHandler/admin.class.php @@ -390,9 +390,9 @@ class AjaxAdmin extends AjaxHandler DB::Aowow()->query('REPLACE INTO ?_spawns_override VALUES (?d, ?d, ?d, ?d, ?d)', $type, $guid, $area, $floor, AOWOW_REVISION); - if ($wPos = Game::getWorldPosForGUID($type, $guid)) + if ($wPos = WorldPosition::getForGUID($type, $guid)) { - if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $wPos[$guid]['posX'], $wPos[$guid]['posY'], $area, $floor)) + if ($point = WorldPosition::toZonePos($wPos[$guid]['mapId'], $wPos[$guid]['posX'], $wPos[$guid]['posY'], $area, $floor)) { $updGUIDs = [$guid]; $newPos = array( @@ -417,7 +417,7 @@ class AjaxAdmin extends AjaxHandler { foreach ($swp as $w) { - if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $w['posX'], $w['posY'], $area, $floor)) + if ($point = WorldPosition::toZonePos($wPos[$guid]['mapId'], $w['posX'], $w['posY'], $area, $floor)) { $p = array( 'posX' => $point[0]['posX'], diff --git a/includes/ajaxHandler.class.php b/includes/ajaxHandler/ajaxHandler.class.php similarity index 100% rename from includes/ajaxHandler.class.php rename to includes/ajaxHandler/ajaxHandler.class.php diff --git a/includes/config.class.php b/includes/cfg.class.php similarity index 100% rename from includes/config.class.php rename to includes/cfg.class.php diff --git a/includes/components/SmartAI/SmartAction.class.php b/includes/components/SmartAI/SmartAction.class.php index 6ab636e9..ef87fa5d 100644 --- a/includes/components/SmartAI/SmartAction.class.php +++ b/includes/components/SmartAI/SmartAction.class.php @@ -480,7 +480,7 @@ class SmartAction if ($this->smartAI->teleportTargetArea) $this->param[10] = $this->smartAI->teleportTargetArea; // try calc from SmartTarget data - else if ($pos = Game::worldPosToZonePos($this->param[0], $x, $y)) + else if ($pos = WorldPosition::toZonePos($this->param[0], $x, $y)) { $this->param[10] = $pos[0]['areaId']; $this->param[11] = str_pad($pos[0]['posX'] * 10, 3, '0', STR_PAD_LEFT).str_pad($pos[0]['posY'] * 10, 3, '0', STR_PAD_LEFT); diff --git a/includes/community.class.php b/includes/components/communitycontent.class.php similarity index 100% rename from includes/community.class.php rename to includes/components/communitycontent.class.php diff --git a/includes/components/locstring.class.php b/includes/components/locstring.class.php new file mode 100644 index 00000000..90408532 --- /dev/null +++ b/includes/components/locstring.class.php @@ -0,0 +1,39 @@ +store = new \WeakMap(); + + $callback ??= fn($x) => $x; + + if (!array_filter($data, fn($v, $k) => $v && strstr($k, $key.'_loc'), ARRAY_FILTER_USE_BOTH)) + trigger_error('LocString - is entrirely empty', E_USER_WARNING); + + foreach (Locale::cases() as $l) + $this->store[$l] = (string)$callback($data[$key.'_loc'.$l->value] ?? ''); + } + + public function __toString() : string + { + if ($str = $this->store[Lang::getLocale()]) + return $str; + + foreach (Locale::cases() as $l) // desired loc not set, use any other + if ($str = $this->store[$l]) + return Cfg::get('DEBUG') ? '['.$str.']' : $str; + + return Cfg::get('DEBUG') ? '[LOCSTRING]' : ''; + } +} + +?> diff --git a/includes/markup.class.php b/includes/components/markup.class.php similarity index 100% rename from includes/markup.class.php rename to includes/components/markup.class.php diff --git a/includes/profiler.class.php b/includes/components/profiler.class.php similarity index 100% rename from includes/profiler.class.php rename to includes/components/profiler.class.php diff --git a/includes/components/report.class.php b/includes/components/report.class.php new file mode 100644 index 00000000..4cd9b794 --- /dev/null +++ b/includes/components/report.class.php @@ -0,0 +1,274 @@ + array( + self::GEN_FEEDBACK => true, + self::GEN_BUG_REPORT => true, + self::GEN_TYPO_TRANSLATION => true, + self::GEN_OP_ADVERTISING => true, + self::GEN_OP_PARTNERSHIP => true, + self::GEN_PRESS_INQUIRY => true, + self::GEN_MISCELLANEOUS => true, + self::GEN_MISINFORMATION => true + ), + self::MODE_COMMENT => array( + self::CO_ADVERTISING => U_GROUP_MODERATOR, + self::CO_INACCURATE => true, + self::CO_OUT_OF_DATE => true, + self::CO_SPAM => U_GROUP_MODERATOR, + self::CO_INAPPROPRIATE => U_GROUP_MODERATOR, + self::CO_MISCELLANEOUS => U_GROUP_MODERATOR + ), + self::MODE_FORUM_POST => array( + self::FO_ADVERTISING => U_GROUP_MODERATOR, + self::FO_AVATAR => true, + self::FO_INACCURATE => true, + self::FO_OUT_OF_DATE => U_GROUP_MODERATOR, + self::FO_SPAM => U_GROUP_MODERATOR, + self::FO_STICKY_REQUEST => U_GROUP_MODERATOR, + self::FO_INAPPROPRIATE => U_GROUP_MODERATOR + ), + self::MODE_SCREENSHOT => array( + self::SS_INACCURATE => true, + self::SS_OUT_OF_DATE => true, + self::SS_INAPPROPRIATE => U_GROUP_MODERATOR, + self::SS_MISCELLANEOUS => U_GROUP_MODERATOR + ), + self::MODE_CHARACTER => array( + self::PR_INACCURATE_DATA => true, + self::PR_MISCELLANEOUS => true + ), + self::MODE_VIDEO => array( + self::VI_INACCURATE => true, + self::VI_OUT_OF_DATE => true, + self::VI_INAPPROPRIATE => U_GROUP_MODERATOR, + self::VI_MISCELLANEOUS => U_GROUP_MODERATOR + ), + self::MODE_GUIDE => array( + self::AR_INACCURATE => true, + self::AR_OUT_OF_DATE => true, + self::AR_MISCELLANEOUS => true + ) + ); + + private const ERR_NONE = 0; // aka: success + private const ERR_INVALID_CAPTCHA = 1; // captcha not in use + private const ERR_DESC_TOO_LONG = 2; + private const ERR_NO_DESC = 3; + private const ERR_ALREADY_REPORTED = 7; + private const ERR_MISCELLANEOUS = -1; + + public const STATUS_OPEN = 0; + public const STATUS_ASSIGNED = 1; + public const STATUS_CLOSED_WONTFIX = 2; + public const STATUS_CLOSED_SOLVED = 3; + + private int $errorCode = self::ERR_NONE; + + + public function __construct(private int $mode, private int $reason, private ?int $subject = 0) + { + if ($mode < 0 || $reason <= 0) + { + trigger_error('Report - malformed contact request received', E_USER_ERROR); + $this->errorCode = self::ERR_MISCELLANEOUS; + return; + } + + if (!isset($this->context[$mode][$reason])) + { + trigger_error('Report - report has invalid context (mode:'.$mode.' / reason:'.$reason.')', E_USER_ERROR); + $this->errorCode = self::ERR_MISCELLANEOUS; + return; + } + + if (!User::$id && !User::$ip) + { + trigger_error('Report - could not determine IP for anonymous user', E_USER_ERROR); + $this->errorCode = self::ERR_MISCELLANEOUS; + return; + } + + $this->subject ??= 0; // 0 for utility, tools and misc pages? + } + + private function checkTargetContext() : int + { + // check already reported + $field = User::$id ? 'userId' : 'ip'; + if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $this->mode, $this->reason, $this->subject, $field, User::$id ?: User::$ip)) + return self::ERR_ALREADY_REPORTED; + + // check targeted post/postOwner staff status + $ctxCheck = $this->context[$this->mode][$this->reason]; + if (is_int($ctxCheck)) + { + $roles = User::$groups; + if ($this->mode == self::MODE_COMMENT) + $roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_comments WHERE `id` = ?d', $this->subject); + // else if if ($this->mode == self::MODE_FORUM_POST) + // $roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_forum_posts WHERE `id` = ?d', $this->subject); + + return $roles & $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS; + } + else + return $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS; + + // Forum not in use, else: + // check post owner + // User::$id == post.op && !post.sticky; + // check user custom avatar + // g_users[post.user].avatar == 2 && (post.roles & U_GROUP_MODERATOR) == 0 + } + + public function create(string $desc, ?string $userAgent = null, ?string $appName = null, ?string $pageUrl = null, ?string $relUrl = null, ?string $email = null) : bool + { + if ($this->errorCode) + return false; + + if (!$desc) + { + $this->errorCode = self::ERR_NO_DESC; + return false; + } + + if (mb_strlen($desc) > 500) + { + $this->errorCode = self::ERR_DESC_TOO_LONG; + return false; + } + + if($err = $this->checkTargetContext()) + { + $this->errorCode = $err; + return false; + } + + $update = array( + 'userId' => User::$id, + 'createDate' => time(), + 'mode' => $this->mode, + 'reason' => $this->reason, + 'subject' => $this->subject, + 'ip' => User::$ip, + 'description' => $desc, + 'userAgent' => $userAgent ?: $_SERVER['HTTP_USER_AGENT'], + 'appName' => $appName ?: (get_browser(null, true)['browser'] ?: '') + ); + + if ($pageUrl) + $update['url'] = $pageUrl; + + if ($relUrl) + $update['relatedurl'] = $relUrl; + + if ($email) + $update['email'] = $email; + + return DB::Aowow()->query('INSERT INTO ?_reports (?#) VALUES (?a)', array_keys($update), array_values($update)); + } + + public function getSimilar(int ...$status) : array + { + if ($this->errorCode) + return []; + + foreach ($status as &$s) + if ($s < self::STATUS_OPEN || $s > self::STATUS_CLOSED_SOLVED) + unset($s); + + return DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, r.* FROM ?_reports r WHERE {`status` IN (?a) AND }`mode` = ?d AND `reason` = ?d AND `subject` = ?d', + $status ?: DBSIMPLE_SKIP, $this->mode, $this->reason, $this->subject); + } + + public function close(int $closeStatus, bool $inclAssigned = false) : bool + { + if ($closeStatus != self::STATUS_CLOSED_SOLVED && $closeStatus != self::STATUS_CLOSED_WONTFIX) + return false; + + if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_MOD)) + return false; + + $fromStatus = [self::STATUS_OPEN]; + if ($inclAssigned) + $fromStatus[] = self::STATUS_ASSIGNED; + + if ($reports = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `userId` FROM ?_reports WHERE `status` IN (?a) AND `mode` = ?d AND `reason` = ?d AND `subject` = ?d', + $fromStatus, $this->mode, $this->reason, $this->subject)) + { + DB::Aowow()->query('UPDATE ?_reports SET `status` = ?d, `assigned` = 0 WHERE `id` IN (?a)', $closeStatus, array_keys($reports)); + + foreach ($reports as $rId => $uId) + Util::gainSiteReputation($uId, $closeStatus == self::STATUS_CLOSED_SOLVED ? SITEREP_ACTION_GOOD_REPORT : SITEREP_ACTION_BAD_REPORT, ['id' => $rId]); + + return true; + } + + return false; + } + + public function reopen(int $assignedTo = 0) : bool + { + // assignedTo = 0 ? status = STATUS_OPEN : status = STATUS_ASSIGNED, userId = assignedTo + return false; + } + + public function getError() : int + { + return $this->errorCode; + } +} + +?> diff --git a/includes/database.class.php b/includes/database.class.php index 7c9cfc4d..e1b2a718 100644 --- a/includes/database.class.php +++ b/includes/database.class.php @@ -84,7 +84,7 @@ class DB // make number sensible again $data['code'] = abs($data['code']); - if (Cfg::get('DEBUG') >= CLI::LOG_INFO) + if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO) { echo "\nDB ERROR\n"; foreach ($data as $k => $v) diff --git a/includes/defines.php b/includes/defines.php index 42496b14..b6ad89e4 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -19,6 +19,10 @@ define('TDB_WORLD_EXPECTED_VER', 24041); // https://www.wowhead.com/wotlk/es/search=vuelo define('WOWHEAD_LINK', 'https://www.wowhead.com/wotlk/%s/%s=%s'); +define('LOG_LEVEL_ERROR', 1); +define('LOG_LEVEL_WARN', 2); +define('LOG_LEVEL_INFO', 3); + define('MIME_TYPE_TEXT', 'Content-Type: text/plain; charset=utf-8'); define('MIME_TYPE_XML', 'Content-Type: text/xml; charset=utf-8'); define('MIME_TYPE_JSON', 'Content-Type: application/x-javascript; charset=utf-8'); @@ -387,182 +391,6 @@ define('EXP_CLASSIC', 0); define('EXP_BC', 1); define('EXP_WOTLK', 2); -enum ChrClass : int -{ - case WARRIOR = 1; - case PALADIN = 2; - case HUNTER = 3; - case ROGUE = 4; - case PRIEST = 5; - case DEATHKNIGHT = 6; - case SHAMAN = 7; - case MAGE = 8; - case WARLOCK = 9; - case DRUID = 11; - - public const MASK_ALL = 0x5FF; - - public function matches(int $classMask) : bool - { - return !$classMask || $this->value & $classMask; - } - - public function toMask() : int - { - return 1 << ($this->value - 1); - } - - public static function fromMask(int $classMask = self::MASK_ALL) : array - { - $x = []; - foreach (self::cases() as $cl) - if ($cl->toMask() & $classMask) - $x[] = $cl->value; - - return $x; - } - - public function json() : string - { - return match ($this) - { - self::WARRIOR => 'warrior', - self::PALADIN => 'paladin', - self::HUNTER => 'hunter', - self::ROGUE => 'rogue', - self::PRIEST => 'priest', - self::DEATHKNIGHT => 'deathknight', - self::SHAMAN => 'shaman', - self::MAGE => 'mage', - self::WARLOCK => 'warlock', - self::DRUID => 'druid' - }; - } -} - -enum ChrRace : int -{ - case HUMAN = 1; - case ORC = 2; - case DWARF = 3; - case NIGHTELF = 4; - case UNDEAD = 5; - case TAUREN = 6; - case GNOME = 7; - case TROLL = 8; - case BLOODELF = 10; - case DRAENEI = 11; - - public const MASK_ALLIANCE = 0x44D; - public const MASK_HORDE = 0x2B2; - public const MASK_ALL = 0x6FF; - - public function matches(int $raceMask) : bool - { - return !$raceMask || $this->value & $raceMask; - } - - public function toMask() : int - { - return 1 << ($this->value - 1); - } - - public function isAlliance() : bool - { - return $this->toMask() & self::MASK_ALLIANCE; - } - - public function isHorde() : bool - { - return $this->toMask() & self::MASK_HORDE; - } - - public function getSide() : int - { - if ($this->isHorde() && $this->isAlliance()) - return SIDE_BOTH; - else if ($this->isHorde()) - return SIDE_HORDE; - else if ($this->isAlliance()) - return SIDE_ALLIANCE; - else - return SIDE_NONE; - } - - public function getTeam() : int - { - if ($this->isHorde() && $this->isAlliance()) - return TEAM_NEUTRAL; - else if ($this->isHorde()) - return TEAM_HORDE; - else if ($this->isAlliance()) - return TEAM_ALLIANCE; - else - return TEAM_NEUTRAL; - } - - public function json() : string - { - return match ($this) - { - self::HUMAN => 'human', - self::ORC => 'orc', - self::DWARF => 'dwarf', - self::NIGHTELF => 'nightelf', - self::UNDEAD => 'undead', - self::TAUREN => 'tauren', - self::GNOME => 'gnome', - self::TROLL => 'troll', - self::BLOODELF => 'bloodelf', - self::DRAENEI => 'draenei' - }; - } - - public static function fromMask(int $raceMask = self::MASK_ALL) : array - { - $x = []; - foreach (self::cases() as $cl) - if ($cl->toMask() & $raceMask) - $x[] = $cl->value; - - return $x; - } - - public static function sideFromMask(int $raceMask) : int - { - // Any - if (!$raceMask || ($raceMask & self::MASK_ALL) == self::MASK_ALL) - return SIDE_BOTH; - - // Horde - if ($raceMask & self::MASK_HORDE && !($raceMask & self::MASK_ALLIANCE)) - return SIDE_HORDE; - - // Alliance - if ($raceMask & self::MASK_ALLIANCE && !($raceMask & self::MASK_HORDE)) - return SIDE_ALLIANCE; - - return SIDE_BOTH; - } - - public static function teamFromMask(int $raceMask) : int - { - // Any - if (!$raceMask || ($raceMask & self::MASK_ALL) == self::MASK_ALL) - return TEAM_NEUTRAL; - - // Horde - if ($raceMask & self::MASK_HORDE && !($raceMask & self::MASK_ALLIANCE)) - return TEAM_HORDE; - - // Alliance - if ($raceMask & self::MASK_ALLIANCE && !($raceMask & self::MASK_HORDE)) - return TEAM_ALLIANCE; - - return TEAM_NEUTRAL; - } -} - // SpellFamilyNames define('SPELLFAMILY_GENERIC', 0); define('SPELLFAMILY_UNK1', 1); // events, holidays diff --git a/includes/game/chrclass.class.php b/includes/game/chrclass.class.php new file mode 100644 index 00000000..3a94bc4b --- /dev/null +++ b/includes/game/chrclass.class.php @@ -0,0 +1,79 @@ +value & $classMask; + } + + public function toMask() : int + { + return 1 << ($this->value - 1); + } + + public static function fromMask(int $classMask = self::MASK_ALL) : array + { + $x = []; + foreach (self::cases() as $cl) + if ($cl->toMask() & $classMask) + $x[] = $cl->value; + + return $x; + } + + public function json() : string + { + return match ($this) + { + self::WARRIOR => 'warrior', + self::PALADIN => 'paladin', + self::HUNTER => 'hunter', + self::ROGUE => 'rogue', + self::PRIEST => 'priest', + self::DEATHKNIGHT => 'deathknight', + self::SHAMAN => 'shaman', + self::MAGE => 'mage', + self::WARLOCK => 'warlock', + self::DRUID => 'druid' + }; + } + + public function spellFamily() : int + { + return match ($this) + { + self::WARRIOR => SPELLFAMILY_WARRIOR, + self::PALADIN => SPELLFAMILY_PALADIN, + self::HUNTER => SPELLFAMILY_HUNTER, + self::ROGUE => SPELLFAMILY_ROGUE, + self::PRIEST => SPELLFAMILY_PRIEST, + self::DEATHKNIGHT => SPELLFAMILY_DEATHKNIGHT, + self::SHAMAN => SPELLFAMILY_SHAMAN, + self::MAGE => SPELLFAMILY_MAGE, + self::WARLOCK => SPELLFAMILY_WARLOCK, + self::DRUID => SPELLFAMILY_DRUID + }; + } +} + +?> diff --git a/includes/game/chrrace.class.php b/includes/game/chrrace.class.php new file mode 100644 index 00000000..aab28ed7 --- /dev/null +++ b/includes/game/chrrace.class.php @@ -0,0 +1,132 @@ +value & $raceMask; + } + + public function toMask() : int + { + return 1 << ($this->value - 1); + } + + public function isAlliance() : bool + { + return $this->toMask() & self::MASK_ALLIANCE; + } + + public function isHorde() : bool + { + return $this->toMask() & self::MASK_HORDE; + } + + public function getSide() : int + { + if ($this->isHorde() && $this->isAlliance()) + return SIDE_BOTH; + else if ($this->isHorde()) + return SIDE_HORDE; + else if ($this->isAlliance()) + return SIDE_ALLIANCE; + else + return SIDE_NONE; + } + + public function getTeam() : int + { + if ($this->isHorde() && $this->isAlliance()) + return TEAM_NEUTRAL; + else if ($this->isHorde()) + return TEAM_HORDE; + else if ($this->isAlliance()) + return TEAM_ALLIANCE; + else + return TEAM_NEUTRAL; + } + + public function json() : string + { + return match ($this) + { + self::HUMAN => 'human', + self::ORC => 'orc', + self::DWARF => 'dwarf', + self::NIGHTELF => 'nightelf', + self::UNDEAD => 'undead', + self::TAUREN => 'tauren', + self::GNOME => 'gnome', + self::TROLL => 'troll', + self::BLOODELF => 'bloodelf', + self::DRAENEI => 'draenei' + }; + } + + public static function fromMask(int $raceMask = self::MASK_ALL) : array + { + $x = []; + foreach (self::cases() as $cl) + if ($cl->toMask() & $raceMask) + $x[] = $cl->value; + + return $x; + } + + public static function sideFromMask(int $raceMask) : int + { + // Any + if (!$raceMask || ($raceMask & self::MASK_ALL) == self::MASK_ALL) + return SIDE_BOTH; + + // Horde + if ($raceMask & self::MASK_HORDE && !($raceMask & self::MASK_ALLIANCE)) + return SIDE_HORDE; + + // Alliance + if ($raceMask & self::MASK_ALLIANCE && !($raceMask & self::MASK_HORDE)) + return SIDE_ALLIANCE; + + return SIDE_BOTH; + } + + public static function teamFromMask(int $raceMask) : int + { + // Any + if (!$raceMask || ($raceMask & self::MASK_ALL) == self::MASK_ALL) + return TEAM_NEUTRAL; + + // Horde + if ($raceMask & self::MASK_HORDE && !($raceMask & self::MASK_ALLIANCE)) + return TEAM_HORDE; + + // Alliance + if ($raceMask & self::MASK_ALLIANCE && !($raceMask & self::MASK_HORDE)) + return TEAM_ALLIANCE; + + return TEAM_NEUTRAL; + } +} + +?> diff --git a/includes/stats.class.php b/includes/game/chrstatistics.php similarity index 100% rename from includes/stats.class.php rename to includes/game/chrstatistics.php diff --git a/includes/loot.class.php b/includes/game/loot.class.php similarity index 100% rename from includes/loot.class.php rename to includes/game/loot.class.php diff --git a/includes/game.php b/includes/game/misc.php similarity index 69% rename from includes/game.php rename to includes/game/misc.php index a067dcde..3756976d 100644 --- a/includes/game.php +++ b/includes/game/misc.php @@ -133,11 +133,6 @@ class Game 'meta', 'red', 'yellow', 'blue' ); - public static $class2SpellFamily = array( - // null Warrior Paladin Hunter Rogue Priest DK Shaman Mage Warlock null Druid - null, 4, 10, 9, 8, 6, 15, 11, 3, 5, null, 7 - ); - public static function getReputationLevelForPoints($pts) { if ($pts >= 41999) @@ -216,157 +211,6 @@ class Game return $pages; } - - /*********************/ - /* World Pos. Checks */ - /*********************/ - - private static $alphaMapCache = []; - - private static function alphaMapCheck(int $areaId, array &$set) : bool - { - $file = 'cache/alphaMaps/'.$areaId.'.png'; - if (!file_exists($file)) // file does not exist (probably instanced area) - return false; - - // invalid and corner cases (literally) - if (!is_array($set) || empty($set['posX']) || empty($set['posY']) || $set['posX'] >= 100 || $set['posY'] >= 100) - { - $set = null; - return true; - } - - if (empty(self::$alphaMapCache[$areaId])) - self::$alphaMapCache[$areaId] = imagecreatefrompng($file); - - // alphaMaps are 1000 x 1000, adapt points [black => valid point] - if (!imagecolorat(self::$alphaMapCache[$areaId], $set['posX'] * 10, $set['posY'] * 10)) - $set = null; - - return true; - } - - public static function checkCoords(array $points) : array - { - $result = []; - $capitals = array( // capitals take precedence over their surroundings - 1497, 1637, 1638, 3487, // Undercity, Ogrimmar, Thunder Bluff, Silvermoon City - 1519, 1537, 1657, 3557, // Stormwind City, Ironforge, Darnassus, The Exodar - 3703, 4395 // Shattrath City, Dalaran - ); - - foreach ($points as $res) - { - if (self::alphaMapCheck($res['areaId'], $res)) - { - if (!$res) - continue; - - // some rough measure how central the spawn is on the map (the lower the number, the better) - // 0: perfect center; 1: touches a border - $q = abs( (($res['posX'] - 50) / 50) * (($res['posY'] - 50) / 50) ); - - if (empty($result) || $result[0] > $q) - $result = [$q, $res]; - } - else if (in_array($res['areaId'], $capitals)) // capitals (auto-discovered) and no hand-made alphaMap available - return $res; - else if (empty($result)) // add with lowest quality if alpha map is missing - $result = [1.0, $res]; - } - - // spawn does not really match on a map, but we need at least one result - if (!$result) - { - usort($points, function ($a, $b) { return ($a['dist'] < $b['dist']) ? -1 : 1; }); - $result = [1.0, $points[0]]; - } - - return $result[1]; - } - - public static function getWorldPosForGUID(int $type, int ...$guids) : array - { - $result = []; - - switch ($type) - { - case Type::NPC: - $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids); - break; - case Type::OBJECT: - $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids); - break; - case Type::SOUND: - $result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM ?_soundemitters WHERE `id` IN (?a)', $guids); - break; - case Type::ZONE: - $result = DB::Aowow()->select('SELECT -`id` AS ARRAY_KEY, `id`, `parentMapId` AS `mapId`, `parentX` AS `posX`, `parentY` AS `posY` FROM ?_zones WHERE -`id` IN (?a)', $guids); - break; - case Type::AREATRIGGER: - $result = []; - if ($base = array_filter($guids, function ($x) { return $x > 0; })) - $result = array_replace($result, DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM ?_areatrigger WHERE `id` IN (?a)', $base)); - if ($endpoints = array_filter($guids, function ($x) { return $x < 0; })) - $result = array_replace($result, DB::World()->select( - 'SELECT -`ID` AS ARRAY_KEY, ID AS `id`, `target_map` AS `mapId`, `target_position_x` AS `posX`, `target_position_y` AS `posY` FROM areatrigger_teleport WHERE -`id` IN (?a) UNION - SELECT -`entryorguid` AS ARRAY_KEY, entryorguid AS `id`, `action_param1` AS `mapId`, `target_x` AS `posX`, `target_y` AS `posY` FROM smart_scripts WHERE -`entryorguid` IN (?a) AND `source_type` = ?d AND `action_type` = ?d', - $endpoints, $endpoints, SmartAI::SRC_TYPE_AREATRIGGER, SmartAction::ACTION_TELEPORT - )); - break; - default: - trigger_error('Game::getWorldPosForGUID - instanced with unsupported TYPE #'.$type, E_USER_WARNING); - } - - if ($diff = array_diff($guids, array_keys($result))) - trigger_error('Game::getWorldPosForGUID - no spawn points for TYPE #'.$type.' GUIDS: '.implode(', ', $diff), E_USER_WARNING); - - return $result; - } - - public static function worldPosToZonePos(int $mapId, float $posX, float $posY, int $areaId = 0, int $floor = -1) : array - { - if (!$mapId < 0) - return []; - - $query = - 'SELECT - x.`id`, - x.`areaId`, - IF(x.`defaultDungeonMapId` < 0, x.`floor` + 1, x.`floor`) AS `floor`, - IF(dm.`id` IS NOT NULL OR x.`defaultDungeonMapId` < 0, 1, 0) AS `multifloor`, - ROUND((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`), 1) AS `posX`, - ROUND((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`), 1) AS `posY`, - SQRT(POWER(ABS((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`) - 50), 2) + - POWER(ABS((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`) - 50), 2)) AS `dist` - FROM - (SELECT 0 AS `id`, `areaId`, `mapId`, `right` AS `minY`, `left` AS `maxY`, `top` AS `maxX`, `bottom` AS `minX`, 0 AS `floor`, 0 AS `worldMapAreaId`, `defaultDungeonMapId` FROM ?_worldmaparea wma UNION - SELECT dm.`id`, `areaId`, wma.`mapId`, `minY`, `maxY`, `maxX`, `minX`, `floor`, `worldMapAreaId`, `defaultDungeonMapId` FROM ?_worldmaparea wma - JOIN ?_dungeonmap dm ON dm.`mapId` = wma.`mapId` WHERE wma.`mapId` NOT IN (0, 1, 530, 571) OR wma.`areaId` = 4395) x - LEFT JOIN - ?_dungeonmap dm ON dm.`mapId` = x.`mapId` AND dm.`worldMapAreaId` = x.`worldMapAreaId` AND dm.`floor` <> x.`floor` AND dm.`worldMapAreaId` > 0 - WHERE - x.`mapId` = ?d AND IF(?d, x.`areaId` = ?d, x.`areaId` <> 0){ AND x.`floor` = ?d - IF(x.`defaultDungeonMapId` < 0, 1, 0)} - GROUP BY - x.`id`, x.`areaId` - HAVING - (`posX` BETWEEN 0.1 AND 99.9 AND `posY` BETWEEN 0.1 AND 99.9) - ORDER BY - `multifloor` DESC, `dist` ASC'; - - // dist BETWEEN 0 (center) AND 70.7 (corner) - $points = DB::Aowow()->select($query, $posY, $posX, $posY, $posX, $mapId, $areaId, $areaId, $floor < 0 ? DBSIMPLE_SKIP : $floor); - if (!$points) // retry: pre-instance subareas belong to the instance-maps but are displayed on the outside. There also cases where the zone reaches outside it's own map. - $points = DB::Aowow()->select($query, $posY, $posX, $posY, $posX, $mapId, 0, 0, DBSIMPLE_SKIP); - if (!is_array($points)) - { - trigger_error('Game::worldPosToZonePos - dbc query failed', E_USER_ERROR); - return []; - } - - return $points; - } - public static function getQuotesForCreature(int $creatureId, bool $asHTML = false, string $talkSource = '') : array { $nQuotes = 0; diff --git a/includes/game/worldposition.class.php b/includes/game/worldposition.class.php new file mode 100644 index 00000000..9f8d4ff3 --- /dev/null +++ b/includes/game/worldposition.class.php @@ -0,0 +1,160 @@ += 100 || $set['posY'] >= 100) + { + $set = null; + return true; + } + + if (empty(self::$alphaMapCache[$areaId])) + self::$alphaMapCache[$areaId] = imagecreatefrompng($file); + + // alphaMaps are 1000 x 1000, adapt points [black => valid point] + if (!imagecolorat(self::$alphaMapCache[$areaId], $set['posX'] * 10, $set['posY'] * 10)) + $set = null; + + return true; + } + + public static function checkZonePos(array $points) : array + { + $result = []; + + foreach ($points as $res) + { + if (self::alphaMapCheck($res['areaId'], $res)) + { + if (!$res) + continue; + + // some rough measure how central the spawn is on the map (the lower the number, the better) + // 0: perfect center; 1: touches a border + $q = abs( (($res['posX'] - 50) / 50) * (($res['posY'] - 50) / 50) ); + + if (empty($result) || $result[0] > $q) + $result = [$q, $res]; + } + // capitals (auto-discovered) and no hand-made alphaMap available + else if (in_array($res['areaId'], self::$capitalCities)) + return $res; + // add with lowest quality if alpha map is missing + else if (empty($result)) + $result = [1.0, $res]; + } + + // spawn does not really match on a map, but we need at least one result + if (!$result) + { + usort($points, function ($a, $b) { return ($a['dist'] < $b['dist']) ? -1 : 1; }); + $result = [1.0, $points[0]]; + } + + return $result[1]; + } + + public static function getForGUID(int $type, int ...$guids) : array + { + $result = []; + + switch ($type) + { + case Type::NPC: + $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids); + break; + case Type::OBJECT: + $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids); + break; + case Type::SOUND: + $result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM ?_soundemitters WHERE `id` IN (?a)', $guids); + break; + case Type::ZONE: + $result = DB::Aowow()->select('SELECT -`id` AS ARRAY_KEY, `id`, `parentMapId` AS `mapId`, `parentX` AS `posX`, `parentY` AS `posY` FROM ?_zones WHERE -`id` IN (?a)', $guids); + break; + case Type::AREATRIGGER: + $result = []; + if ($base = array_filter($guids, fn($x) => $x > 0)) + $result = array_replace($result, DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM ?_areatrigger WHERE `id` IN (?a)', $base)); + if ($endpoints = array_filter($guids, fn($x) => $x < 0)) + $result = array_replace($result, DB::World()->select( + 'SELECT -`ID` AS ARRAY_KEY, ID AS `id`, `target_map` AS `mapId`, `target_position_x` AS `posX`, `target_position_y` AS `posY` FROM areatrigger_teleport WHERE -`id` IN (?a) UNION + SELECT -`entryorguid` AS ARRAY_KEY, entryorguid AS `id`, `action_param1` AS `mapId`, `target_x` AS `posX`, `target_y` AS `posY` FROM smart_scripts WHERE -`entryorguid` IN (?a) AND `source_type` = ?d AND `action_type` = ?d', + $endpoints, $endpoints, SmartAI::SRC_TYPE_AREATRIGGER, SmartAction::ACTION_TELEPORT + )); + break; + default: + trigger_error('WorldPosition::getForGUID - unsupported TYPE #'.$type, E_USER_WARNING); + } + + if ($diff = array_diff($guids, array_keys($result))) + trigger_error('WorldPosition::getForGUID - no spawn points for TYPE #'.$type.' GUIDS: '.implode(', ', $diff), E_USER_WARNING); + + return $result; + } + + public static function toZonePos(int $mapId, float $mapX, float $mapY, int $preferedAreaId = 0, int $preferedFloor = -1) : array + { + if (!$mapId < 0) + return []; + + $query = + 'SELECT + x.`id`, + x.`areaId`, + IF(x.`defaultDungeonMapId` < 0, x.`floor` + 1, x.`floor`) AS `floor`, + IF(dm.`id` IS NOT NULL OR x.`defaultDungeonMapId` < 0, 1, 0) AS `multifloor`, + ROUND((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`), 1) AS `posX`, + ROUND((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`), 1) AS `posY`, + SQRT(POWER(ABS((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`) - 50), 2) + + POWER(ABS((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`) - 50), 2)) AS `dist` + FROM + (SELECT 0 AS `id`, `areaId`, `mapId`, `right` AS `minY`, `left` AS `maxY`, `top` AS `maxX`, `bottom` AS `minX`, 0 AS `floor`, 0 AS `worldMapAreaId`, `defaultDungeonMapId` FROM ?_worldmaparea wma UNION + SELECT dm.`id`, `areaId`, wma.`mapId`, `minY`, `maxY`, `maxX`, `minX`, `floor`, `worldMapAreaId`, `defaultDungeonMapId` FROM ?_worldmaparea wma + JOIN ?_dungeonmap dm ON dm.`mapId` = wma.`mapId` WHERE wma.`mapId` NOT IN (0, 1, 530, 571) OR wma.`areaId` = 4395) x + LEFT JOIN + ?_dungeonmap dm ON dm.`mapId` = x.`mapId` AND dm.`worldMapAreaId` = x.`worldMapAreaId` AND dm.`floor` <> x.`floor` AND dm.`worldMapAreaId` > 0 + WHERE + x.`mapId` = ?d AND IF(?d, x.`areaId` = ?d, x.`areaId` <> 0){ AND x.`floor` = ?d - IF(x.`defaultDungeonMapId` < 0, 1, 0)} + GROUP BY + x.`id`, x.`areaId` + HAVING + (`posX` BETWEEN 0.1 AND 99.9 AND `posY` BETWEEN 0.1 AND 99.9) + ORDER BY + `multifloor` DESC, `dist` ASC'; + + // dist BETWEEN 0 (center) AND 70.7 (corner) + $points = DB::Aowow()->select($query, $mapY, $mapX, $mapY, $mapX, $mapId, $preferedAreaId, $preferedAreaId, $preferedFloor < 0 ? DBSIMPLE_SKIP : $preferedFloor); + if (!$points) // retry: pre-instance subareas belong to the instance-maps but are displayed on the outside. There also cases where the zone reaches outside it's own map. + $points = DB::Aowow()->select($query, $mapY, $mapX, $mapY, $mapX, $mapId, 0, 0, DBSIMPLE_SKIP); + if (!is_array($points)) + { + trigger_error('WorldPosition::toZonePos - query failed', E_USER_ERROR); + return []; + } + + return $points; + } +} + +?> diff --git a/includes/kernel.php b/includes/kernel.php index 427bc40e..83d084b8 100644 --- a/includes/kernel.php +++ b/includes/kernel.php @@ -34,19 +34,40 @@ require_once 'localization/lang.class.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/database.class.php'; // wrap DBSimple require_once 'includes/utilities.php'; // helper functions -require_once 'includes/config.class.php'; // Config holder +require_once 'includes/type.class.php'; // DB types storage and factory +require_once 'includes/cfg.class.php'; // Config holder require_once 'includes/user.class.php'; // Session handling (could be skipped for CLI context except for username and password validation used in account creation) +require_once 'includes/game/misc.php'; // Misc game related data & functions -// todo: make everything below autoloaded -require_once 'includes/stats.class.php'; // Game entity statistics conversion -require_once 'includes/game.php'; // game related data & functions -require_once 'includes/profiler.class.php'; // Profiler feature -require_once 'includes/markup.class.php'; // manipulate markup text -require_once 'includes/community.class.php'; // handle comments, screenshots and videos -require_once 'includes/loot.class.php'; // build lv-tabs containing loot-information -require_once 'pages/genericPage.class.php'; +// game client data interfaces +spl_autoload_register(function ($class) +{ + if ($i = strrpos($class, '\\')) + $class = substr($class, $i + 1); -// TC systems + if (preg_match('/[^\w]/i', $class)) + return; + + if ($class == 'Stats' || $class == 'StatsContainer') // entity statistics conversion + require_once 'includes/game/chrstatistics.php'; + else if (file_exists('includes/game/'.strtolower($class).'.class.php')) + require_once 'includes/game/'.strtolower($class).'.class.php'; +}); + +// our site components +spl_autoload_register(function ($class) +{ + if ($i = strrpos($class, '\\')) + $class = substr($class, $i + 1); + + if (preg_match('/[^\w]/i', $class)) + return; + + if (file_exists('includes/components/'.strtolower($class).'.class.php')) + require_once 'includes/components/'.strtolower($class).'.class.php'; +}); + +// TC systems in components spl_autoload_register(function ($class) { switch ($class) @@ -66,52 +87,73 @@ spl_autoload_register(function ($class) } }); -// autoload List-classes, associated filters and pages +// autoload List-classes, associated filters spl_autoload_register(function ($class) { - $class = strtolower(str_replace('ListFilter', 'List', $class)); - - if (class_exists($class)) // already registered - return; - if ($i = strrpos($class, '\\')) $class = substr($class, $i + 1); - if (preg_match('/[^\w]/i', $class)) // name should contain only letters + if (preg_match('/[^\w]/i', $class)) return; - if (stripos($class, 'list')) + if (!stripos($class, 'list')) + return; + + $class = strtolower(str_replace('ListFilter', 'List', $class)); + + $cl = match ($class) { - require_once 'includes/basetype.class.php'; + 'localprofilelist', + 'remoteprofilelist' => 'profile', + 'localarenateamlist', + 'remotearenateamlist' => 'arenateam', + 'localguildlist', + 'remoteguildlist' => 'guild', + default => strtr($class, ['list' => '']) + }; - $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: '.$cl); - - return; + if (file_exists('includes/types/'.$cl.'.class.php')) + { + require_once 'includes/types/basetype.class.php'; + require_once 'includes/types/'.$cl.'.class.php'; } - else if (stripos($class, 'ajax') === 0) - { - require_once 'includes/ajaxHandler.class.php'; // handles ajax and jsonp requests + else + throw new \Exception('could not register type class: '.$cl); +}); +// endpoint loader +spl_autoload_register(function ($class) +{ + if ($i = strrpos($class, '\\')) + $class = substr($class, $i + 1); + + if (preg_match('/[^\w]/i', $class)) + return; + + $class = strtolower($class); + + if (stripos($class, 'ajax') === 0) // handles ajax and jsonp requests + { if (file_exists('includes/ajaxHandler/'.strtr($class, ['ajax' => '']).'.class.php')) + { + require_once 'includes/ajaxHandler/ajaxHandler.class.php'; require_once 'includes/ajaxHandler/'.strtr($class, ['ajax' => '']).'.class.php'; + } else throw new \Exception('could not register ajaxHandler class: '.$class); return; } - else if (file_exists('pages/'.strtr($class, ['page' => '']).'.php')) - require_once 'pages/'.strtr($class, ['page' => '']).'.php'; + else if (stripos($class, 'page')) // handles templated pages + { + if (file_exists('pages/'.strtr($class, ['page' => '']).'.php')) + { + require_once 'pages/genericPage.class.php'; + require_once 'pages/'.strtr($class, ['page' => '']).'.php'; + } + else if ($class == 'genericpage') // may be called directly in fatal error case + require_once 'pages/genericPage.class.php'; + } }); set_error_handler(function($errNo, $errStr, $errFile, $errLine) @@ -120,28 +162,25 @@ set_error_handler(function($errNo, $errStr, $errFile, $errLine) if (strstr($errStr, 'mysqli_connect') && $errNo == E_WARNING) return true; - $errName = 'unknown error'; // errors not in this list can not be handled by set_error_handler (as per documentation) or are ignored - $logLevel = CLI::logLevelFromE($errNo); + // we do not log deprecation notices + if ($errNo & (E_DEPRECATED | E_USER_DEPRECATED)) + return true; - switch ($errNo) + $logLevel = match($errNo) { - case E_WARNING: - case E_USER_WARNING: - $errName = 'WARNING'; - break; - case E_NOTICE: - case E_USER_NOTICE: - $errName = 'NOTICE'; - break; - case E_USER_ERROR: - $errName = 'USER_ERROR'; - case E_USER_ERROR: - $errName = 'RECOVERABLE_ERROR'; - case E_STRICT: // ignore STRICT and DEPRECATED - case E_DEPRECATED: - case E_USER_DEPRECATED: - return true; - } + E_RECOVERABLE_ERROR, E_USER_ERROR => LOG_LEVEL_ERROR, + E_WARNING, E_USER_WARNING => LOG_LEVEL_WARN, + E_NOTICE, E_USER_NOTICE => LOG_LEVEL_INFO, + default => 0 + }; + $errName = match($errNo) + { + E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR', + E_USER_ERROR => 'USER_ERROR', + E_USER_WARNING, E_WARNING => 'WARNING', + E_USER_NOTICE, E_NOTICE => 'NOTICE', + default => 'UNKNOWN_ERROR' // errors not in this list can not be handled by set_error_handler (as per documentation) or are ignored + }; if (DB::isConnected(DB_AOWOW)) DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `post`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()', @@ -168,7 +207,7 @@ set_exception_handler(function ($e) fwrite(STDERR, "\nException - ".$e->getMessage()."\n ".$e->getFile(). '('.$e->getLine().")\n".$e->getTraceAsString()."\n\n"); else { - Util::addNote('Exception - '.$e->getMessage().' @ '.$e->getFile(). ':'.$e->getLine()."\n".$e->getTraceAsString(), U_GROUP_EMPLOYEE, CLI::LOG_ERROR); + Util::addNote('Exception - '.$e->getMessage().' @ '.$e->getFile(). ':'.$e->getLine()."\n".$e->getTraceAsString(), U_GROUP_EMPLOYEE, LOG_LEVEL_ERROR); (new GenericPage())->error(); } }); @@ -253,7 +292,7 @@ if (!CLI) Lang::load(User::$preferedLoc); // set up some logging (~10 queries will execute before we init the user and load the config) - if (Cfg::get('DEBUG') >= CLI::LOG_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)) + if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)) { DB::Aowow()->setLogger(DB::profiler(...)); DB::World()->setLogger(DB::profiler(...)); diff --git a/includes/locale.class.php b/includes/locale.class.php index 235d2d92..af653bd2 100644 --- a/includes/locale.class.php +++ b/includes/locale.class.php @@ -176,36 +176,4 @@ enum Locale : int } } - -/* The shape of things to come */ -class LocString -{ - private \WeakMap $store; - - public function __construct(array $data, string $key = 'name', ?callable $callback = null) - { - $this->store = new \WeakMap(); - - $callback ??= fn($x) => $x; - - if (!array_filter($data, fn($v, $k) => $v && strstr($k, $key.'_loc'), ARRAY_FILTER_USE_BOTH)) - trigger_error('LocString - is entrirely empty', E_USER_WARNING); - - foreach (Locale::cases() as $l) - $this->store[$l] = (string)$callback($data[$key.'_loc'.$l->value] ?? ''); - } - - public function __toString() : string - { - if ($str = $this->store[Lang::getLocale()]) - return $str; - - foreach (Locale::cases() as $l) // desired loc not set, use any other - if ($str = $this->store[$l]) - return Cfg::get('DEBUG') ? '['.$str.']' : $str; - - return Cfg::get('DEBUG') ? '[LOCSTRING]' : ''; - } -} - ?> diff --git a/includes/setup/cli.class.php b/includes/setup/cli.class.php new file mode 100644 index 00000000..20172df6 --- /dev/null +++ b/includes/setup/cli.class.php @@ -0,0 +1,355 @@ + $row) + { + if (!is_array($out[0])) + { + unset($out[$i]); + continue; + } + + $nCols = max($nCols, count($row)); + + for ($j = 0; $j < $nCols; $j++) + $pads[$j] = max($pads[$j] ?? 0, mb_strlen(self::purgeEscapes($row[$j] ?? ''))); + } + + foreach ($out as $i => $row) + { + for ($j = 0; $j < $nCols; $j++) + { + if (!isset($row[$j])) + break; + + $len = ($pads[$j] - mb_strlen(self::purgeEscapes($row[$j]))); + for ($k = 0; $k < $len; $k++) // can't use str_pad(). it counts invisible chars. + $row[$j] .= ' '; + } + + if ($i || $headless) + self::write(' '.implode(' ' . self::tblDelim(' ') . ' ', $row), self::LOG_NONE, $timestamp); + else + self::write(self::tblHead(' '.implode(' ', $row)), self::LOG_NONE, $timestamp); + } + + if (!$headless) + self::write(self::tblHead(str_pad('', array_sum($pads) + count($pads) * 3 - 2)), self::LOG_NONE, $timestamp); + + self::write(); + } + + + /***********/ + /* logging */ + /***********/ + + public static function initLogFile(string $file = '') : void + { + if (!$file) + return; + + $file = self::nicePath($file); + if (!file_exists($file)) + self::$logHandle = fopen($file, 'w'); + else + { + $logFileParts = pathinfo($file); + + $i = 1; + while (file_exists($logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : ''))) + $i++; + + $file = $logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : ''); + self::$logHandle = fopen($file, 'w'); + } + } + + private static function tblHead(string $str) : string + { + return CLI_HAS_E ? "\e[1;48;5;236m".$str."\e[0m" : $str; + } + + private static function tblDelim(string $str) : string + { + return CLI_HAS_E ? "\e[48;5;236m".$str."\e[0m" : $str; + } + + public static function grey(string $str) : string + { + return CLI_HAS_E ? "\e[90m".$str."\e[0m" : $str; + } + + public static function red(string $str) : string + { + return CLI_HAS_E ? "\e[31m".$str."\e[0m" : $str; + } + + public static function green(string $str) : string + { + return CLI_HAS_E ? "\e[32m".$str."\e[0m" : $str; + } + + public static function yellow(string $str) : string + { + return CLI_HAS_E ? "\e[33m".$str."\e[0m" : $str; + } + + public static function blue(string $str) : string + { + return CLI_HAS_E ? "\e[36m".$str."\e[0m" : $str; + } + + public static function bold(string $str) : string + { + return CLI_HAS_E ? "\e[1m".$str."\e[0m" : $str; + } + + public static function write(string $txt = '', int $lvl = self::LOG_BLANK, bool $timestamp = true, bool $tmpRow = false) : void + { + $msg = ''; + if ($txt) + { + if ($timestamp) + $msg = str_pad(date('H:i:s'), 10); + + switch ($lvl) + { + case self::LOG_ERROR: // red critical error + $msg .= '['.self::red('ERR').'] '; + break; + case self::LOG_WARN: // yellow notice + $msg .= '['.self::yellow('WARN').'] '; + break; + case self::LOG_OK: // green success + $msg .= '['.self::green('OK').'] '; + break; + case self::LOG_INFO: // blue info + $msg .= '['.self::blue('INFO').'] '; + break; + case self::LOG_BLANK: + $msg .= ' '; + break; + } + + $msg .= $txt; + } + + // https://shiroyasha.svbtle.com/escape-sequences-a-quick-guide-1#movement_1 + $msg = (self::$overwriteLast && CLI_HAS_E ? "\e[1G\e[0K" : "\n") . $msg; + self::$overwriteLast = $tmpRow; + + fwrite($lvl == self::LOG_ERROR ? STDERR : STDOUT, $msg); + + if (self::$logHandle) // remove control sequences from log + fwrite(self::$logHandle, self::purgeEscapes($msg)); + + flush(); + } + + private static function purgeEscapes(string $msg) : string + { + return preg_replace(["/\e\[[\d;]+[mK]/", "/\e\[\d+G/"], ['', "\n"], $msg); + } + + public static function nicePath(string $fileOrPath, string ...$pathParts) : string + { + $path = ''; + + if ($pathParts) + { + foreach ($pathParts as &$pp) + $pp = trim($pp); + + $path .= implode(DIRECTORY_SEPARATOR, $pathParts); + } + + $path .= ($path ? DIRECTORY_SEPARATOR : '').trim($fileOrPath); + + // remove double quotes (from erronous user input), single quotes are + // valid chars for filenames and removing those mutilates several wow icons + $path = str_replace('"', '', $path); + + if (!$path) // empty strings given. (faulty dbc data?) + return ''; + + if (DIRECTORY_SEPARATOR == '/') // *nix + { + $path = str_replace('\\', '/', $path); + $path = preg_replace('/\/+/i', '/', $path); + } + else if (DIRECTORY_SEPARATOR == '\\') // win + { + $path = str_replace('/', '\\', $path); + $path = preg_replace('/\\\\+/i', '\\', $path); + } + else + self::write('Dafuq! Your directory separator is "'.DIRECTORY_SEPARATOR.'". Please report this!', self::LOG_ERROR); + + // resolve *nix home shorthand + if (!OS_WIN) + { + if (preg_match('/^~(\w+)\/.*/i', $path, $m)) + $path = '/home/'.substr($path, 1); + else if (substr($path, 0, 2) == '~/') + $path = getenv('HOME').substr($path, 1); + else if ($path[0] == DIRECTORY_SEPARATOR && substr($path, 0, 6) != '/home/') + $path = substr($path, 1); + } + + return $path; + } + + + /**************/ + /* read input */ + /**************/ + + /* + since the CLI on WIN ist not interactive, the following things have to be considered + you do not receive keystrokes but whole strings upon pressing (wich also appends a \r) + as such and probably other control chars can not be registered + this also means, you can't hide input at all, least process it + */ + + public static function read(array $fields, ?array &$userInput = []) : bool + { + // first time set + if (self::$hasReadline === null) + self::$hasReadline = function_exists('readline_callback_handler_install'); + + // prevent default output if able + if (self::$hasReadline) + readline_callback_handler_install('', function() { }); + + if (!STDIN) + return false; + + stream_set_blocking(STDIN, false); + + // pad default values onto $fields + array_walk($fields, function(&$val, $_, $pad) { $val += $pad; }, ['', false, false, '']); + + foreach ($fields as $name => [$desc, $isHidden, $singleChar, $validPattern]) + { + $charBuff = ''; + + if ($desc) + fwrite(STDOUT, "\n".$desc.": "); + + while (true) { + if (feof(STDIN)) + return false; + + $r = [STDIN]; + $w = $e = null; + $n = stream_select($r, $w, $e, 200000); + + if (!$n || !in_array(STDIN, $r)) + continue; + + // stream_get_contents is always blocking under WIN - fgets should work similary as php always receives a terminated line of text + $chars = str_split(OS_WIN ? fgets(STDIN) : stream_get_contents(STDIN)); + $ordinals = array_map('ord', $chars); + + if ($ordinals[0] == self::CHR_ESC) + { + if (count($ordinals) == 1) + { + fwrite(STDOUT, chr(self::CHR_BELL)); + return false; + } + else + continue; + } + + foreach ($chars as $idx => $char) + { + $keyId = $ordinals[$idx]; + + // skip char if horizontal tab or \r if followed by \n + if ($keyId == self::CHR_TAB || ($keyId == self::CHR_CR && ($ordinals[$idx + 1] ?? '') == self::CHR_LF)) + continue; + + if ($keyId == self::CHR_BACKSPACE) + { + if (!$charBuff) + continue 2; + + $charBuff = mb_substr($charBuff, 0, -1); + if (!$isHidden && self::$hasReadline) + fwrite(STDOUT, chr(self::CHR_BACK)." ".chr(self::CHR_BACK)); + } + // standalone \n or \r + else if ($keyId == self::CHR_LF || $keyId == self::CHR_CR) + { + $userInput[$name] = $charBuff; + break 2; + } + else if (!$validPattern || preg_match($validPattern, $char)) + { + $charBuff .= $char; + if (!$isHidden && self::$hasReadline) + fwrite(STDOUT, $char); + + if ($singleChar && self::$hasReadline) + { + $userInput[$name] = $charBuff; + break 2; + } + } + } + } + } + + fwrite(STDOUT, chr(self::CHR_BELL)); + + foreach ($userInput as $ui) + if (strlen($ui)) + return true; + + $userInput = null; + return true; + } +} + +?> diff --git a/includes/setup/timer.class.php b/includes/setup/timer.class.php new file mode 100644 index 00000000..a9c22c15 --- /dev/null +++ b/includes/setup/timer.class.php @@ -0,0 +1,39 @@ +intv = $intervall / 1000; // in msec + $this->t_cur = microtime(true); + } + + public function update() : bool + { + $this->t_new = microtime(true); + if ($this->t_new > $this->t_cur + $this->intv) + { + $this->t_cur = $this->t_cur + $this->intv; + return true; + } + + return false; + } + + public function reset() : void + { + $this->t_cur = microtime(true) - $this->intv; + } +} + +?> diff --git a/includes/type.class.php b/includes/type.class.php new file mode 100644 index 00000000..cda57248 --- /dev/null +++ b/includes/type.class.php @@ -0,0 +1,228 @@ + [__NAMESPACE__ . '\CreatureList', 'npc', 'g_npcs', 0x1], + self::OBJECT => [__NAMESPACE__ . '\GameObjectList', 'object', 'g_objects', 0x1], + self::ITEM => [__NAMESPACE__ . '\ItemList', 'item', 'g_items', 0x1], + self::ITEMSET => [__NAMESPACE__ . '\ItemsetList', 'itemset', 'g_itemsets', 0x1], + self::QUEST => [__NAMESPACE__ . '\QuestList', 'quest', 'g_quests', 0x1], + self::SPELL => [__NAMESPACE__ . '\SpellList', 'spell', 'g_spells', 0x1], + self::ZONE => [__NAMESPACE__ . '\ZoneList', 'zone', 'g_gatheredzones', 0x1], + self::FACTION => [__NAMESPACE__ . '\FactionList', 'faction', 'g_factions', 0x1], + self::PET => [__NAMESPACE__ . '\PetList', 'pet', 'g_pets', 0x1], + self::ACHIEVEMENT => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', 0x1], + self::TITLE => [__NAMESPACE__ . '\TitleList', 'title', 'g_titles', 0x1], + self::WORLDEVENT => [__NAMESPACE__ . '\WorldEventList', 'event', 'g_holidays', 0x1], + self::CHR_CLASS => [__NAMESPACE__ . '\CharClassList', 'class', 'g_classes', 0x1], + self::CHR_RACE => [__NAMESPACE__ . '\CharRaceList', 'race', 'g_races', 0x1], + self::SKILL => [__NAMESPACE__ . '\SkillList', 'skill', 'g_skills', 0x1], + self::STATISTIC => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', 0x0], // alias for achievements; exists only for Markup + self::CURRENCY => [__NAMESPACE__ . '\CurrencyList', 'currency', 'g_gatheredcurrencies',0x1], + self::SOUND => [__NAMESPACE__ . '\SoundList', 'sound', 'g_sounds', 0x1], + self::ICON => [__NAMESPACE__ . '\IconList', 'icon', 'g_icons', 0x1], + self::GUIDE => [__NAMESPACE__ . '\GuideList', 'guide', '', 0x0], + self::PROFILE => [__NAMESPACE__ . '\ProfileList', '', '', 0x0], // x - not known in javascript + self::GUILD => [__NAMESPACE__ . '\GuildList', '', '', 0x0], // x + self::ARENA_TEAM => [__NAMESPACE__ . '\ArenaTeamList', '', '', 0x0], // x + self::USER => [__NAMESPACE__ . '\UserList', 'user', 'g_users', 0x0], // x + self::EMOTE => [__NAMESPACE__ . '\EmoteList', 'emote', 'g_emotes', 0x1], + self::ENCHANTMENT => [__NAMESPACE__ . '\EnchantmentList', 'enchantment', 'g_enchantments', 0x1], + self::AREATRIGGER => [__NAMESPACE__ . '\AreatriggerList', 'areatrigger', '', 0x0], + self::MAIL => [__NAMESPACE__ . '\MailList', 'mail', '', 0x1] + ); + + + /********************/ + /* Field Operations */ + /********************/ + + public static function newList(int $type, array $conditions = []) : ?BaseType + { + if (!self::exists($type)) + return null; + + return new (self::$data[$type][self::IDX_LIST_OBJ])($conditions); + } + + public static function getFileString(int $type) : string + { + if (!self::exists($type)) + return ''; + + return self::$data[$type][self::IDX_FILE_STR]; + } + + public static function getJSGlobalString(int $type) : string + { + if (!self::exists($type)) + return ''; + + return self::$data[$type][self::IDX_JSG_TPL]; + } + + public static function getJSGlobalTemplate(int $type) : array + { + if (!self::exists($type) || !self::$data[$type][self::IDX_JSG_TPL]) + return []; + + // [key, [data], [extraData]] + return [self::$data[$type][self::IDX_JSG_TPL], [], []]; + } + + public static function checkClassAttrib(int $type, string $attr, ?int $attrVal = null) : bool + { + if (!self::exists($type)) + return false; + + return isset((self::$data[$type][self::IDX_LIST_OBJ])::$$attr) && ($attrVal === null || ((self::$data[$type][self::IDX_LIST_OBJ])::$$attr & $attrVal)); + } + + public static function getClassAttrib(int $type, string $attr) : mixed + { + if (!self::exists($type)) + return null; + + return (self::$data[$type][self::IDX_LIST_OBJ])::$$attr ?? null; + } + + public static function exists(int $type) : bool + { + return !empty(self::$data[$type]); + } + + public static function getIndexFrom(int $idx, string $match) : int + { + $i = array_search($match, array_column(self::$data, $idx)); + if ($i === false) + return 0; + + return array_keys(self::$data)[$i]; + } + + + /*********************/ + /* Column Operations */ + /*********************/ + + public static function getClassesFor(int $flags = 0x0, string $attr = '', ?int $attrVal = null) : array + { + $x = []; + foreach (self::$data as $k => [$o, , , $f]) + if ($o && (!$flags || $flags & $f)) + if (!$attr || self::checkClassAttrib($k, $attr, $attrVal)) + $x[$k] = $o; + + return $x; + } + + public static function getFileStringsFor(int $flags = 0x0) : array + { + $x = []; + foreach (self::$data as $k => [, $s, , $f]) + if ($s && (!$flags || $flags & $f)) + $x[$k] = $s; + + return $x; + } + + public static function getJSGTemplatesFor(int $flags = 0x0) : array + { + $x = []; + foreach (self::$data as $k => [, , $a, $f]) + if ($a && (!$flags || $flags & $f)) + $x[$k] = $a; + + return $x; + } +} + +?> diff --git a/includes/basetype.class.php b/includes/types/basetype.class.php similarity index 99% rename from includes/basetype.class.php rename to includes/types/basetype.class.php index 2398fab1..c47eddf3 100644 --- a/includes/basetype.class.php +++ b/includes/types/basetype.class.php @@ -612,7 +612,7 @@ trait spawnHelper if (User::isInGroup(U_GROUP_MODERATOR)) if ($guids = array_column(array_filter($spawns, fn($x) => $x['guid'] > 0 || $x['type'] != Type::NPC), 'guid')) - $worldPos = Game::getWorldPosForGUID(self::$type, ...$guids); + $worldPos = WorldPosition::getForGUID(self::$type, ...$guids); foreach ($spawns as $s) { diff --git a/includes/utilities.php b/includes/utilities.php index 6337dc62..5edbe791 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -116,397 +116,6 @@ trait TrRequestData } } -abstract class CLI -{ - private const CHR_BELL = 7; - private const CHR_BACK = 8; - private const CHR_TAB = 9; - private const CHR_LF = 10; - private const CHR_CR = 13; - private const CHR_ESC = 27; - private const CHR_BACKSPACE = 127; - - public const LOG_NONE = -1; - public const LOG_BLANK = 0; - public const LOG_ERROR = 1; - public const LOG_WARN = 2; - public const LOG_INFO = 3; - public const LOG_OK = 4; - - private static $logHandle = null; - private static $hasReadline = null; - - private static $overwriteLast = false; - - /********************/ - /* formatted output */ - /********************/ - - public static function writeTable(array $out, bool $timestamp = false, bool $headless = false) : void - { - if (!$out) - return; - - $pads = []; - $nCols = 0; - - foreach ($out as $i => $row) - { - if (!is_array($out[0])) - { - unset($out[$i]); - continue; - } - - $nCols = max($nCols, count($row)); - - for ($j = 0; $j < $nCols; $j++) - $pads[$j] = max($pads[$j] ?? 0, mb_strlen(self::purgeEscapes($row[$j] ?? ''))); - } - - foreach ($out as $i => $row) - { - for ($j = 0; $j < $nCols; $j++) - { - if (!isset($row[$j])) - break; - - $len = ($pads[$j] - mb_strlen(self::purgeEscapes($row[$j]))); - for ($k = 0; $k < $len; $k++) // can't use str_pad(). it counts invisible chars. - $row[$j] .= ' '; - } - - if ($i || $headless) - self::write(' '.implode(' ' . self::tblDelim(' ') . ' ', $row), CLI::LOG_NONE, $timestamp); - else - self::write(self::tblHead(' '.implode(' ', $row)), CLI::LOG_NONE, $timestamp); - } - - if (!$headless) - self::write(self::tblHead(str_pad('', array_sum($pads) + count($pads) * 3 - 2)), CLI::LOG_NONE, $timestamp); - - self::write(); - } - - - /***********/ - /* logging */ - /***********/ - - public static function initLogFile(string $file = '') : void - { - if (!$file) - return; - - $file = self::nicePath($file); - if (!file_exists($file)) - self::$logHandle = fopen($file, 'w'); - else - { - $logFileParts = pathinfo($file); - - $i = 1; - while (file_exists($logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : ''))) - $i++; - - $file = $logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : ''); - self::$logHandle = fopen($file, 'w'); - } - } - - private static function tblHead(string $str) : string - { - return CLI_HAS_E ? "\e[1;48;5;236m".$str."\e[0m" : $str; - } - - private static function tblDelim(string $str) : string - { - return CLI_HAS_E ? "\e[48;5;236m".$str."\e[0m" : $str; - } - - public static function grey(string $str) : string - { - return CLI_HAS_E ? "\e[90m".$str."\e[0m" : $str; - } - - public static function red(string $str) : string - { - return CLI_HAS_E ? "\e[31m".$str."\e[0m" : $str; - } - - public static function green(string $str) : string - { - return CLI_HAS_E ? "\e[32m".$str."\e[0m" : $str; - } - - public static function yellow(string $str) : string - { - return CLI_HAS_E ? "\e[33m".$str."\e[0m" : $str; - } - - public static function blue(string $str) : string - { - return CLI_HAS_E ? "\e[36m".$str."\e[0m" : $str; - } - - public static function bold(string $str) : string - { - return CLI_HAS_E ? "\e[1m".$str."\e[0m" : $str; - } - - public static function write(string $txt = '', int $lvl = self::LOG_BLANK, bool $timestamp = true, bool $tmpRow = false) : void - { - $msg = ''; - if ($txt) - { - if ($timestamp) - $msg = str_pad(date('H:i:s'), 10); - - switch ($lvl) - { - case self::LOG_ERROR: // red critical error - $msg .= '['.self::red('ERR').'] '; - break; - case self::LOG_WARN: // yellow notice - $msg .= '['.self::yellow('WARN').'] '; - break; - case self::LOG_OK: // green success - $msg .= '['.self::green('OK').'] '; - break; - case self::LOG_INFO: // blue info - $msg .= '['.self::blue('INFO').'] '; - break; - case self::LOG_BLANK: - $msg .= ' '; - break; - } - - $msg .= $txt; - } - - // https://shiroyasha.svbtle.com/escape-sequences-a-quick-guide-1#movement_1 - $msg = (self::$overwriteLast && CLI_HAS_E ? "\e[1G\e[0K" : "\n") . $msg; - self::$overwriteLast = $tmpRow; - - fwrite($lvl == self::LOG_ERROR ? STDERR : STDOUT, $msg); - - if (self::$logHandle) // remove control sequences from log - fwrite(self::$logHandle, self::purgeEscapes($msg)); - - flush(); - } - - public static function logLevelFromE(int $phpError) : int - { - if ($phpError & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) - return self::LOG_ERROR; - - if ($phpError & (E_WARNING | E_USER_WARNING | E_NOTICE | E_USER_NOTICE | E_CORE_WARNING | E_COMPILE_WARNING)) - return self::LOG_WARN; - - if ($phpError & (E_STRICT | E_NOTICE | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED)) - return self::LOG_INFO; - - return self::LOG_BLANK; - } - - private static function purgeEscapes(string $msg) : string - { - return preg_replace(["/\e\[[\d;]+[mK]/", "/\e\[\d+G/"], ['', "\n"], $msg); - } - - public static function nicePath(string $fileOrPath, string ...$pathParts) : string - { - $path = ''; - - if ($pathParts) - { - foreach ($pathParts as &$pp) - $pp = trim($pp); - - $path .= implode(DIRECTORY_SEPARATOR, $pathParts); - } - - $path .= ($path ? DIRECTORY_SEPARATOR : '').trim($fileOrPath); - - // remove double quotes (from erronous user input), single quotes are - // valid chars for filenames and removing those mutilates several wow icons - $path = str_replace('"', '', $path); - - if (!$path) // empty strings given. (faulty dbc data?) - return ''; - - if (DIRECTORY_SEPARATOR == '/') // *nix - { - $path = str_replace('\\', '/', $path); - $path = preg_replace('/\/+/i', '/', $path); - } - else if (DIRECTORY_SEPARATOR == '\\') // win - { - $path = str_replace('/', '\\', $path); - $path = preg_replace('/\\\\+/i', '\\', $path); - } - else - CLI::write('Dafuq! Your directory separator is "'.DIRECTORY_SEPARATOR.'". Please report this!', CLI::LOG_ERROR); - - // resolve *nix home shorthand - if (!OS_WIN) - { - if (preg_match('/^~(\w+)\/.*/i', $path, $m)) - $path = '/home/'.substr($path, 1); - else if (substr($path, 0, 2) == '~/') - $path = getenv('HOME').substr($path, 1); - else if ($path[0] == DIRECTORY_SEPARATOR && substr($path, 0, 6) != '/home/') - $path = substr($path, 1); - } - - return $path; - } - - - /**************/ - /* read input */ - /**************/ - - /* - since the CLI on WIN ist not interactive, the following things have to be considered - you do not receive keystrokes but whole strings upon pressing (wich also appends a \r) - as such and probably other control chars can not be registered - this also means, you can't hide input at all, least process it - */ - - public static function read(array $fields, ?array &$userInput = []) : bool - { - // first time set - if (self::$hasReadline === null) - self::$hasReadline = function_exists('readline_callback_handler_install'); - - // prevent default output if able - if (self::$hasReadline) - readline_callback_handler_install('', function() { }); - - if (!STDIN) - return false; - - stream_set_blocking(STDIN, false); - - // pad default values onto $fields - array_walk($fields, function(&$val, $_, $pad) { $val += $pad; }, ['', false, false, '']); - - foreach ($fields as $name => [$desc, $isHidden, $singleChar, $validPattern]) - { - $charBuff = ''; - - if ($desc) - fwrite(STDOUT, "\n".$desc.": "); - - while (true) { - if (feof(STDIN)) - return false; - - $r = [STDIN]; - $w = $e = null; - $n = stream_select($r, $w, $e, 200000); - - if (!$n || !in_array(STDIN, $r)) - continue; - - // stream_get_contents is always blocking under WIN - fgets should work similary as php always receives a terminated line of text - $chars = str_split(OS_WIN ? fgets(STDIN) : stream_get_contents(STDIN)); - $ordinals = array_map('ord', $chars); - - if ($ordinals[0] == self::CHR_ESC) - { - if (count($ordinals) == 1) - { - fwrite(STDOUT, chr(self::CHR_BELL)); - return false; - } - else - continue; - } - - foreach ($chars as $idx => $char) - { - $keyId = $ordinals[$idx]; - - // skip char if horizontal tab or \r if followed by \n - if ($keyId == self::CHR_TAB || ($keyId == self::CHR_CR && ($ordinals[$idx + 1] ?? '') == self::CHR_LF)) - continue; - - if ($keyId == self::CHR_BACKSPACE) - { - if (!$charBuff) - continue 2; - - $charBuff = mb_substr($charBuff, 0, -1); - if (!$isHidden && self::$hasReadline) - fwrite(STDOUT, chr(self::CHR_BACK)." ".chr(self::CHR_BACK)); - } - // standalone \n or \r - else if ($keyId == self::CHR_LF || $keyId == self::CHR_CR) - { - $userInput[$name] = $charBuff; - break 2; - } - else if (!$validPattern || preg_match($validPattern, $char)) - { - $charBuff .= $char; - if (!$isHidden && self::$hasReadline) - fwrite(STDOUT, $char); - - if ($singleChar && self::$hasReadline) - { - $userInput[$name] = $charBuff; - break 2; - } - } - } - } - } - - fwrite(STDOUT, chr(self::CHR_BELL)); - - foreach ($userInput as $ui) - if (strlen($ui)) - return true; - - $userInput = null; - return true; - } -} - - -class Timer -{ - private $t_cur = 0; - private $t_new = 0; - private $intv = 0; - - public function __construct(int $intervall) - { - $this->intv = $intervall / 1000; // in msec - $this->t_cur = microtime(true); - } - - public function update() : bool - { - $this->t_new = microtime(true); - if ($this->t_new > $this->t_cur + $this->intv) - { - $this->t_cur = $this->t_cur + $this->intv; - return true; - } - - return false; - } - - public function reset() : void - { - $this->t_cur = microtime(true) - $this->intv; - } -} - abstract class Util { @@ -573,7 +182,7 @@ abstract class Util public static $tcEncoding = '0zMcmVokRsaqbdrfwihuGINALpTjnyxtgevElBCDFHJKOPQSUWXYZ123456789'; private static $notes = []; - public static function addNote(string $note, int $uGroupMask = U_GROUP_EMPLOYEE, int $level = CLI::LOG_ERROR) : void + public static function addNote(string $note, int $uGroupMask = U_GROUP_EMPLOYEE, int $level = LOG_LEVEL_ERROR) : void { self::$notes[] = [$note, $uGroupMask, $level]; } @@ -581,7 +190,7 @@ abstract class Util public static function getNotes() : array { $notes = []; - $severity = CLI::LOG_INFO; + $severity = LOG_LEVEL_INFO; foreach (self::$notes as [$note, $uGroup, $level]) { if ($uGroup && !User::isInGroup($uGroup)) @@ -1581,7 +1190,7 @@ abstract class Util public static function buildPosFixMenu(int $mapId, float $posX, float $posY, int $type, int $guid, int $parentArea = 0, int $parentFloor = 0) : array { - $points = Game::worldPosToZonePos($mapId, $posX, $posY); + $points = WorldPosition::toZonePos($mapId, $posX, $posY); if (!$points || count($points) < 2) return []; @@ -1620,490 +1229,4 @@ abstract class Util } } - -abstract class Type -{ - public const NPC = 1; - public const OBJECT = 2; - public const ITEM = 3; - public const ITEMSET = 4; - public const QUEST = 5; - public const SPELL = 6; - public const ZONE = 7; - public const FACTION = 8; - public const PET = 9; - public const ACHIEVEMENT = 10; - public const TITLE = 11; - public const WORLDEVENT = 12; - public const CHR_CLASS = 13; - public const CHR_RACE = 14; - public const SKILL = 15; - public const STATISTIC = 16; - public const CURRENCY = 17; - // PROJECT = 18; - public const SOUND = 19; - // BUILDING = 20; - // FOLLOWER = 21; - // MISSION_ABILITY = 22; - // MISSION = 23; - // SHIP = 25; - // THREAT = 26; - // RESOURCE = 27; - // CHAMPION = 28; - public const ICON = 29; - // ORDER_ADVANCEMENT = 30; - // FOLLOWER_ALLIANCE = 31; - // FOLLOWER_HORDE = 32; - // SHIP_ALLIANCE = 33; - // SHIP_HORDE = 34; - // CHAMPION_ALLIANCE = 35; - // CHAMPION_HORDE = 36; - // TRANSMOG_ITEM = 37; - // BFA_CHAMPION = 38; - // BFA_CHAMPION_ALLIANCE = 39; - // AFFIX = 40; - // BFA_CHAMPION_HORDE = 41; - // AZERITE_ESSENCE_POWER = 42; - // AZERITE_ESSENCE = 43; - // STORYLINE = 44; - // ADVENTURE_COMBATANT_ABILITY = 46; - // ENCOUNTER = 47; - // COVENANT = 48; - // SOULBIND = 49; - // DI_ITEM = 50; - // GATHERER_SCREENSHOT = 91; - // GATHERER_GUIDE_IMAGE = 98; - public const PROFILE = 100; - // our own things - public const GUILD = 101; - // TRANSMOG_SET = 101; // future conflict inc. - public const ARENA_TEAM = 102; - // OUTFIT = 110; - // GEAR_SET = 111; - // GATHERER_LISTVIEW = 158; - // GATHERER_SURVEY_COVENANTS = 161; - // NEWS_POST = 162; - // BATTLE_PET_ABILITY = 200; - public const GUIDE = 300; // should have been 100, but conflicts with old version of Profile/List - public const USER = 500; - public const EMOTE = 501; - public const ENCHANTMENT = 502; - public const AREATRIGGER = 503; - public const MAIL = 504; - // Blizzard API things - // MOUNT = -1000; - // RECIPE = -1001; - // BATTLE_PET = -1002; - - public const FLAG_NONE = 0x0; - public const FLAG_RANDOM_SEARCHABLE = 0x1; - /* public const FLAG_SEARCHABLE = 0x2 general search? */ - - public const IDX_LIST_OBJ = 0; - public const IDX_FILE_STR = 1; - public const IDX_JSG_TPL = 2; - public const IDX_FLAGS = 3; - - private static array $data = array( - self::NPC => [__NAMESPACE__ . '\CreatureList', 'npc', 'g_npcs', 0x1], - self::OBJECT => [__NAMESPACE__ . '\GameObjectList', 'object', 'g_objects', 0x1], - self::ITEM => [__NAMESPACE__ . '\ItemList', 'item', 'g_items', 0x1], - self::ITEMSET => [__NAMESPACE__ . '\ItemsetList', 'itemset', 'g_itemsets', 0x1], - self::QUEST => [__NAMESPACE__ . '\QuestList', 'quest', 'g_quests', 0x1], - self::SPELL => [__NAMESPACE__ . '\SpellList', 'spell', 'g_spells', 0x1], - self::ZONE => [__NAMESPACE__ . '\ZoneList', 'zone', 'g_gatheredzones', 0x1], - self::FACTION => [__NAMESPACE__ . '\FactionList', 'faction', 'g_factions', 0x1], - self::PET => [__NAMESPACE__ . '\PetList', 'pet', 'g_pets', 0x1], - self::ACHIEVEMENT => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', 0x1], - self::TITLE => [__NAMESPACE__ . '\TitleList', 'title', 'g_titles', 0x1], - self::WORLDEVENT => [__NAMESPACE__ . '\WorldEventList', 'event', 'g_holidays', 0x1], - self::CHR_CLASS => [__NAMESPACE__ . '\CharClassList', 'class', 'g_classes', 0x1], - self::CHR_RACE => [__NAMESPACE__ . '\CharRaceList', 'race', 'g_races', 0x1], - self::SKILL => [__NAMESPACE__ . '\SkillList', 'skill', 'g_skills', 0x1], - self::STATISTIC => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', 0x0], // alias for achievements; exists only for Markup - self::CURRENCY => [__NAMESPACE__ . '\CurrencyList', 'currency', 'g_gatheredcurrencies',0x1], - self::SOUND => [__NAMESPACE__ . '\SoundList', 'sound', 'g_sounds', 0x1], - self::ICON => [__NAMESPACE__ . '\IconList', 'icon', 'g_icons', 0x1], - self::GUIDE => [__NAMESPACE__ . '\GuideList', 'guide', '', 0x0], - self::PROFILE => [__NAMESPACE__ . '\ProfileList', '', '', 0x0], // x - not known in javascript - self::GUILD => [__NAMESPACE__ . '\GuildList', '', '', 0x0], // x - self::ARENA_TEAM => [__NAMESPACE__ . '\ArenaTeamList', '', '', 0x0], // x - self::USER => [__NAMESPACE__ . '\UserList', 'user', 'g_users', 0x0], // x - self::EMOTE => [__NAMESPACE__ . '\EmoteList', 'emote', 'g_emotes', 0x1], - self::ENCHANTMENT => [__NAMESPACE__ . '\EnchantmentList', 'enchantment', 'g_enchantments', 0x1], - self::AREATRIGGER => [__NAMESPACE__ . '\AreatriggerList', 'areatrigger', '', 0x0], - self::MAIL => [__NAMESPACE__ . '\MailList', 'mail', '', 0x1] - ); - - - /********************/ - /* Field Operations */ - /********************/ - - public static function newList(int $type, array $conditions = []) : ?BaseType - { - if (!self::exists($type)) - return null; - - return new (self::$data[$type][self::IDX_LIST_OBJ])($conditions); - } - - public static function getFileString(int $type) : string - { - if (!self::exists($type)) - return ''; - - return self::$data[$type][self::IDX_FILE_STR]; - } - - public static function getJSGlobalString(int $type) : string - { - if (!self::exists($type)) - return ''; - - return self::$data[$type][self::IDX_JSG_TPL]; - } - - public static function getJSGlobalTemplate(int $type) : array - { - if (!self::exists($type) || !self::$data[$type][self::IDX_JSG_TPL]) - return []; - - // [key, [data], [extraData]] - return [self::$data[$type][self::IDX_JSG_TPL], [], []]; - } - - public static function checkClassAttrib(int $type, string $attr, ?int $attrVal = null) : bool - { - if (!self::exists($type)) - return false; - - return isset((self::$data[$type][self::IDX_LIST_OBJ])::$$attr) && ($attrVal === null || ((self::$data[$type][self::IDX_LIST_OBJ])::$$attr & $attrVal)); - } - - public static function getClassAttrib(int $type, string $attr) : mixed - { - if (!self::exists($type)) - return null; - - return (self::$data[$type][self::IDX_LIST_OBJ])::$$attr ?? null; - } - - public static function exists(int $type) : bool - { - return !empty(self::$data[$type]); - } - - public static function getIndexFrom(int $idx, string $match) : int - { - $i = array_search($match, array_column(self::$data, $idx)); - if ($i === false) - return 0; - - return array_keys(self::$data)[$i]; - } - - - /*********************/ - /* Column Operations */ - /*********************/ - - public static function getClassesFor(int $flags = 0x0, string $attr = '', ?int $attrVal = null) : array - { - $x = []; - foreach (self::$data as $k => [$o, , , $f]) - if ($o && (!$flags || $flags & $f)) - if (!$attr || self::checkClassAttrib($k, $attr, $attrVal)) - $x[$k] = $o; - - return $x; - } - - public static function getFileStringsFor(int $flags = 0x0) : array - { - $x = []; - foreach (self::$data as $k => [, $s, , $f]) - if ($s && (!$flags || $flags & $f)) - $x[$k] = $s; - - return $x; - } - - public static function getJSGTemplatesFor(int $flags = 0x0) : array - { - $x = []; - foreach (self::$data as $k => [, , $a, $f]) - if ($a && (!$flags || $flags & $f)) - $x[$k] = $a; - - return $x; - } -} - - -class Report -{ - public const MODE_GENERAL = 0; - public const MODE_COMMENT = 1; - public const MODE_FORUM_POST = 2; - public const MODE_SCREENSHOT = 3; - public const MODE_CHARACTER = 4; - public const MODE_VIDEO = 5; - public const MODE_GUIDE = 6; - - public const GEN_FEEDBACK = 1; - public const GEN_BUG_REPORT = 2; - public const GEN_TYPO_TRANSLATION = 3; - public const GEN_OP_ADVERTISING = 4; - public const GEN_OP_PARTNERSHIP = 5; - public const GEN_PRESS_INQUIRY = 6; - public const GEN_MISCELLANEOUS = 7; - public const GEN_MISINFORMATION = 8; - public const CO_ADVERTISING = 15; - public const CO_INACCURATE = 16; - public const CO_OUT_OF_DATE = 17; - public const CO_SPAM = 18; - public const CO_INAPPROPRIATE = 19; - public const CO_MISCELLANEOUS = 20; - public const FO_ADVERTISING = 30; - public const FO_AVATAR = 31; - public const FO_INACCURATE = 32; - public const FO_OUT_OF_DATE = 33; - public const FO_SPAM = 34; - public const FO_STICKY_REQUEST = 35; - public const FO_INAPPROPRIATE = 36; - public const FO_MISCELLANEOUS = 37; - public const SS_INACCURATE = 45; - public const SS_OUT_OF_DATE = 46; - public const SS_INAPPROPRIATE = 47; - public const SS_MISCELLANEOUS = 48; - public const PR_INACCURATE_DATA = 60; - public const PR_MISCELLANEOUS = 61; - public const VI_INACCURATE = 45; - public const VI_OUT_OF_DATE = 46; - public const VI_INAPPROPRIATE = 47; - public const VI_MISCELLANEOUS = 48; - public const AR_INACCURATE = 45; - public const AR_OUT_OF_DATE = 46; - public const AR_MISCELLANEOUS = 48; - - private array $context = array( - self::MODE_GENERAL => array( - self::GEN_FEEDBACK => true, - self::GEN_BUG_REPORT => true, - self::GEN_TYPO_TRANSLATION => true, - self::GEN_OP_ADVERTISING => true, - self::GEN_OP_PARTNERSHIP => true, - self::GEN_PRESS_INQUIRY => true, - self::GEN_MISCELLANEOUS => true, - self::GEN_MISINFORMATION => true - ), - self::MODE_COMMENT => array( - self::CO_ADVERTISING => U_GROUP_MODERATOR, - self::CO_INACCURATE => true, - self::CO_OUT_OF_DATE => true, - self::CO_SPAM => U_GROUP_MODERATOR, - self::CO_INAPPROPRIATE => U_GROUP_MODERATOR, - self::CO_MISCELLANEOUS => U_GROUP_MODERATOR - ), - self::MODE_FORUM_POST => array( - self::FO_ADVERTISING => U_GROUP_MODERATOR, - self::FO_AVATAR => true, - self::FO_INACCURATE => true, - self::FO_OUT_OF_DATE => U_GROUP_MODERATOR, - self::FO_SPAM => U_GROUP_MODERATOR, - self::FO_STICKY_REQUEST => U_GROUP_MODERATOR, - self::FO_INAPPROPRIATE => U_GROUP_MODERATOR - ), - self::MODE_SCREENSHOT => array( - self::SS_INACCURATE => true, - self::SS_OUT_OF_DATE => true, - self::SS_INAPPROPRIATE => U_GROUP_MODERATOR, - self::SS_MISCELLANEOUS => U_GROUP_MODERATOR - ), - self::MODE_CHARACTER => array( - self::PR_INACCURATE_DATA => true, - self::PR_MISCELLANEOUS => true - ), - self::MODE_VIDEO => array( - self::VI_INACCURATE => true, - self::VI_OUT_OF_DATE => true, - self::VI_INAPPROPRIATE => U_GROUP_MODERATOR, - self::VI_MISCELLANEOUS => U_GROUP_MODERATOR - ), - self::MODE_GUIDE => array( - self::AR_INACCURATE => true, - self::AR_OUT_OF_DATE => true, - self::AR_MISCELLANEOUS => true - ) - ); - - private const ERR_NONE = 0; // aka: success - private const ERR_INVALID_CAPTCHA = 1; // captcha not in use - private const ERR_DESC_TOO_LONG = 2; - private const ERR_NO_DESC = 3; - private const ERR_ALREADY_REPORTED = 7; - private const ERR_MISCELLANEOUS = -1; - - public const STATUS_OPEN = 0; - public const STATUS_ASSIGNED = 1; - public const STATUS_CLOSED_WONTFIX = 2; - public const STATUS_CLOSED_SOLVED = 3; - - private int $errorCode = self::ERR_NONE; - - - public function __construct(private int $mode, private int $reason, private ?int $subject = 0) - { - if ($mode < 0 || $reason <= 0) - { - trigger_error('Report - malformed contact request received', E_USER_ERROR); - $this->errorCode = self::ERR_MISCELLANEOUS; - return; - } - - if (!isset($this->context[$mode][$reason])) - { - trigger_error('Report - report has invalid context (mode:'.$mode.' / reason:'.$reason.')', E_USER_ERROR); - $this->errorCode = self::ERR_MISCELLANEOUS; - return; - } - - if (!User::$id && !User::$ip) - { - trigger_error('Report - could not determine IP for anonymous user', E_USER_ERROR); - $this->errorCode = self::ERR_MISCELLANEOUS; - return; - } - - $this->subject ??= 0; // 0 for utility, tools and misc pages? - } - - private function checkTargetContext() : int - { - // check already reported - $field = User::$id ? 'userId' : 'ip'; - if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $this->mode, $this->reason, $this->subject, $field, User::$id ?: User::$ip)) - return self::ERR_ALREADY_REPORTED; - - // check targeted post/postOwner staff status - $ctxCheck = $this->context[$this->mode][$this->reason]; - if (is_int($ctxCheck)) - { - $roles = User::$groups; - if ($this->mode == self::MODE_COMMENT) - $roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_comments WHERE `id` = ?d', $this->subject); - // else if if ($this->mode == self::MODE_FORUM_POST) - // $roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_forum_posts WHERE `id` = ?d', $this->subject); - - return $roles & $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS; - } - else - return $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS; - - // Forum not in use, else: - // check post owner - // User::$id == post.op && !post.sticky; - // check user custom avatar - // g_users[post.user].avatar == 2 && (post.roles & U_GROUP_MODERATOR) == 0 - } - - public function create(string $desc, ?string $userAgent = null, ?string $appName = null, ?string $pageUrl = null, ?string $relUrl = null, ?string $email = null) : bool - { - if ($this->errorCode) - return false; - - if (!$desc) - { - $this->errorCode = self::ERR_NO_DESC; - return false; - } - - if (mb_strlen($desc) > 500) - { - $this->errorCode = self::ERR_DESC_TOO_LONG; - return false; - } - - if($err = $this->checkTargetContext()) - { - $this->errorCode = $err; - return false; - } - - $update = array( - 'userId' => User::$id, - 'createDate' => time(), - 'mode' => $this->mode, - 'reason' => $this->reason, - 'subject' => $this->subject, - 'ip' => User::$ip, - 'description' => $desc, - 'userAgent' => $userAgent ?: $_SERVER['HTTP_USER_AGENT'], - 'appName' => $appName ?: (get_browser(null, true)['browser'] ?: '') - ); - - if ($pageUrl) - $update['url'] = $pageUrl; - - if ($relUrl) - $update['relatedurl'] = $relUrl; - - if ($email) - $update['email'] = $email; - - return DB::Aowow()->query('INSERT INTO ?_reports (?#) VALUES (?a)', array_keys($update), array_values($update)); - } - - public function getSimilar(int ...$status) : array - { - if ($this->errorCode) - return []; - - foreach ($status as &$s) - if ($s < self::STATUS_OPEN || $s > self::STATUS_CLOSED_SOLVED) - unset($s); - - return DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, r.* FROM ?_reports r WHERE {`status` IN (?a) AND }`mode` = ?d AND `reason` = ?d AND `subject` = ?d', - $status ?: DBSIMPLE_SKIP, $this->mode, $this->reason, $this->subject); - } - - public function close(int $closeStatus, bool $inclAssigned = false) : bool - { - if ($closeStatus != self::STATUS_CLOSED_SOLVED && $closeStatus != self::STATUS_CLOSED_WONTFIX) - return false; - - if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_MOD)) - return false; - - $fromStatus = [self::STATUS_OPEN]; - if ($inclAssigned) - $fromStatus[] = self::STATUS_ASSIGNED; - - if ($reports = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `userId` FROM ?_reports WHERE `status` IN (?a) AND `mode` = ?d AND `reason` = ?d AND `subject` = ?d', - $fromStatus, $this->mode, $this->reason, $this->subject)) - { - DB::Aowow()->query('UPDATE ?_reports SET `status` = ?d, `assigned` = 0 WHERE `id` IN (?a)', $closeStatus, array_keys($reports)); - - foreach ($reports as $rId => $uId) - Util::gainSiteReputation($uId, $closeStatus == self::STATUS_CLOSED_SOLVED ? SITEREP_ACTION_GOOD_REPORT : SITEREP_ACTION_BAD_REPORT, ['id' => $rId]); - - return true; - } - - return false; - } - - public function reopen(int $assignedTo = 0) : bool - { - // assignedTo = 0 ? status = STATUS_OPEN : status = STATUS_ASSIGNED, userId = assignedTo - return false; - } - - public function getError() : int - { - return $this->errorCode; - } -} - ?> diff --git a/pages/genericPage.class.php b/pages/genericPage.class.php index 9a1c6962..9781eba6 100644 --- a/pages/genericPage.class.php +++ b/pages/genericPage.class.php @@ -607,9 +607,9 @@ class GenericPage { array_unshift($notes, 'One or more issues occured, while generating this page.'); $colors = array( // [border, text] - CLI::LOG_ERROR => ['C50F1F', 'E51223'], - CLI::LOG_WARN => ['C19C00', 'E5B700'], - CLI::LOG_INFO => ['3A96DD', '42ADFF'] + LOG_LEVEL_ERROR => ['C50F1F', 'E51223'], + LOG_LEVEL_WARN => ['C19C00', 'E5B700'], + LOG_LEVEL_INFO => ['3A96DD', '42ADFF'] ); $this->announcements[0] = array( diff --git a/pages/spell.php b/pages/spell.php index 95b1d9c1..1f0e0f36 100644 --- a/pages/spell.php +++ b/pages/spell.php @@ -109,8 +109,13 @@ class SpellPage extends GenericPage case -13: if ($cl = $this->subject->getField('reqClassMask')) $this->path[] = log($cl, 2) + 1; - else if ($cl = array_search($this->subject->getField('spellFamilyId'), Game::$class2SpellFamily)) - $this->path[] = $cl; + else if ($sf = $this->subject->getField('spellFamilyId')) + foreach (ChrClass::cases() as $cl) + if ($cl->spellFamily() == $sf) + { + $this->path[] = $cl->value; + break; + } if ($cat == -13) $this->path[] = ($cf & (SPELL_CU_GLYPH_MAJOR | SPELL_CU_GLYPH_MINOR)) >> 6; diff --git a/pages/zone.php b/pages/zone.php index 7ee4bec5..30f2a820 100644 --- a/pages/zone.php +++ b/pages/zone.php @@ -803,7 +803,7 @@ class ZonePage extends GenericPage if (!User::isInGroup(U_GROUP_EMPLOYEE)) return; - $worldPos = Game::getWorldPosForGUID(Type::ZONE, -$this->typeId); + $worldPos = WorldPosition::getForGUID(Type::ZONE, -$this->typeId); if (!$worldPos) return; diff --git a/prQueue b/prQueue index 92166883..c206e197 100755 --- a/prQueue +++ b/prQueue @@ -3,7 +3,8 @@ namespace Aowow; -require 'includes/kernel.php'; +require_once 'includes/kernel.php'; +require_once 'includes/setup/cli.class.php'; /* todo (med): * tidy this file diff --git a/setup/tools/clisetup/setup.us.php b/setup/tools/clisetup/setup.us.php index df10a90c..0c84b0a1 100644 --- a/setup/tools/clisetup/setup.us.php +++ b/setup/tools/clisetup/setup.us.php @@ -121,7 +121,7 @@ CLISetup::registerUtility(new class extends UtilityScript if (CLI::read(['x' => ['['.CLI::bold('c').']ontinue anyway? ['.CLI::bold('r').']etry? ['.CLI::bold('a').']bort?', true, true, '/c|r|a/i']], $uiCRA) && $uiCRA) { CLI::write(); - switch(strtolower($uiCRA['x'])) + switch (strtolower($uiCRA['x'])) { case 'c': $this->saveProgress($idx); diff --git a/setup/tools/sqlgen/spawns.ss.php b/setup/tools/sqlgen/spawns.ss.php index 6ebd9135..f139a424 100644 --- a/setup/tools/sqlgen/spawns.ss.php +++ b/setup/tools/sqlgen/spawns.ss.php @@ -312,13 +312,13 @@ CLISetup::registerSetup("sql", new class extends SetupScript $notice = '[points] '.str_pad('['.$point['guid'].']', 9).' manually moved to [A:'.($point['areaId'] ?? 0).' => '.$area.'; F: '.$floor.']'; } - if ($points = Game::worldPosToZonePos($point['map'], $point['posX'], $point['posY'], $area, $floor)) + if ($points = WorldPosition::toZonePos($point['map'], $point['posX'], $point['posY'], $area, $floor)) { // if areaId is set and we match it .. we're fine .. mostly if (count($points) == 1 && $area == $points[0]['areaId']) return ['areaId' => $points[0]['areaId'], 'posX' => $points[0]['posX'], 'posY' => $points[0]['posY'], 'floor' => $points[0]['floor']]; - $point = Game::checkCoords($points); // try to determine best found point by alphamap + $point = WorldPosition::checkZonePos($points); // try to determine best found point by alphamap return ['areaId' => $point['areaId'], 'posX' => $point['posX'], 'posY' => $point['posY'], 'floor' => $point['floor']]; } diff --git a/template/bricks/footer.tpl.php b/template/bricks/footer.tpl.php index 01890bd5..b63bfba3 100644 --- a/template/bricks/footer.tpl.php +++ b/template/bricks/footer.tpl.php @@ -35,7 +35,7 @@ endif; = CLI::LOG_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)): +if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)): ?>