mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
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
This commit is contained in:
7
aowow
7
aowow
@@ -6,7 +6,10 @@ if (PHP_SAPI !== 'cli')
|
|||||||
if (PHP_SAPI === 'cli' && getcwd().DIRECTORY_SEPARATOR.'aowow' != __FILE__)
|
if (PHP_SAPI === 'cli' && getcwd().DIRECTORY_SEPARATOR.'aowow' != __FILE__)
|
||||||
die("this script must be run from the aowow root directory\n");
|
die("this script must be run from the aowow root directory\n");
|
||||||
|
|
||||||
require 'includes/kernel.php';
|
require_once 'includes/kernel.php';
|
||||||
require 'setup/setup.php';
|
require_once 'includes/setup/cli.class.php';
|
||||||
|
require_once 'includes/setup/timer.class.php';
|
||||||
|
|
||||||
|
require_once 'setup/setup.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);
|
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];
|
$updGUIDs = [$guid];
|
||||||
$newPos = array(
|
$newPos = array(
|
||||||
@@ -417,7 +417,7 @@ class AjaxAdmin extends AjaxHandler
|
|||||||
{
|
{
|
||||||
foreach ($swp as $w)
|
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(
|
$p = array(
|
||||||
'posX' => $point[0]['posX'],
|
'posX' => $point[0]['posX'],
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ class SmartAction
|
|||||||
if ($this->smartAI->teleportTargetArea)
|
if ($this->smartAI->teleportTargetArea)
|
||||||
$this->param[10] = $this->smartAI->teleportTargetArea;
|
$this->param[10] = $this->smartAI->teleportTargetArea;
|
||||||
// try calc from SmartTarget data
|
// 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[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);
|
$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);
|
||||||
|
|||||||
39
includes/components/locstring.class.php
Normal file
39
includes/components/locstring.class.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
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]' : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
274
includes/components/report.class.php
Normal file
274
includes/components/report.class.php
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -84,7 +84,7 @@ class DB
|
|||||||
// make number sensible again
|
// make number sensible again
|
||||||
$data['code'] = abs($data['code']);
|
$data['code'] = abs($data['code']);
|
||||||
|
|
||||||
if (Cfg::get('DEBUG') >= CLI::LOG_INFO)
|
if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO)
|
||||||
{
|
{
|
||||||
echo "\nDB ERROR\n";
|
echo "\nDB ERROR\n";
|
||||||
foreach ($data as $k => $v)
|
foreach ($data as $k => $v)
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ define('TDB_WORLD_EXPECTED_VER', 24041);
|
|||||||
// https://www.wowhead.com/wotlk/es/search=vuelo
|
// https://www.wowhead.com/wotlk/es/search=vuelo
|
||||||
define('WOWHEAD_LINK', 'https://www.wowhead.com/wotlk/%s/%s=%s');
|
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_TEXT', 'Content-Type: text/plain; charset=utf-8');
|
||||||
define('MIME_TYPE_XML', 'Content-Type: text/xml; 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');
|
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_BC', 1);
|
||||||
define('EXP_WOTLK', 2);
|
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
|
// SpellFamilyNames
|
||||||
define('SPELLFAMILY_GENERIC', 0);
|
define('SPELLFAMILY_GENERIC', 0);
|
||||||
define('SPELLFAMILY_UNK1', 1); // events, holidays
|
define('SPELLFAMILY_UNK1', 1); // events, holidays
|
||||||
|
|||||||
79
includes/game/chrclass.class.php
Normal file
79
includes/game/chrclass.class.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
132
includes/game/chrrace.class.php
Normal file
132
includes/game/chrrace.class.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -133,11 +133,6 @@ class Game
|
|||||||
'meta', 'red', 'yellow', 'blue'
|
'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)
|
public static function getReputationLevelForPoints($pts)
|
||||||
{
|
{
|
||||||
if ($pts >= 41999)
|
if ($pts >= 41999)
|
||||||
@@ -216,157 +211,6 @@ class Game
|
|||||||
return $pages;
|
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
|
public static function getQuotesForCreature(int $creatureId, bool $asHTML = false, string $talkSource = '') : array
|
||||||
{
|
{
|
||||||
$nQuotes = 0;
|
$nQuotes = 0;
|
||||||
160
includes/game/worldposition.class.php
Normal file
160
includes/game/worldposition.class.php
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
abstract class WorldPosition
|
||||||
|
{
|
||||||
|
private static array $alphaMapCache = [];
|
||||||
|
private static array $capitalCities = array( // capitals take precedence over their surrounding area
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -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/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/database.class.php'; // wrap DBSimple
|
||||||
require_once 'includes/utilities.php'; // helper functions
|
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/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
|
// game client data interfaces
|
||||||
require_once 'includes/stats.class.php'; // Game entity statistics conversion
|
spl_autoload_register(function ($class)
|
||||||
require_once 'includes/game.php'; // game related data & functions
|
{
|
||||||
require_once 'includes/profiler.class.php'; // Profiler feature
|
if ($i = strrpos($class, '\\'))
|
||||||
require_once 'includes/markup.class.php'; // manipulate markup text
|
$class = substr($class, $i + 1);
|
||||||
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';
|
|
||||||
|
|
||||||
// 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)
|
spl_autoload_register(function ($class)
|
||||||
{
|
{
|
||||||
switch ($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)
|
spl_autoload_register(function ($class)
|
||||||
{
|
{
|
||||||
$class = strtolower(str_replace('ListFilter', 'List', $class));
|
|
||||||
|
|
||||||
if (class_exists($class)) // already registered
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ($i = strrpos($class, '\\'))
|
if ($i = strrpos($class, '\\'))
|
||||||
$class = substr($class, $i + 1);
|
$class = substr($class, $i + 1);
|
||||||
|
|
||||||
if (preg_match('/[^\w]/i', $class)) // name should contain only letters
|
if (preg_match('/[^\w]/i', $class))
|
||||||
return;
|
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 (file_exists('includes/types/'.$cl.'.class.php'))
|
||||||
if ($cl == 'remoteprofile' || $cl == 'localprofile')
|
{
|
||||||
$cl = 'profile';
|
require_once 'includes/types/basetype.class.php';
|
||||||
if ($cl == 'remotearenateam' || $cl == 'localarenateam')
|
require_once 'includes/types/'.$cl.'.class.php';
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
else if (stripos($class, 'ajax') === 0)
|
else
|
||||||
{
|
throw new \Exception('could not register type class: '.$cl);
|
||||||
require_once 'includes/ajaxHandler.class.php'; // handles ajax and jsonp requests
|
});
|
||||||
|
|
||||||
|
// 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'))
|
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';
|
require_once 'includes/ajaxHandler/'.strtr($class, ['ajax' => '']).'.class.php';
|
||||||
|
}
|
||||||
else
|
else
|
||||||
throw new \Exception('could not register ajaxHandler class: '.$class);
|
throw new \Exception('could not register ajaxHandler class: '.$class);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (file_exists('pages/'.strtr($class, ['page' => '']).'.php'))
|
else if (stripos($class, 'page')) // handles templated pages
|
||||||
require_once 'pages/'.strtr($class, ['page' => '']).'.php';
|
{
|
||||||
|
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)
|
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)
|
if (strstr($errStr, 'mysqli_connect') && $errNo == E_WARNING)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
$errName = 'unknown error'; // errors not in this list can not be handled by set_error_handler (as per documentation) or are ignored
|
// we do not log deprecation notices
|
||||||
$logLevel = CLI::logLevelFromE($errNo);
|
if ($errNo & (E_DEPRECATED | E_USER_DEPRECATED))
|
||||||
|
return true;
|
||||||
|
|
||||||
switch ($errNo)
|
$logLevel = match($errNo)
|
||||||
{
|
{
|
||||||
case E_WARNING:
|
E_RECOVERABLE_ERROR, E_USER_ERROR => LOG_LEVEL_ERROR,
|
||||||
case E_USER_WARNING:
|
E_WARNING, E_USER_WARNING => LOG_LEVEL_WARN,
|
||||||
$errName = 'WARNING';
|
E_NOTICE, E_USER_NOTICE => LOG_LEVEL_INFO,
|
||||||
break;
|
default => 0
|
||||||
case E_NOTICE:
|
};
|
||||||
case E_USER_NOTICE:
|
$errName = match($errNo)
|
||||||
$errName = 'NOTICE';
|
{
|
||||||
break;
|
E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
|
||||||
case E_USER_ERROR:
|
E_USER_ERROR => 'USER_ERROR',
|
||||||
$errName = 'USER_ERROR';
|
E_USER_WARNING, E_WARNING => 'WARNING',
|
||||||
case E_USER_ERROR:
|
E_USER_NOTICE, E_NOTICE => 'NOTICE',
|
||||||
$errName = 'RECOVERABLE_ERROR';
|
default => 'UNKNOWN_ERROR' // errors not in this list can not be handled by set_error_handler (as per documentation) or are ignored
|
||||||
case E_STRICT: // ignore STRICT and DEPRECATED
|
};
|
||||||
case E_DEPRECATED:
|
|
||||||
case E_USER_DEPRECATED:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DB::isConnected(DB_AOWOW))
|
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()',
|
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");
|
fwrite(STDERR, "\nException - ".$e->getMessage()."\n ".$e->getFile(). '('.$e->getLine().")\n".$e->getTraceAsString()."\n\n");
|
||||||
else
|
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();
|
(new GenericPage())->error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -253,7 +292,7 @@ if (!CLI)
|
|||||||
Lang::load(User::$preferedLoc);
|
Lang::load(User::$preferedLoc);
|
||||||
|
|
||||||
// set up some logging (~10 queries will execute before we init the user and load the config)
|
// 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::Aowow()->setLogger(DB::profiler(...));
|
||||||
DB::World()->setLogger(DB::profiler(...));
|
DB::World()->setLogger(DB::profiler(...));
|
||||||
|
|||||||
@@ -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]' : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
355
includes/setup/cli.class.php
Normal file
355
includes/setup/cli.class.php
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
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 = LOG_LEVEL_ERROR;
|
||||||
|
public const LOG_WARN = LOG_LEVEL_WARN;
|
||||||
|
public const LOG_INFO = LOG_LEVEL_INFO;
|
||||||
|
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), 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 <Enter> (wich also appends a \r)
|
||||||
|
as such <ESC> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
39
includes/setup/timer.class.php
Normal file
39
includes/setup/timer.class.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
228
includes/type.class.php
Normal file
228
includes/type.class.php
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Aowow;
|
||||||
|
|
||||||
|
if (!defined('AOWOW_REVISION'))
|
||||||
|
die('illegal access');
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -612,7 +612,7 @@ trait spawnHelper
|
|||||||
|
|
||||||
if (User::isInGroup(U_GROUP_MODERATOR))
|
if (User::isInGroup(U_GROUP_MODERATOR))
|
||||||
if ($guids = array_column(array_filter($spawns, fn($x) => $x['guid'] > 0 || $x['type'] != Type::NPC), 'guid'))
|
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)
|
foreach ($spawns as $s)
|
||||||
{
|
{
|
||||||
@@ -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 <Enter> (wich also appends a \r)
|
|
||||||
as such <ESC> 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
|
abstract class Util
|
||||||
{
|
{
|
||||||
@@ -573,7 +182,7 @@ abstract class Util
|
|||||||
public static $tcEncoding = '0zMcmVokRsaqbdrfwihuGINALpTjnyxtgevElBCDFHJKOPQSUWXYZ123456789';
|
public static $tcEncoding = '0zMcmVokRsaqbdrfwihuGINALpTjnyxtgevElBCDFHJKOPQSUWXYZ123456789';
|
||||||
private static $notes = [];
|
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];
|
self::$notes[] = [$note, $uGroupMask, $level];
|
||||||
}
|
}
|
||||||
@@ -581,7 +190,7 @@ abstract class Util
|
|||||||
public static function getNotes() : array
|
public static function getNotes() : array
|
||||||
{
|
{
|
||||||
$notes = [];
|
$notes = [];
|
||||||
$severity = CLI::LOG_INFO;
|
$severity = LOG_LEVEL_INFO;
|
||||||
foreach (self::$notes as [$note, $uGroup, $level])
|
foreach (self::$notes as [$note, $uGroup, $level])
|
||||||
{
|
{
|
||||||
if ($uGroup && !User::isInGroup($uGroup))
|
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
|
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)
|
if (!$points || count($points) < 2)
|
||||||
return [];
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -607,9 +607,9 @@ class GenericPage
|
|||||||
{
|
{
|
||||||
array_unshift($notes, 'One or more issues occured, while generating this page.');
|
array_unshift($notes, 'One or more issues occured, while generating this page.');
|
||||||
$colors = array( // [border, text]
|
$colors = array( // [border, text]
|
||||||
CLI::LOG_ERROR => ['C50F1F', 'E51223'],
|
LOG_LEVEL_ERROR => ['C50F1F', 'E51223'],
|
||||||
CLI::LOG_WARN => ['C19C00', 'E5B700'],
|
LOG_LEVEL_WARN => ['C19C00', 'E5B700'],
|
||||||
CLI::LOG_INFO => ['3A96DD', '42ADFF']
|
LOG_LEVEL_INFO => ['3A96DD', '42ADFF']
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->announcements[0] = array(
|
$this->announcements[0] = array(
|
||||||
|
|||||||
@@ -109,8 +109,13 @@ class SpellPage extends GenericPage
|
|||||||
case -13:
|
case -13:
|
||||||
if ($cl = $this->subject->getField('reqClassMask'))
|
if ($cl = $this->subject->getField('reqClassMask'))
|
||||||
$this->path[] = log($cl, 2) + 1;
|
$this->path[] = log($cl, 2) + 1;
|
||||||
else if ($cl = array_search($this->subject->getField('spellFamilyId'), Game::$class2SpellFamily))
|
else if ($sf = $this->subject->getField('spellFamilyId'))
|
||||||
$this->path[] = $cl;
|
foreach (ChrClass::cases() as $cl)
|
||||||
|
if ($cl->spellFamily() == $sf)
|
||||||
|
{
|
||||||
|
$this->path[] = $cl->value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if ($cat == -13)
|
if ($cat == -13)
|
||||||
$this->path[] = ($cf & (SPELL_CU_GLYPH_MAJOR | SPELL_CU_GLYPH_MINOR)) >> 6;
|
$this->path[] = ($cf & (SPELL_CU_GLYPH_MAJOR | SPELL_CU_GLYPH_MINOR)) >> 6;
|
||||||
|
|||||||
@@ -803,7 +803,7 @@ class ZonePage extends GenericPage
|
|||||||
if (!User::isInGroup(U_GROUP_EMPLOYEE))
|
if (!User::isInGroup(U_GROUP_EMPLOYEE))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
$worldPos = Game::getWorldPosForGUID(Type::ZONE, -$this->typeId);
|
$worldPos = WorldPosition::getForGUID(Type::ZONE, -$this->typeId);
|
||||||
if (!$worldPos)
|
if (!$worldPos)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
3
prQueue
3
prQueue
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
namespace Aowow;
|
namespace Aowow;
|
||||||
|
|
||||||
require 'includes/kernel.php';
|
require_once 'includes/kernel.php';
|
||||||
|
require_once 'includes/setup/cli.class.php';
|
||||||
|
|
||||||
/* todo (med):
|
/* todo (med):
|
||||||
* tidy this file
|
* tidy this file
|
||||||
|
|||||||
@@ -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)
|
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();
|
CLI::write();
|
||||||
switch(strtolower($uiCRA['x']))
|
switch (strtolower($uiCRA['x']))
|
||||||
{
|
{
|
||||||
case 'c':
|
case 'c':
|
||||||
$this->saveProgress($idx);
|
$this->saveProgress($idx);
|
||||||
|
|||||||
@@ -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.']';
|
$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 areaId is set and we match it .. we're fine .. mostly
|
||||||
if (count($points) == 1 && $area == $points[0]['areaId'])
|
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']];
|
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']];
|
return ['areaId' => $point['areaId'], 'posX' => $point['posX'], 'posY' => $point['posY'], 'floor' => $point['floor']];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ endif;
|
|||||||
|
|
||||||
<script type="text/javascript">DomContentLoaded.now()</script>
|
<script type="text/javascript">DomContentLoaded.now()</script>
|
||||||
<?php
|
<?php
|
||||||
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)):
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.open("/", "SqlLog", "width=1800,height=200,top=100,left=100,status=no,location=no,toolbar=no,menubar=no").document.write('<?=DB::getProfiles();?>');
|
window.open("/", "SqlLog", "width=1800,height=200,top=100,left=100,status=no,location=no,toolbar=no,menubar=no").document.write('<?=DB::getProfiles();?>');
|
||||||
|
|||||||
Reference in New Issue
Block a user