Template/Endpoints (Prep)

* modernize DB-Types
   - long term: should be split in class that describes the DB-Type and container class that handles multiples
 * make unchanging filter props static, allow lookup of criteria indizes through filter
 * move username/mail/password checks to util and make them usable as input filter
This commit is contained in:
Sarjuuk
2025-08-06 20:47:20 +02:00
parent 8cf0b6243d
commit aeb84327d6
47 changed files with 1163 additions and 1082 deletions

View File

@@ -0,0 +1,390 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AchievementList extends DBTypeList
{
use listviewHelper;
public static int $type = Type::ACHIEVEMENT;
public static string $brickFile = 'achievement';
public static string $dataTable = '?_achievement';
public array $criteria = [];
protected string $queryBase = 'SELECT `a`.*, `a`.`id` AS ARRAY_KEY FROM ?_achievement a';
protected array $queryOpts = array(
'a' => [['ic'], 'o' => 'orderInGroup ASC'],
'ic' => ['j' => ['?_icons ic ON ic.id = a.iconId', true], 's' => ', ic.name AS iconString'],
'ac' => ['j' => ['?_achievementcriteria AS `ac` ON `ac`.`refAchievementId` = `a`.`id`', true], 'g' => '`a`.`id`']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
if ($this->error)
return;
// post processing
$rewards = DB::World()->select(
'SELECT ar.`ID` AS ARRAY_KEY, ar.`TitleA`, ar.`TitleH`, ar.`ItemID`, ar.`Sender` AS "sender", ar.`MailTemplateID`,
ar.`Subject` AS "subject_loc0", IFNULL(arl2.`Subject`, "") AS "subject_loc2", IFNULL(arl3.`Subject`, "") AS "subject_loc3", IFNULL(arl4.`Subject`, "") AS "subject_loc4", IFNULL(arl6.`Subject`, "") AS "subject_loc6", IFNULL(arl8.`Subject`, "") AS "subject_loc8",
ar.`Body` AS "text_loc0", IFNULL(arl2.`Body`, "") AS "text_loc2", IFNULL(arl3.`Body`, "") AS "text_loc3", IFNULL(arl4.`Body`, "") AS "text_loc4", IFNULL(arl6.`Body`, "") AS "text_loc6", IFNULL(arl8.`Body`, "") AS "text_loc8"
FROM achievement_reward ar
LEFT JOIN achievement_reward_locale arl2 ON arl2.`ID` = ar.`ID` AND arl2.`Locale` = "frFR"
LEFT JOIN achievement_reward_locale arl3 ON arl3.`ID` = ar.`ID` AND arl3.`Locale` = "deDE"
LEFT JOIN achievement_reward_locale arl4 ON arl4.`ID` = ar.`ID` AND arl4.`Locale` = "zhCN"
LEFT JOIN achievement_reward_locale arl6 ON arl6.`ID` = ar.`ID` AND arl6.`Locale` = "esES"
LEFT JOIN achievement_reward_locale arl8 ON arl8.`ID` = ar.`ID` AND arl8.`Locale` = "ruRU"
WHERE ar.`ID` IN (?a)',
$this->getFoundIDs()
);
foreach ($this->iterate() as $_id => &$_curTpl)
{
$_curTpl['rewards'] = [];
if (!empty($rewards[$_id]))
{
$_curTpl = array_merge($rewards[$_id], $_curTpl);
$_curTpl['mailTemplate'] = $rewards[$_id]['MailTemplateID'];
if ($rewards[$_id]['MailTemplateID'])
{
// using class Loot creates an inifinite loop cirling between Loot, ItemList and SpellList or something
// $mailSrc = new Loot();
// $mailSrc->getByContainer(LOOT_MAIL, $rewards[$_id]['MailTemplateID']);
// foreach ($mailSrc->iterate() as $loot)
// $_curTpl['rewards'][] = [Type::ITEM, $loot['id']];
// lets just assume for now, that mailRewards for achievements do not contain references
$mailRew = DB::World()->selectCol('SELECT `Item` FROM mail_loot_template WHERE `Reference` <= 0 AND `entry` = ?d', $rewards[$_id]['MailTemplateID']);
foreach ($mailRew AS $mr)
$_curTpl['rewards'][] = [Type::ITEM, $mr];
}
}
//"rewards":[[11,137],[3,138]] [type, typeId]
if (!empty($_curTpl['ItemID']))
$_curTpl['rewards'][] = [Type::ITEM, $_curTpl['ItemID']];
if (!empty($_curTpl['itemExtra']))
$_curTpl['rewards'][] = [Type::ITEM, $_curTpl['itemExtra']];
if (!empty($_curTpl['TitleA']))
$_curTpl['rewards'][] = [Type::TITLE, $_curTpl['TitleA']];
if (!empty($_curTpl['TitleH']))
if (empty($_curTpl['TitleA']) || $_curTpl['TitleA'] != $_curTpl['TitleH'])
$_curTpl['rewards'][] = [Type::TITLE, $_curTpl['TitleH']];
// icon
$_curTpl['iconString'] = $_curTpl['iconString'] ?: 'trade_engineering';
}
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if ($addMask & GLOBALINFO_SELF)
$data[Type::ACHIEVEMENT][$this->id] = ['icon' => $this->curTpl['iconString'], 'name' => $this->getField('name', true)];
if ($addMask & GLOBALINFO_REWARDS)
foreach ($this->curTpl['rewards'] as $_)
$data[$_[0]][$_[1]] = $_[1];
}
return $data;
}
public function getListviewData(int $addInfoMask = 0x0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'name' => $this->getField('name', true),
'description' => $this->getField('description', true),
'points' => $this->curTpl['points'],
'side' => $this->curTpl['faction'],
'category' => $this->curTpl['category'],
'parentcat' => $this->curTpl['parentCat'],
);
if ($addInfoMask & ACHIEVEMENTINFO_PROFILE)
$data[$this->id]['icon'] = $this->curTpl['iconString'];
// going out on a limb here: type = 1 if in level 3 of statistics tree, so, IF (statistic AND parentCat NOT statistic (1)) i guess
if ($this->curTpl['flags'] & ACHIEVEMENT_FLAG_COUNTER && $this->curTpl['parentCat'] != 1)
$data[$this->id]['type'] = 1;
if ($_ = $this->curTpl['rewards'])
$data[$this->id]['rewards'] = $_;
else if ($_ = $this->getField('reward', true))
$data[$this->id]['reward'] = $_;
}
return $data;
}
// only for current template
public function getCriteria() : array
{
if (isset($this->criteria[$this->id]))
return $this->criteria[$this->id];
$result = DB::Aowow()->Select('SELECT * FROM ?_achievementcriteria WHERE `refAchievementId` = ?d ORDER BY `order` ASC', $this->curTpl['refAchievement'] ?: $this->id);
if (!$result)
return [];
$this->criteria[$this->id] = $result;
return $this->criteria[$this->id];
}
public function renderTooltip() : ?string
{
$criteria = $this->getCriteria();
$tmp = [];
$rows = [];
$i = 0;
foreach ($criteria as $_row)
{
if ($i++ % 2)
$tmp[] = $_row;
else
$rows[] = $_row;
}
if ($tmp)
$rows = array_merge($rows, $tmp);
$description = $this->getField('description', true);
$name = $this->getField('name', true);
$criteria = '';
$i = 0;
foreach ($rows as $crt)
{
$obj = (int)$crt['value1'];
$qty = (int)$crt['value2'];
// we could show them, but the tooltips are cluttered
if (($crt['completionFlags'] & ACHIEVEMENT_CRITERIA_FLAG_HIDDEN) && User::isInGroup(U_GROUP_STAFF))
continue;
$crtName = Util::localizedString($crt, 'name');
switch ($crt['type'])
{
// link to quest
case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST:
if (!$crtName)
$crtName = QuestList::getName($obj);
break;
// link to spell (/w icon)
case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET:
case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2:
case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL:
case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL:
case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2:
if (!$crtName)
$crtName = SpellList::getName($obj);
break;
// link to item (/w icon)
case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM:
case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM:
case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM:
case ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM:
if (!$crtName)
$crtName = ItemList::getName($obj);
break;
// link to faction (/w target reputation)
case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION:
if (!$crtName)
$crtName = FactionList::getName($obj);
break;
}
$criteria .= '<!--cr'.$crt['id'].':'.$crt['type'].':'.$crt['value1'].'-->- '.$crtName;
if ($crt['completionFlags'] & ACHIEVEMENT_CRITERIA_FLAG_MONEY_COUNTER)
$criteria .= '&nbsp;<span class="moneygold">'.Lang::nf($crt['value2' ] / 10000).'</span>';
$criteria .= '<br />';
if (++$i == round(count($rows)/2))
$criteria .= '</small></td><th class="q0" style="white-space: nowrap; text-align: left"><small>';
}
$x = '<table><tr><td><b class="q">';
$x .= $name;
$x .= '</b></td></tr></table>';
if ($description || $criteria)
$x .= '<table><tr><td>';
if ($description)
$x .= '<br />'.$description.'<br />';
if ($criteria)
{
$x .= '<br /><span class="q">'.Lang::achievement('criteria').':</span>';
$x .= '<table width="100%"><tr><td class="q0" style="white-space: nowrap"><small>'.$criteria.'</small></th></tr></table>';
}
if ($description || $criteria)
$x .= '</td></tr></table>';
return $x;
}
public function getSourceData(int $id = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if ($id && $id != $this->id)
continue;
$data[$this->id] = array(
"n" => $this->getField('name', true),
"s" => $this->curTpl['faction'],
"t" => Type::ACHIEVEMENT,
"ti" => $this->id
);
}
return $data;
}
}
class AchievementListFilter extends Filter
{
protected string $type = 'achievements';
protected static array $enums = array(
4 => parent::ENUM_ZONE, // location
11 => array(
327 => 160, // Lunar Festival
423 => 187, // Love is in the Air
181 => 159, // Noblegarden
201 => 163, // Children's Week
341 => 161, // Midsummer Fire Festival
372 => 162, // Brewfest
324 => 158, // Hallow's End
404 => 14981, // Pilgrim's Bounty
141 => 156, // Feast of Winter Veil
409 => -3456, // Day of the Dead
398 => -3457, // Pirates' Day
parent::ENUM_ANY => true,
parent::ENUM_NONE => false,
283 => -1, // valid events without achievements
285 => -1, 353 => -1, 420 => -1,
400 => -1, 284 => -1, 374 => -1,
321 => -1, 424 => -1, 301 => -1
)
);
protected static array $genericFilter = array(
2 => [parent::CR_BOOLEAN, 'reward_loc0', true ], // givesreward
3 => [parent::CR_STRING, 'reward', STR_LOCALIZED ], // rewardtext
4 => [parent::CR_NYI_PH, null, 1, ], // location [enum]
5 => [parent::CR_CALLBACK, 'cbSeries', ACHIEVEMENT_CU_FIRST_SERIES, null], // first in series [yn]
6 => [parent::CR_CALLBACK, 'cbSeries', ACHIEVEMENT_CU_LAST_SERIES, null], // last in series [yn]
7 => [parent::CR_BOOLEAN, 'chainId', ], // partseries
9 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
10 => [parent::CR_STRING, 'ic.name', ], // icon
11 => [parent::CR_CALLBACK, 'cbRelEvent', null, null], // related event [enum]
14 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
15 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
16 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
18 => [parent::CR_STAFFFLAG, 'flags', ] // flags
);
protected static array $inputFields = array(
'cr' => [parent::V_RANGE, [2, 18], true ], // criteria ids
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 99999]], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiters
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / description - only printable chars, no delimiter
'ex' => [parent::V_EQUAL, 'on', false], // extended name search
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'si' => [parent::V_LIST, [SIDE_ALLIANCE, SIDE_HORDE, SIDE_BOTH, -SIDE_ALLIANCE, -SIDE_HORDE], false], // side
'minpt' => [parent::V_RANGE, [1, 99], false], // required level min
'maxpt' => [parent::V_RANGE, [1, 99], false] // required level max
);
protected function createSQLForValues() : array
{
$parts = [];
$_v = &$this->values;
// name ex: +description, +rewards
if ($_v['na'])
{
$_ = [];
if ($_v['ex'] == 'on')
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value, 'reward_loc'.Lang::getLocale()->value, 'description_loc'.Lang::getLocale()->value]);
else
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]);
if ($_)
$parts[] = $_;
}
// points min
if ($_v['minpt'])
$parts[] = ['points', $_v['minpt'], '>='];
// points max
if ($_v['maxpt'])
$parts[] = ['points', $_v['maxpt'], '<='];
// faction (side)
if ($_v['si'])
{
$parts[] = match ($_v['si'])
{
-SIDE_ALLIANCE, // equals faction
-SIDE_HORDE => ['faction', -$_v['si']],
SIDE_ALLIANCE, // includes faction
SIDE_HORDE,
SIDE_BOTH => ['faction', $_v['si'], '&']
};
}
return $parts;
}
protected function cbRelEvent(int $cr, int $crs, string $crv) : ?array
{
if (!isset(self::$enums[$cr][$crs]))
return null;
$_ = self::$enums[$cr][$crs];
if (is_int($_))
return ($_ > 0) ? ['category', $_] : ['id', abs($_)];
else
{
$ids = array_filter(self::$enums[$cr], fn($x) => is_int($x) && $x > 0);
return ['category', $ids, $_ ? null : '!'];
}
return null;
}
protected function cbSeries(int $cr, int $crs, string $crv, int $seriesFlag) : ?array
{
if ($this->int2Bool($crs))
return $crs ? ['AND', ['chainId', 0, '!'], ['cuFlags', $seriesFlag, '&']] : ['AND', ['chainId', 0, '!'], [['cuFlags', $seriesFlag, '&'], 0]];
return null;
}
}
?>

View File

@@ -0,0 +1,99 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AreaTriggerList extends DBTypeList
{
use spawnHelper;
public static int $type = Type::AREATRIGGER;
public static string $brickFile = 'areatrigger';
public static string $dataTable = '?_areatrigger';
public static int $contribute = CONTRIBUTE_CO;
protected string $queryBase = 'SELECT a.*, a.id AS ARRAY_KEY FROM ?_areatrigger a';
protected array $queryOpts = array(
'a' => [['s']], // guid < 0 are teleporter targets, so exclude them here
's' => ['j' => ['?_spawns s ON s.`type` = 503 AND s.`typeId` = a.`id` AND s.`guid` > 0', true], 's' => ', GROUP_CONCAT(s.`areaId`) AS "areaId"', 'g' => 'a.`id`']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
foreach ($this->iterate() as $id => &$_curTpl)
if (!$_curTpl['name'])
$_curTpl['name'] = 'Unnamed Areatrigger #' . $id;
}
public static function getName(int $id) : ?LocString
{
if ($n = DB::Aowow()->SelectRow('SELECT IF(`name`, `name`, CONCAT("Unnamed Areatrigger #", `id`) AS "name_loc0" FROM ?# WHERE `id` = ?d', self::$dataTable, $id))
return new LocString($n);
return null;
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->curTpl['id'],
'type' => $this->curTpl['type'],
'name' => $this->curTpl['name'],
);
if ($_ = $this->curTpl['areaId'])
$data[$this->id]['location'] = explode(',', $_);
}
return $data;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array { return []; }
public function renderTooltip() : ?string { return null; }
}
class AreaTriggerListFilter extends Filter
{
protected string $type = 'areatrigger';
protected static array $genericFilter = array(
2 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT] // id
);
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected static array $inputFields = array(
'cr' => [parent::V_LIST, [2], true ], // criteria ids
'crs' => [parent::V_RANGE, [1, 6], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - all criteria are numeric here
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'ty' => [parent::V_RANGE, [0, 5], true ] // types
);
protected function createSQLForValues() : array
{
$parts = [];
$_v = &$this->values;
// name [str]
if ($_v['na'])
if ($_ = $this->tokenizeString(['name']))
$parts[] = $_;
// type [list]
if ($_v['ty'])
$parts[] = ['type', $_v['ty']];
return $parts;
}
}
?>

View File

@@ -0,0 +1,374 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class ArenaTeamList extends DBTypeList
{
use profilerHelper, listviewHelper;
public static int $contribute = CONTRIBUTE_NONE;
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'name' => $this->curTpl['name'],
'realm' => Profiler::urlize($this->curTpl['realmName'], true),
'realmname' => $this->curTpl['realmName'],
// 'battlegroup' => Profiler::urlize($this->curTpl['battlegroup']), // was renamed to subregion somewhere around cata release
// 'battlegroupname' => $this->curTpl['battlegroup'],
'region' => Profiler::urlize($this->curTpl['region']),
'faction' => $this->curTpl['faction'],
'size' => $this->curTpl['type'],
'rank' => $this->curTpl['rank'],
'wins' => $this->curTpl['seasonWins'],
'games' => $this->curTpl['seasonGames'],
'rating' => $this->curTpl['rating'],
'members' => $this->curTpl['members']
);
}
return $data;
}
// plz dont..
public static function getName(int|string $id) : ?LocString { return null; }
public function renderTooltip() : ?string { return null; }
public function getJSGlobals(int $addMask = 0) : array { return []; }
}
class ArenaTeamListFilter extends Filter
{
use TrProfilerFilter;
protected string $type = 'arenateams';
protected static array $genericFilter = [];
protected static array $inputFields = array(
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'ex' => [parent::V_EQUAL, 'on', false], // only match exact
'si' => [parent::V_LIST, [1, 2], false], // side
'sz' => [parent::V_LIST, [2, 3, 5], false], // tema size
'rg' => [parent::V_CALLBACK, 'cbRegionCheck', false], // region
'sv' => [parent::V_CALLBACK, 'cbServerCheck', false], // server
);
public array $extraOpts = [];
protected function createSQLForValues() : array
{
$parts = [];
$_v = $this->values;
// region (rg), battlegroup (bg) and server (sv) are passed to ArenaTeamList as miscData and handled there
// name [str]
if ($_v['na'])
if ($_ = $this->tokenizeString(['at.name'], $_v['na'], $_v['ex'] == 'on'))
$parts[] = $_;
// side [list]
if ($_v['si'] == SIDE_ALLIANCE)
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_ALLIANCE)];
else if ($_v['si'] == SIDE_HORDE)
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_HORDE)];
// size [int]
if ($_v['sz'])
$parts[] = ['at.type', $_v['sz']];
return $parts;
}
}
class RemoteArenaTeamList extends ArenaTeamList
{
protected string $queryBase = 'SELECT `at`.*, `at`.`arenaTeamId` AS ARRAY_KEY FROM arena_team at';
protected array $queryOpts = array(
'at' => [['atm', 'c'], 'g' => 'ARRAY_KEY', 'o' => 'rating DESC'],
'atm' => ['j' => 'arena_team_member atm ON atm.`arenaTeamId` = at.`arenaTeamId`'],
'c' => ['j' => 'characters c ON c.`guid` = atm.`guid` AND c.`deleteInfos_Account` IS NULL AND c.`level` <= 80 AND (c.`extra_flags` & '.Profiler::CHAR_GMFLAGS.') = 0', 's' => ', BIT_OR(IF(c.`race` IN (1, 3, 4, 7, 11), 1, 2)) - 1 AS "faction"']
);
private array $members = [];
private array $rankOrder = [];
public function __construct(array $conditions = [], array $miscData = [])
{
// select DB by realm
if (!$this->selectRealms($miscData))
{
trigger_error('RemoteArenaTeamList::__construct - cannot access any realm.', E_USER_WARNING);
return;
}
parent::__construct($conditions, $miscData);
if ($this->error)
return;
// ranks in DB are inaccurate. recalculate from rating (fetched as DESC from DB)
foreach ($this->dbNames as $rId => $__)
foreach ([2, 3, 5] as $type)
$this->rankOrder[$rId][$type] = DB::Characters($rId)->selectCol('SELECT `arenaTeamId` FROM arena_team WHERE `type` = ?d ORDER BY `rating` DESC', $type);
reset($this->dbNames); // only use when querying single realm
$realms = Profiler::getRealms();
$distrib = [];
// post processing
foreach ($this->iterate() as $guid => &$curTpl)
{
// battlegroup
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
// realm, rank
$r = explode(':', $guid);
if (!empty($realms[$r[0]]))
{
$curTpl['realm'] = $r[0];
$curTpl['realmName'] = $realms[$r[0]]['name'];
$curTpl['region'] = $realms[$r[0]]['region'];
$curTpl['rank'] = array_search($curTpl['arenaTeamId'], $this->rankOrder[$r[0]][$curTpl['type']]) + 1;
}
else
{
trigger_error('arena team #'.$guid.' belongs to nonexistent realm #'.$r, E_USER_WARNING);
unset($this->templates[$guid]);
continue;
}
// empty name
if (!$curTpl['name'])
{
trigger_error('arena team #'.$guid.' on realm #'.$r.' has empty name.', E_USER_WARNING);
unset($this->templates[$guid]);
continue;
}
// team members
$this->members[$r[0]][$r[1]] = $r[1];
// equalize distribution
if (empty($distrib[$curTpl['realm']]))
$distrib[$curTpl['realm']] = 1;
else
$distrib[$curTpl['realm']]++;
}
// get team members
foreach ($this->members as $realmId => &$teams)
$teams = DB::Characters($realmId)->select(
'SELECT at.`arenaTeamId` AS ARRAY_KEY, c.`guid` AS ARRAY_KEY2, c.`name` AS "0", c.`class` AS "1", IF(at.`captainguid` = c.`guid`, 1, 0) AS "2"
FROM arena_team at
JOIN arena_team_member atm ON atm.`arenaTeamId` = at.`arenaTeamId` JOIN characters c ON c.`guid` = atm.`guid`
WHERE at.`arenaTeamId` IN (?a) AND c.`deleteInfos_Account` IS NULL AND c.`level` <= ?d AND (c.`extra_flags` & ?d) = 0',
$teams, MAX_LEVEL, Profiler::CHAR_GMFLAGS
);
// equalize subject distribution across realms
foreach ($conditions as $c)
if (is_int($c))
$limit = $c;
$limit ??= Cfg::get('SQL_LIMIT_DEFAULT');
if (!$limit) // int:0 means unlimited, so skip early
return;
$total = array_sum($distrib);
foreach ($distrib as &$d)
$d = ceil($limit * $d / $total);
foreach ($this->iterate() as $guid => &$curTpl)
{
if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0)
{
unset($this->templates[$guid]);
continue;
}
$r = explode(':', $guid);
if (isset($this->members[$r[0]][$r[1]]))
$curTpl['members'] = array_values($this->members[$r[0]][$r[1]]); // [name, classId, isCaptain]
$distrib[$curTpl['realm']]--;
$limit--;
}
}
public function initializeLocalEntries() : void
{
if (!$this->templates)
return;
$profiles = [];
// init members for tooltips
foreach ($this->members as $realmId => $teams)
{
$gladiators = [];
foreach ($teams as $team)
$gladiators = array_merge($gladiators, array_keys($team));
$profiles[$realmId] = new RemoteProfileList(array(['c.guid', $gladiators], Cfg::get('SQL_LIMIT_NONE')), ['sv' => $realmId]);
if (!$profiles[$realmId]->error)
$profiles[$realmId]->initializeLocalEntries();
}
$data = [];
foreach ($this->iterate() as $guid => $__)
{
$data[$guid] = array(
'realm' => $this->getField('realm'),
'realmGUID' => $this->getField('arenaTeamId'),
'name' => $this->getField('name'),
'nameUrl' => Profiler::urlize($this->getField('name')),
'type' => $this->getField('type'),
'rating' => $this->getField('rating'),
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
);
}
// basic arena team data
foreach (Util::createSqlBatchInsert($data) as $ins)
DB::Aowow()->query('INSERT INTO ?_profiler_arena_team (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `id` = `id`', array_keys(reset($data)));
// merge back local ids
$localIds = DB::Aowow()->selectCol(
'SELECT CONCAT(`realm`, ":", `realmGUID`) AS ARRAY_KEY, `id` FROM ?_profiler_arena_team WHERE `realm` IN (?a) AND `realmGUID` IN (?a)',
array_column($data, 'realm'),
array_column($data, 'realmGUID')
);
foreach ($this->iterate() as $guid => &$_curTpl)
if (isset($localIds[$guid]))
$_curTpl['id'] = $localIds[$guid];
// profiler_arena_team_member requires profiles and arena teams to be filled
foreach ($this->members as $realmId => $teams)
{
if (empty($profiles[$realmId]))
continue;
$memberData = [];
foreach ($teams as $teamId => $team)
{
$clearMembers = [];
foreach ($team as $memberId => $member)
{
$clearMembers[] = $profiles[$realmId]->getEntry($realmId.':'.$memberId)['id'];
$memberData[] = array(
'arenaTeamId' => $localIds[$realmId.':'.$teamId],
'profileId' => $profiles[$realmId]->getEntry($realmId.':'.$memberId)['id'],
'captain' => $member[2]
);
}
// Delete members from other teams of the same type
DB::Aowow()->query(
'DELETE atm
FROM ?_profiler_arena_team_member atm
JOIN ?_profiler_arena_team at ON atm.`arenaTeamId` = at.`id` AND at.`type` = ?d
WHERE atm.`profileId` IN (?a)',
$data[$realmId.':'.$teamId]['type'] ?? 0,
$clearMembers
);
}
foreach (Util::createSqlBatchInsert($memberData) as $ins)
DB::Aowow()->query('INSERT INTO ?_profiler_arena_team_member (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `profileId` = `profileId`', array_keys(reset($memberData)));
}
}
}
class LocalArenaTeamList extends ArenaTeamList
{
protected string $queryBase = 'SELECT at.*, at.id AS ARRAY_KEY FROM ?_profiler_arena_team at';
protected array $queryOpts = array(
'at' => [['atm', 'c'], 'g' => 'ARRAY_KEY', 'o' => 'rating DESC'],
'atm' => ['j' => '?_profiler_arena_team_member atm ON atm.`arenaTeamId` = at.`id`'],
'c' => ['j' => '?_profiler_profiles c ON c.`id` = atm.`profileId`', 's' => ', BIT_OR(IF(c.`race` IN (1, 3, 4, 7, 11), 1, 2)) - 1 AS "faction"']
);
public function __construct(array $conditions = [], array $miscData = [])
{
$realms = Profiler::getRealms();
// graft realm selection from miscData onto conditions
if (isset($miscData['sv']))
$realms = array_filter($realms, fn($x) => Profiler::urlize($x['name']) == Profiler::urlize($miscData['sv']));
if (isset($miscData['rg']))
$realms = array_filter($realms, fn($x) => $x['region'] == $miscData['rg']);
if (!$realms)
{
trigger_error('LocalArenaTeamList::__construct - cannot access any realm.', E_USER_WARNING);
return;
}
if ($conditions)
{
array_unshift($conditions, 'AND');
$conditions = ['AND', ['realm', array_keys($realms)], $conditions];
}
else
$conditions = [['realm', array_keys($realms)]];
parent::__construct($conditions, $miscData);
if ($this->error)
return;
// post processing
$members = DB::Aowow()->select(
'SELECT `arenaTeamId` AS ARRAY_KEY, p.`id` AS ARRAY_KEY2, p.`name` AS "0", p.`class` AS "1", atm.`captain` AS "2"
FROM ?_profiler_arena_team_member atm
JOIN ?_profiler_profiles p ON p.`id` = atm.`profileId`
WHERE `arenaTeamId` IN (?a)',
$this->getFoundIDs()
);
foreach ($this->iterate() as $id => &$curTpl)
{
if ($curTpl['realm'] && !isset($realms[$curTpl['realm']]))
continue;
if (isset($realms[$curTpl['realm']]))
{
$curTpl['realmName'] = $realms[$curTpl['realm']]['name'];
$curTpl['region'] = $realms[$curTpl['realm']]['region'];
}
// battlegroup
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
$curTpl['members'] = array_values($members[$id]);
}
}
public function getProfileUrl() : string
{
$url = '?arena-team=';
return $url.implode('.', array(
$this->getField('region'),
Profiler::urlize($this->getField('realmName'), true),
Profiler::urlize($this->getField('name'))
));
}
}
?>

View File

@@ -0,0 +1,64 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class CharClassList extends DBTypeList
{
public static int $type = Type::CHR_CLASS;
public static string $brickFile = 'class';
public static string $dataTable = '?_classes';
protected string $queryBase = 'SELECT c.*, id AS ARRAY_KEY FROM ?_classes c';
public function __construct($conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
foreach ($this->iterate() as $k => &$_curTpl)
$_curTpl['skills'] = explode(' ', $_curTpl['skills']);
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'name' => $this->getField('name', true),
'races' => $this->curTpl['raceMask'],
'roles' => $this->curTpl['roles'],
'weapon' => $this->curTpl['weaponTypeMask'],
'armor' => $this->curTpl['armorTypeMask'],
'power' => $this->curTpl['powerType'],
);
if ($this->curTpl['flags'] & 0x40)
$data[$this->id]['hero'] = 1;
if ($this->curTpl['expansion'])
$data[$this->id]['expansion'] = $this->curTpl['expansion'];
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[self::$type][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,53 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class CharRaceList extends DBTypeList
{
public static int $type = Type::CHR_RACE;
public static string $brickFile = 'race';
public static string $dataTable = '?_races';
protected string $queryBase = 'SELECT r.*, id AS ARRAY_KEY FROM ?_races r';
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'name' => $this->getField('name', true),
'classes' => $this->curTpl['classMask'],
'faction' => $this->curTpl['factionId'],
'leader' => $this->curTpl['leader'],
'zone' => $this->curTpl['startAreaId'],
'side' => $this->curTpl['side']
);
if ($this->curTpl['expansion'])
$data[$this->id]['expansion'] = $this->curTpl['expansion'];
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::CHR_RACE][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,538 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class CreatureList extends DBTypeList
{
use spawnHelper;
public static int $type = Type::NPC;
public static string $brickFile = 'npc';
public static string $dataTable = '?_creature';
protected string $queryBase = 'SELECT ct.*, ct.`id` AS ARRAY_KEY FROM ?_creature ct';
public array $queryOpts = array(
'ct' => [['ft', 'qse', 'dct1', 'dct2', 'dct3'], 's' => ', IFNULL(dct1.`id`, IFNULL(dct2.`id`, IFNULL(dct3.`id`, 0))) AS "parentId", IFNULL(dct1.`name_loc0`, IFNULL(dct2.`name_loc0`, IFNULL(dct3.`name_loc0`, ""))) AS "parent_loc0", IFNULL(dct1.`name_loc2`, IFNULL(dct2.`name_loc2`, IFNULL(dct3.`name_loc2`, ""))) AS "parent_loc2", IFNULL(dct1.`name_loc3`, IFNULL(dct2.`name_loc3`, IFNULL(dct3.`name_loc3`, ""))) AS "parent_loc3", IFNULL(dct1.`name_loc4`, IFNULL(dct2.`name_loc4`, IFNULL(dct3.`name_loc4`, ""))) AS "`parent_loc4`", IFNULL(dct1.`name_loc6`, IFNULL(dct2.`name_loc6`, IFNULL(dct3.`name_loc6`, ""))) AS "`parent_loc6`", IFNULL(dct1.name_loc8, IFNULL(dct2.`name_loc8`, IFNULL(dct3.`name_loc8`, ""))) AS "parent_loc8", IF(dct1.`difficultyEntry1` = ct.`id`, 1, IF(dct2.`difficultyEntry2` = ct.`id`, 2, IF(dct3.`difficultyEntry3` = ct.`id`, 3, 0))) AS "difficultyMode"'],
'dct1' => ['j' => ['?_creature dct1 ON ct.`cuFlags` & 0x02 AND dct1.`difficultyEntry1` = ct.`id`', true]],
'dct2' => ['j' => ['?_creature dct2 ON ct.`cuFlags` & 0x02 AND dct2.`difficultyEntry2` = ct.`id`', true]],
'dct3' => ['j' => ['?_creature dct3 ON ct.`cuFlags` & 0x02 AND dct3.`difficultyEntry3` = ct.`id`', true]],
'ft' => ['j' => '?_factiontemplate ft ON ft.`id` = ct.`faction`', 's' => ', ft.`factionId`, IFNULL(ft.`A`, 0) AS "A", IFNULL(ft.`H`, 0) AS "H"'],
'qse' => ['j' => ['?_quests_startend qse ON qse.`type` = 1 AND qse.`typeId` = ct.id', true], 's' => ', IF(MIN(qse.`method`) = 1 OR MAX(qse.`method`) = 3, 1, 0) AS "startsQuests", IF(MIN(qse.`method`) = 2 OR MAX(qse.`method`) = 3, 1, 0) AS "endsQuests"', 'g' => 'ct.`id`'],
'qt' => ['j' => '?_quests qt ON qse.`questId` = qt.`id`'],
's' => ['j' => ['?_spawns s ON s.`type` = 1 AND s.`typeId` = ct.`id`', true]]
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
if ($this->error)
return;
// post processing
foreach ($this->iterate() as $_id => &$curTpl)
{
// check for attackspeeds
if (!$curTpl['atkSpeed'])
$curTpl['atkSpeed'] = 2.0;
else
$curTpl['atkSpeed'] /= 1000;
if (!$curTpl['rngAtkSpeed'])
$curTpl['rngAtkSpeed'] = 2.0;
else
$curTpl['rngAtkSpeed'] /= 1000;
}
}
public function renderTooltip() : ?string
{
if (!$this->curTpl)
return null;
$level = '??';
$type = $this->curTpl['type'];
$row3 = [Lang::game('level')];
$fam = $this->curTpl['family'];
if (!($this->curTpl['typeFlags'] & 0x4))
{
$level = $this->curTpl['minLevel'];
if ($level != $this->curTpl['maxLevel'])
$level .= ' - '.$this->curTpl['maxLevel'];
}
else
$level = '??';
$row3[] = $level;
if ($type)
$row3[] = Lang::game('ct', $type);
if ($_ = Lang::npc('rank', $this->curTpl['rank']))
$row3[] = '('.$_.')';
$x = '<table>';
$x .= '<tr><td><b class="q">'.Util::htmlEscape($this->getField('name', true)).'</b></td></tr>';
if ($sn = $this->getField('subname', true))
$x .= '<tr><td>'.Util::htmlEscape($sn).'</td></tr>';
$x .= '<tr><td>'.implode(' ', $row3).'</td></tr>';
if ($type == 1 && $fam) // 1: Beast
$x .= '<tr><td>'.Lang::game('fa', $fam).'</td></tr>';
$fac = new FactionList(array([['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], ['id', (int)$this->getField('factionId')]));
if (!$fac->error)
$x .= '<tr><td>'.$fac->getField('name', true).'</td></tr>';
$x .= '</table>';
return $x;
}
public function getRandomModelId() : int
{
// dwarf?? [null, 30754, 30753, 30755, 30736]
// totems use hardcoded models, tauren model is base
$totems = [null, 4589, 4588, 4587, 4590]; // slot => modelId
$data = [];
for ($i = 1; $i < 5; $i++)
if ($_ = $this->curTpl['displayId'.$i])
$data[] = $_;
if (count($data) == 1 && ($slotId = array_search($data[0], $totems)))
$data = DB::World()->selectCol('SELECT `DisplayId` FROM player_totem_model WHERE `TotemSlot` = ?d', $slotId);
return !$data ? 0 : $data[array_rand($data)];
}
public function getBaseStats(string $type) : array
{
// i'm aware of the BaseVariance/RangedVariance fields ... i'm just totaly unsure about the whole damage calculation
switch ($type)
{
case 'health':
$hMin = $this->getField('healthMin');
$hMax = $this->getField('healthMax');
return [$hMin, $hMax];
case 'power':
$mMin = $this->getField('manaMin');
$mMax = $this->getField('manaMax');
return [$mMin, $mMax];
case 'armor':
$aMin = $this->getField('armorMin');
$aMax = $this->getField('armorMax');
return [$aMin, $aMax];
case 'melee':
$mleMin = ($this->getField('dmgMin') + ($this->getField('mleAtkPwrMin') / 14)) * $this->getField('dmgMultiplier') * $this->getField('atkSpeed');
$mleMax = ($this->getField('dmgMax') * 1.5 + ($this->getField('mleAtkPwrMax') / 14)) * $this->getField('dmgMultiplier') * $this->getField('atkSpeed');
return [$mleMin, $mleMax];
case 'ranged':
$rngMin = ($this->getField('dmgMin') + ($this->getField('rngAtkPwrMin') / 14)) * $this->getField('dmgMultiplier') * $this->getField('rngAtkSpeed');
$rngMax = ($this->getField('dmgMax') * 1.5 + ($this->getField('rngAtkPwrMax') / 14)) * $this->getField('dmgMultiplier') * $this->getField('rngAtkSpeed');
return [$rngMin, $rngMax];
case 'resistance':
$r = [];
for ($i = SPELL_SCHOOL_HOLY; $i < SPELL_SCHOOL_ARCANE+1; $i++)
$r[$i] = $this->getField('resistance'.$i);
return $r;
default:
return [];
}
}
public function isBoss() : bool
{
return ($this->curTpl['cuFlags'] & NPC_CU_INSTANCE_BOSS) || ($this->curTpl['typeFlags'] & NPC_TYPEFLAG_BOSS_MOB && $this->curTpl['rank']);
}
public function getListviewData(int $addInfoMask = 0x0) : array
{
/* looks like this data differs per occasion
*
* NPCINFO_TAMEABLE (0x1): include texture & react
* NPCINFO_MODEL (0x2):
* NPCINFO_REP (0x4): include repreward
*/
$data = [];
$rewRep = [];
if ($addInfoMask & NPCINFO_REP && $this->getFoundIDs())
{
$rewRep = DB::World()->selectCol(
'SELECT `creature_id` AS ARRAY_KEY, `RewOnKillRepFaction1` AS ARRAY_KEY2, `RewOnKillRepValue1` FROM creature_onkill_reputation WHERE `creature_id` IN (?a) AND `RewOnKillRepFaction1` > 0 UNION
SELECT `creature_id` AS ARRAY_KEY, `RewOnKillRepFaction2` AS ARRAY_KEY2, `RewOnKillRepValue2` FROM creature_onkill_reputation WHERE `creature_id` IN (?a) AND `RewOnKillRepFaction2` > 0',
$this->getFoundIDs(),
$this->getFoundIDs()
);
}
foreach ($this->iterate() as $__)
{
if ($addInfoMask & NPCINFO_MODEL)
{
$texStr = strtolower($this->curTpl['textureString']);
if (isset($data[$texStr]))
{
if ($data[$texStr]['minLevel'] > $this->curTpl['minLevel'])
$data[$texStr]['minLevel'] = $this->curTpl['minLevel'];
if ($data[$texStr]['maxLevel'] < $this->curTpl['maxLevel'])
$data[$texStr]['maxLevel'] = $this->curTpl['maxLevel'];
$data[$texStr]['count']++;
}
else
$data[$texStr] = array(
'family' => $this->curTpl['family'],
'minLevel' => $this->curTpl['minLevel'],
'maxLevel' => $this->curTpl['maxLevel'],
'modelId' => $this->curTpl['modelId'],
'displayId' => $this->curTpl['displayId1'],
'skin' => $texStr,
'count' => 1
);
}
else
{
$data[$this->id] = array(
'family' => $this->curTpl['family'],
'minlevel' => $this->curTpl['minLevel'],
'maxlevel' => $this->curTpl['maxLevel'],
'id' => $this->id,
'boss' => $this->isBoss() ? 1 : 0,
'classification' => $this->curTpl['rank'],
'location' => $this->getSpawns(SPAWNINFO_ZONES),
'name' => $this->getField('name', true),
'type' => $this->curTpl['type'],
'react' => [$this->curTpl['A'], $this->curTpl['H']],
);
if ($this->getField('startsQuests'))
$data[$this->id]['hasQuests'] = 1;
if ($_ = $this->getField('subname', true))
$data[$this->id]['tag'] = $_;
if ($addInfoMask & NPCINFO_TAMEABLE) // only first skin of first model ... we're omitting potentially 11 skins here .. but the lv accepts only one .. w/e
$data[$this->id]['skin'] = $this->curTpl['textureString'];
if ($addInfoMask & NPCINFO_REP)
{
$data[$this->id]['reprewards'] = [];
if ($rewRep[$this->id])
foreach ($rewRep[$this->id] as $fac => $val)
$data[$this->id]['reprewards'][] = [$fac, $val];
}
}
}
ksort($data);
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::NPC][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}
public function getSourceData(int $id = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if ($id && $id != $this->id)
continue;
$data[$this->id] = array(
'n' => $this->getField('parentId') ? $this->getField('parent', true) : $this->getField('name', true),
't' => Type::NPC,
'ti' => $this->getField('parentId') ?: $this->id
);
}
return $data;
}
}
class CreatureListFilter extends Filter
{
protected string $type = 'npcs';
protected static array $enums = array(
3 => parent::ENUM_FACTION, // faction
6 => parent::ENUM_ZONE, // foundin
42 => parent::ENUM_FACTION, // increasesrepwith
43 => parent::ENUM_FACTION, // decreasesrepwith
38 => parent::ENUM_EVENT // relatedevent
);
protected static array $genericFilter = array(
1 => [parent::CR_CALLBACK, 'cbHealthMana', 'healthMax', 'healthMin'], // health [num]
2 => [parent::CR_CALLBACK, 'cbHealthMana', 'manaMin', 'manaMax' ], // mana [num]
3 => [parent::CR_CALLBACK, 'cbFaction', null, null ], // faction [enum]
5 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_REPAIRER ], // canrepair
6 => [parent::CR_ENUM, 's.areaId', false, true ], // foundin
7 => [parent::CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [enum]
8 => [parent::CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [enum]
9 => [parent::CR_BOOLEAN, 'lootId', ], // lootable
10 => [parent::CR_CALLBACK, 'cbRegularSkinLoot', NPC_TYPEFLAG_SPECIALLOOT ], // skinnable [yn]
11 => [parent::CR_BOOLEAN, 'pickpocketLootId', ], // pickpocketable
12 => [parent::CR_CALLBACK, 'cbMoneyDrop', null, null ], // averagemoneydropped [op] [int]
15 => [parent::CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_SKIN_WITH_HERBALISM, null ], // gatherable [yn]
16 => [parent::CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_SKIN_WITH_MINING, null ], // minable [yn]
18 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_AUCTIONEER ], // auctioneer
19 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_BANKER ], // banker
20 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_BATTLEMASTER ], // battlemaster
21 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_FLIGHT_MASTER ], // flightmaster
22 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // guildmaster
23 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_INNKEEPER ], // innkeeper
24 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_CLASS_TRAINER ], // talentunlearner
25 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // tabardvendor
27 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_STABLE_MASTER ], // stablemaster
28 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_TRAINER ], // trainer
29 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_VENDOR ], // vendor
31 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
32 => [parent::CR_FLAG, 'cuFlags', NPC_CU_INSTANCE_BOSS ], // instanceboss
33 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
34 => [parent::CR_STRING, 'modelId', STR_MATCH_EXACT | STR_ALLOW_SHORT ], // usemodel [str] (wants int in string fmt <_<)
35 => [parent::CR_STRING, 'textureString' ], // useskin [str]
37 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true ], // id
38 => [parent::CR_CALLBACK, 'cbRelEvent', null, null ], // relatedevent [enum]
40 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
41 => [parent::CR_NYI_PH, 1, null ], // haslocation [yn] [staff]
42 => [parent::CR_CALLBACK, 'cbReputation', '>', null ], // increasesrepwith [enum]
43 => [parent::CR_CALLBACK, 'cbReputation', '<', null ], // decreasesrepwith [enum]
44 => [parent::CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_SKIN_WITH_ENGINEERING, null ] // salvageable [yn]
);
protected static array $inputFields = array(
'cr' => [parent::V_LIST, [[1, 3],[5, 12], 15, 16, [18, 25], [27, 29], [31, 35], 37, 38, [40, 44]], true ], // criteria ids
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 9999]], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiter
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / subname - only printable chars, no delimiter
'ex' => [parent::V_EQUAL, 'on', false], // also match subname
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'fa' => [parent::V_CALLBACK, 'cbPetFamily', true ], // pet family [list] - cat[0] == 1
'minle' => [parent::V_RANGE, [1, 99], false], // min level [int]
'maxle' => [parent::V_RANGE, [1, 99], false], // max level [int]
'cl' => [parent::V_RANGE, [0, 4], true ], // classification [list]
'ra' => [parent::V_LIST, [-1, 0, 1], false], // react alliance [int]
'rh' => [parent::V_LIST, [-1, 0, 1], false] // react horde [int]
);
public array $extraOpts = [];
protected function createSQLForValues() : array
{
$parts = [];
$_v = &$this->values;
// name [str]
if ($_v['na'])
{
$_ = [];
if ($_v['ex'] == 'on')
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value, 'subname_loc'.Lang::getLocale()->value]);
else
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]);
if ($_)
$parts[] = $_;
}
// pet family [list]
if ($_v['fa'])
$parts[] = ['family', $_v['fa']];
// creatureLevel min [int]
if ($_v['minle'])
$parts[] = ['minLevel', $_v['minle'], '>='];
// creatureLevel max [int]
if ($_v['maxle'])
$parts[] = ['maxLevel', $_v['maxle'], '<='];
// classification [list]
if ($_v['cl'])
$parts[] = ['rank', $_v['cl']];
// react Alliance [int]
if ($_v['ra'])
$parts[] = ['ft.A', $_v['ra']];
// react Horde [int]
if ($_v['rh'])
$parts[] = ['ft.H', $_v['rh']];
return $parts;
}
protected function cbPetFamily(string &$val) : bool
{
if (!$this->parentCats || $this->parentCats[0] != 1)
return false;
if (!Util::checkNumeric($val, NUM_CAST_INT))
return false;
$type = parent::V_LIST;
$valid = [[1, 9], 11, 12, 20, 21, [24, 27], [30, 35], [37, 39], [41, 46]];
return $this->checkInput($type, $valid, $val);
}
protected function cbRelEvent(int $cr, int $crs, string $crv) : ?array
{
if ($crs == parent::ENUM_ANY)
{
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
if ($cGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_creature WHERE `eventEntry` IN (?a)', $eventIds))
return ['s.guid', $cGuids];
return [0];
}
else if ($crs == parent::ENUM_NONE)
{
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
if ($cGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_creature WHERE `eventEntry` IN (?a)', $eventIds))
return ['s.guid', $cGuids, '!'];
return [0];
}
else if (in_array($crs, self::$enums[$cr]))
{
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` = ?d', $crs))
if ($cGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM `game_event_creature` WHERE `eventEntry` IN (?a)', $eventIds))
return ['s.guid', $cGuids];
return [0];
}
return null;
}
protected function cbMoneyDrop(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
return ['AND', ['((minGold + maxGold) / 2)', $crv, $crs]];
}
protected function cbQuestRelation(int $cr, int $crs, string $crv, $field, $val) : ?array
{
switch ($crs)
{
case 1: // any
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!']];
case 2: // alliance
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_HORDE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&']];
case 3: // horde
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']];
case 4: // both
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], ['OR', ['AND', ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']], ['qt.reqRaceMask', 0]]];
case 5: // none
$this->extraOpts['ct']['h'][] = $field.' = 0';
return [1];
}
return null;
}
protected function cbHealthMana(int $cr, int $crs, string $crv, $minField, $maxField) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
// remap OP for this special case
switch ($crs)
{
case '=': // min > max is totally possible
$this->extraOpts['ct']['h'][] = $minField.' = '.$maxField.' AND '.$minField.' = '.$crv;
break;
case '>':
case '>=':
case '<':
case '<=':
$this->extraOpts['ct']['h'][] = 'IF('.$minField.' > '.$maxField.', '.$maxField.', '.$minField.') '.$crs.' '.$crv;
break;
}
return [1]; // always true, use post-filter
}
protected function cbSpecialSkinLoot(int $cr, int $crs, string $crv, $typeFlag) : ?array
{
if (!$this->int2Bool($crs))
return null;
if ($crs)
return ['AND', ['skinLootId', 0, '>'], ['typeFlags', $typeFlag, '&']];
else
return ['OR', ['skinLootId', 0], [['typeFlags', $typeFlag, '&'], 0]];
}
protected function cbRegularSkinLoot(int $cr, int $crs, string $crv, $typeFlag) : ?array
{
if (!$this->int2Bool($crs))
return null;
if ($crs)
return ['AND', ['skinLootId', 0, '>'], [['typeFlags', $typeFlag, '&'], 0]];
else
return ['OR', ['skinLootId', 0], ['typeFlags', $typeFlag, '&']];
}
protected function cbReputation(int $cr, int $crs, string $crv, $op) : ?array
{
if (!in_array($crs, self::$enums[$cr]))
return null;
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_factions WHERE `id` = ?d', $crs))
$this->fiReputationCols[] = [$crs, Util::localizedString($_, 'name')];
if ($cIds = DB::World()->selectCol('SELECT `creature_id` FROM creature_onkill_reputation WHERE (`RewOnKillRepFaction1` = ?d AND `RewOnKillRepValue1` '.$op.' 0) OR (`RewOnKillRepFaction2` = ?d AND `RewOnKillRepValue2` '.$op.' 0)', $crs, $crs))
return ['id', $cIds];
else
return [0];
}
protected function cbFaction(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crs, NUM_CAST_INT))
return null;
if (!in_array($crs, self::$enums[$cr]))
return null;
$facTpls = [];
$facs = new FactionList(array('OR', ['parentFactionId', $crs], ['id', $crs]));
foreach ($facs->iterate() as $__)
$facTpls = array_merge($facTpls, $facs->getField('templateIds'));
return $facTpls ? ['faction', $facTpls] : [0];
}
}
?>

View File

@@ -0,0 +1,88 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class CurrencyList extends DBTypeList
{
public static int $type = Type::CURRENCY;
public static string $brickFile = 'currency';
public static string $dataTable = '?_currencies';
protected string $queryBase = 'SELECT c.*, c.`id` AS ARRAY_KEY FROM ?_currencies c';
protected array $queryOpts = array(
'c' => [['ic']],
'ic' => ['j' => ['?_icons ic ON ic.`id` = c.`iconId`', true], 's' => ', ic.`name` AS "iconString"']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
foreach ($this->iterate() as &$_curTpl)
$_curTpl['iconString'] = $_curTpl['iconString'] ?: DEFAULT_ICON;
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'category' => $this->curTpl['category'],
'name' => $this->getField('name', true),
'icon' => $this->curTpl['iconString']
);
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
// todo (low): un-hardcode icon strings
$icon = match ($this->id)
{
CURRENCY_HONOR_POINTS => ['pvp-currency-alliance', 'pvp-currency-horde' ],
CURRENCY_ARENA_POINTS => ['pvp-arenapoints-icon', 'pvp-arenapoints-icon' ],
default => [$this->curTpl['iconString'], $this->curTpl['iconString']]
};
$data[Type::CURRENCY][$this->id] = ['name' => $this->getField('name', true), 'icon' => $icon];
}
return $data;
}
public function renderTooltip() : ?string
{
if (!$this->curTpl)
return null;
$x = '<table><tr><td>';
$x .= '<b>'.$this->getField('name', true).'</b><br />';
// cata+ (or go fill it by hand)
if ($_ = $this->getField('description', true))
$x .= '<div style="max-width: 300px" class="q">'.$_.'</div>';
if ($_ = $this->getField('cap'))
$x .= '<br /><span class="q">'.Lang::currency('cap').Lang::main('colon').'</span>'.Lang::nf($_).'<br />';
$x .= '</td></tr></table>';
return $x;
}
}
?>

View File

@@ -0,0 +1,65 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class EmoteList extends DBTypeList
{
public static int $type = Type::EMOTE;
public static string $brickFile = 'emote';
public static string $dataTable = '?_emotes';
protected string $queryBase = 'SELECT e.*, e.`id` AS ARRAY_KEY FROM ?_emotes e';
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
// post processing
foreach ($this->iterate() as &$curTpl)
{
// remap for generic access
$curTpl['name'] = $curTpl['cmd'];
}
}
public static function getName(int $id) : ?LocString
{
if ($n = DB::Aowow()->SelectRow('SELECT `cmd` AS "name_loc0" FROM ?# WHERE `id` = ?d', self::$dataTable, $id))
return new LocString($n);
return null;
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->curTpl['id'],
'name' => $this->curTpl['cmd'],
'preview' => Util::parseHtmlText($this->getField('meToExt', true) ?: $this->getField('meToNone', true) ?: $this->getField('extToMe', true) ?: $this->getField('extToExt', true) ?: $this->getField('extToNone', true), true)
);
}
return $data;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::EMOTE][$this->id] = ['name' => $this->getField('cmd')];
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,257 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class EnchantmentList extends DBTypeList
{
use listviewHelper;
public static $type = Type::ENCHANTMENT;
public static $brickFile = 'enchantment';
public static $dataTable = '?_itemenchantment';
private array $jsonStats = [];
private ?SpellList $relSpells = null;
private array $triggerIds = [];
protected $queryBase = 'SELECT ie.*, ie.id AS ARRAY_KEY FROM ?_itemenchantment ie';
protected $queryOpts = array( // 502 => Type::ENCHANTMENT
'ie' => [['is']],
'is' => ['j' => ['?_item_stats `is` ON `is`.`type` = 502 AND `is`.`typeId` = `ie`.`id`', true], 's' => ', `is`.*'],
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
$relSpells = [];
// post processing
foreach ($this->iterate() as &$curTpl)
{
$curTpl['spells'] = []; // [spellId, triggerType, charges, chanceOrPpm]
for ($i = 1; $i <=3; $i++)
{
if ($curTpl['object'.$i] <= 0)
continue;
switch ($curTpl['type'.$i]) // SPELL_TRIGGER_* just reused for wording
{
case ENCHANTMENT_TYPE_COMBAT_SPELL:
$proc = -$this->getField('ppmRate') ?: ($this->getField('procChance') ?: $this->getField('amount'.$i));
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_HIT, $curTpl['charges'], $proc];
$relSpells[] = $curTpl['object'.$i];
break;
case ENCHANTMENT_TYPE_EQUIP_SPELL:
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_EQUIP, $curTpl['charges'], 0];
$relSpells[] = $curTpl['object'.$i];
break;
case ENCHANTMENT_TYPE_USE_SPELL:
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_USE, $curTpl['charges'], 0];
$relSpells[] = $curTpl['object'.$i];
break;
}
}
// issue with scaling stats enchantments
// stats are stored as NOT NULL to be usable by the search filters and such become indistinguishable from scaling enchantments that _actually_ use the value 0
// so filter the stats container and if it is empty, rebuild from self. .. there are no mixed scaling/static enchantments, right!?
$this->jsonStats[$this->id] = (new StatsContainer())->fromJson($curTpl, true)->filter();
if (!count($this->jsonStats[$this->id]))
$this->jsonStats[$this->id]->fromEnchantment($curTpl);
}
if ($relSpells)
$this->relSpells = new SpellList(array(['id', $relSpells]));
}
public function getListviewData(int $addInfoMask = 0x0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'name' => $this->getField('name', true),
'spells' => []
);
if ($this->curTpl['skillLine'] > 0)
$data[$this->id]['reqskill'] = $this->curTpl['skillLine'];
if ($this->curTpl['skillLevel'] > 0)
$data[$this->id]['reqskillrank'] = $this->curTpl['skillLevel'];
if ($this->curTpl['requiredLevel'] > 0)
$data[$this->id]['reqlevel'] = $this->curTpl['requiredLevel'];
foreach ($this->curTpl['spells'] as [$spellId, $trigger, $charges, $procChance])
{
// spell is procing
$trgSpell = 0;
if ($this->relSpells && $this->relSpells->getEntry($spellId) && ($_ = $this->relSpells->canTriggerSpell()))
{
foreach ($_ as $idx)
{
if ($trgSpell = $this->relSpells->getField('effect'.$idx.'TriggerSpell'))
{
$this->triggerIds[] = $trgSpell;
$data[$this->id]['spells'][$trgSpell] = $charges;
}
}
}
// spell was not proccing
if (!$trgSpell)
$data[$this->id]['spells'][$spellId] = $charges;
}
if (!$data[$this->id]['spells'])
unset($data[$this->id]['spells']);
Util::arraySumByKey($data[$this->id], $this->jsonStats[$this->id]->toJson());
}
return $data;
}
public function getStatGainForCurrent() : array
{
return $this->jsonStats[$this->id]->toJson();
}
public function getRelSpell(int $id) : ?array
{
if ($this->relSpells)
return $this->relSpells->getEntry($id);
return null;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
$data = [];
if ($addMask & GLOBALINFO_SELF)
foreach ($this->iterate() as $__)
$data[Type::ENCHANTMENT][$this->id] = ['name' => $this->getField('name', true)];
if ($addMask & GLOBALINFO_RELATED)
{
if ($this->relSpells)
$data = $this->relSpells->getJSGlobals(GLOBALINFO_SELF);
foreach ($this->triggerIds as $tId)
if (empty($data[Type::SPELL][$tId]))
$data[Type::SPELL][$tId] = $tId;
}
return $data;
}
public function renderTooltip() : ?string { return null; }
}
class EnchantmentListFilter extends Filter
{
protected string $type = 'enchantments';
protected static array $enums = array(
3 => parent::ENUM_PROFESSION // requiresprof
);
protected static array $genericFilter = array(
2 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
3 => [parent::CR_ENUM, 'skillLine' ], // requiresprof
4 => [parent::CR_NUMERIC, 'skillLevel', NUM_CAST_INT ], // reqskillrank
5 => [parent::CR_BOOLEAN, 'conditionId' ], // hascondition
10 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
11 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
12 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
20 => [parent::CR_NUMERIC, 'is.str', NUM_CAST_INT, true], // str
21 => [parent::CR_NUMERIC, 'is.agi', NUM_CAST_INT, true], // agi
22 => [parent::CR_NUMERIC, 'is.sta', NUM_CAST_INT, true], // sta
23 => [parent::CR_NUMERIC, 'is.int', NUM_CAST_INT, true], // int
24 => [parent::CR_NUMERIC, 'is.spi', NUM_CAST_INT, true], // spi
25 => [parent::CR_NUMERIC, 'is.arcres', NUM_CAST_INT, true], // arcres
26 => [parent::CR_NUMERIC, 'is.firres', NUM_CAST_INT, true], // firres
27 => [parent::CR_NUMERIC, 'is.natres', NUM_CAST_INT, true], // natres
28 => [parent::CR_NUMERIC, 'is.frores', NUM_CAST_INT, true], // frores
29 => [parent::CR_NUMERIC, 'is.shares', NUM_CAST_INT, true], // shares
30 => [parent::CR_NUMERIC, 'is.holres', NUM_CAST_INT, true], // holres
32 => [parent::CR_NUMERIC, 'is.dps', NUM_CAST_FLOAT, true], // dps
34 => [parent::CR_NUMERIC, 'is.dmg', NUM_CAST_FLOAT, true], // dmg
37 => [parent::CR_NUMERIC, 'is.mleatkpwr', NUM_CAST_INT, true], // mleatkpwr
38 => [parent::CR_NUMERIC, 'is.rgdatkpwr', NUM_CAST_INT, true], // rgdatkpwr
39 => [parent::CR_NUMERIC, 'is.rgdhitrtng', NUM_CAST_INT, true], // rgdhitrtng
40 => [parent::CR_NUMERIC, 'is.rgdcritstrkrtng', NUM_CAST_INT, true], // rgdcritstrkrtng
41 => [parent::CR_NUMERIC, 'is.armor', NUM_CAST_INT, true], // armor
42 => [parent::CR_NUMERIC, 'is.defrtng', NUM_CAST_INT, true], // defrtng
43 => [parent::CR_NUMERIC, 'is.block', NUM_CAST_INT, true], // block
44 => [parent::CR_NUMERIC, 'is.blockrtng', NUM_CAST_INT, true], // blockrtng
45 => [parent::CR_NUMERIC, 'is.dodgertng', NUM_CAST_INT, true], // dodgertng
46 => [parent::CR_NUMERIC, 'is.parryrtng', NUM_CAST_INT, true], // parryrtng
48 => [parent::CR_NUMERIC, 'is.splhitrtng', NUM_CAST_INT, true], // splhitrtng
49 => [parent::CR_NUMERIC, 'is.splcritstrkrtng', NUM_CAST_INT, true], // splcritstrkrtng
50 => [parent::CR_NUMERIC, 'is.splheal', NUM_CAST_INT, true], // splheal
51 => [parent::CR_NUMERIC, 'is.spldmg', NUM_CAST_INT, true], // spldmg
52 => [parent::CR_NUMERIC, 'is.arcsplpwr', NUM_CAST_INT, true], // arcsplpwr
53 => [parent::CR_NUMERIC, 'is.firsplpwr', NUM_CAST_INT, true], // firsplpwr
54 => [parent::CR_NUMERIC, 'is.frosplpwr', NUM_CAST_INT, true], // frosplpwr
55 => [parent::CR_NUMERIC, 'is.holsplpwr', NUM_CAST_INT, true], // holsplpwr
56 => [parent::CR_NUMERIC, 'is.natsplpwr', NUM_CAST_INT, true], // natsplpwr
57 => [parent::CR_NUMERIC, 'is.shasplpwr', NUM_CAST_INT, true], // shasplpwr
60 => [parent::CR_NUMERIC, 'is.healthrgn', NUM_CAST_INT, true], // healthrgn
61 => [parent::CR_NUMERIC, 'is.manargn', NUM_CAST_INT, true], // manargn
77 => [parent::CR_NUMERIC, 'is.atkpwr', NUM_CAST_INT, true], // atkpwr
78 => [parent::CR_NUMERIC, 'is.mlehastertng', NUM_CAST_INT, true], // mlehastertng
79 => [parent::CR_NUMERIC, 'is.resirtng', NUM_CAST_INT, true], // resirtng
84 => [parent::CR_NUMERIC, 'is.mlecritstrkrtng', NUM_CAST_INT, true], // mlecritstrkrtng
94 => [parent::CR_NUMERIC, 'is.splpen', NUM_CAST_INT, true], // splpen
95 => [parent::CR_NUMERIC, 'is.mlehitrtng', NUM_CAST_INT, true], // mlehitrtng
96 => [parent::CR_NUMERIC, 'is.critstrkrtng', NUM_CAST_INT, true], // critstrkrtng
97 => [parent::CR_NUMERIC, 'is.feratkpwr', NUM_CAST_INT, true], // feratkpwr
101 => [parent::CR_NUMERIC, 'is.rgdhastertng', NUM_CAST_INT, true], // rgdhastertng
102 => [parent::CR_NUMERIC, 'is.splhastertng', NUM_CAST_INT, true], // splhastertng
103 => [parent::CR_NUMERIC, 'is.hastertng', NUM_CAST_INT, true], // hastertng
114 => [parent::CR_NUMERIC, 'is.armorpenrtng', NUM_CAST_INT, true], // armorpenrtng
115 => [parent::CR_NUMERIC, 'is.health', NUM_CAST_INT, true], // health
116 => [parent::CR_NUMERIC, 'is.mana', NUM_CAST_INT, true], // mana
117 => [parent::CR_NUMERIC, 'is.exprtng', NUM_CAST_INT, true], // exprtng
119 => [parent::CR_NUMERIC, 'is.hitrtng', NUM_CAST_INT, true], // hitrtng
123 => [parent::CR_NUMERIC, 'is.splpwr', NUM_CAST_INT, true] // splpwr
);
protected static array $inputFields = array(
'cr' => [parent::V_RANGE, [2, 123], true ], // criteria ids
'crs' => [parent::V_RANGE, [1, 15], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - only numerals
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'ty' => [parent::V_RANGE, [1, 8], true ] // types
);
protected function createSQLForValues() : array
{
$parts = [];
$_v = &$this->values;
//string
if ($_v['na'])
if ($_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]))
$parts[] = $_;
// type
if ($_v['ty'])
$parts[] = ['OR', ['type1', $_v['ty']], ['type2', $_v['ty']], ['type3', $_v['ty']]];
return $parts;
}
}
?>

View File

@@ -0,0 +1,83 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class FactionList extends DBTypeList
{
public static int $type = Type::FACTION;
public static string $brickFile = 'faction';
public static string $dataTable = '?_factions';
protected string $queryBase = 'SELECT f.*, f.`parentFactionId` AS "cat", f.`id` AS ARRAY_KEY FROM ?_factions f';
protected array $queryOpts = array(
'f' => [['f2']],
'f2' => ['j' => ['?_factions f2 ON f.`parentFactionId` = f2.`id`', true], 's' => ', IFNULL(f2.`parentFactionId`, 0) AS "cat2"'],
'ft' => ['j' => '?_factiontemplate ft ON ft.`factionId` = f.`id`']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
if ($this->error)
return;
// post processing
foreach ($this->iterate() as &$_curTpl)
{
// prepare factionTemplates
$_curTpl['templateIds'] = $_curTpl['templateIds'] ? explode(' ', $_curTpl['templateIds']) : [];
// prepare quartermaster
$_curTpl['qmNpcIds'] = $_curTpl['qmNpcIds'] ? explode(' ', $_curTpl['qmNpcIds']) : [];
}
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'expansion' => $this->curTpl['expansion'],
'id' => $this->id,
'side' => $this->curTpl['side'],
'name' => $this->getField('name', true)
);
if ($this->curTpl['cat2'])
{
$data[$this->id]['category'] = $this->curTpl['cat'];
$data[$this->id]['category2'] = $this->curTpl['cat2'];
}
else
{
$data[$this->id]['category'] = $this->curTpl['cat2'];
$data[$this->id]['category2'] = $this->curTpl['cat'];
}
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::FACTION][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,246 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class GameObjectList extends DBTypeList
{
use listviewHelper, spawnHelper;
public static int $type = Type::OBJECT;
public static string $brickFile = 'object';
public static string $dataTable = '?_objects';
protected string $queryBase = 'SELECT o.*, o.`id` AS ARRAY_KEY FROM ?_objects o';
protected array $queryOpts = array(
'o' => [['ft', 'qse']],
'ft' => ['j' => ['?_factiontemplate ft ON ft.`id` = o.`faction`', true], 's' => ', ft.`factionId`, IFNULL(ft.`A`, 0) AS "A", IFNULL(ft.`H`, 0) AS "H"'],
'qse' => ['j' => ['?_quests_startend qse ON qse.`type` = 2 AND qse.`typeId` = o.id', true], 's' => ', IF(MIN(qse.`method`) = 1 OR MAX(qse.`method`) = 3, 1, 0) AS "startsQuests", IF(MIN(qse.`method`) = 2 OR MAX(qse.`method`) = 3, 1, 0) AS "endsQuests"', 'g' => 'o.`id`'],
'qt' => ['j' => '?_quests qt ON qse.`questId` = qt.`id`'],
's' => ['j' => '?_spawns s ON s.`type` = 2 AND s.`typeId` = o.`id`']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
if ($this->error)
return;
// post processing
foreach ($this->iterate() as $_id => &$curTpl)
{
if (!$curTpl['name_loc0'])
$curTpl['name_loc0'] = 'Unnamed Object #' . $_id;
// unpack miscInfo
$curTpl['lootStack'] = [];
$curTpl['spells'] = [];
if (in_array($curTpl['type'], [OBJECT_GOOBER, OBJECT_RITUAL, OBJECT_SPELLCASTER, OBJECT_FLAGSTAND, OBJECT_FLAGDROP, OBJECT_AURA_GENERATOR, OBJECT_TRAP]))
$curTpl['spells'] = array_combine(['onUse', 'onSuccess', 'aura', 'triggered'], [$curTpl['onUseSpell'], $curTpl['onSuccessSpell'], $curTpl['auraSpell'], $curTpl['triggeredSpell']]);
if (!$curTpl['miscInfo'])
continue;
switch ($curTpl['type'])
{
case OBJECT_CHEST:
case OBJECT_FISHINGHOLE:
$curTpl['lootStack'] = explode(' ', $curTpl['miscInfo']);
break;
case OBJECT_CAPTURE_POINT:
$curTpl['capture'] = explode(' ', $curTpl['miscInfo']);
break;
case OBJECT_MEETINGSTONE:
$curTpl['mStone'] = explode(' ', $curTpl['miscInfo']);
break;
}
}
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'name' => Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_RAW),
'type' => $this->curTpl['typeCat'],
'location' => $this->getSpawns(SPAWNINFO_ZONES)
);
if (!empty($this->curTpl['reqSkill']))
$data[$this->id]['skill'] = $this->curTpl['reqSkill'];
if ($this->curTpl['startsQuests'])
$data[$this->id]['hasQuests'] = 1;
}
return $data;
}
public function renderTooltip($interactive = false) : ?string
{
if (!$this->curTpl)
return null;
$x = '<table>';
$x .= '<tr><td><b class="q">'.Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_HTML).'</b></td></tr>';
if ($this->curTpl['typeCat'])
if ($_ = Lang::gameObject('type', $this->curTpl['typeCat']))
$x .= '<tr><td>'.$_.'</td></tr>';
if (isset($this->curTpl['lockId']))
if ($locks = Lang::getLocks($this->curTpl['lockId']))
foreach ($locks as $l)
$x .= '<tr><td>'.sprintf(Lang::game('requires'), $l).'</td></tr>';
$x .= '</table>';
return $x;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::OBJECT][$this->id] = ['name' => Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_RAW)];
return $data;
}
public function getSourceData(int $id = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if ($id && $id != $this->id)
continue;
$data[$this->id] = array(
'n' => $this->getField('name', true),
't' => Type::OBJECT,
'ti' => $this->id
);
}
return $data;
}
}
class GameObjectListFilter extends Filter
{
protected string $type = 'objects';
protected static array $enums = array(
1 => parent::ENUM_ZONE,
16 => parent::ENUM_EVENT,
50 => [1, 2, 3, 4, 663, 883]
);
protected static array $genericFilter = array(
1 => [parent::CR_ENUM, 's.areaId', false, true], // foundin
2 => [parent::CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [side]
3 => [parent::CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [side]
4 => [parent::CR_CALLBACK, 'cbOpenable', null, null], // openable [yn]
5 => [parent::CR_NYI_PH, null, 0 ], // averagemoneycontained [op] [int] - GOs don't contain money, match against 0
7 => [parent::CR_NUMERIC, 'reqSkill', NUM_CAST_INT ], // requiredskilllevel
11 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
13 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
15 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT ], // id
16 => [parent::CR_CALLBACK, 'cbRelEvent', null, null], // relatedevent (ignore removed by event)
18 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
50 => [parent::CR_ENUM, 'spellFocusId', true, true], // spellfocus
);
protected static array $inputFields = array(
'cr' => [parent::V_LIST, [[1, 5], 7, 11, 13, 15, 16, 18, 50], true ], // criteria ids
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 5000]], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - only numeric input values expected
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false] // match any / all filter
);
public array $extraOpts = [];
protected function createSQLForValues() : array
{
$parts = [];
$_v = $this->values;
// name
if ($_v['na'])
if ($_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]))
$parts[] = $_;
return $parts;
}
protected function cbOpenable(int $cr, int $crs, string $crv) : ?array
{
if ($this->int2Bool($crs))
return $crs ? ['OR', ['flags', 0x2, '&'], ['type', 3]] : ['AND', [['flags', 0x2, '&'], 0], ['type', 3, '!']];
return null;
}
protected function cbQuestRelation(int $cr, int $crs, string $crv, $field, $value) : ?array
{
switch ($crs)
{
case 1: // any
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!']];
case 2: // alliance only
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_HORDE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&']];
case 3: // horde only
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']];
case 4: // both
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], ['OR', ['AND', ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']], ['qt.reqRaceMask', 0]]];
case 5: // none todo (low): broken, if entry starts and ends quests...
$this->extraOpts['o']['h'][] = $field.' = 0';
return [1];
}
return null;
}
protected function cbRelEvent(int $cr, int $crs, string $crv) : ?array
{
if ($crs == parent::ENUM_ANY)
{
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
if ($goGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_gameobject WHERE `eventEntry` IN (?a)', $eventIds))
return ['s.guid', $goGuids];
return [0];
}
else if ($crs == parent::ENUM_NONE)
{
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
if ($goGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_gameobject WHERE `eventEntry` IN (?a)', $eventIds))
return ['s.guid', $goGuids, '!'];
return [0];
}
else if (in_array($crs, self::$enums[$cr]))
{
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` = ?d', $crs))
if ($goGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_gameobject WHERE `eventEntry` IN (?a)', $eventIds))
return ['s.guid', $goGuids];
return [0];
}
return null;
}
}
?>

View File

@@ -0,0 +1,182 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class GuideList extends DBTypeList
{
use ListviewHelper;
public const /* array */ STATUS_COLORS = array(
GUIDE_STATUS_DRAFT => '#71D5FF',
GUIDE_STATUS_REVIEW => '#FFFF00',
GUIDE_STATUS_APPROVED => '#1EFF00',
GUIDE_STATUS_REJECTED => '#FF4040',
GUIDE_STATUS_ARCHIVED => '#FFD100'
);
public static int $type = Type::GUIDE;
public static string $brickFile = 'guide';
public static string $dataTable = '?_guides';
public static int $contribute = CONTRIBUTE_CO;
private array $article = [];
private array $jsGlobals = [];
protected string $queryBase = 'SELECT g.*, g.`id` AS ARRAY_KEY FROM ?_guides g';
protected array $queryOpts = array(
'g' => [['a', 'c'], 'g' => 'g.`id`'],
'a' => ['j' => ['?_account a ON a.`id` = g.`userId`', true], 's' => ', IFNULL(a.`username`, "") AS "author"'],
'c' => ['j' => ['?_comments c ON c.`type` = '.Type::GUIDE.' AND c.`typeId` = g.`id` AND (c.`flags` & '.CC_FLAG_DELETED.') = 0', true], 's' => ', COUNT(c.`id`) AS "comments"']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
if ($this->error)
return;
$ratings = DB::Aowow()->select('SELECT `entry` AS ARRAY_KEY, IFNULL(SUM(`value`), 0) AS `t`, IFNULL(COUNT(*), 0) AS `n`, IFNULL(MAX(IF(`userId` = ?d, `value`, 0)), 0) AS `s` FROM ?_user_ratings WHERE `type` = ?d AND `entry` IN (?a)', User::$id, RATING_GUIDE, $this->getFoundIDs());
// post processing
foreach ($this->iterate() as $id => &$_curTpl)
{
if (isset($ratings[$id]))
{
$_curTpl['nvotes'] = $ratings[$id]['n'];
$_curTpl['rating'] = $ratings[$id]['n'] < 5 ? -1 : $ratings[$id]['t'] / $ratings[$id]['n'];
$_curTpl['_self'] = $ratings[$id]['s'];
}
else
{
$_curTpl['nvotes'] = 0;
$_curTpl['rating'] = -1;
}
}
}
public static function getName(int $id) : ?LocString
{
if ($n = DB::Aowow()->SelectRow('SELECT `title` AS "name_loc0" FROM ?# WHERE `id` = ?d', self::$dataTable, $id))
return new LocString($n);
return null;
}
public function getArticle(int $rev = -1) : string
{
if ($rev < -1)
$rev = -1;
if (empty($this->article[$rev]))
{
$a = DB::Aowow()->selectRow('SELECT `article`, `rev` FROM ?_articles WHERE ((`type` = ?d AND `typeId` = ?d){ OR `url` = ?}){ AND `rev`= ?d} ORDER BY `rev` DESC LIMIT 1',
Type::GUIDE, $this->id, $this->getField('url') ?: DBSIMPLE_SKIP, $rev < 0 ? DBSIMPLE_SKIP : $rev);
$this->article[$a['rev']] = $a['article'];
if ($this->article[$a['rev']])
{
Markup::parseTags($this->article[$a['rev']], $this->jsGlobals);
return $this->article[$a['rev']];
}
else
trigger_error('GuideList::getArticle - linked article is missing');
}
return $this->article[$rev] ?? '';
}
public function getListviewData(bool $addDescription = false) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'category' => $this->getField('category'),
'title' => $this->getField('title'),
'description' => $this->getField('description'),
'sticky' => !!($this->getField('cuFlags') & CC_FLAG_STICKY),
'nvotes' => $this->getField('nvotes'),
'url' => '?guide=' . ($this->getField('url') ?: $this->id),
'status' => $this->getField('status'),
'author' => $this->getField('author'),
'authorroles' => $this->getField('roles'),
'rating' => $this->getField('rating'),
'views' => $this->getField('views'),
'comments' => $this->getField('comments'),
// 'patch' => $this->getField(''), // 30305 - patch is pointless, use date instead
'date' => $this->getField('date'), // ok
'when' => date(Util::$dateFormatInternal, $this->getField('date'))
);
if ($this->getField('category') == 1)
{
$data[$this->id]['classs'] = $this->getField('classId');
$data[$this->id]['spec'] = $this->getField('specId');
}
}
return $data;
}
public function userCanView() : bool
{
// is owner || is staff
return $this->getField('userId') == User::$id || User::isInGroup(U_GROUP_STAFF);
}
public function canBeViewed() : bool
{
// currently approved || has prev. approved version
return $this->getField('status') == GUIDE_STATUS_APPROVED || $this->getField('rev') > 0;
}
public function canBeReported() : bool
{
// not own guide && is not archived
return $this->getField('userId') != User::$id && $this->getField('status') != GUIDE_STATUS_ARCHIVED;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
return $this->jsGlobals;
}
public function renderTooltip() : ?string
{
$specStr = '';
if ($this->getField('classId') && $this->getField('category') == 1)
{
if ($c = $this->getField('classId'))
{
$n = Lang::game('cl', $c);
$specStr .= '&nbsp;&nbsp;&nbsp;&nbsp;<span class="icontiny c'.$c.'" style="background-image: url('.Cfg::get('STATIC_URL').'/images/wow/icons/tiny/class_'.ChrClass::tryFrom($c)->json().'.gif)">%s</span>';
if (($s = $this->getField('specId')) > -1)
{
$i = Game::$specIconStrings[$c][$s];
$n = '';
$specStr .= '<span class="icontiny c'.$c.'" style="background-image: url('.Cfg::get('STATIC_URL').'/images/wow/icons/tiny/'.$i.'.gif)">'.Lang::game('classSpecs', $c, $s).'</span>';
}
$specStr = sprintf($specStr, $n);
}
}
$tt = '<table><tr><td><div style="max-width: 320px"><b class="q">'.$this->getField('title').'</b><br />';
$tt .= '<table width="100%"><tr><td>'.Lang::game('guide').'</td><th>'.Lang::guide('byAuthor', [$this->getField('author')]).'</th></tr></table>';
$tt .= '<table width="100%"><tr><td>'.Lang::guide('category', $this->getField('category')).$specStr.'</td><th>'.Lang::guide('patch').' 3.3.5</th></tr></table>';
$tt .= '<div class="q" style="margin: 0.25em 0">'.$this->getField('description').'</div>';
$tt .= '</div></td></tr></table>';
return $tt;
}
}
?>

View File

@@ -0,0 +1,314 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class GuildList extends DBTypeList
{
use profilerHelper, listviewHelper;
public static int $contribute = CONTRIBUTE_NONE;
public function getListviewData() : array
{
$this->getGuildScores();
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'name' => '$"'.str_replace ('"', '', $this->curTpl['name']).'"', // MUST be a string, omit any quotes in name
'members' => $this->curTpl['members'],
'faction' => $this->curTpl['faction'],
'achievementpoints' => $this->getField('achievementpoints'),
'gearscore' => $this->getField('gearscore'),
'realm' => Profiler::urlize($this->curTpl['realmName'], true),
'realmname' => $this->curTpl['realmName'],
// 'battlegroup' => Profiler::urlize($this->curTpl['battlegroup']), // was renamed to subregion somewhere around cata release
// 'battlegroupname' => $this->curTpl['battlegroup'],
'region' => Profiler::urlize($this->curTpl['region'])
);
}
return $data;
}
private function getGuildScores() : void
{
/*
Guild gear scores and achievement points are derived using a weighted average of all of the known characters in that guild.
Guilds with at least 25 level 80 players receive full benefit of the top 25 characters' gear scores, while guilds with at least 10 level 80 characters receive a slight penalty,
at least 1 level 80 a moderate penalty, and no level 80 characters a severe penalty. [...]
Instead of being based on level, achievement point averages are based around 1,500 points, but the same penalties apply.
*/
$guilds = array_column($this->templates, 'id');
if (!$guilds)
return;
$stats = DB::Aowow()->select('SELECT `guild` AS ARRAY_KEY, `id` AS ARRAY_KEY2, `level`, `gearscore`, `achievementpoints`, IF(`cuFlags` & ?d, 0, 1) AS "synced" FROM ?_profiler_profiles WHERE `guild` IN (?a) ORDER BY `gearscore` DESC', PROFILER_CU_NEEDS_RESYNC, $guilds);
foreach ($this->iterate() as &$_curTpl)
{
$id = $_curTpl['id'];
if (empty($stats[$id]))
continue;
$guildStats = array_filter($stats[$id], function ($x) { return $x['synced']; } );
if (!$guildStats)
continue;
$nMaxLevel = count(array_filter($stats[$id], function ($x) { return $x['level'] >= MAX_LEVEL; } ));
$levelMod = 1.0;
if ($nMaxLevel < 25)
$levelMod = 0.85;
if ($nMaxLevel < 10)
$levelMod = 0.66;
if ($nMaxLevel < 1)
$levelMod = 0.20;
$totalGS = $totalAP = $nMembers = 0;
foreach ($guildStats as $gs)
{
$totalGS += $gs['gearscore'] * $levelMod * min($gs['level'], MAX_LEVEL) / MAX_LEVEL;
$totalAP += $gs['achievementpoints'] * $levelMod * min($gs['achievementpoints'], 1500) / 1500;
$nMembers += min($gs['level'], MAX_LEVEL) / MAX_LEVEL;
}
$_curTpl['gearscore'] = intval($totalGS / $nMembers);
$_curTpl['achievementpoints'] = intval($totalAP / $nMembers);
}
}
public static function getName(int $id) : ?LocString { return null; }
public function renderTooltip() : ?string { return null; }
public function getJSGlobals(int $addMask = 0) : array { return []; }
}
class GuildListFilter extends Filter
{
use TrProfilerFilter;
protected string $type = 'guilds';
protected static array $genericFilter = [];
protected static array $inputFields = array(
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'ex' => [parent::V_EQUAL, 'on', false], // only match exact
'si' => [parent::V_LIST, [SIDE_ALLIANCE, SIDE_HORDE], false], // side
'rg' => [parent::V_CALLBACK, 'cbRegionCheck', false], // region
'sv' => [parent::V_CALLBACK, 'cbServerCheck', false], // server
);
public array $extraOpts = [];
protected function createSQLForValues() : array
{
$parts = [];
$_v = $this->values;
// region (rg), battlegroup (bg) and server (sv) are passed to GuildList as miscData and handled there
// name [str]
if ($_v['na'])
if ($_ = $this->tokenizeString(['g.name'], $_v['na'], $_v['ex'] == 'on'))
$parts[] = $_;
// side [list]
if ($_v['si'] == SIDE_ALLIANCE)
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_ALLIANCE)];
else if ($_v['si'] == SIDE_HORDE)
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_HORDE)];
return $parts;
}
}
class RemoteGuildList extends GuildList
{
protected string $queryBase = 'SELECT `g`.*, `g`.`guildid` AS ARRAY_KEY FROM guild g';
protected array $queryOpts = array(
'g' => [['gm', 'c'], 'g' => 'ARRAY_KEY'],
'gm' => ['j' => 'guild_member gm ON gm.`guildid` = g.`guildid`', 's' => ', COUNT(1) AS "members"'],
'c' => ['j' => 'characters c ON c.`guid` = gm.`guid`', 's' => ', BIT_OR(IF(c.`race` IN (1, 3, 4, 7, 11), 1, 2)) - 1 AS "faction"']
);
public function __construct(array $conditions = [], array $miscData = [])
{
// select DB by realm
if (!$this->selectRealms($miscData))
{
trigger_error('RemoteGuildList::__construct - cannot access any realm.', E_USER_WARNING);
return;
}
parent::__construct($conditions, $miscData);
if ($this->error)
return;
reset($this->dbNames); // only use when querying single realm
$realms = Profiler::getRealms();
$distrib = [];
// post processing
foreach ($this->iterate() as $guid => &$curTpl)
{
// battlegroup
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
$r = explode(':', $guid)[0];
if (!empty($realms[$r]))
{
$curTpl['realm'] = $r;
$curTpl['realmName'] = $realms[$r]['name'];
$curTpl['region'] = $realms[$r]['region'];
}
else
{
trigger_error('guild #'.$guid.' belongs to nonexistent realm #'.$r, E_USER_WARNING);
unset($this->templates[$guid]);
continue;
}
// empty name
if (!$curTpl['name'])
{
trigger_error('guild #'.$guid.' on realm #'.$r.' has empty name.', E_USER_WARNING);
unset($this->templates[$guid]);
continue;
}
// equalize distribution
if (empty($distrib[$curTpl['realm']]))
$distrib[$curTpl['realm']] = 1;
else
$distrib[$curTpl['realm']]++;
}
foreach ($conditions as $c)
if (is_int($c))
$limit = $c;
$limit ??= Cfg::get('SQL_LIMIT_DEFAULT');
if (!$limit) // int:0 means unlimited, so skip early
return;
$total = array_sum($distrib);
foreach ($distrib as &$d)
$d = ceil($limit * $d / $total);
foreach ($this->iterate() as $guid => &$curTpl)
{
if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0)
{
unset($this->templates[$guid]);
continue;
}
$distrib[$curTpl['realm']]--;
$limit--;
}
}
public function initializeLocalEntries() : void
{
$data = [];
foreach ($this->iterate() as $guid => $__)
{
$data[$guid] = array(
'realm' => $this->getField('realm'),
'realmGUID' => $this->getField('guildid'),
'name' => $this->getField('name'),
'nameUrl' => Profiler::urlize($this->getField('name')),
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
);
}
// basic guild data
foreach (Util::createSqlBatchInsert($data) as $ins)
DB::Aowow()->query('INSERT INTO ?_profiler_guild (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `id` = `id`', array_keys(reset($data)));
// merge back local ids
$localIds = DB::Aowow()->selectCol(
'SELECT CONCAT(`realm`, ":", `realmGUID`) AS ARRAY_KEY, `id` FROM ?_profiler_guild WHERE `realm` IN (?a) AND `realmGUID` IN (?a)',
array_column($data, 'realm'),
array_column($data, 'realmGUID')
);
foreach ($this->iterate() as $guid => &$_curTpl)
if (isset($localIds[$guid]))
$_curTpl['id'] = $localIds[$guid];
}
}
class LocalGuildList extends GuildList
{
protected string $queryBase = 'SELECT g.*, g.`id` AS ARRAY_KEY FROM ?_profiler_guild g';
public function __construct(array $conditions = [], array $miscData = [])
{
$realms = Profiler::getRealms();
// graft realm selection from miscData onto conditions
if (isset($miscData['sv']))
$realms = array_filter($realms, fn($x) => Profiler::urlize($x['name']) == Profiler::urlize($miscData['sv']));
if (isset($miscData['rg']))
$realms = array_filter($realms, fn($x) => $x['region'] == $miscData['rg']);
if (!$realms)
{
trigger_error('LocalGuildList::__construct - cannot access any realm.', E_USER_WARNING);
return;
}
if ($conditions)
{
array_unshift($conditions, 'AND');
$conditions = ['AND', ['realm', array_keys($realms)], $conditions];
}
else
$conditions = [['realm', array_keys($realms)]];
parent::__construct($conditions, $miscData);
if ($this->error)
return;
foreach ($this->iterate() as $id => &$curTpl)
{
if ($curTpl['realm'] && !isset($realms[$curTpl['realm']]))
continue;
if (isset($realms[$curTpl['realm']]))
{
$curTpl['realmName'] = $realms[$curTpl['realm']]['name'];
$curTpl['region'] = $realms[$curTpl['realm']]['region'];
}
// battlegroup
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
}
}
public function getProfileUrl() : string
{
$url = '?guild=';
return $url.implode('.', array(
$this->getField('region'),
Profiler::urlize($this->getField('realmName'), true),
Profiler::urlize($this->getField('name'))
));
}
}
?>

View File

@@ -0,0 +1,227 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class IconList extends DBTypeList
{
use listviewHelper;
public static int $type = Type::ICON;
public static string $brickFile = 'icongallery';
public static string $dataTable = '?_icons';
public static int $contribute = CONTRIBUTE_CO;
private string $pseudoQry = 'SELECT `iconId` AS ARRAY_KEY, COUNT(*) FROM ?# WHERE `iconId` IN (?a) GROUP BY `iconId`';
private array $pseudoJoin = array(
'nItems' => '?_items',
'nSpells' => '?_spell',
'nAchievements' => '?_achievement',
'nCurrencies' => '?_currencies',
'nPets' => '?_pet'
);
protected string $queryBase = 'SELECT ic.*, ic.`id` AS ARRAY_KEY FROM ?_icons ic';
/* this works, but takes ~100x more time than i'm comfortable with .. kept as reference
protected array $queryOpts = array( // 29 => Type::ICON
'ic' => [['s', 'i', 'a', 'c', 'p'], 'g' => 'ic.id'],
'i' => ['j' => ['?_items `i` ON `i`.`iconId` = `ic`.`id`', true], 's' => ', COUNT(DISTINCT `i`.`id`) AS "nItems"'],
's' => ['j' => ['?_spell `s` ON `s`.`iconId` = `ic`.`id`', true], 's' => ', COUNT(DISTINCT `s`.`id`) AS "nSpells"'],
'a' => ['j' => ['?_achievement `a` ON `a`.`iconId` = `ic`.`id`', true], 's' => ', COUNT(DISTINCT `a`.`id`) AS "nAchievements"'],
'c' => ['j' => ['?_currencies `c` ON `c`.`iconId` = `ic`.`id`', true], 's' => ', COUNT(DISTINCT `c`.`id`) AS "nCurrencies"'],
'p' => ['j' => ['?_pet `p` ON `p`.`iconId` = `ic`.`id`', true], 's' => ', COUNT(DISTINCT `p`.`id`) AS "nPets"']
);
*/
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
if (!$this->getFoundIDs())
return;
foreach ($this->pseudoJoin as $var => $tbl)
{
$res = DB::Aowow()->selectCol($this->pseudoQry, $tbl, $this->getFoundIDs());
foreach ($res as $icon => $qty)
$this->templates[$icon][$var] = $qty;
}
}
public static function getName(int $id) : ?LocString
{
if ($n = DB::Aowow()->selectRow('SELECT `name` AS "name_loc0" FROM ?# WHERE `id` = ?d', self::$dataTable, $id))
return new LocString($n);
return null;
}
public function getListviewData(int $addInfoMask = 0x0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'name' => $this->getField('name', true, true),
'icon' => $this->getField('name', true, true),
'itemcount' => (int)$this->getField('nItems'),
'spellcount' => (int)$this->getField('nSpells'),
'achievementcount' => (int)$this->getField('nAchievements'),
'npccount' => 0, // UNUSED
'petabilitycount' => 0, // UNUSED
'currencycount' => (int)$this->getField('nCurrencies'),
'missionabilitycount' => 0, // UNUSED
'buildingcount' => 0, // UNUSED
'petcount' => (int)$this->getField('nPets'),
'threatcount' => 0, // UNUSED
'classcount' => 0 // class icons are hardcoded and not referenced in dbc
);
}
return $data;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::ICON][$this->id] = ['name' => $this->getField('name', true, true), 'icon' => $this->getField('name', true, true)];
return $data;
}
public function renderTooltip() : ?string { return null; }
}
class IconListFilter extends Filter
{
private array $totalUses = [];
private array $criterion2field = array(
1 => '?_items', // items [num]
2 => '?_spell', // spells [num]
3 => '?_achievement', // achievements [num]
// 4 => '', // battlepets [num]
// 5 => '', // battlepetabilities [num]
6 => '?_currencies', // currencies [num]
// 7 => '', // garrisonabilities [num]
// 8 => '', // garrisonbuildings [num]
9 => '?_pet', // hunterpets [num]
// 10 => '', // garrisonmissionthreats [num]
11 => '', // classes [num]
13 => '' // used [num]
);
protected string $type = 'icons';
protected static array $genericFilter = array(
1 => [parent::CR_CALLBACK, 'cbUseAny' ], // items [num]
2 => [parent::CR_CALLBACK, 'cbUseAny' ], // spells [num]
3 => [parent::CR_CALLBACK, 'cbUseAny' ], // achievements [num]
6 => [parent::CR_CALLBACK, 'cbUseAny' ], // currencies [num]
9 => [parent::CR_CALLBACK, 'cbUseAny' ], // hunterpets [num]
11 => [parent::CR_NYI_PH, null, 0], // classes [num]
13 => [parent::CR_CALLBACK, 'cbUseAll' ] // used [num]
);
protected static array $inputFields = array(
'cr' => [parent::V_LIST, [1, 2, 3, 6, 9, 11, 13], true ], // criteria ids
'crs' => [parent::V_RANGE, [1, 6], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - all criteria are numeric here
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false] // match any / all filter
);
public array $extraOpts = [];
private function _getCnd(string $op, int $val, string $tbl) : ?array
{
switch ($op)
{
case '>':
case '>=':
case '=':
$ids = DB::Aowow()->selectCol('SELECT `iconId` AS ARRAY_KEY, COUNT(*) AS "n" FROM ?# GROUP BY `iconId` HAVING n '.$op.' '.$val, $tbl);
return $ids ? ['id', array_keys($ids)] : [1];
case '<=':
if ($val)
$op = '>';
break;
case '<':
if ($val)
$op = '>=';
break;
case '!=':
if ($val)
$op = '=';
break;
default:
return null;
}
$ids = DB::Aowow()->selectCol('SELECT `iconId` AS ARRAY_KEY, COUNT(*) AS "n" FROM ?# GROUP BY `iconId` HAVING n '.$op.' '.$val, $tbl);
return $ids ? ['id', array_keys($ids), '!'] : [1];
}
protected function createSQLForValues() : array
{
$parts = [];
$_v = &$this->values;
//string
if ($_v['na'])
if ($_ = $this->tokenizeString(['name']))
$parts[] = $_;
return $parts;
}
protected function cbUseAny(int $cr, int $crs, string $crv) : ?array
{
if (Util::checkNumeric($crv, NUM_CAST_INT) && $this->int2Op($crs))
return $this->_getCnd($crs, $crv, $this->criterion2field[$cr]);
return null;
}
protected function cbUseAll(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
if (!$this->totalUses)
{
foreach ($this->criterion2field as $tbl)
{
if (!$tbl)
continue;
$res = DB::Aowow()->selectCol('SELECT `iconId` AS ARRAY_KEY, COUNT(*) AS "n" FROM ?# GROUP BY `iconId`', $tbl);
Util::arraySumByKey($this->totalUses, $res);
}
}
if ($crs == '=')
$crs = '==';
$op = $crs;
if ($crs == '<=' && $crv)
$op = '>';
else if ($crs == '<' && $crv)
$op = '>=';
else if ($crs == '!=' && $crv)
$op = '==';
$ids = array_filter($this->totalUses, fn($x) => eval('return '.$x.' '.$op.' '.$crv.';'));
if ($crs != $op)
return $ids ? ['id', array_keys($ids), '!'] : [1];
else
return $ids ? ['id', array_keys($ids)] : ['id', array_keys($this->totalUses), '!'];
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class ItemsetList extends DBTypeList
{
use ListviewHelper;
public static int $type = Type::ITEMSET;
public static string $brickFile = 'itemset';
public static string $dataTable = '?_itemset';
public array $pieceToSet = []; // used to build g_items and search
private array $classes = []; // used to build g_classes
protected string $queryBase = 'SELECT `set`.*, `set`.`id` AS ARRAY_KEY FROM ?_itemset `set`';
protected array $queryOpts = array(
'set' => ['o' => 'maxlevel DESC'],
'e' => ['j' => ['?_events e ON `e`.`id` = `set`.`eventId`', true], 's' => ', e.`holidayId`'],
'src' => ['j' => ['?_source src ON `src`.`typeId` = `set`.`id` AND `src`.`type` = 4', true], 's' => ', `src1`, `src2`, `src3`, `src4`, `src5`, `src6`, `src7`, `src8`, `src9`, `src10`, `src11`, `src12`, `src13`, `src14`, `src15`, `src16`, `src17`, `src18`, `src19`, `src20`, `src21`, `src22`, `src23`, `src24`']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
// post processing
foreach ($this->iterate() as &$_curTpl)
{
$_curTpl['classes'] = ChrClass::fromMask($_curTpl['classMask']);
$this->classes = array_merge($this->classes, $_curTpl['classes']);
$_curTpl['pieces'] = [];
for ($i = 1; $i < 10; $i++)
{
if ($piece = $_curTpl['item'.$i])
{
$_curTpl['pieces'][] = $piece;
$this->pieceToSet[$piece] = $this->id;
}
}
}
$this->classes = array_unique($this->classes);
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'idbak' => $this->curTpl['refSetId'],
'name' => (7 - $this->curTpl['quality']).$this->getField('name', true),
'minlevel' => $this->curTpl['minLevel'],
'maxlevel' => $this->curTpl['maxLevel'],
'note' => $this->curTpl['contentGroup'],
'type' => $this->curTpl['type'],
'reqclass' => $this->curTpl['classMask'],
'classes' => $this->curTpl['classes'],
'pieces' => $this->curTpl['pieces'],
'heroic' => $this->curTpl['heroic']
);
}
return $data;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
$data = [];
if ($this->classes && ($addMask & GLOBALINFO_RELATED))
$data[Type::CHR_CLASS] = array_combine($this->classes, $this->classes);
if ($this->pieceToSet && ($addMask & GLOBALINFO_SELF))
$data[Type::ITEM] = array_combine(array_keys($this->pieceToSet), array_keys($this->pieceToSet));
if ($addMask & GLOBALINFO_SELF)
foreach ($this->iterate() as $id => $__)
$data[Type::ITEMSET][$id] = ['name' => $this->getField('name', true)];
return $data;
}
public function renderTooltip() : ?string
{
if (!$this->curTpl)
return null;
$x = '<table><tr><td>';
$x .= '<span class="q'.$this->getField('quality').'">'.$this->getField('name', true).'</span><br />';
$nCl = 0;
if ($_ = $this->getField('classMask'))
{
$jsg = [];
$cl = Lang::getClassString($_, $jsg);
$nCl = count($jsg);
$x .= Util::ucFirst($nCl > 1 ? Lang::game('classes') : Lang::game('class')).Lang::main('colon').$cl.'<br />';
}
if ($_ = $this->getField('contentGroup'))
$x .= Lang::itemset('notes', $_).($this->getField('heroic') ? ' <i class="q2">('.Lang::item('heroic').')</i>' : '').'<br />';
if (!$nCl || !$this->getField('type'))
$x.= Lang::itemset('types', $this->getField('type')).'<br />';
if ($bonuses = $this->getBonuses())
{
$x .= '<span>';
foreach ($bonuses as $b)
$x .= '<br /><span class="q13">'.$b['bonus'].' '.Lang::itemset('_pieces').Lang::main('colon').'</span>'.$b['desc'];
$x .= '</span>';
}
$x .= '</td></tr></table>';
return $x;
}
public function getBonuses() : array
{
$spells = [];
for ($i = 1; $i < 9; $i++)
{
$spl = $this->getField('spell'.$i);
$qty = $this->getField('bonus'.$i);
// cant use spell as index, would change order
if ($spl && $qty)
$spells[] = ['id' => $spl, 'bonus' => $qty];
}
// sort by required pieces ASC
usort($spells, fn(array $a, array $b) => $a['bonus'] <=> $b['bonus']);
$setSpells = new SpellList(array(['s.id', array_column($spells, 'id')]));
foreach ($setSpells->iterate() as $spellId => $__)
{
foreach ($spells as &$s)
{
if ($spellId != $s['id'])
continue;
$s['desc'] = $setSpells->parseText('description', $this->getField('reqLevel') ?: MAX_LEVEL)[0];
}
}
return $spells;
}
}
// missing filter: "Available to Players"
class ItemsetListFilter extends Filter
{
protected string $type = 'itemsets';
protected static array $enums = array(
6 => parent::ENUM_EVENT
);
protected static array $genericFilter = array(
2 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
3 => [parent::CR_NUMERIC, 'npieces', NUM_CAST_INT ], // pieces
4 => [parent::CR_STRING, 'bonusText', STR_LOCALIZED ], // bonustext
5 => [parent::CR_BOOLEAN, 'heroic' ], // heroic
6 => [parent::CR_ENUM, 'e.holidayId', true, true], // relatedevent
8 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
9 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
10 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
12 => [parent::CR_CALLBACK, 'cbAvaliable', ] // available to players [yn]
);
protected static array $inputFields = array(
'cr' => [parent::V_RANGE, [2, 12], true ], // criteria ids
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 424]], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiters
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / description - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'qu' => [parent::V_RANGE, [0, 7], true ], // quality
'ty' => [parent::V_RANGE, [1, 12], true ], // set type
'minle' => [parent::V_RANGE, [1, 999], false], // min item level
'maxle' => [parent::V_RANGE, [1, 999], false], // max itemlevel
'minrl' => [parent::V_RANGE, [1, MAX_LEVEL], false], // min required level
'maxrl' => [parent::V_RANGE, [1, MAX_LEVEL], false], // max required level
'cl' => [parent::V_LIST, [[1, 9], 11], false], // class
'ta' => [parent::V_RANGE, [1, 30], false] // tag / content group
);
protected function createSQLForValues() : array
{
$parts = [];
$_v = &$this->values;
// name [str]
if ($_v['na'])
if ($_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]))
$parts[] = $_;
// quality [enum]
if ($_v['qu'])
$parts[] = ['quality', $_v['qu']];
// type [enum]
if ($_v['ty'])
$parts[] = ['type', $_v['ty']];
// itemLevel min [int]
if ($_v['minle'])
$parts[] = ['minLevel', $_v['minle'], '>='];
// itemLevel max [int]
if ($_v['maxle'])
$parts[] = ['maxLevel', $_v['maxle'], '<='];
// reqLevel min [int]
if ($_v['minrl'])
$parts[] = ['reqLevel', $_v['minrl'], '>='];
// reqLevel max [int]
if ($_v['maxrl'])
$parts[] = ['reqLevel', $_v['maxrl'], '<='];
// class [enum]
if ($_v['cl'])
$parts[] = ['classMask', $this->list2Mask([$_v['cl']]), '&'];
// tag [enum]
if ($_v['ta'])
$parts[] = ['contentGroup', intVal($_v['ta'])];
return $parts;
}
protected function cbAvaliable(int $cr, int $crs, string $crv) : ?array
{
return match ($crs)
{
1 => ['src.typeId', null, '!'], // Yes
2 => ['src.typeId', null], // No
default => null
};
}
}
?>

View File

@@ -0,0 +1,77 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class MailList extends DBTypeList
{
public static int $type = Type::MAIL;
public static string $brickFile = 'mail';
public static string $dataTable = '?_mails';
protected string $queryBase = 'SELECT m.*, m.`id` AS ARRAY_KEY FROM ?_mails m';
protected array $queryOpts = [];
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
if ($this->error)
return;
// post processing
foreach ($this->iterate() as $_id => &$_curTpl)
{
$_curTpl['name'] = Util::localizedString($_curTpl, 'subject', true);
if (!$_curTpl['name'])
{
$_curTpl['name'] = sprintf(Lang::mail('untitled'), $_id);
$_curTpl['subject_loc0'] = $_curTpl['name'];
}
}
}
public static function getName(int $id) : ?LocString
{
if ($n = DB::Aowow()->SelectRow('SELECT `subject_loc0`, `subject_loc2`, `subject_loc3`, `subject_loc4`, `subject_loc6`, `subject_loc8` FROM ?# WHERE `id` = ?d', self::$dataTable, $id))
return new LocString($n, 'subject');
return null;
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$body = str_replace('[br]', ' ', Util::parseHtmlText($this->getField('text', true), true));
$data[$this->id] = array(
'id' => $this->id,
'subject' => $this->getField('subject', true),
'body' => Lang::trimTextClean($body),
'attachments' => [$this->getField('attachment')]
);
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
if ($a = $this->curTpl['attachment'])
$data[Type::ITEM][$a] = $a;
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,76 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class PetList extends DBTypeList
{
use ListviewHelper;
public static int $type = Type::PET;
public static string $brickFile = 'pet';
public static string $dataTable = '?_pet';
protected string $queryBase = 'SELECT p.*, p.`id` AS ARRAY_KEY FROM ?_pet p';
protected array $queryOpts = array(
'p' => [['ic']],
'ic' => ['j' => ['?_icons ic ON p.`iconId` = ic.`id`', true], 's' => ', ic.`name` AS "iconString"'],
);
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'armor' => $this->curTpl['armor'],
'damage' => $this->curTpl['damage'],
'health' => $this->curTpl['health'],
'diet' => $this->curTpl['foodMask'],
'icon' => $this->curTpl['iconString'],
'id' => $this->id,
'maxlevel' => $this->curTpl['maxLevel'],
'minlevel' => $this->curTpl['minLevel'],
'name' => $this->getField('name', true),
'type' => $this->curTpl['type'],
'exotic' => $this->curTpl['exotic'],
'spells' => []
);
if ($this->curTpl['expansion'] > 0)
$data[$this->id]['expansion'] = $this->curTpl['expansion'];
for ($i = 1; $i <= 4; $i++)
if ($this->curTpl['spellId'.$i] > 0)
$data[$this->id]['spells'][] = $this->curTpl['spellId'.$i];
}
return $data;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if ($addMask & GLOBALINFO_RELATED)
for ($i = 1; $i <= 4; $i++)
if ($this->curTpl['spellId'.$i] > 0)
$data[Type::SPELL][$this->curTpl['spellId'.$i]] = $this->curTpl['spellId'.$i];
if ($addMask & GLOBALINFO_SELF)
$data[Type::PET][$this->id] = ['icon' => $this->curTpl['iconString']];
}
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,728 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class ProfileList extends DBTypeList
{
use profilerHelper, listviewHelper;
public static int $contribute = CONTRIBUTE_NONE;
public function getListviewData(int $addInfoMask = 0, array $reqCols = []) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if (!$this->isVisibleToUser())
continue;
if (($addInfoMask & PROFILEINFO_PROFILE) && !$this->isCustom())
continue;
if (($addInfoMask & PROFILEINFO_CHARACTER) && $this->isCustom())
continue;
$data[$this->id] = array(
'id' => $this->getField('id'),
'name' => $this->getField('name'),
'race' => $this->getField('race'),
'classs' => $this->getField('class'),
'gender' => $this->getField('gender'),
'level' => $this->getField('level'),
'faction' => ChrRace::tryFrom($this->getField('race'))?->isAlliance() ? 0 : 1,
'talenttree1' => $this->getField('talenttree1'),
'talenttree2' => $this->getField('talenttree2'),
'talenttree3' => $this->getField('talenttree3'),
'talentspec' => $this->getField('activespec') + 1, // 0 => 1; 1 => 2
'achievementpoints' => $this->getField('achievementpoints'),
'guild' => $this->curTpl['guildname'] ? '$"'.str_replace ('"', '', $this->curTpl['guildname']).'"' : '', // force this to be a string
'guildrank' => $this->getField('guildrank'),
'realm' => Profiler::urlize($this->getField('realmName'), true),
'realmname' => $this->getField('realmName'),
// 'battlegroup' => Profiler::urlize($this->getField('battlegroup')), // was renamed to subregion somewhere around cata release
// 'battlegroupname' => $this->getField('battlegroup'),
'gearscore' => $this->getField('gearscore')
);
if ($addInfoMask & PROFILEINFO_USER)
$data[$this->id]['published'] = (int)!!($this->getField('cuFlags') & PROFILER_CU_PUBLISHED);
// for the lv this determins if the link is profile=<id> or profile=<region>.<realm>.<name>
if (!$this->isCustom())
$data[$this->id]['region'] = Profiler::urlize($this->getField('region'));
if ($addInfoMask & PROFILEINFO_ARENA)
{
$data[$this->id]['rating'] = $this->getField('rating');
$data[$this->id]['captain'] = $this->getField('captain');
$data[$this->id]['games'] = $this->getField('seasonGames');
$data[$this->id]['wins'] = $this->getField('seasonWins');
}
// Filter asked for skills - add them
foreach ($reqCols as $col)
$data[$this->id][$col] = $this->getField($col);
if ($addInfoMask & PROFILEINFO_PROFILE)
{
if ($_ = $this->getField('description'))
$data[$this->id]['description'] = $_;
if ($_ = $this->getField('icon'))
$data[$this->id]['icon'] = $_;
}
if ($addInfoMask & PROFILEINFO_CHARACTER)
if ($_ = $this->getField('renameItr'))
$data[$this->id]['renameItr'] = $_;
if ($this->getField('cuFlags') & PROFILER_CU_PINNED)
$data[$this->id]['pinned'] = 1;
if ($this->getField('cuFlags') & PROFILER_CU_DELETED)
$data[$this->id]['deleted'] = 1;
}
return $data;
}
public function renderTooltip() : ?string
{
if (!$this->curTpl)
return null;
$title = '';
$name = $this->getField('name');
if ($_ = $this->getField('title'))
$title = (new TitleList(array(['id', $_])))->getField($this->getField('gender') ? 'female' : 'male', true);
if ($this->isCustom())
$name .= Lang::profiler('customProfile');
else if ($title)
$name = sprintf($title, $name);
$x = '<table>';
$x .= '<tr><td><b class="q">'.$name.'</b></td></tr>';
if ($g = $this->getField('guildname'))
$x .= '<tr><td>&lt;'.$g.'&gt;</td></tr>';
else if ($d = $this->getField('description'))
$x .= '<tr><td>'.$d.'</td></tr>';
$x .= '<tr><td>'.Lang::game('level').' '.$this->getField('level').' '.Lang::game('ra', $this->getField('race')).' '.Lang::game('cl', $this->getField('class')).'</td></tr>';
$x .= '</table>';
return $x;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
$realms = Profiler::getRealms();
foreach ($this->iterate() as $id => $__)
{
if (($addMask & PROFILEINFO_PROFILE) && $this->isCustom())
{
$profile = array(
'id' => $this->getField('id'),
'name' => $this->getField('name'),
'race' => $this->getField('race'),
'classs' => $this->getField('class'),
'level' => $this->getField('level'),
'gender' => $this->getField('gender')
);
if ($_ = $this->getField('icon'))
$profile['icon'] = $_;
$data[] = $profile;
continue;
}
if ($addMask & PROFILEINFO_CHARACTER && !$this->isCustom())
{
if (!isset($realms[$this->getField('realm')]))
continue;
$data[] = array(
'id' => $this->getField('id'),
'name' => $this->getField('name'),
'realmname' => $realms[$this->getField('realm')]['name'],
'region' => $realms[$this->getField('realm')]['region'],
'realm' => Profiler::urlize($realms[$this->getField('realm')]['name']),
'race' => $this->getField('race'),
'classs' => $this->getField('class'),
'level' => $this->getField('level'),
'gender' => $this->getField('gender'),
'pinned' => $this->getField('cuFlags') & PROFILER_CU_PINNED ? 1 : 0
);
}
}
return $data;
}
public function isCustom() : bool
{
return $this->getField('cuFlags') & PROFILER_CU_PROFILE;
}
public function isVisibleToUser() : bool
{
if (!$this->isCustom() || User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
return true;
if ($this->getField('cuFlags') & PROFILER_CU_DELETED)
return false;
if (User::$id == $this->getField('user'))
return true;
return (bool)($this->getField('cuFlags') & PROFILER_CU_PUBLISHED);
}
public function getIcon() : string
{
if ($_ = $this->getField('icon'))
return $_;
return sprintf('chr_%s_%s_%s%02d',
ChrRace::from($this->getField('race'))->json(),
$this->getField('gender') ? 'female' : 'male',
ChrClass::from($this->getField('class'))->json(),
max(1, floor(($this->getField('level') - 60) / 10) + 2)
);
}
public static function getName(int $id) : ?LocString { return null; }
}
class ProfileListFilter extends Filter
{
use TrProfilerFilter;
protected string $type = 'profiles';
protected static array $genericFilter = array(
2 => [parent::CR_NUMERIC, 'gearscore', NUM_CAST_INT ], // gearscore [num]
3 => [parent::CR_CALLBACK, 'cbAchievs', null, null], // achievementpoints [num]
5 => [parent::CR_NUMERIC, 'talenttree1', NUM_CAST_INT ], // talenttree1 [num]
6 => [parent::CR_NUMERIC, 'talenttree2', NUM_CAST_INT ], // talenttree2 [num]
7 => [parent::CR_NUMERIC, 'talenttree3', NUM_CAST_INT ], // talenttree3 [num]
9 => [parent::CR_STRING, 'g.name' ], // guildname
10 => [parent::CR_CALLBACK, 'cbHasGuildRank', null, null], // guildrank
12 => [parent::CR_CALLBACK, 'cbTeamName', 2, null], // teamname2v2
15 => [parent::CR_CALLBACK, 'cbTeamName', 3, null], // teamname3v3
18 => [parent::CR_CALLBACK, 'cbTeamName', 5, null], // teamname5v5
13 => [parent::CR_CALLBACK, 'cbTeamRating', 2, null], // teamrtng2v2
16 => [parent::CR_CALLBACK, 'cbTeamRating', 3, null], // teamrtng3v3
19 => [parent::CR_CALLBACK, 'cbTeamRating', 5, null], // teamrtng5v5
14 => [parent::CR_NYI_PH, null, 0 /* 2 */ ], // teamcontrib2v2 [num]
17 => [parent::CR_NYI_PH, null, 0 /* 3 */ ], // teamcontrib3v3 [num]
20 => [parent::CR_NYI_PH, null, 0 /* 5 */ ], // teamcontrib5v5 [num]
21 => [parent::CR_CALLBACK, 'cbWearsItems', null, null], // wearingitem [str]
23 => [parent::CR_CALLBACK, 'cbCompletedAcv', null, null], // completedachievement
25 => [parent::CR_CALLBACK, 'cbProfession', SKILL_ALCHEMY, null], // alchemy [num]
26 => [parent::CR_CALLBACK, 'cbProfession', SKILL_BLACKSMITHING, null], // blacksmithing [num]
27 => [parent::CR_CALLBACK, 'cbProfession', SKILL_ENCHANTING, null], // enchanting [num]
28 => [parent::CR_CALLBACK, 'cbProfession', SKILL_ENGINEERING, null], // engineering [num]
29 => [parent::CR_CALLBACK, 'cbProfession', SKILL_HERBALISM, null], // herbalism [num]
30 => [parent::CR_CALLBACK, 'cbProfession', SKILL_INSCRIPTION, null], // inscription [num]
31 => [parent::CR_CALLBACK, 'cbProfession', SKILL_JEWELCRAFTING, null], // jewelcrafting [num]
32 => [parent::CR_CALLBACK, 'cbProfession', SKILL_LEATHERWORKING, null], // leatherworking [num]
33 => [parent::CR_CALLBACK, 'cbProfession', SKILL_MINING, null], // mining [num]
34 => [parent::CR_CALLBACK, 'cbProfession', SKILL_SKINNING, null], // skinning [num]
35 => [parent::CR_CALLBACK, 'cbProfession', SKILL_TAILORING, null], // tailoring [num]
36 => [parent::CR_CALLBACK, 'cbHasGuild', null, null] // hasguild [yn]
);
protected static array $inputFields = array(
'cr' => [parent::V_RANGE, [1, 36], true ], // criteria ids
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 5000]], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'ex' => [parent::V_EQUAL, 'on', false], // only match exact
'si' => [parent::V_LIST, [SIDE_ALLIANCE, SIDE_HORDE], false], // side
'ra' => [parent::V_LIST, [[1, 8], 10, 11], true ], // race
'cl' => [parent::V_LIST, [[1, 9], 11], true ], // class
'minle' => [parent::V_RANGE, [1, MAX_LEVEL], false], // min level
'maxle' => [parent::V_RANGE, [1, MAX_LEVEL], false], // max level
'rg' => [parent::V_CALLBACK, 'cbRegionCheck', false], // region
'sv' => [parent::V_CALLBACK, 'cbServerCheck', false], // server
);
public bool $useLocalList = false;
public array $extraOpts = [];
/* heads up!
a couple of filters are too complex to be run against the characters database
if they are selected, force useage of LocalProfileList
*/
public function __construct(string|array $data, array $opts = [])
{
parent::__construct($data, $opts);
if (!empty($this->criteria['cr']))
if (array_intersect($this->criteria['cr'], [2, 5, 6, 7, 21]))
$this->useLocalList = true;
}
protected function createSQLForValues() : array
{
$parts = [];
$_v = $this->values;
// region (rg), battlegroup (bg) and server (sv) are passed to ProflieList as miscData and handled there
// table key differs between remote and local :<
$k = $this->useLocalList ? 'p' : 'c';
// name [str] - the table is case sensitive. Since i don't want to destroy indizes, lets alter the search terms
if ($_v['na'])
{
$lower = $this->tokenizeString([$k.'.name'], Util::lower($_v['na']), $_v['ex'] == 'on', true);
$proper = $this->tokenizeString([$k.'.name'], Util::ucWords($_v['na']), $_v['ex'] == 'on', true);
$parts[] = ['OR', $lower, $proper];
}
// side [list]
if ($_v['si'] == SIDE_ALLIANCE)
$parts[] = [$k.'.race', ChrRace::fromMask(ChrRace::MASK_ALLIANCE)];
else if ($_v['si'] == SIDE_HORDE)
$parts[] = [$k.'.race', ChrRace::fromMask(ChrRace::MASK_HORDE)];
// race [list]
if ($_v['ra'])
$parts[] = [$k.'.race', $_v['ra']];
// class [list]
if ($_v['cl'])
$parts[] = [$k.'.class', $_v['cl']];
// min level [int]
if ($_v['minle'])
$parts[] = [$k.'.level', $_v['minle'], '>='];
// max level [int]
if ($_v['maxle'])
$parts[] = [$k.'.level', $_v['maxle'], '<='];
return $parts;
}
protected function cbProfession(int $cr, int $crs, string $crv, $skillId) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
$k = 'sk_'.Util::createHash(12);
$col = 'skill-'.$skillId;
$this->fiExtraCols[$skillId] = $col;
if ($this->useLocalList)
{
$this->extraOpts[$k] = array(
'j' => [sprintf('?_profiler_completion_skills %1$s ON `%1$s`.`id` = p.`id` AND `%1$s`.`skillId` = %2$d AND `%1$s`.`value` %3$s %4$d', $k, $skillId, $crs, $crv), true],
's' => [', '.$k.'.`value` AS "'.$col.'"']
);
return [$k.'.skillId', null, '!'];
}
else
{
$this->extraOpts[$k] = array(
'j' => [sprintf('character_skills %1$s ON `%1$s`.`guid` = c.`guid` AND `%1$s`.`skill` = %2$d AND `%1$s`.`value` %3$s %4$d', $k, $skillId, $crs, $crv), true],
's' => [', '.$k.'.`value` AS "'.$col.'"']
);
return [$k.'.skill', null, '!'];
}
}
protected function cbCompletedAcv(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT))
return null;
if (!Type::validateIds(Type::ACHIEVEMENT, $crv))
return null;
$k = 'acv_'.Util::createHash(12);
if ($this->useLocalList)
{
$this->extraOpts[$k] = ['j' => [sprintf('?_profiler_completion_achievements %1$s ON `%1$s`.`id` = p.`id` AND `%1$s`.`achievementId` = %2$d', $k, $crv), true]];
return [$k.'.achievementId', null, '!'];
}
else
{
$this->extraOpts[$k] = ['j' => [sprintf('character_achievement %1$s ON `%1$s`.`guid` = c.`guid` AND `%1$s`.`achievement` = %2$d', $k, $crv), true]];
return [$k.'.achievement', null, '!'];
}
}
protected function cbWearsItems(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT))
return null;
if (!Type::validateIds(Type::ITEM, $crv))
return null;
$k = 'i_'.Util::createHash(12);
$this->extraOpts[$k] = ['j' => [sprintf('?_profiler_items %1$s ON `%1$s`.`id` = p.`id` AND `%1$s`.`item` = %2$d', $k, $crv), true]];
return [$k.'.item', null, '!'];
}
protected function cbHasGuild(int $cr, int $crs, string $crv) : ?array
{
if (!$this->int2Bool($crs))
return null;
if ($this->useLocalList)
return ['p.guild', null, $crs ? '!' : null];
else
return ['gm.guildId', null, $crs ? '!' : null];
}
protected function cbHasGuildRank(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
if ($this->useLocalList)
return ['p.guildrank', $crv, $crs];
else
return ['gm.rank', $crv, $crs];
}
protected function cbTeamName(int $cr, int $crs, string $crv, $size) : ?array
{
if ($_ = $this->tokenizeString(['at.name'], $crv))
return ['AND', ['at.type', $size], $_];
return null;
}
protected function cbTeamRating(int $cr, int $crs, string $crv, $size) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
return ['AND', ['at.type', $size], ['at.rating', $crv, $crs]];
}
protected function cbAchievs(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
if ($this->useLocalList)
return ['p.achievementpoints', $crv, $crs];
else
return ['cap.counter', $crv, $crs];
}
}
class RemoteProfileList extends ProfileList
{
protected string $queryBase = 'SELECT `c`.*, `c`.`guid` AS ARRAY_KEY FROM characters c';
protected array $queryOpts = array(
'c' => [['gm', 'g', 'cap']], // 12698: use criteria of Achievement 4496 as shortcut to get total achievement points
'cap' => ['j' => ['character_achievement_progress cap ON cap.`guid` = c.`guid` AND cap.`criteria` = 12698', true], 's' => ', IFNULL(cap.`counter`, 0) AS "achievementpoints"'],
'gm' => ['j' => ['guild_member gm ON gm.`guid` = c.`guid`', true], 's' => ', gm.`rank` AS "guildrank"'],
'g' => ['j' => ['guild g ON g.`guildid` = gm.`guildid`', true], 's' => ', g.`guildid` AS "guild", g.`name` AS "guildname"'],
'atm' => ['j' => ['arena_team_member atm ON atm.`guid` = c.`guid`', true], 's' => ', atm.`personalRating` AS "rating"'],
'at' => [['atm'], 'j' => 'arena_team at ON atm.`arenaTeamId` = at.`arenaTeamId`', 's' => ', at.`name` AS "arenateam", IF(at.`captainGuid` = c.`guid`, 1, 0) AS "captain"']
);
private array $rnItr = []; // rename iterator [name => nCharsWithThisName]
public function __construct(array $conditions = [], array $miscData = [])
{
// select DB by realm
if (!$this->selectRealms($miscData))
{
trigger_error('RemoteProfileList::__construct - cannot access any realm.', E_USER_WARNING);
return;
}
parent::__construct($conditions, $miscData);
if ($this->error)
return;
reset($this->dbNames); // only use when querying single realm
$realmId = key($this->dbNames);
$realms = Profiler::getRealms();
$talentSpells = [];
$talentLookup = [];
$distrib = [];
// post processing
foreach ($this->iterate() as $guid => &$curTpl)
{
// battlegroup
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
// realm
[$r, $g] = explode(':', $guid);
if (!empty($realms[$r]))
{
$curTpl['realm'] = $r;
$curTpl['realmName'] = $realms[$r]['name'];
$curTpl['region'] = $realms[$r]['region'];
}
else
{
trigger_error('char #'.$guid.' belongs to nonexistent realm #'.$r, E_USER_WARNING);
unset($this->templates[$guid]);
continue;
}
// empty name
if (!$curTpl['name'])
{
trigger_error('char #'.$guid.' on realm #'.$r.' has empty name.', E_USER_WARNING);
unset($this->templates[$guid]);
continue;
}
// temp id
$curTpl['id'] = 0;
// talent points pre
$talentLookup[$r][$g] = [];
$talentSpells[] = $curTpl['class'];
$curTpl['activespec'] = $curTpl['activeTalentGroup'];
// equalize distribution
if (empty($distrib[$curTpl['realm']]))
$distrib[$curTpl['realm']] = 1;
else
$distrib[$curTpl['realm']]++;
// char is pending rename
if ($curTpl['at_login'] & 0x1)
{
if (!isset($this->rnItr[$curTpl['name']]))
$this->rnItr[$curTpl['name']] = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND `name` = ?', $r, $curTpl['name']) ?: 0;
// already saved as "pending rename"
if ($rnItr = DB::Aowow()->selectCell('SELECT `renameItr` FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` = ?d', $r, $g))
$curTpl['renameItr'] = $rnItr;
// not yet recognized: get max itr
else
$curTpl['renameItr'] = ++$this->rnItr[$curTpl['name']];
}
else
$curTpl['renameItr'] = 0;
$curTpl['cuFlags'] = 0;
}
foreach ($talentLookup as $realm => $chars)
$talentLookup[$realm] = DB::Characters($realm)->selectCol('SELECT `guid` AS ARRAY_KEY, `spell` AS ARRAY_KEY2, `talentGroup` FROM character_talent ct WHERE `guid` IN (?a)', array_keys($chars));
$talentSpells = DB::Aowow()->select('SELECT `spell` AS ARRAY_KEY, `tab`, `rank` FROM ?_talents WHERE `class` IN (?a)', array_unique($talentSpells));
foreach ($conditions as $c)
if (is_int($c))
$limit = $c;
$limit ??= Cfg::get('SQL_LIMIT_DEFAULT');
if (!$limit) // int:0 means unlimited, so skip process
$distrib = [];
$total = array_sum($distrib);
foreach ($distrib as &$d)
$d = ceil($limit * $d / $total);
foreach ($this->iterate() as $guid => &$curTpl)
{
if ($distrib)
{
if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0)
{
unset($this->templates[$guid]);
continue;
}
$distrib[$curTpl['realm']]--;
$limit--;
}
[$r, $g] = explode(':', $guid);
// talent points post
$curTpl['talenttree1'] = 0;
$curTpl['talenttree2'] = 0;
$curTpl['talenttree3'] = 0;
if (!empty($talentLookup[$r][$g]))
{
$talents = array_filter($talentLookup[$r][$g], function($v) use ($curTpl) { return $curTpl['activespec'] == $v; } );
foreach (array_intersect_key($talentSpells, $talents) as $spell => $data)
$curTpl['talenttree'.($data['tab'] + 1)] += $data['rank'];
}
}
}
public function getListviewData(int $addInfoMask = 0, array $reqCols = []) : array
{
$data = parent::getListviewData($addInfoMask, $reqCols);
// not wanted on server list
foreach ($data as &$d)
unset($d['published']);
return $data;
}
public function initializeLocalEntries() : void
{
$baseData = $guildData = [];
foreach ($this->iterate() as $guid => $__)
{
$realmId = $this->getField('realm');
$guildGUID = $this->getField('guild');
$baseData[$guid] = array(
'realm' => $realmId,
'realmGUID' => $this->getField('guid'),
'name' => $this->getField('name'),
'renameItr' => $this->getField('renameItr'),
'race' => $this->getField('race'),
'class' => $this->getField('class'),
'level' => $this->getField('level'),
'gender' => $this->getField('gender'),
'guild' => $guildGUID ?: null,
'guildrank' => $guildGUID ? $this->getField('guildrank') : null,
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
);
if ($guildGUID && empty($guildData[$realmId.'-'.$guildGUID]))
$guildData[$realmId.'-'.$guildGUID] = array(
'realm' => $realmId,
'realmGUID' => $guildGUID,
'name' => $this->getField('guildname'),
'nameUrl' => Profiler::urlize($this->getField('guildname')),
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
);
}
// basic guild data (satisfying table constraints)
if ($guildData)
{
foreach (Util::createSqlBatchInsert($guildData) as $ins)
DB::Aowow()->query('INSERT INTO ?_profiler_guild (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `id` = `id`', array_keys(reset($guildData)));
// merge back local ids
$localGuilds = DB::Aowow()->selectCol('SELECT `realm` AS ARRAY_KEY, `realmGUID` AS ARRAY_KEY2, `id` FROM ?_profiler_guild WHERE `realm` IN (?a) AND `realmGUID` IN (?a)',
array_column($guildData, 'realm'), array_column($guildData, 'realmGUID')
);
foreach ($baseData as &$bd)
if ($bd['guild'])
$bd['guild'] = $localGuilds[$bd['realm']][$bd['guild']];
}
// basic char data (enough for tooltips)
if ($baseData)
{
foreach ($baseData as $ins)
DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES (?a) ON DUPLICATE KEY UPDATE `name` = ?, `renameItr` = ?d', array_keys($ins), array_values($ins), $ins['name'], $ins['renameItr']);
// merge back local ids
$localIds = DB::Aowow()->select(
'SELECT CONCAT(`realm`, ":", `realmGUID`) AS ARRAY_KEY, `id`, `gearscore` FROM ?_profiler_profiles WHERE (`cuFlags` & ?d) = 0 AND `realm` IN (?a) AND `realmGUID` IN (?a)',
PROFILER_CU_PROFILE,
array_column($baseData, 'realm'),
array_column($baseData, 'realmGUID')
);
foreach ($this->iterate() as $guid => &$_curTpl)
if (isset($localIds[$guid]))
$_curTpl = array_merge($_curTpl, $localIds[$guid]);
}
}
}
class LocalProfileList extends ProfileList
{
protected string $queryBase = 'SELECT p.*, p.`id` AS ARRAY_KEY FROM ?_profiler_profiles p';
protected array $queryOpts = array(
'p' => [['g'], 'g' => 'p.`id`'],
'ap' => ['j' => ['?_account_profiles ap ON ap.`profileId` = p.`id`', true], 's' => ', (IFNULL(ap.`ExtraFlags`, 0) | p.`cuFlags`) AS "cuFlags"'],
'atm' => ['j' => ['?_profiler_arena_team_member atm ON atm.`profileId` = p.`id`', true], 's' => ', atm.`captain`, atm.`personalRating` AS "rating", atm.`seasonGames`, atm.`seasonWins`'],
'at' => [['atm'], 'j' => ['?_profiler_arena_team at ON at.`id` = atm.`arenaTeamId`', true], 's' => ', at.`type`'],
'g' => ['j' => ['?_profiler_guild g ON g.`id` = p.`guild`', true], 's' => ', g.`name` AS "guildname"']
);
public function __construct(array $conditions = [], array $miscData = [])
{
$realms = Profiler::getRealms();
// graft realm selection from miscData onto conditions
$realmIds = [];
if (isset($miscData['sv']))
$realmIds = array_keys(array_filter($realms, fn($x) => Profiler::urlize($x['name']) == Profiler::urlize($miscData['sv'])));
if (isset($miscData['rg']))
$realmIds = array_merge($realmIds, array_keys(array_filter($realms, fn($x) => $x['region'] == $miscData['rg'])));
if ($conditions && $realmIds)
{
array_unshift($conditions, 'AND');
$conditions = ['AND', ['realm', $realmIds], $conditions];
}
else if ($realmIds)
$conditions = [['realm', $realmIds]];
parent::__construct($conditions, $miscData);
if ($this->error)
return;
foreach ($this->iterate() as $id => &$curTpl)
{
if (!$curTpl['realm']) // custom profile w/o realminfo
continue;
if (!isset($realms[$curTpl['realm']]))
{
unset($this->templates[$id]);
continue;
}
$curTpl['realmName'] = $realms[$curTpl['realm']]['name'];
$curTpl['region'] = $realms[$curTpl['realm']]['region'];
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
}
}
public function getProfileUrl() : string
{
$url = '?profile=';
if ($this->isCustom())
return $url.$this->getField('id');
return $url.implode('.', array(
$this->getField('region'),
Profiler::urlize($this->getField('realmName'), true),
urlencode($this->getField('name'))
));
}
}
?>

View File

@@ -0,0 +1,720 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class QuestList extends DBTypeList
{
public static int $type = Type::QUEST;
public static string $brickFile = 'quest';
public static string $dataTable = '?_quests';
public array $requires = [];
public array $rewards = [];
public array $choices = [];
protected string $queryBase = 'SELECT q.*, q.`id` AS ARRAY_KEY FROM ?_quests q';
protected array $queryOpts = array(
'q' => [],
'rsc' => ['j' => '?_spell rsc ON q.`rewardSpellCast` = rsc.`id`'], // limit rewardSpellCasts
'qse' => ['j' => '?_quests_startend qse ON q.`id` = qse.`questId`', 's' => ', qse.`method`'], // groupConcat..?
'e' => ['j' => ['?_events e ON e.`id` = q.`eventId`', true], 's' => ', e.`holidayId`']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
// i don't like this very much
$currencies = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `itemId` FROM ?_currencies');
// post processing
foreach ($this->iterate() as $id => &$_curTpl)
{
$_curTpl['cat1'] = $_curTpl['zoneOrSort']; // should probably be in a method...
$_curTpl['cat2'] = 0;
foreach (Game::$questClasses as $k => $arr)
{
if (in_array($_curTpl['cat1'], $arr))
{
$_curTpl['cat2'] = $k;
break;
}
}
// store requirements
$requires = [];
for ($i = 1; $i < 7; $i++)
{
if ($_ = $_curTpl['reqItemId'.$i])
$requires[Type::ITEM][] = $_;
if ($i > 4)
continue;
if ($_curTpl['reqNpcOrGo'.$i] > 0)
$requires[Type::NPC][] = $_curTpl['reqNpcOrGo'.$i];
else if ($_curTpl['reqNpcOrGo'.$i] < 0)
$requires[Type::OBJECT][] = -$_curTpl['reqNpcOrGo'.$i];
if ($_ = $_curTpl['reqSourceItemId'.$i])
$requires[Type::ITEM][] = $_;
}
if ($requires)
$this->requires[$id] = $requires;
// store rewards
$rewards = [];
$choices = [];
if ($_ = $_curTpl['rewardTitleId'])
$rewards[Type::TITLE][] = $_;
if ($_ = $_curTpl['rewardHonorPoints'])
$rewards[Type::CURRENCY][CURRENCY_HONOR_POINTS] = $_;
if ($_ = $_curTpl['rewardArenaPoints'])
$rewards[Type::CURRENCY][CURRENCY_ARENA_POINTS] = $_;
for ($i = 1; $i < 7; $i++)
{
if ($_ = $_curTpl['rewardChoiceItemId'.$i])
$choices[Type::ITEM][$_] = $_curTpl['rewardChoiceItemCount'.$i];
if ($i > 5)
continue;
if ($_ = $_curTpl['rewardFactionId'.$i])
$rewards[Type::FACTION][$_] = $_curTpl['rewardFactionValue'.$i];
if ($i > 4)
continue;
if ($_ = $_curTpl['rewardItemId'.$i])
{
$qty = $_curTpl['rewardItemCount'.$i];
if (in_array($_, $currencies))
$rewards[Type::CURRENCY][array_search($_, $currencies)] = $qty;
else
$rewards[Type::ITEM][$_] = $qty;
}
}
if ($rewards)
$this->rewards[$id] = $rewards;
if ($choices)
$this->choices[$id] = $choices;
}
}
public function isRepeatable() : bool
{
return $this->curTpl['flags'] & QUEST_FLAG_REPEATABLE || $this->curTpl['specialFlags'] & QUEST_FLAG_SPECIAL_REPEATABLE;
}
public function isDaily() : int
{
if ($this->curTpl['flags'] & QUEST_FLAG_DAILY)
return 1;
if ($this->curTpl['flags'] & QUEST_FLAG_WEEKLY)
return 2;
if ($this->curTpl['specialFlags'] & QUEST_FLAG_SPECIAL_MONTHLY)
return 3;
return 0;
}
// using reqPlayerKills and rewardHonor as a crutch .. has TC this even implemented..?
public function isPvPEnabled() : bool
{
return $this->curTpl['reqPlayerKills'] || $this->curTpl['rewardHonorPoints'] || $this->curTpl['rewardArenaPoints'];
}
// by TC definition
public function isSeasonal() : bool
{
return in_array($this->getField('zoneOrSortBak'), [-22, -284, -366, -369, -370, -376, -374]) && !$this->isRepeatable();
}
public function getSourceData(int $id = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if ($id && $id != $this->id)
continue;
$data[$this->id] = array(
"n" => $this->getField('name', true),
"t" => Type::QUEST,
"ti" => $this->id,
"c" => $this->curTpl['cat1'],
"c2" => $this->curTpl['cat2']
);
}
return $data;
}
public function getSOMData(int $side = SIDE_BOTH) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if (!(ChrRace::sideFromMask($this->curTpl['reqRaceMask']) & $side))
continue;
[$series, $first] = DB::Aowow()->SelectRow(
'SELECT IF(prev.`id` OR cur.`nextQuestIdChain`, 1, 0) AS "0", IF(prev.`id` IS NULL AND cur.`nextQuestIdChain`, 1, 0) AS "1" FROM ?_quests cur LEFT JOIN ?_quests prev ON prev.`nextQuestIdChain` = cur.`id` WHERE cur.`id` = ?d',
$this->id
);
$data[$this->id] = array(
'level' => $this->curTpl['level'] < 0 ? MAX_LEVEL : $this->curTpl['level'],
'name' => $this->getField('name', true),
'category' => $this->curTpl['cat1'],
'category2' => $this->curTpl['cat2'],
'series' => $series,
'first' => $first
);
if ($this->isDaily())
$data[$this->id]['daily'] = 1;
}
return $data;
}
public function getListviewData(int $extraFactionId = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'category' => $this->curTpl['cat1'],
'category2' => $this->curTpl['cat2'],
'id' => $this->id,
'level' => $this->curTpl['level'],
'reqlevel' => $this->curTpl['minLevel'],
'name' => Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_RAW),
'side' => ChrRace::sideFromMask($this->curTpl['reqRaceMask']),
'wflags' => 0x0,
'xp' => $this->curTpl['rewardXP']
);
if (!empty($this->rewards[$this->id][Type::CURRENCY]))
foreach ($this->rewards[$this->id][Type::CURRENCY] as $iId => $qty)
$data[$this->id]['currencyrewards'][] = [$iId, $qty];
if (!empty($this->rewards[$this->id][Type::ITEM]))
foreach ($this->rewards[$this->id][Type::ITEM] as $iId => $qty)
$data[$this->id]['itemrewards'][] = [$iId, $qty];
if (!empty($this->choices[$this->id][Type::ITEM]))
foreach ($this->choices[$this->id][Type::ITEM] as $iId => $qty)
$data[$this->id]['itemchoices'][] = [$iId, $qty];
if ($_ = $this->curTpl['rewardTitleId'])
$data[$this->id]['titlereward'] = $_;
if ($_ = $this->curTpl['type'])
$data[$this->id]['type'] = $_;
if ($_ = $this->curTpl['reqClassMask'])
$data[$this->id]['reqclass'] = $_;
if ($_ = ($this->curTpl['reqRaceMask'] & ChrRace::MASK_ALL))
if ((($_ & ChrRace::MASK_ALLIANCE) != ChrRace::MASK_ALLIANCE) && (($_ & ChrRace::MASK_HORDE) != ChrRace::MASK_HORDE))
$data[$this->id]['reqrace'] = $_;
if ($_ = $this->curTpl['rewardOrReqMoney'])
if ($_ > 0)
$data[$this->id]['money'] = $_;
// todo (med): also get disables
if ($this->curTpl['flags'] & QUEST_FLAG_UNAVAILABLE)
$data[$this->id]['historical'] = true;
// if ($this->isRepeatable()) // dafuque..? says repeatable and is used as 'disabled'..?
// $data[$this->id]['wflags'] |= QUEST_CU_REPEATABLE;
if ($this->curTpl['cuFlags'] & (CUSTOM_UNAVAILABLE | CUSTOM_DISABLED))
$data[$this->id]['wflags'] |= QUEST_CU_REPEATABLE;
if ($this->curTpl['flags'] & QUEST_FLAG_DAILY)
{
$data[$this->id]['wflags'] |= QUEST_CU_DAILY;
$data[$this->id]['daily'] = true;
}
if ($this->curTpl['flags'] & QUEST_FLAG_WEEKLY)
{
$data[$this->id]['wflags'] |= QUEST_CU_WEEKLY;
$data[$this->id]['weekly'] = true;
}
if ($this->isSeasonal())
$data[$this->id]['wflags'] |= QUEST_CU_SEASONAL;
if ($this->curTpl['flags'] & QUEST_FLAG_AUTO_REWARDED) // not shown in log
$data[$this->id]['wflags'] |= QUEST_CU_SKIP_LOG;
if ($this->curTpl['flags'] & QUEST_FLAG_AUTO_ACCEPT) // self-explanatory
$data[$this->id]['wflags'] |= QUEST_CU_AUTO_ACCEPT;
if ($this->isPvPEnabled()) // not sure why this flag also requires auto-accept to be set
$data[$this->id]['wflags'] |= (QUEST_CU_AUTO_ACCEPT | QUEST_CU_PVP_ENABLED);
$data[$this->id]['reprewards'] = [];
for ($i = 1; $i < 6; $i++)
{
$foo = $this->curTpl['rewardFactionId'.$i];
$bar = $this->curTpl['rewardFactionValue'.$i];
if ($foo && $bar)
{
$data[$this->id]['reprewards'][] = [$foo, $bar];
if ($extraFactionId == $foo)
$data[$this->id]['reputation'] = $bar;
}
}
}
return $data;
}
public function parseText(string $type = 'objectives', bool $jsEscaped = true) : string
{
$text = $this->getField($type, true);
if (!$text)
return '';
$text = Util::parseHtmlText($text);
if ($jsEscaped)
$text = Util::jsEscape($text);
return $text;
}
public function renderTooltip() : ?string
{
if (!$this->curTpl)
return null;
$title = Lang::unescapeUISequences(Util::htmlEscape($this->getField('name', true)), Lang::FMT_HTML);
$level = $this->curTpl['level'];
if ($level < 0)
$level = 0;
$x = '';
if ($level)
{
$level = sprintf(Lang::quest('questLevel'), $level);
if ($this->curTpl['flags'] & QUEST_FLAG_DAILY) // daily
$level .= ' '.Lang::quest('daily');
$x .= '<table><tr><td><table width="100%"><tr><td><b class="q">'.$title.'</b></td><th><b class="q0">'.$level.'</b></th></tr></table></td></tr></table>';
}
else
$x .= '<table><tr><td><b class="q">'.$title.'</b></td></tr></table>';
$x .= '<table><tr><td><br />'.$this->parseText('objectives', false);
$xReq = '';
for ($i = 1; $i < 5; $i++)
{
$ot = $this->getField('objectiveText'.$i, true);
$rng = $this->curTpl['reqNpcOrGo'.$i];
$rngQty = $this->curTpl['reqNpcOrGoCount'.$i];
if ($rngQty < 1 && (!$rng || $ot))
continue;
if ($ot)
$name = $ot;
else
$name = $rng > 0 ? CreatureList::getName($rng) : Lang::unescapeUISequences(GameObjectList::getName(-$rng), Lang::FMT_HTML);
if (!$name)
$name = Util::ucFirst(Lang::game($rng > 0 ? 'npc' : 'object')).' #'.abs($rng);
$xReq .= '<br /> - '.$name.($rngQty > 1 ? ' x '.$rngQty : '');
}
for ($i = 1; $i < 7; $i++)
{
$ri = $this->curTpl['reqItemId'.$i];
$riQty = $this->curTpl['reqItemCount'.$i];
if (!$ri || $riQty < 1)
continue;
$name = Lang::unescapeUISequences(ItemList::getName($ri), Lang::FMT_HTML) ?: Util::ucFirst(Lang::game('item')).' #'.$ri;
$xReq .= '<br /> - '.$name.($riQty > 1 ? ' x '.$riQty : '');
}
if ($et = $this->getField('end', true))
$xReq .= '<br /> - '.$et;
if ($_ = $this->getField('rewardOrReqMoney'))
if ($_ < 0)
$xReq .= '<br /> - '.Lang::quest('money').Lang::main('colon').Util::formatMoney(abs($_));
if ($xReq)
$x .= '<br /><br /><span class="q">'.Lang::quest('requirements').Lang::main('colon').'</span>'.$xReq;
$x .= '</td></tr></table>';
return $x;
}
public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
if ($addMask & GLOBALINFO_REWARDS)
{
// items
for ($i = 1; $i < 5; $i++)
if ($this->curTpl['rewardItemId'.$i] > 0)
$data[Type::ITEM][$this->curTpl['rewardItemId'.$i]] = $this->curTpl['rewardItemId'.$i];
for ($i = 1; $i < 7; $i++)
if ($this->curTpl['rewardChoiceItemId'.$i] > 0)
$data[Type::ITEM][$this->curTpl['rewardChoiceItemId'.$i]] = $this->curTpl['rewardChoiceItemId'.$i];
// spells
if ($this->curTpl['rewardSpell'] > 0)
$data[Type::SPELL][$this->curTpl['rewardSpell']] = $this->curTpl['rewardSpell'];
if ($this->curTpl['rewardSpellCast'] > 0)
$data[Type::SPELL][$this->curTpl['rewardSpellCast']] = $this->curTpl['rewardSpellCast'];
// titles
if ($this->curTpl['rewardTitleId'] > 0)
$data[Type::TITLE][$this->curTpl['rewardTitleId']] = $this->curTpl['rewardTitleId'];
// currencies
if (!empty($this->rewards[$this->id][Type::CURRENCY]))
foreach ($this->rewards[$this->id][Type::CURRENCY] as $id => $__)
$data[Type::CURRENCY][$id] = $id;
}
if ($addMask & GLOBALINFO_SELF)
$data[Type::QUEST][$this->id] = ['name' => $this->getField('name', true)];
}
return $data;
}
}
class QuestListFilter extends Filter
{
protected string $type = 'quests';
protected static array $enums = array(
37 => parent::ENUM_CLASSS, // classspecific
38 => parent::ENUM_RACE, // racespecific
9 => parent::ENUM_FACTION, // objectiveearnrepwith
33 => parent::ENUM_EVENT, // relatedevent
43 => parent::ENUM_CURRENCY, // currencyrewarded
1 => parent::ENUM_FACTION, // increasesrepwith
10 => parent::ENUM_FACTION // decreasesrepwith
);
protected static array $genericFilter = array(
1 => [parent::CR_CALLBACK, 'cbReputation', '>', null], // increasesrepwith
2 => [parent::CR_NUMERIC, 'rewardXP', NUM_CAST_INT ], // experiencegained
3 => [parent::CR_NUMERIC, 'rewardOrReqMoney', NUM_CAST_INT ], // moneyrewarded
4 => [parent::CR_CALLBACK, 'cbSpellRewards', null, null], // spellrewarded [yn]
5 => [parent::CR_FLAG, 'flags', QUEST_FLAG_SHARABLE ], // sharable
6 => [parent::CR_NUMERIC, 'timeLimit', NUM_CAST_INT ], // timer
7 => [parent::CR_FLAG, 'cuFlags', QUEST_CU_FIRST_SERIES ], // firstquestseries
9 => [parent::CR_CALLBACK, 'cbEarnReputation', null, null], // objectiveearnrepwith [enum]
10 => [parent::CR_CALLBACK, 'cbReputation', '<', null], // decreasesrepwith
11 => [parent::CR_NUMERIC, 'suggestedPlayers', NUM_CAST_INT ], // suggestedplayers
15 => [parent::CR_FLAG, 'cuFlags', QUEST_CU_LAST_SERIES ], // lastquestseries
16 => [parent::CR_FLAG, 'cuFlags', QUEST_CU_PART_OF_SERIES ], // partseries
18 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
19 => [parent::CR_CALLBACK, 'cbQuestRelation', 0x1, null], // startsfrom [enum]
21 => [parent::CR_CALLBACK, 'cbQuestRelation', 0x2, null], // endsat [enum]
22 => [parent::CR_CALLBACK, 'cbItemRewards', null, null], // itemrewards [op] [int]
23 => [parent::CR_CALLBACK, 'cbItemChoices', null, null], // itemchoices [op] [int]
24 => [parent::CR_CALLBACK, 'cbLacksStartEnd', null, null], // lacksstartend [yn]
25 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
27 => [parent::CR_FLAG, 'flags', QUEST_FLAG_DAILY ], // daily
28 => [parent::CR_FLAG, 'flags', QUEST_FLAG_WEEKLY ], // weekly
29 => [parent::CR_CALLBACK, 'cbRepeatable', null ], // repeatable
30 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
33 => [parent::CR_ENUM, 'e.holidayId', true, true], // relatedevent
34 => [parent::CR_CALLBACK, 'cbAvailable', null, null], // availabletoplayers [yn]
36 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
37 => [parent::CR_CALLBACK, 'cbClassSpec', null, null], // classspecific [enum]
38 => [parent::CR_CALLBACK, 'cbRaceSpec', null, null], // racespecific [enum]
42 => [parent::CR_STAFFFLAG, 'flags' ], // flags
43 => [parent::CR_CALLBACK, 'cbCurrencyReward', null, null], // currencyrewarded [enum]
44 => [parent::CR_CALLBACK, 'cbLoremaster', null, null], // countsforloremaster_stc [yn]
45 => [parent::CR_BOOLEAN, 'rewardTitleId' ] // titlerewarded
);
protected static array $inputFields = array(
'cr' => [parent::V_RANGE, [1, 45], true ], // criteria ids
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 99999]], true ], // criteria operators
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - only numerals
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / text - only printable chars, no delimiter
'ex' => [parent::V_EQUAL, 'on', false], // also match subname
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'minle' => [parent::V_RANGE, [1, 99], false], // min quest level
'maxle' => [parent::V_RANGE, [1, 99], false], // max quest level
'minrl' => [parent::V_RANGE, [1, 99], false], // min required level
'maxrl' => [parent::V_RANGE, [1, 99], false], // max required level
'si' => [parent::V_LIST, [-SIDE_HORDE, -SIDE_ALLIANCE, SIDE_ALLIANCE, SIDE_HORDE, SIDE_BOTH], false], // side
'ty' => [parent::V_LIST, [0, 1, 21, 41, 62, [81, 85], 88, 89], true ] // type
);
public array $extraOpts = [];
protected function createSQLForValues() : array
{
$parts = [];
$_v = $this->values;
// name
if ($_v['na'])
{
$_ = [];
if ($_v['ex'] == 'on')
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value, 'objectives_loc'.Lang::getLocale()->value, 'details_loc'.Lang::getLocale()->value]);
else
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]);
if ($_)
$parts[] = $_;
}
// level min
if ($_v['minle'])
$parts[] = ['level', $_v['minle'], '>=']; // not considering quests that are always at player level (-1)
// level max
if ($_v['maxle'])
$parts[] = ['level', $_v['maxle'], '<='];
// reqLevel min
if ($_v['minrl'])
$parts[] = ['minLevel', $_v['minrl'], '>=']; // ignoring maxLevel
// reqLevel max
if ($_v['maxrl'])
$parts[] = ['minLevel', $_v['maxrl'], '<=']; // ignoring maxLevel
// side
if ($_v['si'])
{
$excl = [['reqRaceMask', ChrRace::MASK_ALL, '&'], ChrRace::MASK_ALL, '!'];
$incl = ['OR', ['reqRaceMask', 0], [['reqRaceMask', ChrRace::MASK_ALL, '&'], ChrRace::MASK_ALL]];
$parts[] = match ($_v['si'])
{
SIDE_BOTH => $incl,
SIDE_HORDE => ['OR', $incl, ['reqRaceMask', ChrRace::MASK_HORDE, '&']],
-SIDE_HORDE => ['AND', $excl, ['reqRaceMask', ChrRace::MASK_HORDE, '&']],
SIDE_ALLIANCE => ['OR', $incl, ['reqRaceMask', ChrRace::MASK_ALLIANCE, '&']],
-SIDE_ALLIANCE => ['AND', $excl, ['reqRaceMask', ChrRace::MASK_ALLIANCE, '&']]
};
}
// type [list]
if ($_v['ty'] !== null)
$parts[] = ['type', $_v['ty']];
return $parts;
}
protected function cbReputation(int $cr, int $crs, string $crv, string $sign) : ?array
{
if (!Util::checkNumeric($crs, NUM_CAST_INT))
return null;
if (!in_array($crs, self::$enums[$cr]))
return null;
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_factions WHERE `id` = ?d', $crs))
$this->fiReputationCols[] = [$crs, Util::localizedString($_, 'name')];
return [
'OR',
['AND', ['rewardFactionId1', $crs], ['rewardFactionValue1', 0, $sign]],
['AND', ['rewardFactionId2', $crs], ['rewardFactionValue2', 0, $sign]],
['AND', ['rewardFactionId3', $crs], ['rewardFactionValue3', 0, $sign]],
['AND', ['rewardFactionId4', $crs], ['rewardFactionValue4', 0, $sign]],
['AND', ['rewardFactionId5', $crs], ['rewardFactionValue5', 0, $sign]]
];
}
protected function cbQuestRelation(int $cr, int $crs, string $crv, $flags) : ?array
{
return match ($crs)
{
Type::NPC,
Type::OBJECT,
Type::ITEM => ['AND', ['qse.type', $crs], ['qse.method', $flags, '&']],
default => null
};
}
protected function cbCurrencyReward(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crs, NUM_CAST_INT))
return null;
if (!in_array($crs, self::$enums[$cr]))
return null;
return [
'OR',
['rewardItemId1', $crs], ['rewardItemId2', $crs], ['rewardItemId3', $crs], ['rewardItemId4', $crs],
['rewardChoiceItemId1', $crs], ['rewardChoiceItemId2', $crs], ['rewardChoiceItemId3', $crs], ['rewardChoiceItemId4', $crs], ['rewardChoiceItemId5', $crs], ['rewardChoiceItemId6', $crs]
];
}
protected function cbAvailable(int $cr, int $crs, string $crv) : ?array
{
if (!$this->int2Bool($crs))
return null;
if ($crs)
return [['cuFlags', CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'], 0];
else
return ['cuFlags', CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'];
}
protected function cbRepeatable(int $cr, int $crs, string $crv) : ?array
{
if (!$this->int2Bool($crs))
return null;
if ($crs)
return ['OR', ['flags', QUEST_FLAG_REPEATABLE, '&'], ['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE, '&']];
else
return ['AND', [['flags', QUEST_FLAG_REPEATABLE, '&'], 0], [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE, '&'], 0]];
}
protected function cbItemChoices(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
$this->extraOpts['q']['s'][] = ', (IF(`rewardChoiceItemId1`, 1, 0) + IF(`rewardChoiceItemId2`, 1, 0) + IF(`rewardChoiceItemId3`, 1, 0) + IF(`rewardChoiceItemId4`, 1, 0) + IF(`rewardChoiceItemId5`, 1, 0) + IF(`rewardChoiceItemId6`, 1, 0)) AS "numChoices"';
$this->extraOpts['q']['h'][] = '`numChoices` '.$crs.' '.$crv;
return [1];
}
protected function cbItemRewards(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
return null;
$this->extraOpts['q']['s'][] = ', (IF(`rewardItemId1`, 1, 0) + IF(`rewardItemId2`, 1, 0) + IF(`rewardItemId3`, 1, 0) + IF(`rewardItemId4`, 1, 0)) AS "numRewards"';
$this->extraOpts['q']['h'][] = '`numRewards` '.$crs.' '.$crv;
return [1];
}
protected function cbLoremaster(int $cr, int $crs, string $crv) : ?array
{
if (!$this->int2Bool($crs))
return null;
if ($crs)
return ['AND', ['zoneOrSort', 0, '>'], [['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE, '&'], 0], [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY, '&'], 0]];
else
return ['OR', ['zoneOrSort', 0, '<'], ['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE, '&'], ['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY, '&']];
}
protected function cbSpellRewards(int $cr, int $crs, string $crv) : ?array
{
if (!$this->int2Bool($crs))
return null;
if ($crs)
return ['OR', ['sourceSpellId', 0, '>'], ['rewardSpell', 0, '>'], ['rsc.effect1Id', SpellList::EFFECTS_TEACH], ['rsc.effect2Id', SpellList::EFFECTS_TEACH], ['rsc.effect3Id', SpellList::EFFECTS_TEACH]];
else
return ['AND', ['sourceSpellId', 0], ['rewardSpell', 0], ['rewardSpellCast', 0]];
}
protected function cbEarnReputation(int $cr, int $crs, string $crv) : ?array
{
if (!Util::checkNumeric($crs, NUM_CAST_INT))
return null;
if ($crs == parent::ENUM_ANY)
return ['OR', ['reqFactionId1', 0, '>'], ['reqFactionId2', 0, '>']];
else if ($crs == parent::ENUM_NONE)
return ['AND', ['reqFactionId1', 0], ['reqFactionId2', 0]];
else if (in_array($crs, self::$enums[$cr]))
return ['OR', ['reqFactionId1', $crs], ['reqFactionId2', $crs]];
return null;
}
protected function cbClassSpec(int $cr, int $crs, string $crv) : ?array
{
if (!isset(self::$enums[$cr][$crs]))
return null;
$_ = self::$enums[$cr][$crs];
if ($_ === true)
return ['AND', ['reqClassMask', 0, '!'], [['reqClassMask', ChrClass::MASK_ALL, '&'], ChrClass::MASK_ALL, '!']];
else if ($_ === false)
return ['OR', ['reqClassMask', 0], [['reqClassMask', ChrClass::MASK_ALL, '&'], ChrClass::MASK_ALL]];
else if (is_int($_))
return ['AND', ['reqClassMask', ChrClass::from($_)->toMask(), '&'], [['reqClassMask', ChrClass::MASK_ALL, '&'], ChrClass::MASK_ALL, '!']];
return null;
}
protected function cbRaceSpec(int $cr, int $crs, string $crv) : ?array
{
if (!isset(self::$enums[$cr][$crs]))
return null;
$_ = self::$enums[$cr][$crs];
if ($_ === true)
return ['AND', ['reqRaceMask', 0, '!'], [['reqRaceMask', ChrRace::MASK_ALL, '&'], ChrRace::MASK_ALL, '!'], [['reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ChrRace::MASK_ALLIANCE, '!'], [['reqRaceMask', ChrRace::MASK_HORDE, '&'], ChrRace::MASK_HORDE, '!']];
else if ($_ === false)
return ['OR', ['reqRaceMask', 0], ['reqRaceMask', ChrRace::MASK_ALL], ['reqRaceMask', ChrRace::MASK_ALLIANCE], ['reqRaceMask', ChrRace::MASK_HORDE]];
else if (is_int($_))
return ['AND', ['reqRaceMask', ChrRace::from($_)->toMask(), '&'], [['reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ChrRace::MASK_ALLIANCE, '!'], [['reqRaceMask', ChrRace::MASK_HORDE, '&'], ChrRace::MASK_HORDE, '!']];
return null;
}
protected function cbLacksStartEnd(int $cr, int $crs, string $crv) : ?array
{
if (!$this->int2Bool($crs))
return null;
$missing = DB::Aowow()->selectCol('SELECT `questId`, BIT_OR(`method`) AS "se" FROM ?_quests_startend GROUP BY `questId` HAVING "se" <> 3');
if ($crs)
return ['id', $missing];
else
return ['id', $missing, '!'];
}
}
?>

View File

@@ -0,0 +1,77 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class SkillList extends DBTypeList
{
public static int $type = Type::SKILL;
public static string $brickFile = 'skill';
public static string $dataTable = '?_skillline';
protected string $queryBase = 'SELECT sl.*, sl.`id` AS ARRAY_KEY FROM ?_skillline sl';
protected array $queryOpts = array(
'sl' => [['ic']],
'ic' => ['j' => ['?_icons ic ON ic.`id` = sl.`iconId`', true], 's' => ', ic.`name` AS "iconString"'],
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
// post processing
foreach ($this->iterate() as &$_curTpl)
{
$_ = &$_curTpl['specializations']; // shorthand
if (!$_)
$_ = [0, 0, 0, 0, 0];
else
{
$_ = explode(' ', $_);
while (count($_) < 5)
$_[] = 0;
}
if (!$_curTpl['iconId'])
$_curTpl['iconString'] = DEFAULT_ICON;
}
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'category' => $this->curTpl['typeCat'],
'categorybak' => $this->curTpl['categoryId'],
'id' => $this->id,
'name' => $this->getField('name', true),
'profession' => $this->curTpl['professionMask'],
'recipeSubclass' => $this->curTpl['recipeSubClass'],
'specializations' => Util::toJSON($this->curTpl['specializations'], JSON_NUMERIC_CHECK),
'icon' => $this->curTpl['iconString']
);
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[self::$type][$this->id] = ['name' => $this->getField('name', true), 'icon' => $this->curTpl['iconString']];
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,129 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class SoundList extends DBTypeList
{
use spawnHelper;
public static int $type = Type::SOUND;
public static string $brickFile = 'sound';
public static string $dataTable = '?_sounds';
public static int $contribute = CONTRIBUTE_CO;
protected string $queryBase = 'SELECT s.*, s.`id` AS ARRAY_KEY FROM ?_sounds s';
private array $fileBuffer = [];
private static array $fileTypes = [SOUND_TYPE_OGG => MIME_TYPE_OGG, SOUND_TYPE_MP3 => MIME_TYPE_MP3];
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
// post processing
foreach ($this->iterate() as $id => &$_curTpl)
{
$_curTpl['files'] = [];
for ($i = 1; $i < 11; $i++)
{
if ($_curTpl['soundFile'.$i])
{
$this->fileBuffer[$_curTpl['soundFile'.$i]] = null;
$_curTpl['files'][] = &$this->fileBuffer[$_curTpl['soundFile'.$i]];
}
unset($_curTpl['soundFile'.$i]);
}
}
if ($this->fileBuffer)
{
$files = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `id`, `file` AS "title", CAST(`type` AS UNSIGNED) AS "type", `path` FROM ?_sounds_files sf WHERE `id` IN (?a)', array_keys($this->fileBuffer));
foreach ($files as $id => $data)
{
// 3.3.5 bandaid - need fullpath to play via wow API, remove for cata and later
$data['path'] = str_replace('\\', '\\\\', $data['path'] ? $data['path'] . '\\' . $data['title'] : $data['title']);
// skip file extension
$data['title'] = substr($data['title'], 0, -4);
// enum to string
$data['type'] = self::$fileTypes[$data['type']];
// get real url
$data['url'] = Cfg::get('STATIC_URL') . '/wowsounds/' . $data['id'];
// v push v
$this->fileBuffer[$id] = $data;
}
}
}
public static function getName(int $id) : ?LocString
{
if ($n = DB::Aowow()->SelectRow('SELECT `name` AS "name_loc0" FROM ?# WHERE `id` = ?d', self::$dataTable, $id))
return new LocString($n);
return null;
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'type' => $this->getField('cat'),
'name' => $this->getField('name'),
'files' => array_values(array_filter($this->getField('files')))
);
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[self::$type][$this->id] = array(
'name' => $this->getField('name', true),
'type' => $this->getField('cat'),
'files' => array_values(array_filter($this->getField('files')))
);
return $data;
}
public function renderTooltip() : ?string { return null; }
}
class SoundListFilter extends Filter
{
protected string $type = 'sounds';
protected static array $inputFields = array(
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
'ty' => [parent::V_LIST, [[1, 4], 6, 9, 10, 12, 13, 14, 16, 17, [19, 31], 50, 52, 53], true ] // type
);
protected function createSQLForValues() : array
{
$parts = [];
$_v = &$this->values;
// name [str]
if ($_v['na'])
if ($_ = $this->tokenizeString(['name']))
$parts[] = $_;
// type [list]
if ($_v['ty'])
$parts[] = ['cat', $_v['ty']];
return $parts;
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class TitleList extends DBTypeList
{
use listviewHelper;
public static int $type = Type::TITLE;
public static string $brickFile = 'title';
public static string $dataTable = '?_titles';
public array $sources = [];
protected string $queryBase = 'SELECT t.*, t.`id` AS ARRAY_KEY FROM ?_titles t';
protected array $queryOpts = array(
't' => [['src']], // 11: Type::TITLE
'src' => ['j' => ['?_source src ON `type` = 11 AND `typeId` = t.`id`', true], 's' => ', `src13`, `moreType`, `moreTypeId`']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
// post processing
foreach ($this->iterate() as &$_curTpl)
{
// preparse sources - notice: under this system titles can't have more than one source (or two for achivements), which is enough for standard TC cases but may break custom cases
if ($_curTpl['moreType'] == Type::ACHIEVEMENT)
$this->sources[$this->id][SRC_ACHIEVEMENT][] = $_curTpl['moreTypeId'];
else if ($_curTpl['moreType'] == Type::QUEST)
$this->sources[$this->id][SRC_QUEST][] = $_curTpl['moreTypeId'];
else if ($_curTpl['src13'])
$this->sources[$this->id][SRC_CUSTOM_STRING][] = $_curTpl['src13'];
// titles display up to two achievements at once
if ($_curTpl['src12Ext'])
$this->sources[$this->id][SRC_ACHIEVEMENT][] = $_curTpl['src12Ext'];
unset($_curTpl['src12Ext']);
unset($_curTpl['moreType']);
unset($_curTpl['moreTypeId']);
unset($_curTpl['src3']);
// shorthand for more generic access; required by CommunityContent to determine subject
foreach (Locale::cases() as $loc)
if ($loc->validate())
$_curTpl['name'] = new LocString($_curTpl, 'male', fn($x) => trim(str_replace('%s', '', $x)));
// $_curTpl['name_loc'.$loc->value] = trim(str_replace('%s', '', $_curTpl['male_loc'.$loc->value]));
}
}
public static function getName(int $id) : ?LocString
{
if ($n = DB::Aowow()->SelectRow('SELECT `male_loc0`, `male_loc2`, `male_loc3`, `male_loc4`, `male_loc6`, `male_loc8` FROM ?# WHERE `id` = ?d', self::$dataTable, $id))
return new LocString($n, 'male', fn($x) => trim(str_replace('%s', '', $x)));
return null;
}
public function getListviewData() : array
{
$data = [];
$this->createSource();
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'name' => $this->getField('male', true),
'namefemale' => $this->getField('female', true),
'side' => $this->curTpl['side'],
'gender' => $this->curTpl['gender'],
'expansion' => $this->curTpl['expansion'],
'category' => $this->curTpl['category']
);
if (!empty($this->curTpl['source']))
$data[$this->id]['source'] = $this->curTpl['source'];
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[Type::TITLE][$this->id]['name'] = $this->getField('male', true);
if ($_ = $this->getField('female', true))
$data[Type::TITLE][$this->id]['namefemale'] = $_;
}
return $data;
}
private function createSource() : void
{
$sources = array(
SRC_QUEST => [],
SRC_ACHIEVEMENT => [],
SRC_CUSTOM_STRING => []
);
foreach ($this->iterate() as $__)
{
if (empty($this->sources[$this->id]))
continue;
foreach (array_keys($sources) as $srcKey)
if (isset($this->sources[$this->id][$srcKey]))
$sources[$srcKey] = array_merge($sources[$srcKey], $this->sources[$this->id][$srcKey]);
}
// fill in the details
if (!empty($sources[SRC_QUEST]))
$sources[SRC_QUEST] = (new QuestList(array(['id', $sources[SRC_QUEST]])))->getSourceData();
if (!empty($sources[SRC_ACHIEVEMENT]))
$sources[SRC_ACHIEVEMENT] = (new AchievementList(array(['id', $sources[SRC_ACHIEVEMENT]])))->getSourceData();
foreach ($this->sources as $Id => $src)
{
$tmp = [];
// Quest-source
if (isset($src[SRC_QUEST]))
{
foreach ($src[SRC_QUEST] as $s)
{
if (isset($sources[SRC_QUEST][$s]['s']))
$this->faction2Side($sources[SRC_QUEST][$s]['s']);
$tmp[SRC_QUEST][] = $sources[SRC_QUEST][$s];
}
}
// Achievement-source
if (isset($src[SRC_ACHIEVEMENT]))
{
foreach ($src[SRC_ACHIEVEMENT] as $s)
{
if (isset($sources[SRC_ACHIEVEMENT][$s]['s']))
$this->faction2Side($sources[SRC_ACHIEVEMENT][$s]['s']);
$tmp[SRC_ACHIEVEMENT][] = $sources[SRC_ACHIEVEMENT][$s];
}
}
// other source (only one item possible, so no iteration needed)
if (isset($src[SRC_CUSTOM_STRING]))
$tmp[SRC_CUSTOM_STRING] = [Lang::game('pvpSources', $Id)];
$this->templates[$Id]['source'] = $tmp;
}
}
public function getHtmlizedName(int $gender = GENDER_MALE) : string
{
$field = $gender == GENDER_FEMALE ? 'female' : 'male';
return str_replace('%s', '<span class="q0">&lt;'.Util::ucFirst(Lang::main('name')).'&gt;</span>', $this->getField($field, true));
}
public function renderTooltip() : ?string { return null; }
private function faction2Side(int &$faction) : void // thats weird.. and hopefully unique to titles
{
if ($faction == 2) // Horde
$faction = 0;
else if ($faction != 1) // Alliance
$faction = -1; // Both
}
}
?>

View File

@@ -0,0 +1,61 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class UserList extends DBTypeList
{
public static int $type = Type::USER;
public static string $brickFile = 'user';
public static string $dataTable = '';
public static int $contribute = CONTRIBUTE_NONE;
protected string $queryBase = 'SELECT *, a.`id` AS ARRAY_KEY FROM ?_account a';
protected array $queryOpts = array(
'a' => [['r']],
'r' => ['j' => ['?_account_reputation r ON r.`userId` = a.`id`', true], 's' => ', IFNULL(SUM(r.`amount`), 0) AS "reputation"', 'g' => 'a.`id`']
);
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->curTpl['username']] = array(
'border' => 0, // border around avatar (rarityColors)
'roles' => $this->curTpl['userGroups'],
'joined' => date(Util::$dateFormatInternal, $this->curTpl['joinDate']),
'posts' => 0, // forum posts
// 'gold' => 0, // achievement system
// 'silver' => 0, // achievement system
// 'copper' => 0, // achievement system
'reputation' => $this->curTpl['reputation']
);
// custom titles (only seen on user page..?)
if ($_ = $this->curTpl['title'])
$data[$this->curTpl['username']]['title'] = $_;
if ($_ = $this->curTpl['avatar'])
{
$data[$this->curTpl['username']]['avatar'] = is_numeric($_) ? 2 : 1;
$data[$this->curTpl['username']]['avatarmore'] = $_;
}
// more optional data
// sig: markdown formated string (only used in forum?)
// border: seen as null|1|3 .. changes the border around the avatar (i suspect its meaning changed and got decoupled from premium-status with the introduction of patreon-status)
}
return [Type::USER => $data];
}
public function getListviewData() : array { return []; }
public function renderTooltip() : ?string { return null; }
}
?>

View File

@@ -0,0 +1,190 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class WorldEventList extends DBTypeList
{
public static int $type = Type::WORLDEVENT;
public static string $brickFile = 'event';
public static string $dataTable = '?_events';
protected string $queryBase = 'SELECT e.`holidayId`, e.`cuFlags`, e.`startTime`, e.`endTime`, e.`occurence`, e.`length`, e.`requires`, e.`description` AS "nameINT", e.`id` AS "eventId", e.`id` AS "ARRAY_KEY", h.* FROM ?_events e';
protected array $queryOpts = array(
'e' => [['h']],
'h' => ['j' => ['?_holidays h ON e.`holidayId` = h.`id`', true], 'o' => '-e.`id` ASC']
);
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
// unseting elements while we iterate over the array will cause the pointer to reset
$replace = [];
// post processing
foreach ($this->iterate() as $__)
{
// emulate category
$sT = $this->curTpl['scheduleType'];
if (!$this->curTpl['holidayId'])
$this->curTpl['category'] = 0;
else if ($sT == 2)
$this->curTpl['category'] = 3;
else if (in_array($sT, [0, 1]))
$this->curTpl['category'] = 2;
else if ($sT == -1)
$this->curTpl['category'] = 1;
// preparse requisites
if ($this->curTpl['requires'])
$this->curTpl['requires'] = explode(' ', $this->curTpl['requires']);
// change Ids if holiday is set
if ($this->curTpl['holidayId'] > 0)
{
$this->curTpl['name'] = $this->getField('name', true);
$replace[$this->id] = $this->curTpl;
}
else // set a name if holiday is missing
{
// template
$this->curTpl['name_loc0'] = $this->curTpl['nameINT'];
$this->curTpl['iconString'] = 'trade_engineering';
$this->curTpl['name'] = '(SERVERSIDE) '.$this->getField('nameINT', true);
$replace[$this->id] = $this->curTpl;
}
}
foreach ($replace as $old => $data)
{
unset($this->templates[$old]);
$this->templates[$data['eventId']] = $data;
}
}
public static function getName(int $id) : ?LocString
{
$row = DB::Aowow()->SelectRow(
'SELECT IFNULL(h.`name_loc0`, e.`description`) AS "name_loc0", h.`name_loc2`, h.`name_loc3`, h.`name_loc4`, h.`name_loc6`, h.`name_loc8`
FROM ?_events e
LEFT JOIN ?_holidays h ON e.`holidayId` = h.`id`
WHERE e.`id` = ?d',
$id
);
return $row ? new LocString($row) : null;
}
public static function updateDates($date = null)
{
if (!$date || empty($date['firstDate']) || empty($date['length']))
{
return array(
'start' => 0,
'end' => 0,
'rec' => 0
);
}
// Convert everything to seconds
$firstDate = intVal($date['firstDate']);
$lastDate = !empty($date['lastDate']) ? intVal($date['lastDate']) : 5000000000; // in the far far FAR future..;
$interval = !empty($date['rec']) ? intVal($date['rec']) : -1;
$length = intVal($date['length']);
$curStart = $firstDate;
$curEnd = $firstDate + $length;
$nextStart = $curStart + $interval;
$nextEnd = $curEnd + $interval;
while ($interval > 0 && $nextEnd <= $lastDate && $curEnd < time())
{
$curStart = $nextStart;
$curEnd = $nextEnd;
$nextStart = $curStart + $interval;
$nextEnd = $curEnd + $interval;
}
return array(
'start' => $curStart,
'end' => $curEnd,
'rec' => $interval
);
}
public function getListviewData(bool $forNow = false) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'category' => $this->curTpl['category'],
'id' => $this->id,
'name' => $this->getField('name', true),
'_date' => array(
'rec' => $this->curTpl['occurence'],
'length' => $this->curTpl['length'],
'firstDate' => $this->curTpl['startTime'],
'lastDate' => $this->curTpl['endTime']
)
);
}
if ($forNow)
{
foreach ($data as &$d)
{
$u = self::updateDates($d['_date']);
unset($d['_date']);
$d['startDate'] = $u['start'];
$d['endDate'] = $u['end'];
$d['rec'] = $u['rec'];
}
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::WORLDEVENT][$this->id] = ['name' => $this->getField('name', true), 'icon' => $this->curTpl['iconString']];
return $data;
}
public function renderTooltip() : ?string
{
if (!$this->curTpl)
return null;
$x = '<table><tr><td>';
// head v that extra % is nesecary because we are using sprintf later on
$x .= '<table width="100%%"><tr><td><b>'.$this->getField('name', true).'</b></td><th><b class="q0">'.Lang::event('category', $this->getField('category')).'</b></th></tr></table>';
// use string-placeholder for dates
// start
$x .= Lang::event('start').Lang::main('colon').'%s<br />';
// end
$x .= Lang::event('end').Lang::main('colon').'%s';
$x .= '</td></tr></table>';
// desc
if ($this->getField('holidayId'))
if ($_ = $this->getField('description', true))
$x .= '<table><tr><td><span class="q">'.$_.'</span></td></tr></table>';
return $x;
}
}
?>

View File

@@ -0,0 +1,106 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class ZoneList extends DBTypeList
{
use listviewHelper;
public static int $type = Type::ZONE;
public static string $brickFile = 'zone';
public static string $dataTable = '?_zones';
protected string $queryBase = 'SELECT z.*, z.`id` AS ARRAY_KEY FROM ?_zones z';
public function __construct(array $conditions = [], array $miscData = [])
{
parent::__construct($conditions, $miscData);
foreach ($this->iterate() as &$_curTpl)
{
// unpack attunements
$_curTpl['attunes'] = [];
if ($_curTpl['attunementsN'])
{
foreach (explode(' ', $_curTpl['attunementsN']) as $req)
{
$req = explode(':', $req);
if (!isset($_curTpl['attunes'][$req[0]]))
$_curTpl['attunes'][$req[0]] = [$req[1]];
else
$_curTpl['attunes'][$req[0]][] = $req[1];
}
}
if ($_curTpl['attunementsH'])
{
foreach (explode(' ', $_curTpl['attunementsH']) as $req)
{
$req = explode(':', $req);
if (!isset($_curTpl['attunes'][$req[0]]))
$_curTpl['attunes'][$req[0]] = [-$req[1]];
else
$_curTpl['attunes'][$req[0]][] = -$req[1];
}
}
unset($_curTpl['attunementsN']);
unset($_curTpl['attunementsH']);
}
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'category' => $this->curTpl['category'],
'territory' => $this->curTpl['faction'],
'minlevel' => $this->curTpl['levelMin'],
'maxlevel' => $this->curTpl['levelMax'],
'name' => $this->getField('name', true)
);
if ($_ = $this->curTpl['expansion'])
$data[$this->id]['expansion'] = $_;
if ($_ = $this->curTpl['type'])
$data[$this->id]['instance'] = $_;
if ($_ = $this->curTpl['maxPlayer'])
$data[$this->id]['nplayers'] = $_;
if ($_ = $this->curTpl['levelReq'])
$data[$this->id]['reqlevel'] = $_;
if ($_ = $this->curTpl['levelReqLFG'])
$data[$this->id]['lfgReqLevel'] = $_;
if ($_ = $this->curTpl['levelHeroic'])
$data[$this->id]['heroicLevel'] = $_;
}
return $data;
}
public function getJSGlobals(int $addMask = 0) : array
{
$data = [];
foreach ($this->iterate() as $__)
$data[Type::ZONE][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}
public function renderTooltip() : ?string { return null; }
}
?>