105 Commits

Author SHA1 Message Date
Sarjuuk
a135dfce90 Profiler/Completions
* add keyed col `exalted` to reputation completions table to speed up lookups
2025-11-09 19:05:33 +01:00
Sarjuuk
0d42d2a2c4 UserPage/Optimization
* split up fetching of custom profiles and characters to make use of existing keys
2025-11-09 16:22:26 +01:00
Sarjuuk
fa89a5ad1e User/Fixup
* fix fetching user characters, borked in 474b5b5aec062b61e8d707c91739b50ad77e81ef
 * take #2
2025-11-09 16:22:00 +01:00
Sarjuuk
6eb5a67add Profiler/Optimization
* move searchable flags to their own db cols to speed up lookups
 * don't cast profile name to LOWER in SQL when displaying tooltips.
2025-11-09 16:20:52 +01:00
Sarjuuk
8a169eb400 Setup/Fixup
* catch error if url from self test is unreachable
2025-11-09 16:16:28 +01:00
Sarjuuk
cf4e8a527c User/Fixup
* fix fetching user characters, borked in 474b5b5aec062b61e8d707c91739b50ad77e81ef
2025-11-08 18:07:26 +01:00
Sarjuuk
48564ab8b5 Misc/Fixup
* fix building num ranges, added in 8212811970
2025-11-07 21:19:43 +01:00
Sarjuuk
edc297f97a NPCs/Fixup
* fix parent npcs name for locales CN and ES
2025-11-07 20:47:39 +01:00
Sarjuuk
5d02a20719 SQL/Misc
* add keys to spells table to speed up related spells queries
2025-11-07 20:34:49 +01:00
Sarjuuk
f44de66de7 User/Profiles
* speed up load of user profiles
2025-11-07 20:34:47 +01:00
Sarjuuk
16c5b73cd3 User/Misc
* don't run querys if not strictly required (e.g. query for chars from ajax context)
 * prepare user globals and favorites so errors can be handled and don't destroy the template
   this also allows for profiling of the affected queries
 * add keys to items table to speed up querying for recipes in general and user completions in particular
2025-11-05 15:39:28 +01:00
Sarjuuk
9020e36db6 SmartAI/Misc
* make errors more verbose if SAI tries to set unexpected flags
 * do not escape strings. By now thats handled by Frontend/Markup
2025-11-04 19:48:26 +01:00
Sarjuuk
597898450d WorldEvent/Misc
* fix excess colons in tooltip
 * fix advancing date window while event is still active
2025-11-04 19:48:19 +01:00
Sarjuuk
6a94888686 Filter/Fixup
* try to prune deselected criteria/weight selectors from filter input
2025-11-04 00:04:24 +01:00
Sarjuuk
e3d6f7b3a7 Profiler/Completions
* show completion info for claimed characters in infobox on
      appropriate db pages
2025-11-03 20:50:54 +01:00
Sarjuuk
37380ff515 Frontend/InfoboxMarkup
* you can now pass attributes to the [li] element
2025-11-03 18:47:06 +01:00
Sarjuuk
8212811970 Misc/Cleanup
* create function for num range .. creation
2025-11-01 21:01:03 +01:00
Sarjuuk
1e9e406ff0 TextResponse/Fixup
* make class non-abstract so we can generate a 403/404 message on base
2025-10-31 16:44:37 +01:00
Sarjuuk
3984bd0ae2 Spells/Misc
* limit chance/ppm precision on spell procs chances
 * do not apply a spells EffectXBonusMultiplier for physical spells
2025-10-29 15:51:23 +01:00
Sarjuuk
441ad38543 Filter/Locales
* fix embedding and triggering fi_toggle on filtrable listviews for locale zhCN
2025-10-28 19:58:47 +01:00
Sarjuuk
88cc76feae Localization/Filters
* add missing spell effects & aura names
 * don't return null for unused effects/auras (triggers an error)
2025-10-28 19:58:44 +01:00
Sarjuuk
96c777191d Spells/Scaling
* move scaling data to the appropriate spell effects (like WH)
2025-10-27 21:19:40 +01:00
Sarjuuk
40b2830cad Spells/Scaling
* hopefully fix a lot of nonsensical spell scaling infos
 * note: an aweful lot of physical spells are hardcoded or have spell scripts and won't display any info
2025-10-27 19:44:00 +01:00
Sarjuuk
9741774683 Spells/Reagents
* fix reagents listing and spell tooltips for nonexistent reagents
2025-10-27 17:02:30 +01:00
Sarjuuk
e8bc37f82f Filter/NPC
* fix react filter
 * remove excess colons
2025-10-27 01:47:27 +01:00
Sarjuuk
7cbe1f6007 Reports/Fixup
* also include source url when checking target context
 * cleanup source url to be usable as key
2025-10-26 19:19:50 +01:00
Sarjuuk
3a25c2390f Listviews/AddIns
* AddIns must be output directly before the listview it is used by
2025-10-26 17:32:18 +01:00
Sarjuuk
cf2ace805b Spawns/Fixup
* fix maps for single-floor dungeons borked in 33cd290dc3
2025-10-24 18:29:41 +02:00
Sarjuuk
2f8e035783 Search/Fixup
* fix redirecting to result on exact hit
2025-10-24 18:28:25 +02:00
Sarjuuk
1a55b30766 UserPage/Fixup
* move description inside text container
 * add missing 'related' heading
2025-10-23 20:49:58 +02:00
Sarjuuk
6d7f9c0f00 Quest/Fixup
* don't try to create an objective for empty SourceItem
 * fixed size of source spell icon
2025-10-23 16:26:59 +02:00
Sarjuuk
862b3dff73 IconElement/Fixup
* do not change type of num / qty params ('+1' is not numeric)
2025-10-23 00:48:04 +02:00
Sarjuuk
b1f22f7e68 Spells/ExtraLoot
* display extra loot from skill_extra_item_template like perfect gems
 * for specializations, display affected spells in Bonus Loot tab
 * cleanup subject->id to typeId
 * closes #286
2025-10-23 00:10:52 +02:00
Sarjuuk
1d922c1147 Locks
* implemented display of LOCK_TYPE_SPELL (3 cases)
 * show "unlocks" tab on spell detail page
 * closes #288
2025-10-22 22:18:27 +02:00
Sarjuuk
f9ace6a671 Spell/Sources
* always display quest source from RewardSpellCast
 * fix inherited quest sources via learn spells
 * closes #353
2025-10-22 18:57:33 +02:00
Sarjuuk
6ea1457c4f Guides/Fixup
* make preview area of class: text so styles are applied as expected
 * fix evaluation of Markup h2/h3 attribute toc=false, so headings are excluded from TOC as expected
2025-10-22 18:06:38 +02:00
Sarjuuk
f522c960d9 Modelviewer/Fixup
* fix buttons of lightbox
2025-10-22 17:08:36 +02:00
Sarjuuk
1365cdb261 Spell/Effects
* show spells affected by SPELL_AURA_IGNORE_COMBAT_RESULT
2025-10-22 01:57:58 +02:00
Sarjuuk
9b591e7a3a Items/Tooltips
* fix itemId to scientific notation conversion, when itemId was joined
   by an enchantmentId (1234e56 => 1.234e53), breaking tooltip display.
2025-10-21 23:35:42 +02:00
Sarjuuk
6da71afc68 Filter/Fixup
* allow unicode chars when checking GET param
2025-10-21 20:53:27 +02:00
Sarjuuk
033a9181ae Profiler/Localization
* TCs guild and arena_team tables as encoded as utf8mb4_general_ci,
   which is not accent-aware. So we have to get all results and filter
   for the correct one in php.
 * fixes an issue where direcly accessing a guild/arena-team whith a name
   simiar to an already known guild/team would lookup the wrong subject
   on the server and then fail to create a local stub with already existing key.
   (Shâdów vs Shadow)
2025-10-21 17:57:00 +02:00
Sarjuuk
1dcc9363da Profiler/Filter
* fix filter params being propagated between different profiler types
   (e.g. arena team size filter being appended to guilds menu)
2025-10-21 15:35:28 +02:00
Sarjuuk
51b6e29316 Profiler/Queue
* send ready status for characters/guilds/arena-teams whose resync
   cooldown hasn't expired yet
2025-10-20 20:38:15 +02:00
Sarjuuk
14c159c164 Setup/Factions
* fix switched base rep field indizes, causing Profiler to miscalculate
   character standing
 * replace hardcoded sql table prefixes
2025-10-20 19:23:52 +02:00
Sarjuuk
33cd290dc3 Setup/Spawns
* fix coords for cases with coords in both WorldMapArea.dbc and
   DungeonMap.dbc without using WorldMapArea.dbc as base floor
2025-10-20 19:23:02 +02:00
Sarjuuk
2e029f3d96 Profiler/Talents
* fix building talent string for hunter pets.
   the alternate spells (e.g. Dash & Swoop) must both be included
 * align talent order in build scripts talenticons, talentcalc with
   Profiler talent string builder
 * fix Shamans gaining 5% Parry by talent Spirit Weapons
 * cleanup
2025-10-20 16:25:24 +02:00
Sarjuuk
6a32c770cd Profiler/Pets
* catch error case where a player owns a pet that is no longer tameable/has no pet family
2025-10-19 22:46:08 +02:00
Sarjuuk
4d421d2bbb Filter/Errors
* handle stat weights quirk, analogous to the criteria quirk
2025-10-18 16:46:25 +02:00
Sarjuuk
176cf137fb Filter/Fixup
* criteria parameters can be placeholder/null
2025-10-17 14:33:52 +02:00
Sarjuuk
830edb8265 PageTemplate/Fixup
* use get_object_vars() instead of property_exists() to test if we can
   load a variable from provided context. The former only returns
   accessible vars while the latter returns true for all properties.
2025-10-16 02:14:47 +02:00
Sarjuuk
7d8b524478 Listview/Cost
* do not append 0 achievementpoints to cost builder. It will be displayed
2025-10-15 21:53:00 +02:00
Sarjuuk
95918c0410 SoundDetailPage/Fixup
* fix exception when assigning WorldState conditions to the zones tab
2025-10-15 01:06:57 +02:00
Sarjuuk
a275955ee3 Admin/Config
* handle Ajax errors
2025-10-15 00:26:05 +02:00
Sarjuuk
37beaa2db5 Filter/Errors
* move checks to __construct so they can be run on $_POST data
   and don't create malformed filter urls
 * if we received malformed $_GET params, build new params and reload
 * do not store error state in cache
 * cleanup
2025-10-15 00:05:55 +02:00
Sarjuuk
c0097f3987 Mapper/Objectives
* fix display of item objectives by making LocString JsonSerializable
2025-10-14 16:17:59 +02:00
Sarjuuk
92c58cc5d1 Localization/UIEscapes
* fixed expanding |2 placeholder in general and when the referenced word itself was a placeholder ($N => <nom>)
 * fixed expanding |3 placeholder for caseIds > 9
2025-10-13 20:37:39 +02:00
Sarjuuk
04f3aa7a82 Setup/Source
* respect disabled Quests and Spells when flagging Items as unavailable
 * reuse data from loot_link to set difficuly bits and zoneId
   for loot container GOs
2025-10-12 22:32:52 +02:00
Sarjuuk
65d490a8ae Enchantments/Stats
* entirely forgo ?_item_stats table when calculating enchantment stats
2025-10-12 22:24:09 +02:00
Sarjuuk
816eacaf73 Summary/Fixup
* allow signed integers (random enchantments) in summary definition
2025-10-12 22:24:03 +02:00
Sarjuuk
034eca1f58 Items/RandEnchants
* fix amount calculation for scaling enchantments
 * cache RandomPropPoints lookups
2025-10-12 22:22:56 +02:00
Sarjuuk
a33abb84fe Setup/Account
* don't overwrite existing account in case of email conflict
2025-10-12 17:48:06 +02:00
Sarjuuk
fb7b22db36 Account/Passwords
* use buildin php functions to handle passwords
 * increase cost of BCRYPT
 * make use of the SensitiveParameter attribute
2025-10-12 17:48:06 +02:00
Sarjuuk
dd838fa994 Misc/Doc
* add several ItemMods unusd by client but still found in item_template as comment
2025-10-12 00:57:09 +02:00
Sarjuuk
d32074fdcd ZoneDetailPage/Tabs
* only offer 'filter result' prompt on tabs for zones that are filtrable
2025-10-12 00:15:59 +02:00
Sarjuuk
494061de82 Cache/Fixup
* correct miscData offset for tooltips introduced in 3edac3c77a
 * fix generating cache key for item upgrade searches
2025-10-12 00:15:52 +02:00
Sarjuuk
9b0aa5c885 Achievements/Fixup
* fix fetching achievements from child catgs if selected catg is empty
2025-10-12 00:15:28 +02:00
Sarjuuk
40e98081c9 LatestComments/RSS
* fix url format for replies
2025-10-12 00:15:15 +02:00
Sarjuuk
77f2a0c21d Profiler/Resync
* fix logging ids on resync failure
2025-10-10 22:33:31 +02:00
Sarjuuk
465e019eaa FactionDetailPage/Tabs
* only offer 'filter result' prompt on tabs for factions that are filtrable
2025-10-10 22:33:25 +02:00
Sarjuuk
63053757c9 Guides/Fixup
* fix showing wrong guide version to staff
 * fix sticky icon offset
2025-10-10 20:53:56 +02:00
Sarjuuk
a96f6c4cdf PageTemplate/Fixup
* really fix merging jsGlobals from comments/etc. into existing PageTemplate
2025-10-10 20:49:56 +02:00
Sarjuuk
b832fc172c Items/Gearscore
* fix warning in GS calculation
2025-10-10 20:49:43 +02:00
Sarjuuk
196f60f176 Filter/Zones
* add missing Ruby Sanctum to zones dropdown
 * sort zones alphabetically
2025-10-10 20:49:32 +02:00
Sarjuuk
204d4b8ae2 Profiler/Sync
* fix SQL FK error when creating guild or arenateam stub.
 * Urlized name field is non-optional
2025-10-10 20:49:14 +02:00
Sarjuuk
5d2fd00358 Profiler/Save
* fixed inventory definitions not allowing for negative ids (random enchantments)
 * added handling invalid inventory definitions
2025-10-10 20:49:07 +02:00
Sarjuuk
1dcdf9623b Endpoints/User
* do not display user page for internal system user
2025-10-08 20:02:48 +02:00
Sarjuuk
215ad39cc6 NPC/Reputation
* try to fix reputation spillover
 * fix extra colon on reputation gains
2025-10-08 19:48:36 +02:00
Sarjuuk
a9ed897ea6 Filter/Fixup
* fix evaluating imbalanced criteria
2025-10-08 19:48:29 +02:00
Sarjuuk
3edac3c77a Endpoints/Cache
* fix cache id collision when category == dbTypeId for a given dbType
 * increment version number to invalidate existing caches
 * maps endpoint doesn't need caching. It is entirely static content.
2025-10-08 19:48:21 +02:00
Sarjuuk
95ee9d2c25 NPCs/Fixup
* fix exception when displaying NPC with elemental resistances in base
   version but not difficulty modes
2025-10-07 15:51:07 +02:00
Sarjuuk
d79742d599 Profiler/Fixup
* dont use unsynced profile stubs in attained % calculation
2025-10-06 23:23:40 +02:00
Sarjuuk
e300086cc8 IconElement/Fixup
* a DOMElements text value must be escaped manually
   (e.g. Foror &amp; Tigule)
2025-10-06 23:23:30 +02:00
Sarjuuk
a7e9ac2cf2 Misc/Fixup
* HTTP_USER_AGENT is not guaranteed to be set
2025-10-06 17:16:41 +02:00
Sarjuuk
05f5b0ed34 Response/Params
* so we can't directly use BackedEnum::tryFrom as validator, because if
   the Enum is of <int> and the string is not what php considers numeric,
   we get a straight TypeError Exception instead of null for failing the tryFrom.
2025-10-06 17:06:49 +02:00
Sarjuuk
e37620c01b Search/Fixup
* fix pruning empty tokens from search
2025-10-06 17:06:43 +02:00
Sarjuuk
c40bd3851b Profiler/Fixup
* fix scoring perm enchantments
2025-10-06 17:06:34 +02:00
Sarjuuk
452615a92d Filters/Misc
* be a bit more lenient on level inputs
 * fix displaying array of requirements on error
2025-10-06 17:06:25 +02:00
Sarjuuk
704894c1e3 Spells/Fixup
* skillLines can be empty for unused glyphs etc.
2025-10-06 17:06:14 +02:00
Sarjuuk
045c16c241 PageTemplate/Profiler
* don't skip running parent::generate for incomplete profiles
2025-10-06 15:00:47 +02:00
Sarjuuk
7b429811a9 Defines/Races
* add unplayable races to ChrRace enum so RaceDetailPage can display them.
 * don't show empty icons for unplayable races
2025-10-05 20:20:40 +02:00
Sarjuuk
aa7c0186fc Profiler/Cleanup
* gracefully handle DB errors when fetching realms instead of crashing
2025-10-05 20:19:48 +02:00
Sarjuuk
7b752143a0 Creature/Quotes
* don't add superfluous creature name placeholder to emotes
2025-10-05 18:55:39 +02:00
Sarjuuk
baf4ba5b98 Codestyle/Cleanup 2025-10-05 00:28:27 +02:00
Sarjuuk
eb95b03e31 SmartAI/Update
* implement events added in 3bb4f56773
2025-10-04 20:02:34 +02:00
Sarjuuk
4fe35d9e3c PageTemplate/Fixup
* fix merging jsGlobals from comments/etc. into existing PageTemplate
2025-10-04 17:13:45 +02:00
Sarjuuk
5355989015 Logging/Misc
* don't log passwords to DB (and neither check_passwords)
2025-10-04 17:13:34 +02:00
Sarjuuk
9fc84cdf9e Comments/Fixup
* fix false error when voting on relies
2025-10-04 15:39:44 +02:00
Sarjuuk
a6108be400 Spells/Parsing
* bandaid fix parsing deeply nested formulas in non-interactive mode
   (should rethink how/when formulas get flagged as un-evalable)
2025-10-04 01:10:38 +02:00
Sarjuuk
ff690770b5 Misc/Fixup
* type error when declaring listview
2025-10-04 00:15:38 +02:00
Sarjuuk
6263ccd92a SkillDetailPage/Tabs
* add tab for spells modifying skill value
2025-10-03 18:32:27 +02:00
Sarjuuk
60eb816002 Quickfacts
* where applicable:
   - show typeId
   - show english name if currently localized
   - show percentage attained by synced profiles
 * fixed some typos
2025-10-03 17:49:50 +02:00
Sarjuuk
bc112b2b16 Template/Fixup
* fix directly adding dataloader to PageTemplate
2025-10-03 17:49:49 +02:00
Sarjuuk
6d86f880f4 Analytics/Cookies
* don't ask users to consent on GA tracking if we don't use GA tracking
2025-10-02 19:53:53 +02:00
Sarjuuk
bd1f139c2e CLI/Misc
* handle error case where setup is run automated and receives no input on STDIN
2025-10-02 16:16:49 +02:00
Yrito
36aa33ac26 Setup/CLI
* Allow starting setup at specific step
2025-10-02 14:35:13 +02:00
176 changed files with 3401 additions and 1729 deletions

View File

@@ -4,7 +4,7 @@ if (!defined('AOWOW_REVISION'))
die('illegal access');
function extAuth(string &$usernameOrEmail, string $password, int &$userId = 0, int &$userGroup = -1) : int
function extAuth(string &$usernameOrEmail, #[\SensitiveParameter] string $password, int &$userId = 0, int &$userGroup = -1) : int
{
/*
insert some auth mechanism here

View File

@@ -21,7 +21,7 @@ class AchievementBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'achievement';
protected string $pageName = 'achievement';
@@ -107,15 +107,32 @@ class AchievementBaseResponse extends TemplateResponse implements ICache
default => Lang::game('si', SIDE_BOTH) // 0, 3
};
// id
$infobox[] = Lang::achievement('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && !($this->subject->getField('flags') & ACHIEVEMENT_FLAG_COUNTER))
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_achievements WHERE `achievementId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', !($this->subject->getField('flags') & ACHIEVEMENT_FLAG_COUNTER));
/**********/

View File

@@ -11,7 +11,7 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ACHIEVEMENT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'achievements';
protected string $pageName = 'achievements';
@@ -54,6 +54,12 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new AchievementListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -69,13 +75,9 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
if ($this->category)
$conditions[] = ['category', end($this->category)];
$this->filter->evalCriteria();
if ($fiCnd = $this->filter->getConditions())
$conditions[] = $fiCnd;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */
@@ -119,7 +121,7 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
$conditions = [];
if ($fiCnd)
$conditions[] = $fiCnd;
if ($catList = DB::Aowow()->SelectCol('SELECT `id` FROM ?_achievementcategory WHERE `parentCat` IN (?a) OR `parentCat2` IN (?a) ', end($this->category), end($this->category)))
if ($catList = DB::Aowow()->SelectCol('SELECT `id` FROM ?_achievementcategory WHERE `parentCat` IN (?a) OR `parentCat2` IN (?a) ', $this->category, $this->category))
$conditions[] = ['category', $catList];
$acvList = new AchievementList($conditions, ['calcTotal' => true]);

View File

@@ -10,7 +10,7 @@ class AreatriggerBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected int $requiredUserGroup = U_GROUP_STAFF;
protected string $template = 'detail-page-generic';

View File

@@ -11,7 +11,7 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::AREATRIGGER;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected int $requiredUserGroup = U_GROUP_STAFF;
protected string $template = 'areatriggers';
@@ -33,6 +33,12 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
parent::__construct($pageParam);
$this->filter = new AreaTriggerListFilter($this->_get['filter'] ?? '');
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -40,8 +46,6 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
{
$this->h1 = Util::ucFirst(Lang::game('areatriggers'));
$this->filter->evalCriteria();
$fiForm = $this->filter->values;
@@ -73,8 +77,6 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
$tabData = [];
$trigger = new AreaTriggerList($conditions, ['calcTotal' => true]);
if (!$trigger->error)

View File

@@ -46,21 +46,24 @@ class ArenateamBaseResponse extends TemplateResponse
// 3 possibilities
// 1) already synced to aowow
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `cuFlags` FROM ?_profiler_arena_team WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `stub` FROM ?_profiler_arena_team WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
{
$this->typeId = $subject['id'];
if ($subject['cuFlags'] & PROFILER_CU_NEEDS_RESYNC)
if ($subject['stub'])
$this->handleIncompleteData(Type::ARENA_TEAM, $subject['realmGUID']);
return;
}
// 2) not yet synced but exists on realm (wont work if we get passed an urlized name, but there is nothing we can do about it)
else if ($subject = DB::Characters($this->realmId)->selectRow('SELECT at.`arenaTeamId` AS "realmGUID", at.`name`, at.`type` FROM arena_team at WHERE at.`name` = ?', Util::ucFirst($this->subjectName)))
$subjects = DB::Characters($this->realmId)->select('SELECT at.`arenaTeamId` AS "realmGUID", at.`name`, at.`type` FROM arena_team at WHERE at.`name` = ?', $this->subjectName);
if ($subject = array_filter($subjects, fn($x) => Util::lower($x['name']) == Util::lower($this->subjectName)))
{
$subject = $subject[0];
$subject['realm'] = $this->realmId;
$subject['cuFlags'] = PROFILER_CU_NEEDS_RESYNC;
$subject['stub'] = 1;
$subject['nameUrl'] = Profiler::urlize($subject['name']);
// create entry from realm with basic info
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team (?#) VALUES (?a)', array_keys($subject), array_values($subject));

View File

@@ -53,6 +53,12 @@ class ArenateamsBaseResponse extends TemplateResponse implements IProfilerList
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ArenaTeamListFilter($this->_get['filter'] ?? '', ['realms' => $realms]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}

View File

@@ -12,7 +12,7 @@ class ClassBaseResponse extends TemplateResponse implements ICache
private const TC_CLASS_IDS = [null, 8, 3, 1, 5, 4, 9, 6, 2, 7, null, 0]; // see TalentCalc.js
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'class';
@@ -103,6 +103,13 @@ class ClassBaseResponse extends TemplateResponse implements ICache
if ($specList)
$infobox[] = Lang::game('specs').'[ul][li]'.implode('[/li][li]', $specList).'[/li][/ul]';
// id
$infobox[] = Lang::chrClass('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class ClassesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::CHR_CLASS;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'classes';

View File

@@ -47,7 +47,7 @@ class CommentDownvotereplyResponse extends TextResponse
User::canSupervote() ? -2 : -1
);
if (!$ok)
if (!is_int($ok))
{
trigger_error('CommentDownvotereplyResponse - write to db failed', E_USER_ERROR);
$this->generate404(User::isInGroup(U_GROUP_STAFF) ? 'write to db failed' : '');

View File

@@ -47,7 +47,7 @@ class CommentUpvotereplyResponse extends TextResponse
User::canSupervote() ? 2 : 1
);
if (!$ok)
if (!is_int($ok))
{
trigger_error('CommentUpvotereplyResponse - write to db failed', E_USER_ERROR);
$this->generate404(User::isInGroup(U_GROUP_STAFF) ? 'write to db failed' : '');

View File

@@ -106,7 +106,7 @@ class CompareBaseResponse extends TemplateResponse
protected static function checkCompareString(string $val) : string
{
$val = urldecode($val);
if (preg_match('/[^\d\.:;]/', $val))
if (preg_match('/[^-?\d\.:;]/', $val))
return '';
return $val;

View File

@@ -11,7 +11,7 @@ class CurrenciesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::CURRENCY;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'currencies';

View File

@@ -10,7 +10,7 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'currency';
@@ -72,13 +72,20 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->subject->getField('cap'))
$infobox[] = Lang::currency('cap').Lang::nf($_);
// id
$infobox[] = Lang::currency('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -9,7 +9,7 @@ if (!defined('AOWOW_REVISION'))
class DataBaseResponse extends TextResponse
{
protected array $expectedGET = array(
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFrom'] ],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkLocale' ]],
't' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine' ]],
'catg' => ['filter' => FILTER_VALIDATE_INT ],
'skill' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkSkill' ]],

View File

@@ -10,7 +10,7 @@ class EmoteBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'emote';
@@ -94,6 +94,9 @@ class EmoteBaseResponse extends TemplateResponse implements ICache
}
}
// id
$infobox[] = Lang::emote('id') . $this->typeId;
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -10,7 +10,7 @@ class EmotesBaseResponse extends TemplateResponse implements ICache
{
use TrListPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected int $type = Type::EMOTE;
protected string $template = 'list-page-generic';

View File

@@ -10,7 +10,7 @@ class EnchantmentBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'enchantment';
protected string $pageName = 'enchantment';
@@ -85,6 +85,13 @@ class EnchantmentBaseResponse extends TemplateResponse implements ICache
$infobox[] = $foo;
}
// id
$infobox[] = Lang::enchantment('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class EnchantmentsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ENCHANTMENT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'enchantments';
protected string $pageName = 'enchantments';
@@ -35,6 +35,12 @@ class EnchantmentsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new EnchantmentListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -42,18 +48,13 @@ class EnchantmentsBaseResponse extends TemplateResponse implements ICache
{
$this->h1 = Util::ucFirst(Lang::game('enchantments'));
$this->filter->evalCriteria();
$conditions = [];
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/**************/
/* Page Title */

View File

@@ -10,7 +10,7 @@ class EventBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'event';
@@ -88,9 +88,16 @@ class EventBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::npc('rank', 3).Lang::main('colon').'[npc='.$_.']';
}
// display internal id to staff
if (User::isInGroup(U_GROUP_STAFF))
$infobox[] = 'Event-Id'.Lang::main('colon').$this->typeId;
// id
$infobox[] = Lang::event('id') . $this->typeId;
// display holiday id to staff
if ($_holidayId && User::isInGroup(U_GROUP_STAFF))
$infobox[] = 'Holiday ID'.Lang::main('colon').$_holidayId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class EventsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::WORLDEVENT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'events';

View File

@@ -10,7 +10,7 @@ class FactionBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'faction';
@@ -96,8 +96,25 @@ class FactionBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->subject->getField('side'))
$infobox[] = Lang::main('side').'[span class=icon-'.($_ == SIDE_ALLIANCE ? 'alliance' : 'horde').']'.Lang::game('si', $_).'[/span]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
// id
$infobox[] = Lang::faction('id') . $this->typeId;
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && !($this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW))
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_reputation WHERE `exalted` = 1 AND `factionId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox) // unsure if this should be tracked (needs data dump in User::getCompletion())
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', 0);
/****************/
@@ -208,7 +225,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($items->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=17;crs='.$this->typeId.';crv=0');
if (!is_null(ItemListFilter::getCriteriaIndex(17, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=17;crs='.$this->typeId.';crv=0');
$this->lvTabs->addListviewTab(new Listview($tabData, ItemList::$brickFile, 'itemStandingCol'));
}
@@ -242,7 +260,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($killCreatures->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=42;crs='.$this->typeId.';crv=0');
if (!is_null(CreatureListFilter::getCriteriaIndex(42, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=42;crs='.$this->typeId.';crv=0');
$this->addDataLoader('zones');
$this->lvTabs->addListviewTab(new Listview($tabData, CreatureList::$brickFile, 'npcRepCol'));
@@ -263,7 +282,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($members->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=3;crs='.$this->typeId.';crv=0');
if (!is_null(CreatureListFilter::getCriteriaIndex(3, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=3;crs='.$this->typeId.';crv=0');
$this->addDataLoader('zones');
$this->lvTabs->addListviewTab(new Listview($tabData, CreatureList::$brickFile));
@@ -301,7 +321,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($quests->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?quests&filter=cr=1;crs='.$this->typeId.';crv=0');
if (!is_null(QuestListFilter::getCriteriaIndex(1, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?quests&filter=cr=1;crs='.$this->typeId.';crv=0');
$this->lvTabs->addListviewTab(new Listview($tabData, QuestList::$brickFile, 'questRepCol'));
}

View File

@@ -11,7 +11,7 @@ class FactionsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::FACTION;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'factions';

View File

@@ -33,7 +33,7 @@ class GuideChangelogResponse extends TemplateResponse
if (!$guide->canBeViewed() && !$guide->userCanView())
$this->forward('?guides='.$guide->getField('category'));
$this->h1 = lang::guide('clTitle', [$this->_get['id'], $guide->getField('title')]);
$this->h1 = Lang::guide('clTitle', [$this->_get['id'], $guide->getField('title')]);
if (!$this->h1)
$this->h1 = $guide->getField('name');

View File

@@ -45,7 +45,7 @@ class GuideEditResponse extends TemplateResponse
'description' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkDescription'] ],
'changelog' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ],
'body' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFrom'] ],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkLocale'] ],
'category' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => 1, 'max_value' => 9] ],
'specId' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => -1, 'max_value' => 2, 'default' => -1]],
'classId' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => 1, 'max_value' => 11, 'default' => 0]]

View File

@@ -10,7 +10,7 @@ class GuideBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'guide';
@@ -68,12 +68,12 @@ class GuideBaseResponse extends TemplateResponse implements ICache
$this->contribute = CONTRIBUTE_NONE;
}
if ($this->articleUrl)
// owner or staff and manual rev passed
if ($this->subject->userCanView() && $this->_get['rev'])
$this->guideRevision = $this->_get['rev'];
// has publicly viewable version
else if ($this->subject->canBeViewed())
$this->guideRevision = $this->subject->getField('rev');
else if ($this->subject->userCanView())
$this->guideRevision = $this->_get['rev'] ?? $this->subject->getField('latest');
else
$this->subject->getField('rev');
$this->h1 = $this->subject->getField('name');
@@ -127,7 +127,7 @@ class GuideBaseResponse extends TemplateResponse implements ICache
$this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], __forceTabs: true);
// the article text itself is added by PageTemplate::addArticle()
// the article text itself is added by TemplateResponse::addArticle()
parent::generate();
$this->result->registerDisplayHook('infobox', [self::class, 'infoboxHook']);

View File

@@ -10,7 +10,7 @@ class GuidesBaseResponse extends TemplateResponse // implements ICache
{
use TrListPage/* , TrCache */;
// protected int $cacheType = CACHE_TYPE_PAGE; // really do? cache would need to be destroyed externally with each guide status update
// protected int $cacheType = CACHE_TYPE_LIST_PAGE; // really do? cache would need to be destroyed externally with each guide status update
protected int $type = Type::GUIDE;
protected string $template = 'list-page-generic';

View File

@@ -46,21 +46,24 @@ class GuildBaseResponse extends TemplateResponse
// 3 possibilities
// 1) already synced to aowow
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `cuFlags` FROM ?_profiler_guild WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `stub` FROM ?_profiler_guild WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
{
$this->typeId = $subject['id'];
if ($subject['cuFlags'] & PROFILER_CU_NEEDS_RESYNC)
if ($subject['stub'])
$this->handleIncompleteData(Type::GUILD, $subject['realmGUID']);
return;
}
// 2) not yet synced but exists on realm (wont work if we get passed an urlized name, but there is nothing we can do about it)
else if ($subject = DB::Characters($this->realmId)->selectRow('SELECT `guildid` AS "realmGUID", `name` FROM guild WHERE `name` = ?', Util::ucFirst($this->subjectName)))
$subjects = DB::Characters($this->realmId)->select('SELECT `guildid` AS "realmGUID", `name` FROM guild WHERE `name` = ?', $this->subjectName);
if ($subject = array_filter($subjects, fn($x) => Util::lower($x['name']) == Util::lower($this->subjectName)))
{
$subject = $subject[0];
$subject['realm'] = $this->realmId;
$subject['cuFlags'] = PROFILER_CU_NEEDS_RESYNC;
$subject['stub'] = 1;
$subject['nameUrl'] = Profiler::urlize($subject['name']);
// create entry from realm with basic info
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES (?a)', array_keys($subject), array_values($subject));

View File

@@ -53,6 +53,12 @@ class GuildsBaseResponse extends TemplateResponse implements IProfilerList
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new GuildListFilter($this->_get['filter'] ?? '', ['realms' => $realms]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}

View File

@@ -10,7 +10,7 @@ class IconBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'icon';
protected string $pageName = 'icon';

View File

@@ -11,7 +11,7 @@ class IconsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ICON;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'icons';
protected string $pageName = 'icons';
@@ -32,6 +32,12 @@ class IconsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new IconListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -43,13 +49,9 @@ class IconsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/**************/
/* Page Title */

View File

@@ -10,7 +10,7 @@ class ItemBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'item';
protected string $pageName = 'item';
@@ -103,10 +103,13 @@ class ItemBaseResponse extends TemplateResponse implements ICache
SIDE_BOTH => Lang::game('si', SIDE_BOTH)
};
// id
$infobox[] = Lang::item('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
@@ -145,9 +148,9 @@ class ItemBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::item('tool').'[url=?items&filter=cr=91;crs='.$tId.';crv=0]'.Util::localizedString($tName, 'name').'[/url]';
// extendedCost
if (!empty($this->subject->getExtendedCost([], $_reqRating)[$this->subject->id]))
if (!empty($this->subject->getExtendedCost([], $_reqRating)[$this->typeId]))
{
$vendors = $this->subject->getExtendedCost()[$this->subject->id];
$vendors = $this->subject->getExtendedCost()[$this->typeId];
$stack = $this->subject->getField('buyCount');
$divisor = $stack;
$each = '';
@@ -316,8 +319,15 @@ class ItemBaseResponse extends TemplateResponse implements ICache
if ($_bagFamily & 0x0100)
$infobox[] = Lang::item('atKeyring');
// completion row added by InfoboxMarkup
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
$hasCompletion = !($this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW) && ($_class == ITEM_CLASS_RECIPE || ($_class == ITEM_CLASS_MISC && in_array($_subClass, [2, 5, -7])));
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', $hasCompletion);
/****************/
@@ -473,9 +483,9 @@ class ItemBaseResponse extends TemplateResponse implements ICache
// tabs: this item contains..
$sourceFor = array(
[LOOT_ITEM, $this->subject->id, '$LANG.tab_contains', 'contains', ['$Listview.extraCols.percent'], [] ],
[LOOT_PROSPECTING, $this->subject->id, '$LANG.tab_prospecting', 'prospecting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[LOOT_MILLING, $this->subject->id, '$LANG.tab_milling', 'milling', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[LOOT_ITEM, $this->typeId, '$LANG.tab_contains', 'contains', ['$Listview.extraCols.percent'], [] ],
[LOOT_PROSPECTING, $this->typeId, '$LANG.tab_prospecting', 'prospecting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[LOOT_MILLING, $this->typeId, '$LANG.tab_milling', 'milling', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[LOOT_DISENCHANT, $this->subject->getField('disenchantId'), '$LANG.tab_disenchanting', 'disenchanting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']]
);
@@ -585,12 +595,14 @@ class ItemBaseResponse extends TemplateResponse implements ICache
), SpellList::$brickFile));
}
// tab: unlocks (object or item) - LOCK_TYPE_ITEM: 1
// tab: unlocks (object or item)
$lockIds = DB::Aowow()->selectCol(
'SELECT `id` FROM ?_lock WHERE (`type1` = 1 AND `properties1` = ?d) OR
(`type2` = 1 AND `properties2` = ?d) OR (`type3` = 1 AND `properties3` = ?d) OR
(`type4` = 1 AND `properties4` = ?d) OR (`type5` = 1 AND `properties5` = ?d)',
$this->typeId, $this->typeId, $this->typeId, $this->typeId, $this->typeId
'SELECT `id` FROM ?_lock WHERE (`type1` = ?d AND `properties1` = ?d) OR
(`type2` = ?d AND `properties2` = ?d) OR (`type3` = ?d AND `properties3` = ?d) OR
(`type4` = ?d AND `properties4` = ?d) OR (`type5` = ?d AND `properties5` = ?d)',
LOCK_TYPE_ITEM, $this->typeId, LOCK_TYPE_ITEM, $this->typeId,
LOCK_TYPE_ITEM, $this->typeId, LOCK_TYPE_ITEM, $this->typeId,
LOCK_TYPE_ITEM, $this->typeId
);
if ($lockIds)
@@ -726,9 +738,9 @@ class ItemBaseResponse extends TemplateResponse implements ICache
}
// tab: sold by
if (!empty($this->subject->getExtendedCost()[$this->subject->id]))
if (!empty($this->subject->getExtendedCost()[$this->typeId]))
{
$vendors = $this->subject->getExtendedCost()[$this->subject->id];
$vendors = $this->subject->getExtendedCost()[$this->typeId];
$soldBy = new CreatureList(array(['id', array_keys($vendors)]));
if (!$soldBy->error)
{

View File

@@ -192,7 +192,7 @@ class ItemXmlResponse extends TextResponse implements ICache
}
// link
$xml->addChild('link', Cfg::get('HOST_URL').'?item='.$this->subject->id);
$xml->addChild('link', Cfg::get('HOST_URL').'?item='.$this->typeId);
$this->result = $root->asXML();
}
@@ -220,8 +220,7 @@ class ItemXmlResponse extends TextResponse implements ICache
{
return array(
$this->type, // DBType
$this->typeId, // DBTypeId
-1, // category
$this->typeId, // DBTypeId/category
-1, // staff mask (content does not diff)
'' // misc (unused)
);

View File

@@ -11,7 +11,7 @@ class ItemsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ITEM;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'items';
protected string $pageName = 'items';
@@ -99,6 +99,12 @@ class ItemsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ItemListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -110,14 +116,9 @@ class ItemsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
$this->filter->evalWeights();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*******************/
/* evaluate filter */

View File

@@ -10,7 +10,7 @@ class ItemsetBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'itemset';
protected string $pageName = 'itemset';
@@ -95,15 +95,7 @@ class ItemsetBaseResponse extends TemplateResponse implements ICache
// itemLevel
if ($min = $this->subject->getField('minLevel'))
{
$foo = Lang::game('level').Lang::main('colon').$min;
$max = $this->subject->getField('maxLevel');
if ($min < $max)
$foo .= ' - '.$max;
$infobox[] = $foo;
}
$infobox[] = Lang::game('level').Lang::main('colon').Util::createNumRange($min, $this->subject->getField('maxLevel'), ' - ');
// class
if ($cl = Lang::getClassString($this->subject->getField('classMask'), $jsg, Lang::FMT_MARKUP))
@@ -125,6 +117,13 @@ class ItemsetBaseResponse extends TemplateResponse implements ICache
if ($_ta)
$infobox[] = Lang::itemset('_tag').'[url=?itemsets&filter=ta='.$_ta.']'.Lang::itemset('notes', $_ta).'[/url]';
// id
$infobox[] = Lang::itemset('id') . $this->subject->getField('refSetId');
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class ItemsetsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ITEMSET;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'itemsets';
protected string $pageName = 'itemsets';
@@ -32,6 +32,12 @@ class ItemsetsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ItemsetListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -43,13 +49,9 @@ class ItemsetsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */

View File

@@ -14,15 +14,20 @@ class LatestcommentsRssResponse extends TextResponse
protected function generate() : void
{
foreach (CommunityContent::getCommentPreviews(dateFmt: false) as $comment)
foreach (CommunityContent::getCommentPreviews(['comments' => 1, 'replies' => 1], dateFmt: false) as $comment)
{
if (empty($comment['commentid']))
$url = Cfg::get('HOST_URL').'/?go-to-comment&amp;id='.$comment['id'];
else
$url = Cfg::get('HOST_URL').'/?go-to-reply&amp;id='.$comment['id'];
// todo (low): preview should be html-formated
$this->feedData[] = array(
'title' => [true, [], Lang::typeName($comment['type']).Lang::main('colon').htmlentities($comment['subject'])],
'link' => [false, [], Cfg::get('HOST_URL').'/?go-to-comment&amp;id='.$comment['id']],
'link' => [false, [], $url],
'description' => [true, [], htmlentities($comment['preview'])."<br /><br />".Lang::main('byUser', [$comment['user'], '']) . Util::formatTimeDiff($comment['date'])],
'pubDate' => [false, [], date(DATE_RSS, $comment['date'])],
'guid' => [false, [], Cfg::get('HOST_URL').'/?go-to-comment&amp;id='.$comment['id']]
'guid' => [false, [], $url]
// 'domain' => [false, [], null]
);
}

View File

@@ -9,7 +9,7 @@ if (!defined('AOWOW_REVISION'))
class LocaleBaseResponse extends TextResponse
{
protected array $expectedGET = array(
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFrom']]
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkLocale']]
);
protected function generate() : void

View File

@@ -10,7 +10,7 @@ class MailBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'mail';
@@ -108,6 +108,9 @@ class MailBaseResponse extends TemplateResponse implements ICache
}
}
// id
$infobox[] = Lang::mail('id') . $this->typeId;
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class MailsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::MAIL;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'mails';

View File

@@ -8,8 +8,6 @@ if (!defined('AOWOW_REVISION'))
class MapsBaseResponse extends TemplateResponse
{
protected int $cacheType = CACHE_TYPE_PAGE;
protected string $template = 'maps';
protected string $pageName = 'maps';
protected ?int $activeTab = parent::TAB_TOOLS;

View File

@@ -10,7 +10,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'npc';
protected string $pageName = 'npc';
@@ -194,6 +194,13 @@ class NpcBaseResponse extends TemplateResponse implements ICache
if ($this->subject->getField('npcflag') & (NPC_FLAG_SPIRIT_HEALER | NPC_FLAG_SPIRIT_GUIDE))
$infobox[] = Lang::npc('extraFlags', CREATURE_FLAG_EXTRA_GHOST_VISIBILITY);
// id
$infobox[] = Lang::npc('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if (User::isInGroup(U_GROUP_EMPLOYEE))
{
// AI
@@ -526,7 +533,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
$soldItems = new ItemList(array(['id', $sells]));
if (!$soldItems->error)
{
$colAddIn = null;
$colAddIn = '';
$extraCols = ["\$Listview.funcBox.createSimpleCol('stack', 'stack', '10%', 'stack')", '$Listview.extraCols.cost'];
$lvData = $soldItems->getListviewData(ITEMINFO_VENDOR, [Type::NPC => [$this->typeId]]);
@@ -825,7 +832,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
$spill[0][1] = $set[1][1] / 2;
$spillover[$factions->getField('cat')] = $spill;
$set[6] = $factions->getField('cat'); // set spillover
$set[5] = $factions->getField('cat'); // set spillover
}
$result[] = $set;
@@ -865,7 +872,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
foreach ($reputation as $i => [, $data])
{
foreach ($data as [$factionId, , , , , , $spillover])
foreach ($data as [$factionId, , , , , $spillover])
{
if (!$spillover)
continue;
@@ -999,10 +1006,13 @@ class NpcBaseResponse extends TemplateResponse implements ICache
$modes['ranged'][] = sprintf($modeRow, $m, Lang::nf($ranged[0]).' - '.Lang::nf($ranged[1]));
}
// todo: resistances can be present/missing in either $stats or $modes
// should be handled separately..?
if ($modes)
foreach ($stats as $k => $v)
if ($v)
$stats[$k] = sprintf($hint, implode('[/tr][tr]', $modes[$k]), $v, $k);
$stats[$k] = isset($modes[$k]) ? sprintf($hint, implode('[/tr][tr]', $modes[$k]), $v, $k) : $v;
return $stats;
}

View File

@@ -11,7 +11,7 @@ class NpcsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::NPC;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'npcs';
protected string $pageName = 'npcs';
@@ -35,6 +35,12 @@ class NpcsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new CreatureListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -46,13 +52,9 @@ class NpcsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
if ($this->category)
{
$conditions[] = ['type', $this->category[0]];

View File

@@ -10,7 +10,7 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'object';
protected string $pageName = 'object';
@@ -148,9 +148,7 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
if ($this->subject->getField('lootStack'))
{
[$min, $max, $restock] = $this->subject->getField('lootStack');
$buff = Lang::spell('spellModOp', 4).Lang::main('colon').$min;
if ($min < $max)
$buff .= Lang::game('valueDelim').$max;
$buff = Lang::spell('spellModOp', 4).Lang::main('colon').Util::createNumRange($min, $max);
// since Veins don't have charges anymore, the timer is questionable
$infobox[] = $restock > 1 ? '[tooltip name=restock]'.Lang::gameObject('restock', [Util::formatTime($restock * 1000)]).'[/tooltip][span class=tip tooltip=restock]'.$buff.'[/span]' : $buff;
@@ -163,10 +161,7 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
$this->extendGlobalIds(Type::ZONE, $zone);
$m = Lang::game('meetingStone').'[zone='.$zone.']';
$l = $minLevel;
if ($minLevel > 1 && $maxLevel > $minLevel)
$l .= Lang::game('valueDelim').min($maxLevel, MAX_LEVEL);
$l = Util::createNumRange($minLevel, min($maxLevel, MAX_LEVEL));
$infobox[] = $l ? '[tooltip name=meetingstone]'.Lang::game('reqLevel', [$l]).'[/tooltip][span class=tip tooltip=meetingstone]'.$m.'[/span]' : $m;
}
@@ -181,11 +176,11 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
if ($minTime > 1 || $minPlayer || $radius)
$buff .= Lang::main('colon').'[ul]';
if ($minTime > 1)
$buff .= '[li]'.Lang::game('duration').Lang::main('colon').($maxTime > $minTime ? Util::FormatTime($maxTime * 1000, true).' - ' : '').Util::FormatTime($minTime * 1000, true).'[/li]';
if ($minTime > 1) // sign shenannigans reverse the display order
$buff .= '[li]'.Lang::game('duration').Lang::main('colon').Util::createNumRange(-$maxTime, -$minTime, fn: fn($x) => Util::FormatTime(-$x * 1000, true)).'[/li]';
if ($minPlayer)
$buff .= '[li]'.Lang::main('players').Lang::main('colon').$minPlayer.($maxPlayer > $minPlayer ? ' - '.$maxPlayer : '').'[/li]';
$buff .= '[li]'.Lang::main('players').Lang::main('colon').Util::createNumRange($minPlayer, $maxPlayer).'[/li]';
if ($radius)
$buff .= '[li]'.Lang::spell('range', [$radius]).'[/li]';
@@ -196,6 +191,13 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
$infobox[] = $buff;
}
// id
$infobox[] = Lang::gameObject('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
// AI
if (User::isInGroup(U_GROUP_EMPLOYEE))
if ($_ = $this->subject->getField('ScriptOrAI'))

View File

@@ -11,7 +11,7 @@ class ObjectsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::OBJECT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'objects';
protected string $pageName = 'objects';
@@ -35,6 +35,12 @@ class ObjectsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new GameObjectListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -46,16 +52,12 @@ class ObjectsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
if ($this->category)
$conditions[] = ['typeCat', (int)$this->category[0]];
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */

View File

@@ -10,7 +10,7 @@ class PetBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'pet';
@@ -73,13 +73,20 @@ class PetBaseResponse extends TemplateResponse implements ICache
if ($this->subject->getField('exotic'))
$infobox[] = '[url=?spell=53270]'.Lang::pet('exotic').'[/url]';
// id
$infobox[] = Lang::pet('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class PetsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::PET;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'pets';

View File

@@ -37,10 +37,8 @@ class ProfileDeleteResponse extends TextResponse
// only flag as deleted; only custom profiles
DB::Aowow()->query(
'UPDATE ?_profiler_profiles SET `cuFlags` = `cuFlags` | ?d WHERE `id` IN (?a) AND `cuFlags` & ?d {AND `user` = ?d}',
PROFILER_CU_DELETED,
'UPDATE ?_profiler_profiles SET `deleted` = 1 WHERE `id` IN (?a) AND `custom` = 1 {AND `user` = ?d}',
$this->_get['id'],
PROFILER_CU_PROFILE,
User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU) ? DBSIMPLE_SKIP : User::$id
);
}

View File

@@ -38,8 +38,8 @@ class ProfileLinkResponse extends TextResponse
// only link characters, not custom profiles
$newId = DB::Aowow()->query(
'REPLACE INTO ?_account_profiles (`accountId`, `profileId`, `extraFlags`)
SELECT ?d, p.`id`, 0 FROM ?_profiler_profiles p WHERE p.`id` = ?d AND (`cuFlags` & ?d) = 0',
User::$id, $this->_get['id'], PROFILER_CU_PROFILE
SELECT ?d, p.`id`, 0 FROM ?_profiler_profiles p WHERE p.`id` = ?d AND `custom` = 0',
User::$id, $this->_get['id']
);
if (!is_int($newId))

View File

@@ -47,7 +47,7 @@ class ProfileLoadResponse extends TextResponse
return;
}
if (($pBase['cuFlags'] & PROFILER_CU_DELETED) && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
if ($pBase['deleted'] && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
return;
@@ -56,7 +56,7 @@ class ProfileLoadResponse extends TextResponse
if ($rId == $pBase['realm'])
break;
if (!$rData) // realm doesn't exist or access is restricted
if ($pBase['realm'] && !$rData) // realm doesn't exist or access is restricted
return;
$profile = array(
@@ -103,7 +103,7 @@ class ProfileLoadResponse extends TextResponse
'activity' => [], // recent raid activity [achievementId => 1] (is a subset of statistics)
);
if ($pBase['cuFlags'] & PROFILER_CU_PROFILE)
if ($pBase['custom'])
{
// this parameter is _really_ strange .. probably still not doing this right
$profile['source'] = $pBase['realm'] ? $pBase['sourceId'] : 0;
@@ -135,7 +135,7 @@ class ProfileLoadResponse extends TextResponse
$profile['pets'] = $pets;
// source for custom profiles; profileId => [name, ownerId, iconString(optional)]
if ($customs = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `name`, `user`, `icon` FROM ?_profiler_profiles WHERE `sourceId` = ?d AND `sourceId` <> `id` {AND (`cuFlags` & ?d) = 0}', $pBase['id'], User::isInGroup(U_GROUP_STAFF) ? DBSIMPLE_SKIP : PROFILER_CU_DELETED))
if ($customs = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `name`, `user`, `icon` FROM ?_profiler_profiles WHERE `sourceId` = ?d AND `sourceId` <> `id` {AND `deleted` = ?d}', $pBase['id'], User::isInGroup(U_GROUP_STAFF) ? DBSIMPLE_SKIP : 0))
{
foreach ($customs as $id => $cu)
{

View File

@@ -67,11 +67,11 @@ class ProfileBaseResponse extends TemplateResponse
// 3 possibilities
// 1) already synced to aowow
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `cuFlags` FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND `name` = ? AND `renameItr` = ?d', $this->realmId, Util::ucFirst($this->subjectName), $rnItr))
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `stub` FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ? AND `renameItr` = ?d', $this->realmId, Util::ucFirst($this->subjectName), $rnItr))
{
$this->typeId = $subject['id'];
if ($subject['cuFlags'] & PROFILER_CU_NEEDS_RESYNC)
if ($subject['stub'])
$this->handleIncompleteData(Type::PROFILE, $subject['realmGUID']);
return;
@@ -82,27 +82,29 @@ class ProfileBaseResponse extends TemplateResponse
$this->notFound();
// 2) not yet synced but exists on realm (and not a gm character)
if ($subject = DB::Characters($this->realmId)->selectRow(
$subjects = DB::Characters($this->realmId)->select(
'SELECT c.`guid` AS "realmGUID", c.`name`, c.`race`, c.`class`, c.`level`, c.`gender`, c.`at_login`, g.`guildid` AS "guildGUID", IFNULL(g.`name`, "") AS "guildName", IFNULL(gm.`rank`, 0) AS "guildRank"
FROM characters c
LEFT JOIN guild_member gm ON gm.`guid` = c.`guid`
LEFT JOIN guild g ON g.`guildid` = gm.`guildid`
WHERE c.`name` = ? AND `level` <= ?d AND (`extra_flags` & ?d) = 0',
Util::ucFirst($this->subjectName), MAX_LEVEL, Profiler::CHAR_GMFLAGS
))
);
if ($subject = array_filter($subjects, fn($x) => Util::lower($x['name']) == Util::lower($this->subjectName)))
{
$subject['realm'] = $this->realmId;
$subject['cuFlags'] = PROFILER_CU_NEEDS_RESYNC;
$subject = $subject[0];
$subject['realm'] = $this->realmId;
$subject['stub'] = 1;
if ($subject['at_login'] & 0x1)
$subject['renameItr'] = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND `name` = ?', $this->realmId, $subject['name']);
$subject['renameItr'] = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ?', $this->realmId, $subject['name']);
if ($subject['guildGUID'])
{
// create empty guild if nessecary to satisfy foreign keys
// create empty guild if necessary to satisfy foreign keys
$subject['guild'] = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_guild WHERE `realm` = ?d AND `realmGUID` = ?d', $this->realmId, $subject['guildGUID']);
if (!$subject['guild'])
$subject['guild'] = DB::Aowow()->query('INSERT INTO ?_profiler_guild (`realm`, `realmGUID`, `cuFlags`, `name`) VALUES (?d, ?d, ?d, ?)', $this->realmId, $subject['guildGUID'], PROFILER_CU_NEEDS_RESYNC, $subject['guildName']);
$subject['guild'] = DB::Aowow()->query('INSERT INTO ?_profiler_guild (`realm`, `realmGUID`, `stub`, `name`, `nameUrl`) VALUES (?d, ?d, 1, ?, ?)', $this->realmId, $subject['guildGUID'], $subject['guildName'], Profiler::urlize($subject['guildName']));
}
unset($subject['guildGUID'], $subject['guildName'], $subject['at_login']);
@@ -122,7 +124,10 @@ class ProfileBaseResponse extends TemplateResponse
protected function generate() : void
{
if ($this->doResync)
{
parent::generate();
return;
}
if ($this->typeId)
{

View File

@@ -38,7 +38,7 @@ class ProfilePowerResponse extends TextResponse implements ICache
if (preg_match('/([^\-]+)-(\d+)/i', $this->subjectName, $m))
[, $this->subjectName, $renameItr] = $m;
if ($x = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND LOWER(`name`) = ? AND `renameItr` = ?d', $this->realmId, $this->subjectName, $renameItr ?? 0))
if ($x = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ? AND `renameItr` = ?d', $this->realmId, Util::ucWords($this->subjectName), $renameItr ?? 0))
$this->typeId = $x;
}

View File

@@ -32,7 +32,7 @@ class ProfileSaveResponse extends TextResponse
'copy' => ['filter' => FILTER_VALIDATE_INT ],
'public' => ['filter' => FILTER_VALIDATE_INT ],
'gearscore' => ['filter' => FILTER_VALIDATE_INT ],
'inv' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned'], 'flags' => FILTER_REQUIRE_ARRAY]
'inv' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdList'], 'flags' => FILTER_REQUIRE_ARRAY]
);
public function __construct(string $pageParam)
@@ -72,7 +72,8 @@ class ProfileSaveResponse extends TextResponse
'glyphs2' => $this->_post['glyphs2'],
'gearscore' => $this->_post['gearscore'],
'icon' => $this->_post['icon'],
'cuFlags' => PROFILER_CU_PROFILE | ($this->_post['public'] ? PROFILER_CU_PUBLISHED : 0)
'custom' => 1,
'cuFlags' => $this->_post['public'] ? PROFILER_CU_PUBLISHED : 0
);
// remnant of a conflict between wotlk generic icons and cata+ auto-generated, char-based icons (see profile=avatar)
@@ -88,7 +89,7 @@ class ProfileSaveResponse extends TextResponse
if ($_ = $this->_post['copy']) // gets set to source profileId when "save as" is clicked. Whats the difference to 'source' though?
{
// get character origin info if possible
if ($r = DB::Aowow()->selectCell('SELECT `realm` FROM ?_profiler_profiles WHERE `id` = ?d AND `realm` IS NOT NULL', $_))
if ($r = DB::Aowow()->selectCell('SELECT `realm` FROM ?_profiler_profiles WHERE `id` = ?d AND `custom` = 0', $_))
$cuProfile['realm'] = $r;
$cuProfile['sourceId'] = $_;
@@ -105,7 +106,7 @@ class ProfileSaveResponse extends TextResponse
}
else // new
{
$nProfiles = DB::Aowow()->selectCell('SELECT COUNT(*) FROM ?_profiler_profiles WHERE `user` = ?d AND (`cuFlags` & ?d) = 0 AND `realmGUID` IS NULL', User::$id, PROFILER_CU_DELETED);
$nProfiles = DB::Aowow()->selectCell('SELECT COUNT(*) FROM ?_profiler_profiles WHERE `user` = ?d AND `deleted` = 0 AND `custom` = 1', User::$id);
if ($nProfiles < 10 || User::isPremium())
if ($newId = DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES (?a)', array_keys($cuProfile), array_values($cuProfile)))
$charId = $newId;
@@ -139,6 +140,12 @@ class ProfileSaveResponse extends TextResponse
{
foreach ($this->_post['inv'] as $slot => $itemData)
{
if (!$itemData)
{
trigger_error('ProfileSaveResponse::generate - skipping malformed inventory definition for slot #'.$slot.': '.Util::toString($itemData), E_USER_NOTICE);
continue;
}
if ($slot + 1 == array_sum($itemData)) // only slot definition set => empty slot
{
DB::Aowow()->query('DELETE FROM ?_profiler_items WHERE `id` = ?d AND `slot` = ?d', $charId, $itemData[0]);

View File

@@ -39,7 +39,7 @@ class ProfileStatusResponse extends TextResponse
if (!$ids)
{
trigger_error('ProfileStatusResponse - no profileIds to resync'.($this->_get['guild'] ? ' for guild #'.$this->_get['guild'] : ($this->_get['arena-team'] ? ' for areana team #'.$this->_get['arena-team'] : '')), E_USER_ERROR);
trigger_error('ProfileStatusResponse - no profileIds to resync'.($this->_get['guild'] ? ' for guild #' : ($this->_get['arena-team'] ? ' for areana team #' : ' #')).Util::toString($this->_get['id']), E_USER_WARNING);
$this->result = Util::toJSON([1, [PR_QUEUE_STATUS_ERROR, 0, 0, PR_QUEUE_ERROR_CHAR]]);
}

View File

@@ -57,6 +57,12 @@ class ProfilesBaseResponse extends TemplateResponse implements IProfilerList
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ProfileListFilter($this->_get['filter'] ?? '', ['realms' => $realms]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -64,8 +70,6 @@ class ProfilesBaseResponse extends TemplateResponse implements IProfilerList
{
$this->h1 = Util::ucFirst(Lang::game('profiles'));
$this->filter->evalCriteria();
/*************/
/* Menu Path */
@@ -94,8 +98,6 @@ class ProfilesBaseResponse extends TemplateResponse implements IProfilerList
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
$fiExtraCols = $this->filter->fiExtraCols;
$lvData = [];

View File

@@ -10,7 +10,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'quest';
protected string $pageName = 'quest';
@@ -64,6 +64,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$_flags = $this->subject->getField('flags');
$_specialFlags = $this->subject->getField('specialFlags');
$_side = ChrRace::sideFromMask($this->subject->getField('reqRaceMask'));
$hasCompletion = !($_flags & QUEST_FLAG_UNAVAILABLE || $this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW);
/*************/
@@ -271,8 +272,25 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::game('difficulty').implode('[small] &nbsp;[/small]', $_);
}
// id
$infobox[] = Lang::quest('id') . $this->typeId;
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && $hasCompletion)
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_quests WHERE `questId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', $hasCompletion);
/*******************/
@@ -300,7 +318,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$olItems[$i] = [$id, $qty, $id == $olItems[0][0]];
}
if ($ids = array_column($olItems, 0))
if ($ids = array_filter(array_column($olItems, 0)))
{
$olItemData = new ItemList(array(['id', $ids]));
$this->extendGlobalData($olItemData->getJSGlobals(GLOBALINFO_SELF));
@@ -478,7 +496,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->subject->getField('sourceSpellId'))
{
$this->extendGlobalIds(Type::SPELL, $_);
$this->objectiveList[] = [0, new IconElement(Type::SPELL, $_, SpellList::getName($_), extraText: Lang::quest('provided'), element: 'iconlist-icon')];
$this->objectiveList[] = [0, new IconElement(Type::SPELL, $_, SpellList::getName($_), extraText: Lang::quest('provided'), element: 'iconlist-icon', size: IconElement::SIZE_SMALL)];
}
// required money

View File

@@ -32,7 +32,7 @@ class QuestsBaseResponse extends TemplateResponse implements ICache
);
protected int $type = Type::QUEST;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'quests';
protected string $pageName = 'quests';
@@ -53,6 +53,12 @@ class QuestsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new QuestListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -64,13 +70,9 @@ class QuestsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
if (isset($this->category[1]))
$conditions[] = ['zoneOrSort', $this->category[1]];
else if (isset($this->category[0]))

View File

@@ -16,7 +16,7 @@ class RaceBaseResponse extends TemplateResponse implements ICache
[7952, 33554], null, [16264, 33557], [17584, 33657]
);
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'race';
@@ -99,6 +99,13 @@ class RaceBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::race('startZone').Lang::main('colon').'[zone='.$_.']';
}
// id
$infobox[] = Lang::race('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
@@ -108,12 +115,14 @@ class RaceBaseResponse extends TemplateResponse implements ICache
/****************/
$this->expansion = Util::$expansionString[$this->subject->getField('expansion')];
$this->headIcons = ['race_'.$ra->json().'_male', 'race_'.$ra->json().'_female'];
$this->redButtons = array(
BUTTON_WOWHEAD => true,
BUTTON_LINKS => ['type' => $this->type, 'typeId' => $this->typeId]
);
if ($_ = $ra->json())
$this->headIcons = ['race_'.$_.'_male', 'race_'.$_.'_female'];
/**************/
/* Extra Tabs */

View File

@@ -11,7 +11,7 @@ class RacesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::CHR_RACE;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'races';

View File

@@ -70,7 +70,7 @@ class SearchBaseResponse extends TemplateResponse implements ICache
$this->lvTabs->addListviewTab(new Listview(...$lvData));
// we already have a target > can't have more targets > no redirects
if ($canRedirect && $redirectTo)
if (($canRedirect && $redirectTo) || count($lvData[0]['data']) > 1)
$canRedirect = false;
if ($canRedirect) // note - we are very lucky that in case of searches $template is identical to the typeString

View File

@@ -10,7 +10,7 @@ class SkillBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'skill';
@@ -70,6 +70,9 @@ class SkillBaseResponse extends TemplateResponse implements ICache
$infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags'));
// id
$infobox[] = Lang::skill('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
@@ -77,6 +80,10 @@ class SkillBaseResponse extends TemplateResponse implements ICache
$this->extendGlobalIds(Type::ICON, $_);
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
@@ -220,6 +227,26 @@ class SkillBaseResponse extends TemplateResponse implements ICache
}
}
// tab: modified by [spell]
$conditions = array(
'OR',
['AND', ['effect1AuraId', [SPELL_AURA_MOD_SKILL, SPELL_AURA_MOD_SKILL_TALENT]], ['effect1MiscValue', $this->typeId]],
['AND', ['effect2AuraId', [SPELL_AURA_MOD_SKILL, SPELL_AURA_MOD_SKILL_TALENT]], ['effect2MiscValue', $this->typeId]],
['AND', ['effect3AuraId', [SPELL_AURA_MOD_SKILL, SPELL_AURA_MOD_SKILL_TALENT]], ['effect3MiscValue', $this->typeId]]
);
$modBy = new SpellList($conditions);
if (!$modBy->error)
{
$this->extendGlobalData($modBy->getJSGlobals());
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $modBy->getListviewData(),
'id' => 'modified-by',
'name' => '$LANG.tab_modifiedby',
'hiddenCols' => ['skill'],
), SpellList::$brickFile));
}
// tab: spells [spells] (exclude first tab)
$reqClass = 0x0;
$reqRace = 0x0;

View File

@@ -11,7 +11,7 @@ class SkillsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::SKILL;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'skills';

View File

@@ -10,7 +10,7 @@ class SoundBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'sound';
protected string $pageName = 'sound';
@@ -212,7 +212,7 @@ class SoundBaseResponse extends TemplateResponse implements ICache
}
}
if ($worldStates = array_filter($zoneIds, function ($x) { return $x['worldStateId'] > 0; }))
if ($worldStates = array_filter($zoneIds, fn($x) => $x['worldStateId'] > 0))
{
$tabData['extraCols'] = ['$Listview.extraCols.condition'];
@@ -222,7 +222,7 @@ class SoundBaseResponse extends TemplateResponse implements ICache
Conditions::extendListviewRow($zoneData[$state['id']], Conditions::SRC_NONE, $this->typeId, [Conditions::WORLD_STATE, $state['worldStateId'], $state['worldStateValue']]);
else
foreach ($zoneData as &$d)
if (in_array($state['id'], $d['subzones']))
if (in_array($state['id'], $d['subzones'] ?? []))
Conditions::extendListviewRow($d, Conditions::SRC_NONE, $this->typeId, [Conditions::WORLD_STATE, $state['worldStateId'], $state['worldStateValue']]);
}
}

View File

@@ -11,7 +11,7 @@ class SoundsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::SOUND;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'sounds';
protected string $pageName = 'sounds';
@@ -34,6 +34,12 @@ class SoundsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new SoundListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -41,18 +47,13 @@ class SoundsBaseResponse extends TemplateResponse implements ICache
{
$this->h1 = Util::ucFirst(Lang::game('sounds'));
$this->filter->evalCriteria();
$conditions = [];
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/**************/
/* Page Title */

View File

@@ -14,9 +14,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
SPELL_AURA_ABILITY_PERIODIC_CRIT, SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL, SPELL_AURA_ABILITY_IGNORE_AURASTATE,
SPELL_AURA_ALLOW_ONLY_ABILITY, SPELL_AURA_IGNORE_MELEE_RESET, SPELL_AURA_ABILITY_CONSUME_NO_AMMO,
SPELL_AURA_MOD_IGNORE_SHAPESHIFT, SPELL_AURA_PERIODIC_HASTE, SPELL_AURA_OVERRIDE_CLASS_SCRIPTS,
SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELL_AURA_ADD_TARGET_TRIGGER, /* SPELL_AURA_DUMMY ? */];
SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELL_AURA_ADD_TARGET_TRIGGER, SPELL_AURA_IGNORE_COMBAT_RESULT, /* SPELL_AURA_DUMMY ? */];
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'spell';
protected string $pageName = 'spell';
@@ -26,7 +26,6 @@ class SpellBaseResponse extends TemplateResponse implements ICache
public int $type = Type::SPELL;
public int $typeId = 0;
public array $reagents = [false, null];
public array $scaling = [];
public string $items = '';
public array $tools = [];
public array $effects = [];
@@ -161,13 +160,6 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->createReagentList();
/**********************/
/* Spell Scaling Info */
/**********************/
$this->createScalingData();
/******************/
/* Required Items */
/******************/
@@ -449,7 +441,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
['s.effect1Id', $this->subject->getField('effect1Id')],
['s.effect2Id', $this->subject->getField('effect2Id')],
['s.effect3Id', $this->subject->getField('effect3Id')],
['s.id', $this->subject->id, '!'],
['s.id', $this->typeId, '!'],
['s.name_loc'.Lang::getLocale()->value, $this->subject->getField('name', true)]
);
@@ -534,7 +526,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// tab: used by - spell
if ($so = DB::Aowow()->selectCell('SELECT `id` FROM ?_spelloverride WHERE `spellId1` = ?d OR `spellId2` = ?d OR `spellId3` = ?d OR `spellId4` = ?d OR `spellId5` = ?d', $this->subject->id, $this->subject->id, $this->subject->id, $this->subject->id, $this->subject->id))
if ($so = DB::Aowow()->selectCell('SELECT `id` FROM ?_spelloverride WHERE `spellId1` = ?d OR `spellId2` = ?d OR `spellId3` = ?d OR `spellId4` = ?d OR `spellId5` = ?d', $this->typeId, $this->typeId, $this->typeId, $this->typeId, $this->typeId))
{
$conditions = array(
'OR',
@@ -559,8 +551,8 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: used by - itemset
$conditions = array(
'OR',
['spell1', $this->subject->id], ['spell2', $this->subject->id], ['spell3', $this->subject->id], ['spell4', $this->subject->id],
['spell5', $this->subject->id], ['spell6', $this->subject->id], ['spell7', $this->subject->id], ['spell8', $this->subject->id]
['spell1', $this->typeId], ['spell2', $this->typeId], ['spell3', $this->typeId], ['spell4', $this->typeId],
['spell5', $this->typeId], ['spell6', $this->typeId], ['spell7', $this->typeId], ['spell8', $this->typeId]
);
$ubSets = new ItemsetList($conditions);
@@ -578,11 +570,11 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: used by - item
$conditions = array(
'OR',
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN, '!'], ['spellId1', $this->subject->id]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN, '!'], ['spellId2', $this->subject->id]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN, '!'], ['spellId3', $this->subject->id]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN, '!'], ['spellId4', $this->subject->id]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN, '!'], ['spellId5', $this->subject->id]]
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN, '!'], ['spellId1', $this->typeId]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN, '!'], ['spellId2', $this->typeId]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN, '!'], ['spellId3', $this->typeId]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN, '!'], ['spellId4', $this->typeId]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN, '!'], ['spellId5', $this->typeId]]
);
$ubItems = new ItemList($conditions);
@@ -600,8 +592,8 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: used by - object
$conditions = array(
'OR',
['onUseSpell', $this->subject->id], ['onSuccessSpell', $this->subject->id],
['auraSpell', $this->subject->id], ['triggeredSpell', $this->subject->id]
['onUseSpell', $this->typeId], ['onSuccessSpell', $this->typeId],
['auraSpell', $this->typeId], ['triggeredSpell', $this->typeId]
);
if (!empty($ubSAI[Type::OBJECT]))
$conditions[] = ['id', $ubSAI[Type::OBJECT]];
@@ -656,54 +648,53 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// tab: contains
// spell_loot_template & skill_extra_item_template
$extraItem = DB::World()->selectRow('SELECT * FROM skill_extra_item_template WHERE `spellid` = ?d', $this->subject->id);
// spell_loot_template
$spellLoot = new Loot();
if ($spellLoot->getByContainer(LOOT_SPELL, $this->subject->id) || $extraItem)
if ($spellLoot->getByContainer(LOOT_SPELL, $this->typeId))
{
$this->extendGlobalData($spellLoot->jsGlobals);
$lv = $spellLoot->getResult();
$extraCols = $spellLoot->extraCols;
$extraCols[] = '$Listview.extraCols.percent';
$lvName = '$LANG.tab_contains';
if ($extraItem && $this->subject->canCreateItem())
{
$foo = $this->subject->relItems->getListviewData();
for ($i = 1; $i < 4; $i++)
{
if (($bar = $this->subject->getField('effect'.$i.'CreateItemId')) && isset($foo[$bar]))
{
$lvName = '$LANG.tab_bonusloot';
$lv[$bar] = $foo[$bar];
$lv[$bar]['percent'] = $extraItem['additionalCreateChance'];
$lv[$bar]['pctstack'] = $this->buildPctStack($extraItem['additionalCreateChance'] / 100, $extraItem['additionalMaxNum']);
if ($max = ($extraItem['additionalMaxNum'] - 1))
$lv[$bar]['stack'] = [1, $max];
if (Conditions::extendListviewRow($lv[$bar], Conditions::SRC_NONE, $this->typeId, [Conditions::SPELL, $extraItem['requiredSpecialization']]))
{
$this->extendGlobalIds(Type::SPELL, $extraItem['requiredSpecialization']);
$extraCols[] = '$Listview.extraCols.condition';
}
break; // skill_extra_item_template can only contain 1 item
}
}
}
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lv,
'name' => $lvName,
'data' => $spellLoot->getResult(),
'name' => '$LANG.tab_contains',
'id' => 'contains',
'hiddenCols' => ['side', 'slot', 'source', 'reqlevel'],
'extraCols' => array_unique($extraCols)
), ItemList::$brickFile));
}
// tab: bonus loot
if ($extraItemData = DB::World()->select('SELECT `spellId` AS ARRAY_KEY, `additionalCreateChance` AS "0", `additionalMaxNum` AS "1" FROM skill_extra_item_template WHERE `requiredSpecialization` = ?d', $this->typeId))
{
$extraSpells = new SpellList(array(['id', array_keys($extraItemData)]));
if (!$extraSpells->error)
{
$this->extendGlobalData($extraSpells->getJSGlobals(GLOBALINFO_RELATED));
$lvItems = $extraSpells->getListviewData();
foreach ($lvItems as $iId => $data)
{
[$chance, $maxItr] = $extraItemData[$iId];
$lvItems[$iId]['count'] = 1; // expected by js or the pct-col becomes unsortable
$lvItems[$iId]['percent'] = $chance;
$lvItems[$iId]['pctstack'] = $this->buildPctStack($chance / 100, $maxItr, $data['creates'][1]);
$lvItems[$iId]['creates'][2] *= $maxItr;
}
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lvItems,
'name' => '$LANG.tab_bonusloot',
'id' => 'bonusloot',
'hiddenCols' => ['side', 'reqlevel'],
'extraCols' => ['$Listview.extraCols.percent']
), SpellList::$brickFile));
}
}
// tab: exclusive with
if ($this->firstRank && DB::World()->selectCell('SELECT 1 FROM spell_group WHERE `spell_id` = ?d', $this->firstRank))
{
@@ -819,9 +810,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: triggered by
$conditions = array(
'OR',
['AND', ['OR', ['effect1Id', SpellList::EFFECTS_TRIGGER], ['effect1AuraId', SpellList::AURAS_TRIGGER]], ['effect1TriggerSpell', $this->subject->id]],
['AND', ['OR', ['effect2Id', SpellList::EFFECTS_TRIGGER], ['effect2AuraId', SpellList::AURAS_TRIGGER]], ['effect2TriggerSpell', $this->subject->id]],
['AND', ['OR', ['effect3Id', SpellList::EFFECTS_TRIGGER], ['effect3AuraId', SpellList::AURAS_TRIGGER]], ['effect3TriggerSpell', $this->subject->id]],
['AND', ['OR', ['effect1Id', SpellList::EFFECTS_TRIGGER], ['effect1AuraId', SpellList::AURAS_TRIGGER]], ['effect1TriggerSpell', $this->typeId]],
['AND', ['OR', ['effect2Id', SpellList::EFFECTS_TRIGGER], ['effect2AuraId', SpellList::AURAS_TRIGGER]], ['effect2TriggerSpell', $this->typeId]],
['AND', ['OR', ['effect3Id', SpellList::EFFECTS_TRIGGER], ['effect3AuraId', SpellList::AURAS_TRIGGER]], ['effect3TriggerSpell', $this->typeId]],
);
$trigger = new SpellList($conditions);
@@ -1057,15 +1048,16 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: taught by spell
$conditions = array(
'OR',
['AND', ['effect1Id', SpellList::EFFECTS_TEACH], ['effect1TriggerSpell', $this->subject->id]],
['AND', ['effect2Id', SpellList::EFFECTS_TEACH], ['effect2TriggerSpell', $this->subject->id]],
['AND', ['effect3Id', SpellList::EFFECTS_TEACH], ['effect3TriggerSpell', $this->subject->id]],
['AND', ['effect1Id', SpellList::EFFECTS_TEACH], ['effect1TriggerSpell', $this->typeId]],
['AND', ['effect2Id', SpellList::EFFECTS_TEACH], ['effect2TriggerSpell', $this->typeId]],
['AND', ['effect3Id', SpellList::EFFECTS_TEACH], ['effect3TriggerSpell', $this->typeId]],
);
$tbSpell = new SpellList($conditions);
$tbsData = [];
if (!$tbSpell->error)
{
$tbsData = $tbSpell->getFoundIDs();
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $tbSpell->getListviewData(),
'id' => 'taught-by-spell',
@@ -1076,15 +1068,14 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// tab: taught by quest
$conditions = ['OR', ['sourceSpellId', $this->typeId], ['rewardSpell', $this->typeId]];
$conditions = array(
'OR',
['sourceSpellId', $this->typeId],
['rewardSpell', $this->typeId],
['rewardSpellCast', $this->typeId]
);
if ($tbsData)
{
$conditions[] = ['rewardSpell', array_keys($tbsData)];
if (User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = ['rewardSpellCast', array_keys($tbsData)];
}
if (User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = ['rewardSpellCast', $this->typeId];
array_push($conditions, ['rewardSpell', $tbsData], ['rewardSpellCast', $tbsData]);
$tbQuest = new QuestList($conditions);
if (!$tbQuest->error)
@@ -1101,11 +1092,11 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: taught by item (i'd like to precheck $this->subject->sources, but there is no source:item only complicated crap like "drop" and "vendor")
$conditions = array(
'OR',
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN], ['spellId1', $this->subject->id]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN], ['spellId2', $this->subject->id]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN], ['spellId3', $this->subject->id]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN], ['spellId4', $this->subject->id]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN], ['spellId5', $this->subject->id]],
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN], ['spellId1', $this->typeId]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN], ['spellId2', $this->typeId]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN], ['spellId3', $this->typeId]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN], ['spellId4', $this->typeId]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN], ['spellId5', $this->typeId]],
);
$tbItem = new ItemList($conditions);
@@ -1166,6 +1157,55 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
}
// tab: unlocks (object or item)
$lockIds = DB::Aowow()->selectCol(
'SELECT `id` FROM ?_lock WHERE (`type1` = ?d AND `properties1` = ?d) OR
(`type2` = ?d AND `properties2` = ?d) OR (`type3` = ?d AND `properties3` = ?d) OR
(`type4` = ?d AND `properties4` = ?d) OR (`type5` = ?d AND `properties5` = ?d)',
LOCK_TYPE_SPELL, $this->typeId, LOCK_TYPE_SPELL, $this->typeId,
LOCK_TYPE_SPELL, $this->typeId, LOCK_TYPE_SPELL, $this->typeId,
LOCK_TYPE_SPELL, $this->typeId
);
// we know this spell effect is only in use on index 1
if ($this->subject->getField('effect1Id') == SPELL_EFFECT_OPEN_LOCK && ($lockId = $this->subject->getField('effect1MiscValue')))
$lockIds += DB::Aowow()->selectCol(
'SELECT `id` FROM ?_lock WHERE (`type1` = ?d AND `properties1` = ?d) OR
(`type2` = ?d AND `properties2` = ?d) OR (`type3` = ?d AND `properties3` = ?d) OR
(`type4` = ?d AND `properties4` = ?d) OR (`type5` = ?d AND `properties5` = ?d)',
LOCK_TYPE_SKILL, $lockId, LOCK_TYPE_SKILL, $lockId,
LOCK_TYPE_SKILL, $lockId, LOCK_TYPE_SKILL, $lockId,
LOCK_TYPE_SKILL, $lockId
);
if ($lockIds)
{
// objects
$lockedObj = new GameObjectList(array(Cfg::get('SQL_LIMIT_NONE'), ['lockId', $lockIds]));
if (!$lockedObj->error)
{
$this->addDataLoader('zones');
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lockedObj->getListviewData(),
'name' => '$LANG.tab_unlocks',
'id' => 'unlocks-object',
'visibleCols' => $lockedObj->hasSetFields('reqSkill') ? ['skill'] : null
), GameObjectList::$brickFile));
}
$lockedItm = new ItemList(array(Cfg::get('SQL_LIMIT_NONE'), ['lockId', $lockIds]));
if (!$lockedItm->error)
{
$this->extendGlobalData($lockedItm->getJSGlobals(GLOBALINFO_SELF));
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lockedItm->getListviewData(),
'name' => '$LANG.tab_unlocks',
'id' => 'unlocks-item'
), ItemList::$brickFile));
}
}
// find associated NPC, Item and merge results
// taughtbypets (unused..?)
// taughtbyquest (usually the spell casted as quest reward teaches something; exclude those seplls from taughtBySpell)
@@ -1191,7 +1231,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
/* SpellLoot recursive dropchance builder */
/******************************************/
private function buildPctStack(float $baseChance, int $maxStack) : string
private function buildPctStack(float $baseChance, int $maxStack, int $baseCount = 1) : string
{
// note: pctStack does not contain absolute values but chances relative to the overall drop chance
// e.g.: dropChance is 17% then [1 => 50, 2 => 25, 3 => 25] displays > Stack of 1: 8%; Stack of 2: 4%; Stack of 3: 4%
@@ -1212,6 +1252,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// cleanup tiny fractions
$pctStack = array_filter($pctStack, fn($x) => ($x * $baseChance) >= 0.01);
if ($baseCount > 1)
$pctStack = array_combine(array_map(fn($x) => $x * $baseCount, array_keys($pctStack)), $pctStack);
return json_encode($pctStack, JSON_NUMERIC_CHECK); // do not replace with Util::toJSON !
}
@@ -1333,10 +1376,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (!$reagents)
return;
foreach ($this->subject->relItems->iterate() as $iId => $__)
foreach ($reagents as [$iId, $num])
{
if (!in_array($iId, array_keys($reagents)))
continue;
$relItem = $this->subject->relItems->getEntry($iId);
$data = array(
'path' => Type::ITEM.'-'.$iId, // id of the html-element
@@ -1345,11 +1387,12 @@ class SpellBaseResponse extends TemplateResponse implements ICache
'typeStr' => Type::getFileString(Type::ITEM),
'icon' => new IconElement(
Type::ITEM,
$iId,
$this->subject->relItems->getField('name', true),
$reagents[$iId][1],
quality: $this->subject->relItems->getField('quality'),
is_null($relItem) ? 0 : $iId,
is_null($relItem) ? 'Item #'.$iId : $this->subject->relItems->getField('name', true),
$num,
quality: $relItem['quality'] ?? 'q',
size: IconElement::SIZE_SMALL,
link: !is_null($relItem),
align: 'right',
element: 'iconlist-icon'
)
@@ -1372,9 +1415,15 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->reagents = [$enhanced, $reagentResult];
}
private function createScalingData() : void // calculation mostly like seen in TC
private function calculateEffectScaling() : array // calculation mostly like seen in TC
{
$scaling = ['directSP' => 0, 'dotSP' => 0, 'directAP' => 0, 'dotAP' => 0];
if ($this->subject->getField('attributes3') & SPELL_ATTR3_NO_DONE_BONUS)
return [0, 0, 0, 0];
if (!$this->subject->isScalableDamagingSpell() && !$this->subject->isScalableHealingSpell())
return [0, 0, 0, 0];
$scaling = [0, 0, 0, 0];
$pMask = $this->subject->periodicEffectsMask();
$allDoTs = true;
@@ -1385,25 +1434,20 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($pMask & 1 << ($i - 1))
{
$scaling['dotSP'] = $this->subject->getField('effect'.$i.'BonusMultiplier');
$scaling[1] = $this->subject->getField('effect'.$i.'BonusMultiplier');
continue;
}
else
$scaling['directSP'] = $this->subject->getField('effect'.$i.'BonusMultiplier');
else if ($this->subject->getField('damageClass') == SPELL_DAMAGE_CLASS_MAGIC)
$scaling[0] = $this->subject->getField('effect'.$i.'BonusMultiplier');
$allDoTs = false;
}
if ($s = DB::World()->selectRow('SELECT `direct_bonus` AS "directSP", `dot_bonus` AS "dotSP", `ap_bonus` AS "directAP", `ap_dot_bonus` AS "dotAP" FROM spell_bonus_data WHERE `entry` = ?d', $this->firstRank))
if ($s = DB::World()->selectRow('SELECT `direct_bonus` AS "0", `dot_bonus` AS "1", `ap_bonus` AS "2", `ap_dot_bonus` AS "3" FROM spell_bonus_data WHERE `entry` = ?d', $this->firstRank))
$scaling = $s;
if ((!$this->subject->isDamagingSpell() && !$this->subject->isHealingSpell()) ||
!in_array($this->subject->getField('typeCat'), [-2, -3, -7, 7]) ||
$this->subject->getField('damageClass') == SPELL_DAMAGE_CLASS_NONE)
{
$this->scaling = array_filter($scaling, fn($x) => $x > 0);
return;
}
if (!in_array($this->subject->getField('typeCat'), [-2, -3, -7, 7]) || $this->subject->getField('damageClass') == SPELL_DAMAGE_CLASS_NONE)
return array_map(fn($x) => $x < 0 ? 0 : $x, $scaling);
foreach ($scaling as $k => $v)
{
@@ -1412,15 +1456,15 @@ class SpellBaseResponse extends TemplateResponse implements ICache
continue;
// no known calculation for physical abilities
if ($k == 'directAP' || $k == 'dotAP')
if (in_array($k, [2, 3])) // [direct AP, DoT AP]
continue;
// dont use spellPower to scale physical Abilities
if ($this->subject->getField('schoolMask') == (1 << SPELL_SCHOOL_NORMAL) && ($k == 'directSP' || $k == 'dotSP'))
if ($this->subject->getField('schoolMask') == (1 << SPELL_SCHOOL_NORMAL) && in_array($k, [0, 1]))
continue;
$isDOT = false;
if ($k == 'dotSP' || $k == 'dotAP')
if (in_array($k, [1, 3])) // [DoT SP, DoT AP]
{
if ($pMask)
$isDOT = true;
@@ -1458,7 +1502,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
}
if ($this->subject->isHealingSpell())
if ($this->subject->isScalableHealingSpell())
$castingTime *= 1.88;
// SPELL_SCHOOL_MASK_NORMAL
@@ -1468,7 +1512,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$scaling[$k] = 0; // would be 1 ($dotFactor), but we dont want it to be displayed
}
$this->scaling = array_filter($scaling, fn($x) => $x > 0);
return array_map(fn($x) => $x < 0 ? 0 : $x, $scaling);
}
private function createRequiredItems() : void
@@ -1530,6 +1574,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$spellIdx = array_unique(array_merge($this->subject->canTriggerSpell(), $this->subject->canTeachSpell()));
$itemIdx = $this->subject->canCreateItem();
$perfItem = DB::World()->selectRow('SELECT `perfectItemType` AS "itemId", `requiredSpecialization` AS "reqSpellId", `perfectCreateChance` AS "chance" FROM skill_perfect_item_template WHERE `spellId` = ?d', $this->typeId);
$scaling = $this->calculateEffectScaling();
// Iterate through all effects:
for ($i = 1; $i < 4; $i++)
@@ -1561,7 +1606,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
*/
$_nameEffect = $_nameAura = $_nameMV = $_nameMVB = $_markup = '';
$_icon = $_perfItem = $_footer = [];
$_icon = $_perfItem = $_footer = [];
$_footer['value'] = [0, 0];
$valueFmt = '%s';
@@ -1578,7 +1623,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
Type::ITEM,
$itemId,
$itemEntry ? $this->subject->relItems->getField('name', true) : Util::ucFirst(Lang::game('item')).' #'.$itemId,
($effBP + 1) . ($effDS > 1 ? '-' . ($effBP + $effDS) : ''),
$this->createNumRange($effBP, $effDS),
quality: $itemEntry ? $this->subject->relItems->getField('quality') : '',
link: !empty($itemEntry)
);
@@ -1591,7 +1636,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (!$cndSpell->error)
{
$_perfItem = array(
'spellId' => $perfItem['reqSpellId'],
'spellId' => $cndSpell->id,
'spellName' => $cndSpell->getField('name', true),
'icon' => $cndSpell->getField('iconString'),
'chance' => $perfItem['chance'],
@@ -1604,6 +1649,26 @@ class SpellBaseResponse extends TemplateResponse implements ICache
);
}
}
else if ($extraItem = DB::World()->selectRow('SELECT * FROM skill_extra_item_template WHERE `spellid` = ?d', $this->typeId))
{
$cndSpell = new SpellList(array(['id', $extraItem['requiredSpecialization']]));
if (!$cndSpell->error)
{
$_perfItem = array(
'spellId' => $cndSpell->id,
'spellName' => $cndSpell->getField('name', true),
'icon' => $cndSpell->getField('iconString'),
'chance' => $extraItem['additionalCreateChance'],
'item' => new IconElement(
Type::ITEM,
$this->subject->relItems->id,
$this->subject->relItems->getField('name', true),
num: '+'.$this->createNumRange($effBP, $effDS, $extraItem['additionalMaxNum']),
quality: $this->subject->relItems->getField('quality')
)
);
}
}
}
// .. from spell
else if (in_array($i, $spellIdx) || $effId == SPELL_EFFECT_UNLEARN_SPECIALIZATION)
@@ -1645,7 +1710,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (in_array($i, $this->subject->canTriggerSpell()) && $procData['chance'] && $procData['chance'] < 100)
{
$_footer['proc'] = $procData['chance'] < 0 ? Lang::spell('ppm', [Lang::nf(-$procData['chance'], 1)]) : Lang::spell('procChance') . $procData['chance'] . '%';
$_footer['proc'] = $procData['chance'] < 0 ? Lang::spell('ppm', [-$procData['chance']]) : Lang::spell('procChance', [$procData['chance']]);
if ($procData['cooldown'])
$_footer['procCD'] = Lang::game('cooldown', [Util::formatTime($procData['cooldown'], true)]);
}
@@ -1892,6 +1957,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV);
break;
case SPELL_AURA_MOD_LANGUAGE:
case SPELL_AURA_COMPREHEND_LANGUAGE:
if ($_ = Lang::game('languages', $effMV))
$_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV);
break;
@@ -1967,8 +2033,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($_ = Lang::getMagicSchools($effMV))
$_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.Util::asHex($effMV));
break;
case SPELL_AURA_MOD_SKILL:
case SPELL_AURA_MOD_SKILL_TALENT:
case SPELL_AURA_MOD_SKILL: // temp
case SPELL_AURA_MOD_SKILL_TALENT: // perm
$valueFmt = '%+d';
if ($a = SkillList::makeLink($effMV))
$_nameMV = $a;
else
@@ -2137,6 +2204,21 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (isset($_footer['value'][2]))
$buffer .= $_footer['value'][2];
if (in_array($effId, SpellList::EFFECTS_SCALING_DAMAGE))
{
if ($scaling[2])
$buffer .= Lang::spell('apMod', [$scaling[2]]);
if ($scaling[0])
$buffer .= Lang::spell('spMod', [$scaling[0]]);
}
if (in_array($effAura, SpellList::AURAS_SCALING_DAMAGE))
{
if ($scaling[3])
$buffer .= Lang::spell('apMod', [$scaling[3]]);
if ($scaling[1])
$buffer .= Lang::spell('spMod', [$scaling[1]]);
}
$_footer['value'] = $buffer;
}
else
@@ -2197,10 +2279,16 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->attributes = $list;
}
private function createNumRange(int $bp, int $ds, int $mult = 1) : string
{
return Util::createNumRange($bp + 1, ($bp + $ds) * $mult, '-');
}
private function generatePath()
{
$cat = $this->subject->getField('typeCat');
$cf = $this->subject->getField('cuFlags');
$sl = $this->subject->getField('skillLines');
$this->breadcrumb[] = $cat;
@@ -2222,14 +2310,15 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($cat == -13)
$this->breadcrumb[] = ($cf & (SPELL_CU_GLYPH_MAJOR | SPELL_CU_GLYPH_MINOR)) >> 6;
else
$this->breadcrumb[] = $this->subject->getField('skillLines')[0];
else if ($sl)
$this->breadcrumb[] = $sl[0];
break;
case 9:
case -3:
case 11:
$this->breadcrumb[] = $this->subject->getField('skillLines')[0];
if ($sl)
$this->breadcrumb[] = $sl[0];
if ($cat == 11)
if ($_ = $this->subject->getField('reqSpellId'))
@@ -2238,7 +2327,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
break;
case -11:
foreach (SpellList::$skillLines as $line => $skills)
if (in_array($this->subject->getField('skillLines')[0], $skills))
if (in_array($sl[0] ?? [], $skills))
$this->breadcrumb[] = $line;
break;
case -7: // only spells unique in skillLineAbility will always point to the right skillLine :/
@@ -2263,8 +2352,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
private function createInfobox() : void
{
$infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags'));
$typeCat = $this->subject->getField('typeCat');
$infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags'));
$typeCat = $this->subject->getField('typeCat');
$hasCompletion = in_array($typeCat, [-5, -6]) && !($this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW);
// level
if (!in_array($typeCat, [-5, -6])) // not mount or vanity pet
@@ -2309,11 +2399,11 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (in_array($typeCat, [9, 11]))
{
// skill
if ($_ = $this->subject->getField('skillLines')[0])
if ($_ = $this->subject->getField('skillLines'))
{
$this->extendGlobalIds(Type::SKILL, $_);
$this->extendGlobalIds(Type::SKILL, $_[0]);
$bar = Lang::game('requires', ['&nbsp;[skill='.$_.']']);
$bar = Lang::game('requires', ['&nbsp;[skill='.$_[0].']']);
if ($_ = $this->subject->getField('learnedAt'))
$bar .= ' ('.$_.')';
@@ -2345,13 +2435,30 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($cost = $this->subject->getField('trainingCost'))
$infobox[] = Lang::spell('trainingCost').'[money='.$cost.']';
// id
$infobox[] = Lang::spell('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
}
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && $hasCompletion)
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_spells WHERE `spellId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
// used in mode
foreach ($this->difficulties as $n => $id)
if ($id == $this->typeId)
@@ -2375,7 +2482,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$infobox[] = 'Script'.Lang::main('colon').$_;
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', $hasCompletion);
// append glyph symbol if available
$glyphId = 0;

View File

@@ -26,7 +26,7 @@ class SpellsBaseResponse extends TemplateResponse implements ICache
);
protected int $type = Type::SPELL;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'spells';
protected string $pageName = 'spells';
@@ -99,6 +99,12 @@ class SpellsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new SpellListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -110,13 +116,9 @@ class SpellsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */

View File

@@ -10,7 +10,7 @@ class TitleBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected array $breadcrumb = [0, 10];
@@ -84,8 +84,25 @@ class TitleBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::game('eventShort', ['[event='.$eId.']']);
}
// id
$infobox[] = Lang::title('id') . $this->typeId;
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE'))
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_titles WHERE `titleId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', 1);
/****************/

View File

@@ -11,7 +11,7 @@ class TitlesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::TITLE;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'titles';

View File

@@ -42,6 +42,10 @@ class UserBaseResponse extends TemplateResponse
$this->user = $user;
else
$this->generateNotFound(Lang::user('notFound', [$pageParam]));
// do not display system account
if (!$this->user['id'])
$this->generateNotFound(Lang::user('notFound', [$pageParam]));
}
protected function generate() : void
@@ -229,30 +233,33 @@ class UserBaseResponse extends TemplateResponse
if (Cfg::get('PROFILER_ENABLE'))
{
$conditions = array(
['OR', ['cuFlags', PROFILER_CU_PUBLISHED, '&'], ['ap.extraFlags', PROFILER_CU_PUBLISHED, '&']],
[['cuFlags', PROFILER_CU_DELETED, '&'], 0],
['OR', ['user', $this->user['id']], ['ap.accountId', $this->user['id']]]
);
if (User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions = array_slice($conditions, 2);
else if (User::$id == $this->user['id'])
array_shift($conditions);
$conditions = [['user', $this->user['id']]];
if (User::$id != $this->user['id'] && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions[] = ['cuFlags', PROFILER_CU_PUBLISHED, '&'];
if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions[] = ['deleted', 0];
$profiles = new LocalProfileList($conditions);
if (!$profiles->error)
{
$this->addDataLoader('weight-presets');
// Characters
if ($chars = $profiles->getListviewData(PROFILEINFO_CHARACTER | PROFILEINFO_USER))
$this->charactersLvData = array_values($chars);
// Profiles
if ($prof = $profiles->getListviewData(PROFILEINFO_PROFILE | PROFILEINFO_USER))
$this->profilesLvData = array_values($prof);
}
$conditions = [['ap.accountId', $this->user['id']]];
if (User::$id != $this->user['id'] && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions[] = ['ap.extraFlags', PROFILER_CU_PUBLISHED, '&'];
$characters = new LocalProfileList($conditions);
if (!$characters->error)
{
$this->addDataLoader('weight-presets');
if ($chars = $characters->getListviewData(PROFILEINFO_CHARACTER | PROFILEINFO_USER))
$this->charactersLvData = array_values($chars);
}
}
// My Guides

View File

@@ -10,7 +10,7 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'zone';
@@ -188,6 +188,13 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
}
}
// id
$infobox[] = Lang::zone('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($botRows = array_filter($quickFactsRows, fn($x) => $x > 0, ARRAY_FILTER_USE_KEY))
$infobox = array_merge($infobox, $botRows);
@@ -561,10 +568,10 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
// tab: NPCs
if ($cSpawns && !$creatureSpawns->error)
{
$tabData = array(
'data' => $creatureSpawns->getListviewData(),
'note' => sprintf(Util::$filterResultString, '?npcs&filter=cr=6;crs='.$this->typeId.';crv=0')
);
$tabData = ['data' => $creatureSpawns->getListviewData()];
if (!is_null(CreatureListFilter::getCriteriaIndex(6, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=6;crs='.$this->typeId.';crv=0');
if ($creatureSpawns->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['_truncated'] = 1;
@@ -577,10 +584,10 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
// tab: Objects
if ($oSpawns && !$objectSpawns->error)
{
$tabData = array(
'data' => $objectSpawns->getListviewData(),
'note' => sprintf(Util::$filterResultString, '?objects&filter=cr=1;crs='.$this->typeId.';crv=0')
);
$tabData = ['data' => $objectSpawns->getListviewData()];
if (!is_null(GameObjectListFilter::getCriteriaIndex(1, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?objects&filter=cr=1;crs='.$this->typeId.';crv=0');
if ($objectSpawns->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['_truncated'] = 1;
@@ -616,7 +623,10 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
if (!in_array($this->typeId, $children))
continue;
$tabData['note'] = '$$WH.sprintf(LANG.lvnote_zonequests, '.$parent.', '.$this->typeId.',"'.$this->subject->getField('name', true).'", '.$this->typeId.')';
if (!is_null(ItemListFilter::getCriteriaIndex(126, $this->typeId)))
$tabData['note'] = '$$WH.sprintf(LANG.lvnote_zonequests, '.$parent.', '.$this->typeId.',"'.$this->subject->getField('name', true).'", '.$this->typeId.')';
else
$tabData['note'] = '$$WH.sprintf(LANG.lvnote_questsind, '.$parent.', '.$this->typeId.',"'.$this->subject->getField('name', true).'")';
break;
}
@@ -655,11 +665,15 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
$rewards = new ItemList(array(['id', array_unique($rewardsLV)]));
if (!$rewards->error)
{
$note = null;
if (!is_null(ItemListFilter::getCriteriaIndex(126, $this->typeId)))
$note = sprintf(Util::$filterResultString, '?items&filter=cr=126;crs='.$this->typeId.';crv=0');
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $rewards->getListviewData(),
'name' => '$LANG.tab_questrewards',
'id' => 'quest-rewards',
'note' => sprintf(Util::$filterResultString, '?items&filter=cr=126;crs='.$this->typeId.';crv=0')
'note' => $note
), ItemList::$brickFile));
$this->extendGlobalData($rewards->getJSGlobals(GLOBALINFO_SELF));

View File

@@ -11,7 +11,7 @@ class ZonesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ZONE;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'zones';

View File

@@ -21,14 +21,10 @@ trait SmartHelper
private function numRange(int $min, int $max, bool $isTime) : string
{
if (!$min && !$max)
return '';
if ($isTime)
return Util::createNumRange($min, $max, ' &ndash; ', fn($x) => Util::formatTime($x, true));
$str = $isTime ? Util::formatTime($min, true) : $min;
if ($max > $min)
$str .= ' &ndash; '.($isTime ? Util::formatTime($max, true) : $max);
return $str;
return Util::createNumRange($min, $max, ' &ndash; ');
}
private function formatTime(int $time, int $_, bool $isMilliSec) : string
@@ -41,6 +37,12 @@ trait SmartHelper
private function castFlags(int $flags) : string
{
if ($x = ($flags & ~SmartAI::CAST_FLAG_VALIDATE))
{
trigger_error('SmartAI::castFlags - unknown SmartCastFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= SmartAI::CAST_FLAG_VALIDATE;
}
$cf = [];
for ($i = 1; $i <= SmartAI::CAST_FLAG_COMBAT_MOVE; $i <<= 1)
if (($flags & $i) && ($x = Lang::smartAI('castFlags', $i)))
@@ -51,6 +53,12 @@ trait SmartHelper
private function npcFlags(int $flags) : string
{
if ($x = ($flags & ~NPC_FLAG_VALIDATE))
{
trigger_error('SmartAI::npcFlags - unknown NpcFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= NPC_FLAG_VALIDATE;
}
$nf = [];
for ($i = 1; $i <= NPC_FLAG_MAILBOX; $i <<= 1)
if (($flags & $i) && ($x = Lang::npc('npcFlags', $i)))
@@ -61,6 +69,12 @@ trait SmartHelper
private function dynFlags(int $flags) : string
{
if ($x = ($flags & ~UNIT_DYNFLAG_VALIDATE))
{
trigger_error('SmartAI::dynFlags - unknown unit dynFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= UNIT_DYNFLAG_VALIDATE;
}
$df = [];
for ($i = 1; $i <= UNIT_DYNFLAG_TAPPED_BY_ALL_THREAT_LIST; $i <<= 1)
if (($flags & $i) && ($x = Lang::unit('dynFlags', $i)))
@@ -71,6 +85,12 @@ trait SmartHelper
private function goFlags(int $flags) : string
{
if ($x = ($flags & ~GO_FLAG_VALIDATE))
{
trigger_error('SmartAI::goFlags - unknown GameobjectFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= GO_FLAG_VALIDATE;
}
$gf = [];
for ($i = 1; $i <= GO_FLAG_DESTROYED; $i <<= 1)
if (($flags & $i) && ($x = Lang::gameObject('goFlags', $i)))
@@ -81,6 +101,12 @@ trait SmartHelper
private function spawnFlags(int $flags) : string
{
if ($x = ($flags & ~SmartAI::SPAWN_FLAG_VALIDATE))
{
trigger_error('SmartAI::spawnFlags - unknown SmartSpawnFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= SmartAI::SPAWN_FLAG_VALIDATE;
}
$sf = [];
for ($i = 1; $i <= SmartAI::SPAWN_FLAG_NOSAVE_RESPAWN; $i <<= 1)
if (($flags & $i) && ($x = Lang::smartAI('spawnFlags', $i)))
@@ -91,6 +117,18 @@ trait SmartHelper
private function unitFlags(int $flags, int $flags2) : string
{
if ($x = ($flags & ~UNIT_FLAG_VALIDATE))
{
trigger_error('SmartAI::unitFlags - unknown UnitFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= UNIT_FLAG_VALIDATE;
}
if ($x = ($flags2 & ~UNIT_FLAG2_VALIDATE))
{
trigger_error('SmartAI::unitFlags - unknown UnitFlags2 '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags2 &= UNIT_FLAG2_VALIDATE;
}
$field = $flags2 ? 'flags2' : 'flags';
$max = $flags2 ? UNIT_FLAG2_ALLOW_CHEAT_SPELLS : UNIT_FLAG_UNK_31;
$uf = [];
@@ -185,6 +223,7 @@ class SmartAI
// public const CAST_FORCE_TARGET_SELF = 0x10; // the target to cast this spell on itself
public const CAST_FLAG_AURA_MISSING = 0x20; // Only casts the spell if the target does not have an aura from the spell
public const CAST_FLAG_COMBAT_MOVE = 0x40; // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS
public const CAST_FLAG_VALIDATE = self::CAST_FLAG_INTERRUPT_PREV | self::CAST_FLAG_TRIGGERED | self::CAST_FLAG_AURA_MISSING | self::CAST_FLAG_COMBAT_MOVE;
public const REACT_PASSIVE = 0;
public const REACT_DEFENSIVE = 1;
@@ -211,6 +250,7 @@ class SmartAI
public const SPAWN_FLAG_IGNORE_RESPAWN = 0x01; // onSpawnIn - ignore & reset respawn timer
public const SPAWN_FLAG_FORCE_SPAWN = 0x02; // onSpawnIn - force additional spawn if already in world
public const SPAWN_FLAG_NOSAVE_RESPAWN = 0x04; // onDespawn - remove respawn time
public const SPAWN_FLAG_VALIDATE = self::SPAWN_FLAG_IGNORE_RESPAWN | self::SPAWN_FLAG_FORCE_SPAWN | self::SPAWN_FLAG_NOSAVE_RESPAWN;
private array $jsGlobals = [];
private array $rawData = [];

View File

@@ -467,8 +467,8 @@ class SmartAction
WHERE tp.`id` = ?d',
Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, $this->param[0]
);
$this->param[10] = Util::jsEscape(Util::localizedString($nodes, 'start'));
$this->param[11] = Util::jsEscape(Util::localizedString($nodes, 'end'));
$this->param[10] = Util::localizedString($nodes, 'start');
$this->param[11] = Util::localizedString($nodes, 'end');
break;
case self::ACTION_SET_INGAME_PHASE_MASK: // 44 -> any target
if ($this->param[0])

View File

@@ -98,6 +98,10 @@ class SmartEvent
public const EVENT_ON_SPELL_FAILED = 84; // On Unit::InterruptSpell
public const EVENT_ON_SPELL_START = 85; // On Spell::prapare
public const EVENT_ON_DESPAWN = 86; // On before creature removed
public const EVENT_SEND_EVENT_TRIGGER = 87; // [RESERVED] UNUSED NEEDS CHERRYPICK
public const EVENT_AREATRIGGER_EXIT = 88; // [RESERVED] don't use on 3.3.5a
public const EVENT_ON_AURA_APPLIED = 89; //
public const EVENT_ON_AURA_REMOVED = 90; //
public const FLAG_NO_REPEAT = 0x0001;
public const FLAG_DIFFICULTY_0 = 0x0002;
@@ -108,6 +112,7 @@ class SmartEvent
public const FLAG_NO_RESET = 0x0100;
public const FLAG_WHILE_CHARMED = 0x0200;
public const FLAG_ALL_DIFFICULTIES = self::FLAG_DIFFICULTY_0 | self::FLAG_DIFFICULTY_1 | self::FLAG_DIFFICULTY_2 | self::FLAG_DIFFICULTY_3;
public const FLAG_VALIDATE = self::FLAG_NO_REPEAT | self::FLAG_DEBUG_ONLY | self::FLAG_NO_RESET | self::FLAG_WHILE_CHARMED | self::FLAG_ALL_DIFFICULTIES;
private const EVENT_CELL_TPL = '[tooltip name=e-#rowIdx#]%1$s[/tooltip][span tooltip=e-#rowIdx#]%2$s[/span]';
@@ -198,7 +203,11 @@ class SmartEvent
self::EVENT_ON_SPELL_CAST => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_SPELL_FAILED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_SPELL_START => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_DESPAWN => [null, null, null, null, null, 0] // NONE
self::EVENT_ON_DESPAWN => [null, null, null, null, null, 0], // NONE
self::EVENT_SEND_EVENT_TRIGGER => [null, null, null, null, null, 2], // UNUSED NEEDS CHERRYPICK
self::EVENT_AREATRIGGER_EXIT => [null, null, null, null, null, 2], // don't use on 3.3.5a
self::EVENT_ON_AURA_APPLIED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_AURA_REMOVED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0] // SpellID, CooldownMin, CooldownMax
);
private array $jsGlobals = [];
@@ -296,7 +305,7 @@ class SmartEvent
);
if ($gmo)
$this->param[10] = Util::jsEscape(Util::localizedString($gmo, 'text'));
$this->param[10] = Util::localizedString($gmo, 'text');
else
trigger_error('SmartAI::event - could not find gossip menu option for event #'.$this->type);
break;
@@ -366,6 +375,12 @@ class SmartEvent
{
$flags = $this->flags;
if ($x = ($flags & ~self::FLAG_VALIDATE))
{
trigger_error('SmartEvent::formatFlags - unused SmartEventFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= self::FLAG_VALIDATE;
}
if (($flags & self::FLAG_ALL_DIFFICULTIES) == self::FLAG_ALL_DIFFICULTIES)
$flags &= ~self::FLAG_ALL_DIFFICULTIES;

View File

@@ -63,7 +63,7 @@ abstract class Filter
protected const PATTERN_NAME = '/[\p{C};%\\\\]/ui';
protected const PATTERN_CRV = '/[\p{C};:%\\\\]/ui';
protected const PATTERN_INT = '/\D/';
public const PATTERN_PARAM = '/^[\p{L}\p{Sm} \d\p{P}]+$/i';
public const PATTERN_PARAM = '/^[\p{L}\p{Sm} \d\p{P}]+$/ui';
protected const ENUM_FACTION = array( 469, 1037, 1106, 529, 1012, 87, 21, 910, 609, 942, 909, 530, 69, 577, 930, 1068, 1104, 729, 369, 92,
54, 946, 67, 1052, 749, 47, 989, 1090, 1098, 978, 1011, 93, 1015, 1038, 76, 470, 349, 1031, 1077, 809,
@@ -82,7 +82,7 @@ abstract class Filter
3520, 3703, 3711, 1377, 3487, 130, 3679, 406, 1519, 4384, 33, 2017, 1477, 4075, 8, 440, 141, 3428, 3519, 3848,
17, 2366, 3840, 3713, 3847, 3775, 4100, 1581, 3557, 3845, 4500, 4809, 47, 3849, 4265, 4493, 4228, 3698, 4406, 3714,
3717, 3715, 717, 67, 3716, 457, 4415, 400, 1638, 1216, 85, 4723, 4722, 1337, 4273, 490, 1497, 206, 1196, 4603,
718, 3277, 28, 40, 11, 4197, 618, 3521, 3805, 66, 1176, 1977);
718, 3277, 28, 40, 11, 4197, 618, 3521, 3805, 66, 1176, 1977, 4987);
protected const ENUM_HEROICDUNGEON = array( 4494, 3790, 4277, 4196, 4416, 4272, 4820, 4264, 3562, 4131, 3792, 2367, 4813, 3791, 3789, 3848, 2366, 3713, 3847, 4100,
4809, 3849, 4265, 4228, 3714, 3717, 3715, 3716, 4415, 4723, 206, 1196);
protected const ENUM_MULTIMODERAID = array( 4812, 3456, 2159, 4500, 4493, 4722, 4273, 4603, 4987);
@@ -91,12 +91,13 @@ abstract class Filter
protected const ENUM_RACE = array( null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false);
protected const ENUM_PROFESSION = array( null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773);
public bool $error = false; // erroneous search fields
public bool $error = false;
public bool $shouldReload = false; // erroneous params have been corrected. Build GET string and reload
// item related
public array $upgrades = []; // [itemId => slotId]
public array $extraOpts = []; // score for statWeights
public array $wtCnd = []; // DBType condition for statWeights
public array $upgrades = []; // [itemId => slotId]
public array $extraOpts = []; // score for statWeights
public array $wtCnd = []; // DBType condition for statWeights
private array $cndSet = []; // db type query storage
private array $rawData = [];
@@ -106,7 +107,7 @@ abstract class Filter
[self::CR_FLAG, <string:colName>, <int:testBit>, <bool:matchAny>] # default param2: matchExact
[self::CR_NUMERIC, <string:colName>, <int:NUM_FLAGS>, <bool:addExtraCol>]
[self::CR_STRING, <string:colName>, <int:STR_FLAGS>, null]
[self::CR_ENUM, <string:colName>, <bool:ANYNONE>, <bool:isEnumVal>] # param3 ? crv is val in enum : key in enum
[self::CR_ENUM, <string:colName>, <bool:ANY_NONE>, <bool:isEnumVal>] # param3 ? crv is val in enum : key in enum
[self::CR_STAFFFLAG, <string:colName>, null, null]
[self::CR_CALLBACK, <string:fnName>, <mixed:param1>, <mixed:param2>]
[self::CR_NYI_PH, null, <int:returnVal>, param2] # mostly 1: to ignore this criterium; 0: to fail the whole query
@@ -126,8 +127,7 @@ abstract class Filter
public array $fiReputationCols = []; // fn params ([[factionId, factionName], ...])
public array $fiExtraCols = []; //
public string $query = ''; // as in url query params
public array $values = []; // old fiData['v']
public array $criteria = []; // old fiData['c']
public array $values = []; // prefiltered rawData
// parse the provided request into a usable format
public function __construct(string|array $data, array $opts = [])
@@ -157,6 +157,8 @@ abstract class Filter
}
$this->initFields();
$this->evalCriteria();
$this->evalWeights();
}
public function mergeCat(array &$cats) : void
@@ -167,13 +169,13 @@ abstract class Filter
private function &criteriaIterator() : \Generator
{
if (!$this->criteria)
if (empty($this->values['cr']))
return;
for ($i = 0; $i < count($this->criteria['cr']); $i++)
for ($i = 0; $i < count($this->values['cr']); $i++)
{
// throws a notice if yielded directly "Only variable references should be yielded by reference"
$v = [&$this->criteria['cr'][$i], &$this->criteria['crs'][$i], &$this->criteria['crv'][$i]];
$v = [&$this->values['cr'][$i], &$this->values['crs'][$i], &$this->values['crv'][$i]];
yield $i => $v;
}
}
@@ -195,7 +197,7 @@ abstract class Filter
public function buildGETParam(array $override = [], array $addCr = []) : string
{
$get = [];
foreach (array_merge($this->criteria, $this->values, $override) as $k => $v)
foreach (array_merge($this->values, $override) as $k => $v)
{
if (isset($addCr[$k]))
{
@@ -228,7 +230,7 @@ abstract class Filter
$this->cndSet = $this->createSQLForValues();
// criteria
foreach ($this->criteriaIterator() as &$_cr)
foreach ($this->criteriaIterator() as $_cr)
if ($cnd = $this->createSQLForCriterium(...$_cr))
$this->cndSet[] = $cnd;
@@ -241,10 +243,10 @@ abstract class Filter
public function getSetCriteria(int ...$cr) : array
{
if (!$cr || !$this->fiSetCriteria)
return $this->fiSetCriteria;
if (!$cr || empty($this->values['cr']))
return [];
return array_values(array_intersect($this->fiSetCriteria['cr'], $cr));
return array_values(array_intersect($this->values['cr'], $cr));
}
@@ -258,12 +260,17 @@ abstract class Filter
return [];
$data = [];
// someone copy/pasted a WH filter
$get = preg_replace('/^(\d+(:\d+)*);(\d+(:\d+)*);(\P{C}+(:\P{C}+)*)$/', 'cr=$1;crs=$3;crv=$5', $get);
foreach (explode(';', $get) as $field)
{
if (!strstr($field, '='))
{
trigger_error('Filter::transformGET - malformed GET string', E_USER_NOTICE);
$this->error = true;
$this->error =
$this->shouldReload = true;
continue;
}
@@ -272,7 +279,8 @@ abstract class Filter
if (!isset(static::$inputFields[$k]))
{
trigger_error('Filter::transformGET - GET param not in filter: '.$k, E_USER_NOTICE);
$this->error = true;
$this->error =
$this->shouldReload = true;
continue;
}
@@ -286,13 +294,24 @@ abstract class Filter
private function initFields() : void
{
/* quirks:
* - in the POST step there may be excess criteria selectors with a value of '', as unselecting a criteria that is not the last will not remove the row from the UI
* - if there are no criteria selected, the placeholder selection will always be sent as ['', null, null], similar to the previous quirk
*
* same for stat weights on ItemListFilter
*/
if (!empty($this->rawData['cr']))
$this->rawData['cr'] = array_filter($this->rawData['cr'], fn($x) => $x !== '') ?: null;
if (!empty($this->rawData['wt']))
$this->rawData['wt'] = array_filter($this->rawData['wt'], fn($x) => $x !== '') ?: null;
$cleanupCr = [];
foreach (static::$inputFields as $inp => [$type, $valid, $asArray])
{
$var = in_array($inp, ['cr', 'crs', 'crv']) ? 'criteria' : 'values';
if (!isset($this->rawData[$inp]) || $this->rawData[$inp] === '')
{
$this->$var[$inp] = $asArray ? [] : null;
$this->values[$inp] = $asArray ? [] : null;
continue;
}
@@ -300,42 +319,62 @@ abstract class Filter
if ($asArray)
{
// quirk: in the POST step criteria can be [[''], null, null] if not selected.
$buff = [];
foreach ((array)$val as $v) // can be string|int in POST step if only one value present
if ($v !== '' && $this->checkInput($type, $valid, $v))
foreach ((array)$val as $i => $v) // can be string|int in POST step if only one value present
{
if (in_array($inp, ['cr', 'crs', 'crv']))
{
if (!$this->checkInput($type, $valid, $v))
$cleanupCr[] = $i;
$buff[] = $v; // always assign, gets removed later as tuple
}
else if ($this->checkInput($type, $valid, $v))
$buff[] = $v;
}
$this->$var[$inp] = $buff;
$this->values[$inp] = $buff;
}
else
$this->$var[$inp] = $this->checkInput($type, $valid, $val) ? $val : null;
$this->values[$inp] = $this->checkInput($type, $valid, $val) ? $val : null;
}
if ($cleanupCr)
{
$this->error =
$this->shouldReload = true;
foreach (array_unique($cleanupCr) as $i)
unset($this->values['cr'][$i], $this->values['crs'][$i], $this->values['crv'][$i]);
$this->values['cr'] = array_values($this->values['cr']);
$this->values['crs'] = array_values($this->values['crs']);
$this->values['crv'] = array_values($this->values['crv']);
}
}
public function evalCriteria() : void // [cr]iterium, [cr].[s]ign, [cr].[v]alue
private function evalCriteria() : void // [cr]iterium, [cr].[s]ign, [cr].[v]alue
{
if (empty($this->criteria['cr']) && empty($this->criteria['crs']) && empty($this->criteria['crv']))
if (empty($this->values['cr']) && empty($this->values['crs']) && empty($this->values['crv']))
return;
else if (empty($this->criteria['cr']) || empty($this->criteria['crs']) || empty($this->criteria['crv']))
{
unset($this->criteria['cr']);
unset($this->criteria['crs']);
unset($this->criteria['crv']);
trigger_error('Filter::setCriteria - one of cr, crs, crv is missing', E_USER_NOTICE);
$this->error = true;
if (empty($this->values['cr']) || empty($this->values['crs']) || empty($this->values['crv']))
{
trigger_error('Filter::evalCriteria - one of cr, crs, crv is missing', E_USER_NOTICE);
unset($this->values['cr'], $this->values['crs'], $this->values['crv']);
$this->error =
$this->shouldReload = true;
return;
}
$_cr = &$this->criteria['cr'];
$_crs = &$this->criteria['crs'];
$_crv = &$this->criteria['crv'];
$_cr = &$this->values['cr'];
$_crs = &$this->values['crs'];
$_crv = &$this->values['crv'];
if (count($_cr) != count($_crv) || count($_cr) != count($_crs) || count($_cr) > 5 || count($_crs) > 5 /*|| count($_crv) > 5*/)
{
// use min provided criterion as basis; 5 criteria at most
$min = max(5, min(count($_cr), count($_crv), count($_crs)));
$min = min(5, count($_cr), count($_crv), count($_crs));
if (count($_cr) > $min)
array_splice($_cr, $min);
@@ -345,70 +384,88 @@ abstract class Filter
if (count($_crs) > $min)
array_splice($_crs, $min);
trigger_error('Filter::setCriteria - cr, crs, crv are imbalanced', E_USER_NOTICE);
$this->error = true;
trigger_error('Filter::evalCriteria - cr, crs, crv are imbalanced', E_USER_NOTICE);
$this->error =
$this->shouldReload = true;
}
for ($i = 0; $i < count($_cr); $i++)
{
// conduct filter specific checks & casts here
$unsetme = false;
if (isset(static::$genericFilter[$_cr[$i]]))
if (!isset(static::$genericFilter[$_cr[$i]]) || $_crs[$i] === '' || $_crv[$i] === '')
{
$gf = static::$genericFilter[$_cr[$i]];
switch ($gf[0])
{
case self::CR_NUMERIC:
$_ = $_crs[$i];
if (!Util::checkNumeric($_crv[$i], $gf[2]) || !$this->int2Op($_))
$unsetme = true;
break;
case self::CR_BOOLEAN:
case self::CR_FLAG:
$_ = $_crs[$i];
if (!$this->int2Bool($_))
$unsetme = true;
break;
case self::CR_ENUM:
case self::CR_STAFFFLAG:
if (!Util::checkNumeric($_crs[$i], NUM_CAST_INT))
$unsetme = true;
break;
}
if ($_crs[$i] === '' || $_crv[$i] === '')
trigger_error('Filter::evalCriteria - received malformed criterium ["'.$_cr[$i].'", "'.$_crs[$i].'", "'.$_crv[$i].'"]', E_USER_NOTICE);
else
trigger_error('Filter::evalCriteria - received unhandled criterium: '.$_cr[$i], E_USER_NOTICE);
unset($_cr[$i], $_crs[$i], $_crv[$i]);
$this->error =
$this->shouldReload = true;
continue;
}
if (!$unsetme && intval($_cr[$i]) && $_crs[$i] !== '' && $_crv[$i] !== '')
continue;
[$crType, $colOrFn, $param1, $param2] = array_pad(static::$genericFilter[$_cr[$i]], 4, null);
unset($_cr[$i]);
unset($_crs[$i]);
unset($_crv[$i]);
// conduct filter specific checks & casts here
switch ($crType)
{
case self::CR_NUMERIC:
$_ = $_crs[$i];
if (Util::checkNumeric($_crv[$i], $param1) && $this->int2Op($_))
continue 2;
break;
case self::CR_BOOLEAN:
case self::CR_FLAG:
$_ = $_crs[$i];
if ($this->int2Bool($_))
continue 2;
break;
case self::CR_STAFFFLAG:
if (User::isInGroup(U_GROUP_EMPLOYEE) && Util::checkNumeric($_crs[$i], NUM_CAST_INT))
continue 2;
break;
case self::CR_ENUM:
if (Util::checkNumeric($_crs[$i], NUM_CAST_INT) && (
(!$param2 && isset(static::$enums[$_cr[$i]][$_crs[$i]])) ||
($param2 && in_array($_crs[$i], static::$enums[$_cr[$i]])) ||
($param1 && ($_crs[$i] == self::ENUM_ANY || $_crs[$i] == self::ENUM_NONE))
))
continue 2;
break;
case self::CR_STRING:
case self::CR_CALLBACK:
case self::CR_NYI_PH:
continue 2;
default:
trigger_error('Filter::evalCriteria - unknown criteria type: '.$crType, E_USER_WARNING);
break;
}
trigger_error('Filter::setCriteria - generic check failed ["'.$_cr[$i].'", "'.$_crs[$i].'", "'.$_crv[$i].'"]', E_USER_NOTICE);
$this->error = true;
trigger_error('Filter::evalCriteria - generic check failed ["'.$_cr[$i].'", "'.$_crs[$i].'", "'.$_crv[$i].'"]', E_USER_NOTICE);
unset($_cr[$i], $_crs[$i], $_crv[$i]);
$this->error =
$this->shouldReload = true;
}
$this->fiSetCriteria = array(
'cr' => $_cr,
'crs' => $_crs,
'crv' => $_crv
);
$this->fiSetCriteria = [$_cr, $_crs, $_crv];
}
public function evalWeights() : void
private function evalWeights() : void
{
// both empty: not in use; not an error
if (!$this->values['wt'] && !$this->values['wtv'])
if (empty($this->values['wt']) && empty($this->values['wtv']))
return;
// one empty: erroneous manual input?
if (!$this->values['wt'] || !$this->values['wtv'])
{
unset($this->values['wt']);
unset($this->values['wtv']);
trigger_error('Filter::setWeights - one of wt, wtv is missing', E_USER_NOTICE);
$this->error = true;
unset($this->values['wt'], $this->values['wtv']);
$this->error =
$this->shouldReload = true;
return;
}
@@ -421,7 +478,8 @@ abstract class Filter
if ($nwt != $nwtv)
{
trigger_error('Filter::setWeights - wt, wtv are imbalanced', E_USER_NOTICE);
$this->error = true;
$this->error =
$this->shouldReload = true;
}
if ($nwt > $nwtv)
@@ -452,20 +510,18 @@ abstract class Filter
if (!Util::checkNumeric($val, NUM_CAST_INT))
return false;
foreach ($valid as $k => $v)
if (in_array($val, $valid))
return true;
foreach ($valid as $v)
{
if (gettype($v) != 'array')
continue;
if ($this->checkInput(self::V_RANGE, $v, $val, true))
return true;
unset($valid[$k]);
}
if (in_array($val, $valid))
return true;
break;
case self::V_RANGE:
if (Util::checkNumeric($val, NUM_CAST_INT) && $val >= $valid[0] && $val <= $valid[1])
@@ -486,7 +542,7 @@ abstract class Filter
if (!$recursive)
{
trigger_error('Filter::checkInput - check failed [type: '.$type.' valid: '.((string)$valid).' val: '.((string)$val).']', E_USER_NOTICE);
trigger_error('Filter::checkInput - check failed [type: '.$type.' valid: '.Util::toString($valid).' val: '.((string)$val).']', E_USER_NOTICE);
$this->error = true;
}
@@ -651,83 +707,72 @@ abstract class Filter
return null;
}
private function genericCriterion(int $cr, int $crs, string $crv) : ?array
{
[$crType, $colOrFn, $param1, $param2] = array_pad(static::$genericFilter[$cr], 4, null);
$result = null;
switch ($crType)
{
case self::CR_NUMERIC:
$result = $this->genericNumeric($colOrFn, $crv, $crs, $param1);
break;
case self::CR_FLAG:
$result = $this->genericBooleanFlags($colOrFn, $param1, $crs, $param2);
break;
case self::CR_STAFFFLAG:
if (User::isInGroup(U_GROUP_EMPLOYEE) && $crs > 0)
$result = $this->genericBooleanFlags($colOrFn, (1 << ($crs - 1)), true);
break;
case self::CR_BOOLEAN:
$result = $this->genericBoolean($colOrFn, $crs, !empty($param1));
break;
case self::CR_STRING:
$result = $this->genericString($colOrFn, $crv, $param1);
break;
case self::CR_ENUM:
if (!$param2 && isset(static::$enums[$cr][$crs]))
$result = $this->genericEnum($colOrFn, static::$enums[$cr][$crs]);
if ($param2 && in_array($crs, static::$enums[$cr]))
$result = $this->genericEnum($colOrFn, $crs);
else if ($param1 && ($crs == self::ENUM_ANY || $crs == self::ENUM_NONE))
$result = $this->genericEnum($colOrFn, $crs);
break;
case self::CR_CALLBACK:
$result = $this->{$colOrFn}($cr, $crs, $crv, $param1, $param2);
break;
case self::CR_NYI_PH: // do not limit with not implemented filters
if (is_int($param1))
return [$param1];
// for nonsensical values; compare against 0
if ($this->int2Op($crs) && Util::checkNumeric($crv))
{
if ($crs == '=')
$crs = '==';
return eval('return ('.$crv.' '.$crs.' 0);') ? [1] : [0];
}
else
return [0];
}
if ($result && $crType == self::CR_NUMERIC && !empty($param2))
$this->fiExtraCols[] = $cr;
return $result;
}
/***********************************/
/* create conditions from */
/* non-generic values and criteria */
/***********************************/
protected function createSQLForCriterium(int &$cr, int &$crs, string &$crv) : array
protected function createSQLForCriterium(int $cr, int $crs, string $crv) : array
{
if (!static::$genericFilter) // criteria not in use - no error
return [];
if (isset(static::$genericFilter[$cr]))
if ($genCr = $this->genericCriterion($cr, $crs, $crv))
return $genCr;
[$crType, $colOrFn, $param1, $param2] = array_pad(static::$genericFilter[$cr], 4, null);
trigger_error('Filter::createSQLForCriterium - received unhandled criterium: ["'.$cr.'", "'.$crs.'", "'.$crv.'"]', E_USER_NOTICE);
$this->error = true;
$handleEnum = function(int $cr, int $crs, string $col, ?bool $hasAnyNone, ?bool $crsAsVal) : ?array
{
if ($hasAnyNone && ($crs == self::ENUM_ANY || $crs == self::ENUM_NONE))
return $this->genericEnum($col, $crs);
else if (!$crsAsVal && isset(static::$enums[$cr][$crs]))
return $this->genericEnum($col, static::$enums[$cr][$crs]);
else if ($crsAsVal && in_array($crs, static::$enums[$cr]))
return $this->genericEnum($col, $crs);
unset($cr, $crs, $crv);
return null;
};
return [];
$handleNYIPH = function(int $crs, string $crv, ?int $forceResult) : ?array
{
if (is_int($forceResult))
return [$forceResult];
// for nonsensical values; compare against 0
if ($this->int2Op($crs) && Util::checkNumeric($crv))
{
if ($crs == '=')
$crs = '==';
return eval('return ('.$crv.' '.$crs.' 0);') ? [1] : [0];
}
else
return [0];
};
$result = match ($crType)
{
self::CR_NUMERIC => $this->genericNumeric($colOrFn, $crv, $crs, $param1),
self::CR_FLAG => $this->genericBooleanFlags($colOrFn, $param1, $crs, $param2),
self::CR_STAFFFLAG => $this->genericBooleanFlags($colOrFn, (1 << ($crs - 1)), true),
self::CR_BOOLEAN => $this->genericBoolean($colOrFn, $crs, !empty($param1)),
self::CR_STRING => $this->genericString($colOrFn, $crv, $param1),
self::CR_CALLBACK => $this->{$colOrFn}($cr, $crs, $crv, $param1, $param2),
self::CR_ENUM => $handleEnum($cr, $crs, $colOrFn, $param1, $param2),
self::CR_NYI_PH => $handleNYIPH($crs, $crv, $param1),
default => null
};
if (!$result)
{
// this really should not have happened. The relevant checks are run on __construct()
trigger_error('Filter::createSQLForCriterium - failed to resolve criterium: ["'.$cr.'", "'.$crs.'", "'.$crv.'"]', E_USER_WARNING);
return [];
}
if ($crType == self::CR_NUMERIC && !empty($param2))
$this->fiExtraCols[] = $cr;
return $result;
}
abstract protected function createSQLForValues() : array;

View File

@@ -15,12 +15,12 @@ class IconElement
private const CREATE_ICON_TPL = "\$WH.ge('%s%d').appendChild(%s.createIcon(%s));\n";
private int $idx = 0;
private string $href = '';
private bool $noIcon = false;
public readonly string $quality;
public readonly ?string $align;
public readonly ?string $href;
public readonly int $size;
public readonly bool $noIcon;
public function __construct(
public readonly int $type,
@@ -70,6 +70,8 @@ class IconElement
if ($link || $url)
$this->href = $url ?: '?'.Type::getFileString($this->type).'='.$this->typeId;
else
$this->href = null;
// see Spell/Tools having icon container but no actual icon and having to be inline with other IconElements
$this->noIcon = !$typeId || !Type::hasIcon($type);
@@ -102,7 +104,7 @@ class IconElement
}
if ($this->href)
($a = $dom->createElement('a', $this->text))->setAttribute('href', $this->href);
($a = $dom->createElement('a', htmlentities($this->text)))->setAttribute('href', $this->href);
else
$a = $dom->createTextNode($this->text);
@@ -148,9 +150,9 @@ class IconElement
$params = [$this->typeId, $this->size];
if ($this->num || $this->qty)
$params[] = is_numeric($this->num) ? $this->num : "'".$this->num."'";
$params[] = is_int($this->num) ? $this->num : "'".$this->num."'";
if ($this->qty)
$params[] = is_numeric($this->qty) ? $this->qty : "'".$this->qty."'";
$params[] = is_int($this->qty) ? $this->qty : "'".$this->qty."'";
return str_repeat(' ', $lpad) . sprintf(self::CREATE_ICON_TPL, $this->element, $this->idx, Type::getJSGlobalString($this->type), implode(', ', $params));
}

View File

@@ -8,7 +8,7 @@ if (!defined('AOWOW_REVISION'))
class InfoboxMarkup extends Markup
{
public function __construct(private array $items, array $opts, string $parent = '')
public function __construct(private array $items, array $opts, string $parent = '', private int $completionRowType = 0)
{
parent::__construct('', $opts, $parent);
}
@@ -23,27 +23,48 @@ class InfoboxMarkup extends Markup
public function append(string $text) : self
{
if ($this->items && !$this->__text)
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
if ($_ = $this->prepare())
$this->replace($_);
return parent::append($text);
}
public function __toString() : string
{
if ($this->items && !$this->__text)
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
// inject before output to avoid adding it to cache
if ($this->completionRowType && User::getCharacters())
$this->items[] = [Lang::profiler('completion') . '[span class="compact-completion-display"][/span]', ['style' => 'display:none']];
if ($_ = $this->prepare())
$this->replace($_);
return parent::__toString();
}
public function getJsGlobals() : array
{
if ($this->items && !$this->__text)
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
if ($_ = $this->prepare())
$this->replace($_);
return parent::getJsGlobals();
}
private function prepare() : string
{
if (!$this->items || $this->__text)
return '';
$buff = '';
foreach ($this->items as $row)
{
if (is_array($row))
$buff .= '[li'.Util::nodeAttributes($row[1]).']' . $row[0] . '[/li]';
else if (is_string($row))
$buff .= '[li]' . $row . '[/li]';
}
return $buff ? '[ul]'.$buff.'[/ul]' : '';
}
}
?>

View File

@@ -146,9 +146,9 @@ class Listview implements \JsonSerializable
$this->tabs = $tabVar;
}
public function setError() : void
public function setError(bool $enable) : void
{
$this->_errors = 1;
$this->_errors = $enable ? 1 : null;
}
public function jsonSerialize() : array
@@ -164,10 +164,11 @@ class Listview implements \JsonSerializable
public function __toString() : string
{
$addIn = '';
if ($this->__addIn)
include($this->__addIn);
$addIn = file_get_contents($this->__addIn).PHP_EOL;
return "new Listview(".Util::toJSON($this).");\n";
return $addIn.'new Listview('.Util::toJSON($this).');'.PHP_EOL;
}
}

View File

@@ -19,8 +19,11 @@ class Tooltip implements \JsonSerializable
private ?string $buff = null;
private ?array $buffspells = null;
public function __construct(private string $__powerTpl, private string $__subject, array $opts = [])
public function __construct(private string $__powerTpl, private int|string $__subject, array $opts = [])
{
if (!is_int($this->__subject))
$this->__subject = Util::toJSON($this->__subject, JSON_UNESCAPED_UNICODE);
foreach ($opts as $k => $v)
{
if (property_exists($this, $k))
@@ -54,7 +57,7 @@ class Tooltip implements \JsonSerializable
public function __toString() : string
{
return sprintf($this->__powerTpl, Util::toJSON($this->__subject, JSON_AOWOW_POWER), Lang::getLocale()->value, Util::toJSON($this, JSON_AOWOW_POWER))."\n";
return sprintf($this->__powerTpl, $this->__subject, Lang::getLocale()->value, Util::toJSON($this, JSON_AOWOW_POWER))."\n";
}
}

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
die('illegal access');
class LocString
class LocString implements \JsonSerializable
{
private \WeakMap $store;
@@ -24,6 +24,11 @@ class LocString
$this->store[$l] = (string)$callback($data[$key.'_loc'.$l->value] ?? '');
}
public function jsonSerialize() : string
{
return $this->__toString();
}
public function __toString() : string
{
if ($str = $this->store[Lang::getLocale()])

View File

@@ -30,6 +30,8 @@ class PageTemplate
private string $gStaticUrl;
private string $gHost;
private string $gServerTime;
private string $gUser;
private string $gFavorites;
private ?string $analyticsTag = null;
private bool $consentFooter = false;
private string $dbProfiles = '';
@@ -336,7 +338,7 @@ class PageTemplate
$result[] = "var fi_type = '".$this->filter->fiType."'";
if ($this->filter->fiSetCriteria) // arr:criteria, arr:signs, arr:values
$result[] = 'fi_setCriteria('.mb_substr(Util::toJSON(array_values($this->filter->fiSetCriteria)), 1, -1).");";
$result[] = 'fi_setCriteria('.mb_substr(Util::toJSON($this->filter->fiSetCriteria), 1, -1).");";
/*
nt: don't try to match provided weights on predefined weight sets (preselects preset from opt list and ..?)
@@ -470,7 +472,7 @@ class PageTemplate
private function update() : void
{
// analytics + consent
if (!isset($_COOKIE['consent']))
if ($this->analyticsTag && !isset($_COOKIE['consent']))
{
$this->addScript(SC_CSS_FILE, 'css/consent.css');
$this->addScript(SC_JS_FILE, 'js/consent.js');
@@ -484,6 +486,9 @@ class PageTemplate
// js + css
$this->prepareScripts();
$this->gUser = Util::toJSON(User::getUserGlobal());
$this->gFavorites = Util::toJSON(User::getFavorites());
// db profiling
if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
$this->dbProfiles = \Aowow\DB::getProfiles();
@@ -496,7 +501,7 @@ class PageTemplate
foreach ($this->lvTabs->iterate() as $lv)
if ($lv instanceof \Aowow\Listview)
$lv->setError();
$lv->setError(true);
}
// pre-serialization: if a var is relevant it was stored in $rawData
@@ -505,6 +510,11 @@ class PageTemplate
$this->context = null; // unlink from TemplateResponse
$this->pageData = []; // clear modified data
if ($this->lvTabs) // do not store lvErrors in cache
foreach ($this->lvTabs->iterate() as $lv)
if ($lv instanceof \Aowow\Listview)
$lv->setError(false);
$vars = [];
foreach ($this as $k => $_)
$vars[] = $k;
@@ -536,7 +546,7 @@ class PageTemplate
if (!$this->context)
return null;
if (!property_exists($this->context, $var))
if (!isset(get_object_vars($this->context)[$var]))
return null;
$this->rawData[$var] = $this->context->$var;

View File

@@ -8,10 +8,10 @@ if (!defined('AOWOW_REVISION'))
class Profiler
{
public const PID_FILE = 'config/pr-queue-pid';
public const CHAR_GMFLAGS = 0x1 | 0x8 | 0x10 | 0x20; // PLAYER_EXTRA_ :: GM_ON | TAXICHEAT | GM_INVISIBLE | GM_CHAT
public const /* string */ PID_FILE = 'config/pr-queue-pid';
public const /* int */ CHAR_GMFLAGS = 0x1 | 0x8 | 0x10 | 0x20; // PLAYER_EXTRA_ :: GM_ON | TAXICHEAT | GM_INVISIBLE | GM_CHAT
public const REGIONS = array( // see cfg_categories.dbc
public const /* array */ REGIONS = array( // see cfg_categories.dbc
'us' => [2, 3, 4, 5], // US (us, oceanic, latin america, americas - tournament)
'kr' => [6, 7], // KR (kr, tournament)
'eu' => [8, 9, 10, 11, 12, 13], // EU (english, german, french, spanish, russian, eu - tournament)
@@ -20,9 +20,9 @@ class Profiler
'dev' => [1, 26, 27, 28, 30] // Development, Test Server, Test Server - tournament, QA Server, Test Server 2
);
private static $realms = [];
private static array $realms = [];
public static $slot2InvType = array(
public static array $slot2InvType = array(
1 => [INVTYPE_HEAD], // head
2 => [INVTYPE_NECK], // neck
3 => [INVTYPE_SHOULDERS], // shoulder
@@ -44,7 +44,7 @@ class Profiler
19 => [INVTYPE_TABARD], // tabard
);
public static $raidProgression = array( // statisticAchievement => relevantCriterium ; don't forget to enable this in /js/Profiler.js as well
public static array $raidProgression = array( // statisticAchievement => relevantCriterium ; don't forget to enable this in /js/Profiler.js as well
1361 => 5100, 1362 => 5101, 1363 => 5102, 1365 => 5104, 1366 => 5108, 1364 => 5110, 1369 => 5112, 1370 => 5113, 1371 => 5114, 1372 => 5117, 1373 => 5119, 1374 => 5120, 1375 => 7805, 1376 => 5122, 1377 => 5123, // Naxxramas 10
1367 => 5103, 1368 => 5111, 1378 => 5124, 1379 => 5125, 1380 => 5126, 1381 => 5127, 1382 => 5128, 1383 => 7806, 1384 => 5130, 1385 => 5131, 1386 => 5132, 1387 => 5133, 1388 => 5134, 1389 => 5135, 1390 => 5136, // Naxxramas 25
2856 => 9938, 2857 => 9939, 2858 => 9940, 2859 => 9941, 2861 => 9943, 2865 => 9947, 2866 => 9948, 2868 => 9950, 2869 => 9951, 2870 => 9952, 2863 => 10558, 2864 => 10559, 2862 => 10560, 2867 => 10565, 2860 => 10580, // Ulduar 10
@@ -65,7 +65,7 @@ class Profiler
4821 => 13466, // Ruby Sanctum 10 nh
);
public static function getBuyoutForItem($itemId)
public static function getBuyoutForItem(int $itemId) : int
{
if (!$itemId)
return 0;
@@ -75,7 +75,7 @@ class Profiler
return 0;
}
public static function queueStart(&$msg = '')
public static function queueStart(?string &$msg = '') : bool
{
$queuePID = self::queueStatus();
@@ -100,7 +100,7 @@ class Profiler
}
}
public static function queueStatus()
public static function queueStatus() : int
{
if (!file_exists(self::PID_FILE))
return 0;
@@ -117,7 +117,7 @@ class Profiler
return 0;
}
public static function queueLock($pid)
public static function queueLock(int $pid) : bool
{
$queuePID = self::queueStatus();
if ($queuePID && $queuePID != $pid)
@@ -139,12 +139,12 @@ class Profiler
return $ok;
}
public static function queueFree()
public static function queueFree() : void
{
unlink(self::PID_FILE);
}
public static function urlize($str, $allowLocales = false, $profile = false)
public static function urlize(string $str, bool $allowLocales = false, bool $profile = false) : string
{
$search = ['<', '>', ' / ', "'"];
$replace = ['&lt;', '&gt;', '-', '' ];
@@ -194,7 +194,7 @@ class Profiler
if (!DB::isConnectable(DB_AUTH) || self::$realms)
return self::$realms;
self::$realms = DB::Auth()->select(
$realms = DB::Auth()->select(
'SELECT `id` AS ARRAY_KEY,
`name`,
CASE WHEN `timezone` BETWEEN 2 AND 5 THEN "us" # US, Oceanic, Latin America, Americas-Tournament
@@ -209,14 +209,14 @@ class Profiler
WOW_BUILD
);
foreach (self::$realms as $rId => &$rData)
if (!$realms)
return [];
foreach ($realms as $rId => $rData)
{
// realm in db but no connection info set
if (!DB::isConnectable(DB_CHARACTERS . $rId))
{
unset(self::$realms[$rId]);
continue;
}
// filter by access level
if ($rData['access'] == SEC_ADMINISTRATOR && (CLI || User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)))
@@ -226,10 +226,7 @@ class Profiler
else if ($rData['access'] == SEC_MODERATOR && (CLI || User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_MOD | U_GROUP_BUREAU)))
$rData['access'] = U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_MOD | U_GROUP_BUREAU;
else if ($rData['access'] > SEC_PLAYER && !CLI)
{
unset(self::$realms[$rId]);
continue;
}
// filter dev realms
if ($rData['region'] === 'dev')
@@ -237,11 +234,10 @@ class Profiler
if (CLI || User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
$rData['access'] = U_GROUP_DEV | U_GROUP_ADMIN;
else
{
unset(self::$realms[$rId]);
continue;
}
}
self::$realms[$rId] = $rData;
}
return self::$realms;
@@ -255,7 +251,7 @@ class Profiler
return array_unique(array_column(self::$realms, 'region'));
}
private static function queueInsert($realmId, $guid, $type, $localId)
private static function queueInsert(int $realmId, int $guid, int $type, int $localId) : void
{
if ($rData = DB::Aowow()->selectRow('SELECT `requestTime` AS "time", `status` FROM ?_profiler_sync WHERE `realm` = ?d AND `realmGUID` = ?d AND `type` = ?d AND `typeId` = ?d AND `status` <> ?d', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WORKING))
{
@@ -270,7 +266,7 @@ class Profiler
DB::Aowow()->query('REPLACE INTO ?_profiler_sync (`realm`, `realmGUID`, `type`, `typeId`, `requestTime`, `status`, `errorCode`) VALUES (?d, ?d, ?d, ?d, UNIX_TIMESTAMP(), ?d, 0)', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WAITING);
}
public static function scheduleResync($type, $realmId, $guid)
public static function scheduleResync(int $type, int $realmId, int $guid) : int
{
$newId = 0;
@@ -339,7 +335,7 @@ class Profiler
// error out all profiles with status WORKING, that are older than 60sec
DB::Aowow()->query('UPDATE ?_profiler_sync SET `status` = ?d, `errorCode` = ?d WHERE `status` = ?d AND `requestTime` < ?d', PR_QUEUE_STATUS_ERROR, PR_QUEUE_ERROR_UNK, PR_QUEUE_STATUS_WORKING, time() - MINUTE);
$subjectStatus = DB::Aowow()->select('SELECT `typeId` AS ARRAY_KEY, `status`, `realm`, `errorCode` FROM ?_profiler_sync WHERE `type` = ?d AND `typeId` IN (?a)', $type, $subjectGUIDs);
$subjectStatus = DB::Aowow()->select('SELECT `typeId` AS ARRAY_KEY, `status`, `realm`, `errorCode`, `requestTime` FROM ?_profiler_sync WHERE `type` = ?d AND `typeId` IN (?a)', $type, $subjectGUIDs);
$queue = DB::Aowow()->selectCol('SELECT CONCAT(`type`, ":", `typeId`) FROM ?_profiler_sync WHERE `status` = ?d AND `requestTime` < UNIX_TIMESTAMP() ORDER BY `requestTime` ASC', PR_QUEUE_STATUS_WAITING);
foreach ($subjectGUIDs as $guid)
{
@@ -347,6 +343,8 @@ class Profiler
$response[] = [PR_QUEUE_STATUS_ERROR, 0, 0, PR_QUEUE_ERROR_UNK];
else if ($subjectStatus[$guid]['status'] == PR_QUEUE_STATUS_ERROR)
$response[] = [PR_QUEUE_STATUS_ERROR, 0, 0, $subjectStatus[$guid]['errorCode']];
else if ($subjectStatus[$guid]['requestTime'] > time())
$response[] = [PR_QUEUE_STATUS_READY, 0, 0, 0];
else
$response[] = array(
$subjectStatus[$guid]['status'],
@@ -361,7 +359,7 @@ class Profiler
return Util::toJSON($response);
}
public static function getCharFromRealm($realmId, $charGuid)
public static function getCharFromRealm(int $realmId, int $charGuid) : bool
{
$char = DB::Characters($realmId)->selectRow('SELECT c.* FROM characters c WHERE c.`guid` = ?d', $charGuid);
if (!$char)
@@ -505,7 +503,7 @@ class Profiler
// char is flagged for rename
if ($char['at_login'] & 0x1)
{
$ri = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND `name` = ?', $realmId, $char['name']);
$ri = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ?', $realmId, $char['name']);
$data['renameItr'] = $ri ? ++$ri : 1;
}
@@ -580,7 +578,7 @@ class Profiler
// enchantId => multiple spells => multiple items with varying itemlevels, quality, whatevs
// cant reasonably get to the castItem from enchantId and slot
$profSpec = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `skillLevel` AS "1", `skillLine` AS "0" FROM ?_itemenchantment WHERE `id` IN (?a)', $permEnch);
$profSpec = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `skillLevel` AS "1", `skillLine` AS "0" FROM ?_itemenchantment WHERE `id` IN (?a)', $permEnch);
foreach ($permEnch as $slot => $eId)
{
if (!isset($profSpec[$eId]))
@@ -623,7 +621,22 @@ class Profiler
$petData['entry']
);
$_ = DB::Aowow()->selectCol('SELECT `spell` AS ARRAY_KEY, MAX(IF(`spell` IN (?a), `rank`, 0)) FROM ?_talents WHERE `class` = 0 AND `petTypeMask` = ?d GROUP BY `row`, `col` ORDER BY `row`, `col` ASC', $petSpells ?: [0], 1 << $morePet['type']);
if (!$morePet)
{
trigger_error('char #'.$charGuid.' on realm #'.$realmId.' owns pet #'.$petGuid.' (creature entry: #'.$petData['entry'].') without pet family. skipping...', E_USER_WARNING);
continue;
}
$_ = DB::Aowow()->selectCol(
'SELECT IFNULL(t2.`rank`, 0)
FROM ?_talents t1
LEFT JOIN (SELECT `id`, `rank` FROM ?_talents WHERE `spell` IN (?a)) t2 ON t2.`id` = t1.`id`
WHERE `class` = 0 AND `petTypeMask` = ?d
GROUP BY t1.`id`
ORDER BY t1.`row`, t1.`col`, t1.`id` ASC',
$petSpells ?: [0], 1 << $morePet['type']
);
$pet = array(
'id' => $petGuid,
'owner' => $profileId,
@@ -692,10 +705,10 @@ class Profiler
// get base values for this race/class
$reputation = [];
$baseRep = DB::Aowow()->selectCol(
'SELECT `id` AS ARRAY_KEY, `baseRepValue1` FROM aowow_factions WHERE `baseRepValue1` AND (`baseRepRaceMask1` & ?d OR (`baseRepClassMask1` AND NOT `baseRepRaceMask1`)) AND ((`baseRepClassMask1` & ?d) OR NOT `baseRepClassMask1`) UNION
SELECT `id` AS ARRAY_KEY, `baseRepValue2` FROM aowow_factions WHERE `baseRepValue2` AND (`baseRepRaceMask2` & ?d OR (`baseRepClassMask2` AND NOT `baseRepRaceMask2`)) AND ((`baseRepClassMask2` & ?d) OR NOT `baseRepClassMask2`) UNION
SELECT `id` AS ARRAY_KEY, `baseRepValue3` FROM aowow_factions WHERE `baseRepValue3` AND (`baseRepRaceMask3` & ?d OR (`baseRepClassMask3` AND NOT `baseRepRaceMask3`)) AND ((`baseRepClassMask3` & ?d) OR NOT `baseRepClassMask3`) UNION
SELECT `id` AS ARRAY_KEY, `baseRepValue4` FROM aowow_factions WHERE `baseRepValue4` AND (`baseRepRaceMask4` & ?d OR (`baseRepClassMask4` AND NOT `baseRepRaceMask4`)) AND ((`baseRepClassMask4` & ?d) OR NOT `baseRepClassMask4`)',
'SELECT `id` AS ARRAY_KEY, `baseRepValue1` FROM ?_factions WHERE `baseRepValue1` AND (`baseRepRaceMask1` & ?d OR (`baseRepClassMask1` AND NOT `baseRepRaceMask1`)) AND ((`baseRepClassMask1` & ?d) OR NOT `baseRepClassMask1`) UNION
SELECT `id` AS ARRAY_KEY, `baseRepValue2` FROM ?_factions WHERE `baseRepValue2` AND (`baseRepRaceMask2` & ?d OR (`baseRepClassMask2` AND NOT `baseRepRaceMask2`)) AND ((`baseRepClassMask2` & ?d) OR NOT `baseRepClassMask2`) UNION
SELECT `id` AS ARRAY_KEY, `baseRepValue3` FROM ?_factions WHERE `baseRepValue3` AND (`baseRepRaceMask3` & ?d OR (`baseRepClassMask3` AND NOT `baseRepRaceMask3`)) AND ((`baseRepClassMask3` & ?d) OR NOT `baseRepClassMask3`) UNION
SELECT `id` AS ARRAY_KEY, `baseRepValue4` FROM ?_factions WHERE `baseRepValue4` AND (`baseRepRaceMask4` & ?d OR (`baseRepClassMask4` AND NOT `baseRepRaceMask4`)) AND ((`baseRepClassMask4` & ?d) OR NOT `baseRepClassMask4`)',
$ra->toMask(), $cl->toMask(), $ra->toMask(), $cl->toMask(), $ra->toMask(), $cl->toMask(), $ra->toMask(), $cl->toMask()
);
@@ -814,7 +827,7 @@ class Profiler
'realmGUID' => $guild['id'],
'name' => $guild['name'],
'nameUrl' => self::urlize($guild['name']),
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
'stub' => 1
);
$guildId = DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES (?a)', array_keys($gData), array_values($gData));
@@ -840,7 +853,7 @@ class Profiler
'name' => $t['name'],
'nameUrl' => self::urlize($t['name']),
'type' => $t['type'],
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
'stub' => 1
);
$teamId = DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team (?#) VALUES (?a)', array_keys($team), array_values($team));
@@ -876,12 +889,12 @@ class Profiler
/*********************/
if (DB::Aowow()->query('UPDATE ?_profiler_profiles SET ?a WHERE `realm` = ?d AND `realmGUID` = ?d', $data, $realmId, $charGuid) !== null)
DB::Aowow()->query('UPDATE ?_profiler_profiles SET `cuFlags` = `cuFlags` & ?d WHERE `id` = ?d', ~PROFILER_CU_NEEDS_RESYNC, $profileId);
DB::Aowow()->query('UPDATE ?_profiler_profiles SET `stub` = 0 WHERE `id` = ?d', $profileId);
return true;
}
public static function getGuildFromRealm($realmId, $guildGuid)
public static function getGuildFromRealm(int $realmId, int $guildGuid) : bool
{
$guild = DB::Characters($realmId)->selectRow('SELECT `guildId`, `name`, `createDate`, `info`, `backgroundColor`, `emblemStyle`, `emblemColor`, `borderStyle`, `borderColor` FROM guild WHERE `guildId` = ?d', $guildGuid);
if (!$guild)
@@ -943,12 +956,12 @@ class Profiler
/* mark guild as done */
/*********************/
DB::Aowow()->query('UPDATE ?_profiler_guild SET `cuFlags` = `cuFlags` & ?d WHERE `id` = ?d', ~PROFILER_CU_NEEDS_RESYNC, $guildId);
DB::Aowow()->query('UPDATE ?_profiler_guild SET `stub` = 0 WHERE `id` = ?d', $guildId);
return true;
}
public static function getArenaTeamFromRealm($realmId, $teamGuid)
public static function getArenaTeamFromRealm(int $realmId, int $teamGuid) : bool
{
$team = DB::Characters($realmId)->selectRow('SELECT `arenaTeamId`, `name`, `type`, `captainGuid`, `rating`, `seasonGames`, `seasonWins`, `weekGames`, `weekWins`, `rank`, `backgroundColor`, `emblemStyle`, `emblemColor`, `borderStyle`, `borderColor` FROM arena_team WHERE `arenaTeamId` = ?d', $teamGuid);
if (!$team)
@@ -1043,7 +1056,7 @@ class Profiler
/* mark team as done */
/*********************/
DB::Aowow()->query('UPDATE ?_profiler_arena_team SET `cuFlags` = `cuFlags` & ?d WHERE `id` = ?d', ~PROFILER_CU_NEEDS_RESYNC, $teamId);
DB::Aowow()->query('UPDATE ?_profiler_arena_team SET `stub` = 0 WHERE `id` = ?d', $teamId);
return true;
}

View File

@@ -144,11 +144,11 @@ class Report
$this->subject ??= 0; // 0 for utility, tools and misc pages?
}
private function checkTargetContext() : int
private function checkTargetContext(?string $url) : int
{
// check already reported
$field = User::isLoggedIn() ? 'userId' : 'ip';
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $this->mode, $this->reason, $this->subject, $field, User::$id ?: User::$ip))
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d{ AND `url` = ?} AND ?# = ?', $this->mode, $this->reason, $this->subject, $url ?: DBSIMPLE_SKIP, $field, User::$id ?: User::$ip))
return self::ERR_ALREADY_REPORTED;
// check targeted post/postOwner staff status
@@ -190,7 +190,28 @@ class Report
return false;
}
if($err = $this->checkTargetContext())
// clean up src url: dont use anchors, clean up query
if ($pageUrl)
{
$urlParts = parse_url($pageUrl);
if (!empty($urlParts['query']))
{
parse_str($urlParts['query'], $query); // kills redundant param declarations
unset($query['locale']); // locale param shouldn't be needed. more..?
$urlParts['query'] = http_build_query($query);
}
$pageUrl = '';
if (isset($urlParts['scheme']))
$pageUrl .= $urlParts['scheme'].':';
$pageUrl .= '//'.($urlParts['host'] ?? '').($urlParts['path'] ?? '');
if (isset($urlParts['query']))
$pageUrl .= '?'.$urlParts['query'];
}
if ($err = $this->checkTargetContext($pageUrl))
{
$this->errorCode = $err;
return false;

View File

@@ -201,9 +201,9 @@ trait TrCache
{
$this->initCache();
// type+typeId+catg; 3+6+10
// type+typeId/catg+mode; 3+10+1
$cKey = $this->formatCacheKey();
$cKey[2] = substr($cKey[2], 0, 19);
$cKey[2] = substr($cKey[2], 0, 13);
if ($modeMask & CACHE_MODE_MEMCACHED)
foreach ($this->memcached()?->getAllKeys() ?? [] as $k)
@@ -265,29 +265,27 @@ trait TrCache
// https://stackoverflow.com/questions/466521
private function formatCacheKey() : array
{
[$dbType, $dbTypeId, $category, $staffMask, $miscInfo] = $this->getCacheKeyComponents();
[$dbType, $dbTypeIdOrCat, $staffMask, $miscInfo] = $this->getCacheKeyComponents();
$fileKey = '';
// DBType: 3
$fileKey .= str_pad(dechex($dbType & 0xFFF), 3, 0, STR_PAD_LEFT);
// DBTypeId: 6
$fileKey .= str_pad(dechex($dbTypeId & 0xFFFFFF), 6, 0, STR_PAD_LEFT);
// category: (2+4+4)
$fileKey .= str_pad(dechex($category & 0xFFFFFFFFFF), 2+4+4, 0, STR_PAD_LEFT);
// DBTypeId: 6 / category: (2+4+4)
$fileKey .= str_pad(dechex($dbTypeIdOrCat & 0xFFFFFFFFFF), 2+4+4, 0, STR_PAD_LEFT);
// cacheType: 1
$fileKey .= str_pad(dechex($this->_cacheType & 0xF), 1, 0, STR_PAD_LEFT);
// localeId: 2,
$fileKey .= str_pad(dechex(Lang::getLocale()->value & 0xFF), 2, 0, STR_PAD_LEFT);
// staff mask: 4
$fileKey .= str_pad(dechex($staffMask & 0xFFFFFFFF), 4, 0, STR_PAD_LEFT);
// optioal: miscInfo
// optional: miscInfo
if ($miscInfo)
$fileKey .= '-'.$miscInfo;
// topDir, 2ndDir, file
return array(
str_pad(dechex($dbType & 0xFF), 2, 0, STR_PAD_LEFT),
str_pad(dechex(($dbTypeId > 0 ? $dbTypeId : $category) & 0xFF), 2, 0, STR_PAD_LEFT),
str_pad(dechex(($dbTypeIdOrCat) & 0xFF), 2, 0, STR_PAD_LEFT),
$fileKey
);
}
@@ -325,12 +323,17 @@ trait TrSearch
public function getCacheKeyComponents() : array
{
$misc = $this->query . // can be empty for upgrade search
serialize($this->_get['wt'] ?? null) . // extra &_GET not expected for normal and opensearch
serialize($this->_get['wtv'] ?? null) .
serialize($this->_get['type'] ?? null) .
serialize($this->_get['slots'] ?? null);
return array(
-1, // DBType
-1, // DBTypeId
$this->searchMask, // category
$this->searchMask, // DBTypeId/category
User::$groups, // staff mask
md5($this->query) // misc (here search query)
md5($misc) // misc
);
}
}
@@ -645,6 +648,13 @@ abstract class BaseResponse
return preg_replace('/ +/', ' ', trim($str));
}
protected static function checkLocale(string $localeId) : ?Locale
{
if (Util::checkNumeric($localeId, NUM_CAST_INT))
return Locale::tryFrom($localeId);
return null;
}
/********************/
/* child implements */

View File

@@ -21,8 +21,7 @@ trait TrDetailPage
{
return array(
$this->type, // DBType
$this->typeId, // DBTypeId
-1, // category
$this->typeId, // DBTypeId/category
User::$groups, // staff mask
'' // misc (here unused)
);
@@ -54,8 +53,7 @@ trait TrListPage
return array(
$this->type, // DBType
-1, // DBTypeId
$catg ?? -1, // category
$catg ?? -1, // DBTypeId/category
User::$groups, // staff mask
$misc ?? '' // misc (here filter)
);
@@ -217,7 +215,7 @@ class TemplateResponse extends BaseResponse
if (!$this->result)
$this->dataLoader = array_merge($this->dataLoader, $dataFiles);
else
$this->result->addDataLoader($dataFiles);
$this->result->addDataLoader(...$dataFiles);
}
public static function pageStatsHook(Template\PageTemplate &$pt, array &$stats) : void
@@ -420,7 +418,7 @@ class TemplateResponse extends BaseResponse
if (isset($this->guideRevision))
$article = DB::Aowow()->selectRow('SELECT `article`, `locale`, `editAccess` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d AND `rev` = ?d',
Type::GUIDE, $this->typeId, $this->guideRevision);
if (!$article && $this->gPageInfo['articleUrl'])
if (!$article && !empty($this->gPageInfo['articleUrl']))
$article = DB::Aowow()->selectRow('SELECT `article`, `locale`, `editAccess` FROM ?_articles WHERE `url` = ? AND `locale` IN (?a) ORDER BY `locale` DESC, `rev` DESC LIMIT 1',
$this->gPageInfo['articleUrl'], [Lang::getLocale()->value, Locale::EN->value]);
if (!$article && !empty($this->type) && isset($this->typeId))
@@ -625,7 +623,14 @@ class TemplateResponse extends BaseResponse
// as this may be loaded from cache, it will be unlinked from its response
if ($ptJSG = $this->result->jsGlobals)
{
Util::mergeJsGlobals($ptJSG, $this->jsGlobals);
foreach ($this->jsGlobals as $type => [, $data, ])
{
if (!isset($ptJSG[$type]) || $type == Type::USER)
$ptJSG[$type] = $this->jsGlobals[$type];
else
Util::mergeJsGlobals($ptJSG[$type][1], $data);
}
$this->result->jsGlobals = $ptJSG;
}
else if ($this->jsGlobals)

View File

@@ -14,14 +14,13 @@ trait TrTooltip
{
$key = array(
$this->type, // DBType
$this->typeId, // DBTypeId
-1, // category
$this->typeId, // DBTypeId/category
User::$groups, // staff mask
'' // misc (here tooltip)
);
if ($this->enhancedTT)
$key[4] = md5(serialize($this->enhancedTT));
$key[3] = md5(serialize($this->enhancedTT));
return $key;
}
@@ -90,7 +89,7 @@ trait TrCommunityHelper
}
}
abstract class TextResponse extends BaseResponse
class TextResponse extends BaseResponse
{
protected string $contentType = MIME_TYPE_JAVASCRIPT;
protected ?string $redirectTo = null;
@@ -164,6 +163,8 @@ abstract class TextResponse extends BaseResponse
echo $out;
}
protected function generate() : void {}
}
?>

View File

@@ -108,7 +108,7 @@ class Search
{
$clean = str_replace(['\\', '%'], '', $raw);
if (!$clean === '')
if ($clean === '')
continue;
if ($clean[0] == '-')

View File

@@ -235,7 +235,7 @@ class RemoteArenaTeamList extends ArenaTeamList
'nameUrl' => Profiler::urlize($this->getField('name')),
'type' => $this->getField('type'),
'rating' => $this->getField('rating'),
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
'stub' => 1
);
}

View File

@@ -16,7 +16,7 @@ class CreatureList extends DBTypeList
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"'],
'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]],
@@ -332,8 +332,8 @@ class CreatureListFilter extends Filter
'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]
'minle' => [parent::V_RANGE, [0, 99], false], // min level [int]
'maxle' => [parent::V_RANGE, [0, 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]
@@ -376,11 +376,11 @@ class CreatureListFilter extends Filter
$parts[] = ['rank', $_v['cl']];
// react Alliance [int]
if ($_v['ra'])
if (!is_null($_v['ra']))
$parts[] = ['ft.A', $_v['ra']];
// react Horde [int]
if ($_v['rh'])
if (!is_null($_v['rh']))
$parts[] = ['ft.H', $_v['rh']];
return $parts;

View File

@@ -56,17 +56,23 @@ class EnchantmentList extends DBTypeList
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]));
// 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 we can't rely on ?_item_stats and always have to calc stats
foreach ($this->iterate() as $ench)
{
$relSpells = [];
foreach ($ench['spells'] as $s)
if ($_ = $this->relSpells->getEntry($s[0]))
$relSpells[$s[0]] = $_;
$this->jsonStats[$this->id] = (new StatsContainer($relSpells))->fromEnchantment($ench);
}
}
public function getListviewData(int $addInfoMask = 0x0) : array
@@ -122,7 +128,7 @@ class EnchantmentList extends DBTypeList
public function getStatGainForCurrent() : array
{
return $this->jsonStats[$this->id]->toJson(includeEmpty: false);
return $this->jsonStats[$this->id]->toJson(includeEmpty: true);
}
public function getRelSpell(int $id) : ?array

View File

@@ -99,7 +99,7 @@ class GameObjectList extends DBTypeList
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 .= '<tr><td>'.Lang::game('requires', [$l]).'</td></tr>';
$x .= '</table>';

View File

@@ -48,18 +48,16 @@ class GuildList extends DBTypeList
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);
$stats = DB::Aowow()->select('SELECT `guild` AS ARRAY_KEY, `id` AS ARRAY_KEY2, `level`, `gearscore`, `achievementpoints` FROM ?_profiler_profiles WHERE `guild` IN (?a) AND `stub` = 0 ORDER BY `gearscore` DESC', $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;
$guildStats = $stats[$id];
$nMaxLevel = count(array_filter($stats[$id], function ($x) { return $x['level'] >= MAX_LEVEL; } ));
$nMaxLevel = count(array_filter($stats[$id], fn($x) => $x['level'] >= MAX_LEVEL));
$levelMod = 1.0;
if ($nMaxLevel < 25)
@@ -227,7 +225,7 @@ class RemoteGuildList extends GuildList
'realmGUID' => $this->getField('guildid'),
'name' => $this->getField('name'),
'nameUrl' => Profiler::urlize($this->getField('name')),
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
'stub' => 1
);
}

View File

@@ -18,11 +18,12 @@ class ItemList extends DBTypeList
public array $rndEnchIds = [];
public array $subItems = [];
private array $ssd = [];
private array $vendors = [];
private array $jsGlobals = []; // getExtendedCost creates some and has no access to template
private array $enhanceR = [];
private array $relEnchant = [];
private array $randPropPoints = [];
private array $ssd = [];
private array $vendors = [];
private array $jsGlobals = []; // getExtendedCost creates some and has no access to template
private array $enhanceR = [];
private array $relEnchant = [];
protected string $queryBase = 'SELECT i.*, i.`block` AS "tplBlock", i.`armor` AS tplArmor, i.`dmgMin1` AS "tplDmgMin1", i.`dmgMax1` AS "tplDmgMax1", i.`id` AS ARRAY_KEY, i.`id` AS "id" FROM ?_items i';
protected array $queryOpts = array( // 3 => Type::ITEM
@@ -486,6 +487,11 @@ class ItemList extends DBTypeList
'quality' => $this->curTpl['quality'],
'icon' => $this->curTpl['iconString']
);
if ($this->curTpl['class'] == ITEM_CLASS_RECIPE)
$data[Type::ITEM][$id]['completion_category'] = $this->curTpl['class'];
else if ($this->curTpl['class'] == ITEM_CLASS_MISC && in_array($this->curTpl['subClass'], [2, 5, -7]))
$data[Type::ITEM][$id]['completion_category'] = $this->curTpl['class'].'-'.$this->curTpl['subClass'];
}
if ($addMask & GLOBALINFO_EXTRA)
@@ -753,6 +759,13 @@ class ItemList extends DBTypeList
case Stat::INTELLECT:
case Stat::SPIRIT:
case Stat::STAMINA:
// case Stat::ARMOR: // unused by 335a client, still set in item_template
// case Stat::FIRE_RESISTANCE:
// case Stat::FROST_RESISTANCE:
// case Stat::HOLY_RESISTANCE:
// case Stat::SHADOW_RESISTANCE:
// case Stat::NATURE_RESISTANCE:
// case Stat::ARCANE_RESISTANCE:
$x .= '<span><!--stat'.$statId.'-->'.Lang::item('statType', $type, [ord($qty > 0 ? '+' : '-'), abs($qty)]).'</span><br />';
break;
default: // rating with % for reqLevel
@@ -929,7 +942,7 @@ class ItemList extends DBTypeList
// locked or openable
if ($locks = Lang::getLocks($this->curTpl['lockId'], $arr, true))
$x .= '<span class="q0">'.Lang::item('locked').'<br />'.implode('<br />', array_map(function($x) { return sprintf(Lang::game('requires'), $x); }, $locks)).'</span><br />';
$x .= '<span class="q0">'.Lang::item('locked').'<br />'.implode('<br />', array_map(fn($x) => Lang::game('requires', [$x]), $locks)).'</span><br />';
else if ($this->curTpl['flags'] & ITEM_FLAG_OPENABLE)
$x .= '<span class="q2">'.Lang::item('openClick').'</span><br />';
@@ -1221,76 +1234,55 @@ class ItemList extends DBTypeList
}
// from Trinity
public function generateEnchSuffixFactor() : int
public function generateEnchSuffixFactor() : float
{
$rpp = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomproppoints WHERE `id` = ?', $this->curTpl['itemLevel']);
if (!$rpp)
return 0;
if (empty($this->randPropPoints[$this->curTpl['itemLevel']]))
$this->randPropPoints[$this->curTpl['itemLevel']] = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomproppoints WHERE `id` = ?', $this->curTpl['itemLevel']);
switch ($this->curTpl['slot'])
$rpp = &$this->randPropPoints[$this->curTpl['itemLevel']];
if (!$rpp)
return 0.0;
$fieldIdx = match((int)$this->curTpl['slot'])
{
// Items of that type don`t have points
case INVTYPE_NON_EQUIP:
case INVTYPE_BAG:
case INVTYPE_TABARD:
case INVTYPE_AMMO:
case INVTYPE_QUIVER:
case INVTYPE_RELIC:
return 0;
// Select point coefficient
case INVTYPE_HEAD:
case INVTYPE_BODY:
case INVTYPE_CHEST:
case INVTYPE_LEGS:
case INVTYPE_2HWEAPON:
case INVTYPE_ROBE:
$suffixFactor = 1;
break;
case INVTYPE_SHOULDERS:
case INVTYPE_WAIST:
case INVTYPE_FEET:
case INVTYPE_HANDS:
case INVTYPE_TRINKET:
$suffixFactor = 2;
break;
case INVTYPE_NECK:
case INVTYPE_WRISTS:
case INVTYPE_FINGER:
case INVTYPE_SHIELD:
case INVTYPE_CLOAK:
case INVTYPE_HOLDABLE:
$suffixFactor = 3;
break;
case INVTYPE_WEAPON:
case INVTYPE_WEAPONMAINHAND:
case INVTYPE_WEAPONOFFHAND:
$suffixFactor = 4;
break;
case INVTYPE_RANGED:
case INVTYPE_THROWN:
case INVTYPE_RANGEDRIGHT:
$suffixFactor = 5;
break;
default:
return 0;
}
INVTYPE_HEAD,
INVTYPE_BODY,
INVTYPE_CHEST,
INVTYPE_LEGS,
INVTYPE_2HWEAPON,
INVTYPE_ROBE => 1,
INVTYPE_SHOULDERS,
INVTYPE_WAIST,
INVTYPE_FEET,
INVTYPE_HANDS,
INVTYPE_TRINKET => 2,
INVTYPE_NECK,
INVTYPE_WRISTS,
INVTYPE_FINGER,
INVTYPE_SHIELD,
INVTYPE_CLOAK,
INVTYPE_HOLDABLE => 3,
INVTYPE_WEAPON,
INVTYPE_WEAPONMAINHAND,
INVTYPE_WEAPONOFFHAND => 4,
INVTYPE_RANGED,
INVTYPE_THROWN,
INVTYPE_RANGEDRIGHT => 5,
default => 0 // inv types that don`t have points
};
if (!$fieldIdx)
return 0.0;
// Select rare/epic modifier
switch ($this->curTpl['quality'])
return match((int)$this->curTpl['quality'])
{
case ITEM_QUALITY_UNCOMMON:
return $rpp['uncommon'.$suffixFactor] / 10000;
case ITEM_QUALITY_RARE:
return $rpp['rare'.$suffixFactor] / 10000;
case ITEM_QUALITY_EPIC:
return $rpp['epic'.$suffixFactor] / 10000;
case ITEM_QUALITY_LEGENDARY:
case ITEM_QUALITY_ARTIFACT:
return 0; // not have random properties
default:
break;
}
return 0;
ITEM_QUALITY_UNCOMMON => $rpp['uncommon'.$fieldIdx] / 10000,
ITEM_QUALITY_RARE => $rpp['rare'.$fieldIdx] / 10000,
ITEM_QUALITY_EPIC => $rpp['epic'.$fieldIdx] / 10000,
default => 0.0 // qualities that don't have random properties
};
}
public function extendJsonStats() : void
@@ -1664,7 +1656,7 @@ class ItemList extends DBTypeList
$mh = $j;
else if ($j['id'] == $ohItem)
$oh = $j;
else if ($j['gearscore'])
else if (!empty($j['gearscore']))
{
if ($j['slot'] == INVTYPE_RELIC)
$score += 20;
@@ -2021,10 +2013,10 @@ class ItemListFilter extends Filter
'ty' => [parent::V_CALLBACK, 'cbTypeCheck', true ], // item type - dynamic by current group
'sl' => [parent::V_CALLBACK, 'cbSlotCheck', true ], // item slot - dynamic by current group
'si' => [parent::V_LIST, [-SIDE_HORDE, -SIDE_ALLIANCE, SIDE_ALLIANCE, SIDE_HORDE, SIDE_BOTH], false], // side
'minle' => [parent::V_RANGE, [1, 999], false], // item level min
'maxle' => [parent::V_RANGE, [1, 999], false], // item level max
'minrl' => [parent::V_RANGE, [1, MAX_LEVEL], false], // required level min
'maxrl' => [parent::V_RANGE, [1, MAX_LEVEL], false] // required level max
'minle' => [parent::V_RANGE, [0, 999], false], // item level min
'maxle' => [parent::V_RANGE, [0, 999], false], // item level max
'minrl' => [parent::V_RANGE, [0, MAX_LEVEL], false], // required level min
'maxrl' => [parent::V_RANGE, [0, MAX_LEVEL], false] // required level max
);
public array $extraOpts = []; // score for statWeights

View File

@@ -187,10 +187,10 @@ class ItemsetListFilter extends Filter
'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
'minle' => [parent::V_RANGE, [0, 999], false], // min item level
'maxle' => [parent::V_RANGE, [0, 999], false], // max itemlevel
'minrl' => [parent::V_RANGE, [0, MAX_LEVEL], false], // min required level
'maxrl' => [parent::V_RANGE, [0, 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
);

View File

@@ -83,7 +83,7 @@ class ProfileList extends DBTypeList
if ($this->getField('cuFlags') & PROFILER_CU_PINNED)
$data[$this->id]['pinned'] = 1;
if ($this->getField('cuFlags') & PROFILER_CU_DELETED)
if ($this->getField('deleted'))
$data[$this->id]['deleted'] = 1;
}
@@ -168,7 +168,7 @@ class ProfileList extends DBTypeList
public function isCustom() : bool
{
return $this->getField('cuFlags') & PROFILER_CU_PROFILE;
return $this->getField('custom');
}
public function isVisibleToUser() : bool
@@ -176,7 +176,7 @@ class ProfileList extends DBTypeList
if (!$this->isCustom() || User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
return true;
if ($this->getField('cuFlags') & PROFILER_CU_DELETED)
if ($this->getField('deleted'))
return false;
if (User::$id == $this->getField('user'))
@@ -269,8 +269,8 @@ class ProfileListFilter extends Filter
{
parent::__construct($data, $opts);
if (!empty($this->criteria['cr']))
if (array_intersect($this->criteria['cr'], [2, 5, 6, 7, 21]))
if (!empty($this->values['cr']))
if (array_intersect($this->values['cr'], [2, 5, 6, 7, 21]))
$this->useLocalList = true;
}
@@ -515,7 +515,7 @@ class RemoteProfileList extends ProfileList
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;
$this->rnItr[$curTpl['name']] = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 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))
@@ -606,7 +606,7 @@ class RemoteProfileList extends ProfileList
'gender' => $this->getField('gender'),
'guild' => $guildGUID ?: null,
'guildrank' => $guildGUID ? $this->getField('guildrank') : null,
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
'stub' => 1
);
if ($guildGUID && empty($guildData[$realmId.'-'.$guildGUID]))
@@ -615,7 +615,7 @@ class RemoteProfileList extends ProfileList
'realmGUID' => $guildGUID,
'name' => $this->getField('guildname'),
'nameUrl' => Profiler::urlize($this->getField('guildname')),
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
'stub' => 1
);
}
@@ -643,8 +643,7 @@ class RemoteProfileList extends ProfileList
// 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,
'SELECT CONCAT(`realm`, ":", `realmGUID`) AS ARRAY_KEY, `id`, `gearscore` FROM ?_profiler_profiles WHERE `custom` = 0 AND `realm` IN (?a) AND `realmGUID` IN (?a)',
array_column($baseData, 'realm'),
array_column($baseData, 'realmGUID')
);
@@ -662,7 +661,7 @@ 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"'],
'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"']

View File

@@ -415,7 +415,15 @@ class QuestList extends DBTypeList
}
if ($addMask & GLOBALINFO_SELF)
{
$data[Type::QUEST][$this->id] = ['name' => $this->getField('name', true)];
if ($this->curTpl['flags'] & QUEST_FLAG_DAILY)
$data[Type::QUEST][$this->id]['daily'] = true;
if ($this->curTpl['flags'] & QUEST_FLAG_WEEKLY)
$data[Type::QUEST][$this->id]['weekly'] = true;
}
}
return $data;
@@ -478,10 +486,10 @@ class QuestListFilter extends Filter
'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
'minle' => [parent::V_RANGE, [0, 99], false], // min quest level
'maxle' => [parent::V_RANGE, [0, 99], false], // max quest level
'minrl' => [parent::V_RANGE, [0, 99], false], // min required level
'maxrl' => [parent::V_RANGE, [0, 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
);

View File

@@ -23,12 +23,11 @@ class SpellList extends DBTypeList
11 => SKILLS_TRADE_PRIMARY // prim. Professions
);
public const EFFECTS_HEAL = array(
SPELL_EFFECT_NONE, /*SPELL_EFFECT_DUMMY*/ SPELL_EFFECT_HEAL, SPELL_EFFECT_HEAL_MAX_HEALTH, SPELL_EFFECT_HEAL_MECHANICAL,
SPELL_EFFECT_HEAL_PCT
public const EFFECTS_SCALING_HEAL = array( // as per Unit::SpellHealingBonusDone() calls in TC
SPELL_EFFECT_HEAL, SPELL_EFFECT_HEAL_PCT, SPELL_EFFECT_HEAL_MECHANICAL, SPELL_EFFECT_HEALTH_LEECH
);
public const EFFECTS_DAMAGE = array(
SPELL_EFFECT_NONE, SPELL_EFFECT_DUMMY, SPELL_EFFECT_SCHOOL_DAMAGE, SPELL_EFFECT_HEALTH_LEECH, SPELL_EFFECT_POWER_BURN
public const EFFECTS_SCALING_DAMAGE = array( // as per Unit::SpellDamageBonusDone() calls in TC
SPELL_EFFECT_SCHOOL_DAMAGE, SPELL_EFFECT_HEALTH_LEECH, SPELL_EFFECT_POWER_BURN
);
public const EFFECTS_ITEM_CREATE = array(
SPELL_EFFECT_CREATE_ITEM, SPELL_EFFECT_SUMMON_CHANGE_ITEM, SPELL_EFFECT_CREATE_RANDOM_ITEM, SPELL_EFFECT_CREATE_MANA_GEM, SPELL_EFFECT_CREATE_ITEM_2
@@ -48,21 +47,19 @@ class SpellList extends DBTypeList
public const EFFECTS_MODEL_NPC = array(
SPELL_EFFECT_SUMMON, SPELL_EFFECT_SUMMON_PET, SPELL_EFFECT_SUMMON_DEMON, SPELL_EFFECT_KILL_CREDIT, SPELL_EFFECT_KILL_CREDIT2
);
public const EFFECTS_DIRECT_SCALING = array(
public const EFFECTS_DIRECT_SCALING = array( // as per Unit::GetCastingTimeForBonus()
SPELL_EFFECT_SCHOOL_DAMAGE, SPELL_EFFECT_ENVIRONMENTAL_DAMAGE, SPELL_EFFECT_POWER_DRAIN, SPELL_EFFECT_HEALTH_LEECH, SPELL_EFFECT_POWER_BURN,
SPELL_EFFECT_HEAL_MAX_HEALTH
SPELL_EFFECT_HEAL
);
public const EFFECTS_ENCHANTMENT = array(
SPELL_EFFECT_ENCHANT_ITEM, SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY, SPELL_EFFECT_ENCHANT_HELD_ITEM, SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC
);
public const AURAS_HEAL = array(
SPELL_AURA_DUMMY, SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_HEALTH_FUNNEL, SPELL_AURA_SCHOOL_ABSORB, SPELL_AURA_MANA_SHIELD,
SPELL_AURA_PERIODIC_DUMMY
public const AURAS_SCALING_HEAL = array( // as per Unit::SpellHealingBonusDone() calls in TC (SPELL_AURA_SCHOOL_ABSORB + SPELL_AURA_MANA_SHIELD priest/mage cases are scripted)
SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_LEECH, SPELL_AURA_OBS_MOD_HEALTH
);
public const AURAS_DAMAGE = array(
SPELL_AURA_PERIODIC_DAMAGE, SPELL_AURA_DUMMY, SPELL_AURA_DAMAGE_SHIELD, SPELL_AURA_PERIODIC_LEECH, SPELL_AURA_PERIODIC_DAMAGE_PERCENT,
SPELL_AURA_POWER_BURN, SPELL_AURA_PERIODIC_DUMMY
public const AURAS_SCALING_DAMAGE = array( // as per Unit::SpellDamageBonusDone() calls in TC
SPELL_AURA_PERIODIC_DAMAGE, SPELL_AURA_PERIODIC_LEECH, SPELL_AURA_DAMAGE_SHIELD, SPELL_AURA_PROC_TRIGGER_DAMAGE
);
public const AURAS_ITEM_CREATE = array(
SPELL_AURA_CHANNEL_DEATH_ITEM
@@ -75,7 +72,7 @@ class SpellList extends DBTypeList
SPELL_AURA_TRANSFORM, SPELL_AURA_MOUNTED, SPELL_AURA_CHANGE_MODEL_FOR_ALL_HUMANOIDS, SPELL_AURA_X_RAY,
SPELL_AURA_MOD_FAKE_INEBRIATE
);
public const AURAS_PERIODIC_SCALING = array(
public const AURAS_PERIODIC_SCALING = array( // as per Unit::GetCastingTimeForBonus()
SPELL_AURA_PERIODIC_DAMAGE, SPELL_AURA_PERIODIC_HEAL, SPELL_AURA_PERIODIC_LEECH
);
@@ -230,56 +227,48 @@ class SpellList extends DBTypeList
// <statistic> => [123, 'add']
// <statistic> => <value> ... as from getStatGain()
$modXByStat = function (&$arr, $stat, $pts) use (&$mv)
$modXByStat = function (array &$arr, int $srcStat, ?string $destStat, int $pts) : void
{
if ($mv == STAT_STRENGTH)
$arr[$stat ?: 'str'] = [$pts / 100, 'percentOf', 'str'];
else if ($mv == STAT_AGILITY)
$arr[$stat ?: 'agi'] = [$pts / 100, 'percentOf', 'agi'];
else if ($mv == STAT_STAMINA)
$arr[$stat ?: 'sta'] = [$pts / 100, 'percentOf', 'sta'];
else if ($mv == STAT_INTELLECT)
$arr[$stat ?: 'int'] = [$pts / 100, 'percentOf', 'int'];
else if ($mv == STAT_SPIRIT)
$arr[$stat ?: 'spi'] = [$pts / 100, 'percentOf', 'spi'];
match ($srcStat)
{
STAT_STRENGTH => $arr[$destStat ?: 'str'] = [$pts / 100, 'percentOf', 'str'],
STAT_AGILITY => $arr[$destStat ?: 'agi'] = [$pts / 100, 'percentOf', 'agi'],
STAT_STAMINA => $arr[$destStat ?: 'sta'] = [$pts / 100, 'percentOf', 'sta'],
STAT_INTELLECT => $arr[$destStat ?: 'int'] = [$pts / 100, 'percentOf', 'int'],
STAT_SPIRIT => $arr[$destStat ?: 'spi'] = [$pts / 100, 'percentOf', 'spi']
};
};
$modXBySchool = function (&$arr, $stat, $val, $mask = null) use (&$mv)
$modXBySchool = function (array &$arr, int $srcStat, string $destStat, array|int $val) : void
{
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_HOLY))
$arr['hol'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'hol'.$stat];
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_FIRE))
$arr['fir'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fir'.$stat];
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_NATURE))
$arr['nat'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'nat'.$stat];
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_FROST))
$arr['fro'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fro'.$stat];
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_SHADOW))
$arr['sha'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'sha'.$stat];
if (($mask ?: $mv) & (1 << SPELL_SCHOOL_ARCANE))
$arr['arc'.$stat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'arc'.$stat];
if ($srcStat & (1 << SPELL_SCHOOL_HOLY))
$arr['hol'.$destStat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'hol'.$destStat];
if ($srcStat & (1 << SPELL_SCHOOL_FIRE))
$arr['fir'.$destStat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fir'.$destStat];
if ($srcStat & (1 << SPELL_SCHOOL_NATURE))
$arr['nat'.$destStat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'nat'.$destStat];
if ($srcStat & (1 << SPELL_SCHOOL_FROST))
$arr['fro'.$destStat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'fro'.$destStat];
if ($srcStat & (1 << SPELL_SCHOOL_SHADOW))
$arr['sha'.$destStat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'sha'.$destStat];
if ($srcStat & (1 << SPELL_SCHOOL_ARCANE))
$arr['arc'.$destStat] = is_array($val) ? $val : [$val / 100, 'percentOf', 'arc'.$destStat];
};
$jsonStat = function ($stat)
$jsonStat = function (int $statId) : string
{
if ($stat == STAT_STRENGTH)
return 'str';
if ($stat == STAT_AGILITY)
return 'agi';
if ($stat == STAT_STAMINA)
return 'sta';
if ($stat == STAT_INTELLECT)
return 'int';
if ($stat == STAT_SPIRIT)
return 'spi';
return match ($statId)
{
STAT_STRENGTH => Stat::getJsonString(Stat::STRENGTH),
STAT_AGILITY => Stat::getJsonString(Stat::AGILITY),
STAT_STAMINA => Stat::getJsonString(Stat::STAMINA),
STAT_INTELLECT => Stat::getJsonString(Stat::INTELLECT),
STAT_SPIRIT => Stat::getJsonString(Stat::SPIRIT)
};
};
foreach ($this->iterate() as $id => $__)
{
// kept for reference - if (($this->getField('cuFlags') & SPELL_CU_TALENTSPELL) && $id != 20711)
if (!($this->getField('attributes0') & SPELL_ATTR0_PASSIVE))
continue;
// Shaman - Spirit Weapons (16268) (parry is normaly stored in g_statistics)
// i should recurse into SPELL_EFFECT_LEARN_SPELL and apply SPELL_EFFECT_PARRY from there
if ($id == 16268)
@@ -288,6 +277,9 @@ class SpellList extends DBTypeList
continue;
}
if (!($this->getField('attributes0') & SPELL_ATTR0_PASSIVE))
continue;
for ($i = 1; $i < 4; $i++)
{
$pts = $this->calculateAmountForCurrent($i)[1];
@@ -311,18 +303,18 @@ class SpellList extends DBTypeList
if ($mv == (1 << SPELL_SCHOOL_NORMAL))
$data[$id]['armor'] = [$pts / 100, 'percentOf', ['armor', 0]];
else if ($mv)
$modXBySchool($data[$id], 'res', $pts);
$modXBySchool($data[$id], $mv, 'res', $pts);
break;
case SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT:
// Armor only if explicitly specified
if ($mv == (1 << SPELL_SCHOOL_NORMAL))
$data[$id]['armor'] = [$pts / 100, 'percentOf', $jsonStat($mvB)];
else if ($mv)
$modXBySchool($data[$id], 'res', [$pts / 100, 'percentOf', $jsonStat($mvB)]);
$modXBySchool($data[$id], $mv, 'res', [$pts / 100, 'percentOf', $jsonStat($mvB)]);
break;
case SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE:
if ($mv > -1) // one stat
$modXByStat($data[$id], null, $pts);
$modXByStat($data[$id], $mv, null, $pts);
else if ($mv < 0) // all stats
for ($iMod = ITEM_MOD_AGILITY; $iMod <= ITEM_MOD_STAMINA; $iMod++)
if ($idx = Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $iMod))
@@ -331,19 +323,19 @@ class SpellList extends DBTypeList
break;
case SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT:
$mv = $mv ?: SPELL_MAGIC_SCHOOLS;
$modXBySchool($data[$id], 'spldmg', [$pts / 100, 'percentOf', $jsonStat($mvB)]);
$modXBySchool($data[$id], $mv, 'spldmg', [$pts / 100, 'percentOf', $jsonStat($mvB)]);
break;
case SPELL_AURA_MOD_RANGED_ATTACK_POWER_OF_STAT_PERCENT:
$modXByStat($data[$id], 'rgdatkpwr', $pts);
$modXByStat($data[$id], $mv, 'rgdatkpwr', $pts);
break;
case SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT:
$modXByStat($data[$id], 'mleatkpwr', $pts);
$modXByStat($data[$id], $mv, 'mleatkpwr', $pts);
break;
case SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT:
$modXByStat($data[$id], 'splheal', $pts);
$modXByStat($data[$id], $mv, 'splheal', $pts);
break;
case SPELL_AURA_MOD_MANA_REGEN_FROM_STAT:
$modXByStat($data[$id], 'manargn', $pts);
$modXByStat($data[$id], $mv, 'manargn', $pts);
break;
case SPELL_AURA_MOD_MANA_REGEN_INTERRUPT:
$data[$id]['icmanargn'] = [$pts / 100, 'percentOf', 'oocmanargn'];
@@ -351,7 +343,7 @@ class SpellList extends DBTypeList
case SPELL_AURA_MOD_SPELL_CRIT_CHANCE:
case SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL:
$mv = $mv ?: SPELL_MAGIC_SCHOOLS;
$modXBySchool($data[$id], 'splcritstrkpct', [$pts, 'add']);
$modXBySchool($data[$id], $mv, 'splcritstrkpct', [$pts, 'add']);
if (($mv & SPELL_MAGIC_SCHOOLS) == SPELL_MAGIC_SCHOOLS)
$data[$id]['splcritstrkpct'] = [$pts, 'add'];
break;
@@ -392,7 +384,7 @@ class SpellList extends DBTypeList
$data[$id]['health'] = [$pts / 100, 'percentOf', 'health'];
break;
case SPELL_AURA_MOD_BASE_HEALTH_PCT: // only Tauren - Endurance (20550) ... if you are looking for something elegant, look away!
$data[$id]['health'] = [$pts / 100, 'functionOf', '$function(p) { return g_statistics.combo[p.classs][p.level][5]; }'];
$data[$id]['health'] = [$pts / 100, 'functionOf', '$(x) => g_statistics.combo[x.classs][x.level][5]'];
break;
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT:
$data[$id]['block'] = [$pts / 100, 'percentOf', 'block'];
@@ -404,7 +396,7 @@ class SpellList extends DBTypeList
break;
case SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER:
$mv = $mv ?: SPELL_MAGIC_SCHOOLS;
$modXBySchool($data[$id], 'spldmg', [$pts / 100, 'percentOf', 'mleatkpwr']);
$modXBySchool($data[$id], $mv, 'spldmg', [$pts / 100, 'percentOf', 'mleatkpwr']);
break;
case SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER:
$data[$id]['splheal'] = [$pts / 100, 'percentOf', 'mleatkpwr'];
@@ -775,22 +767,22 @@ class SpellList extends DBTypeList
return $this->curTpl['attributes1'] & (SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2);
}
public function isHealingSpell() : bool
public function isScalableHealingSpell() : bool
{
for ($i = 1; $i < 4; $i++)
if (!in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_HEAL) && !in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_HEAL))
return false;
if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_SCALING_HEAL) || in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_SCALING_HEAL))
return true;
return true;
return false;
}
public function isDamagingSpell() : bool
public function isScalableDamagingSpell() : bool
{
for ($i = 1; $i < 4; $i++)
if (!in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_DAMAGE) && !in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_DAMAGE))
return false;
if (in_array($this->curTpl['effect'.$i.'Id'], SpellList::EFFECTS_SCALING_DAMAGE) || in_array($this->curTpl['effect'.$i.'AuraId'], SpellList::AURAS_SCALING_DAMAGE))
return true;
return true;
return false;
}
public function periodicEffectsMask() : int
@@ -910,7 +902,11 @@ class SpellList extends DBTypeList
$formula = preg_replace('/(\+|-|\*|\/)(\+|-|\*|\/)/i', '\1 \2', $formula);
// there should not be any letters without a leading $
return eval('return '.$formula.';');
try { $formula = eval('return '.$formula.';'); }
// but there can be if we are non-interactive
catch (\Throwable $e) { }
return $formula;
}
// description-, buff-parsing component
@@ -1840,8 +1836,13 @@ class SpellList extends DBTypeList
// get reagents
$reagents = $this->getReagentsForCurrent();
foreach ($reagents as &$r)
$r[2] = ItemList::getName($r[0]);
foreach ($reagents as $k => $r)
{
if ($item = $this->relItems->getEntry($r[0]))
$reagents[$k] += [2 => new LocString($item), 3 => true];
else
$reagents[$k] += [2 => 'Item #'.$r[0], 3 => false];
}
$reagents = array_reverse($reagents);
@@ -1941,11 +1942,11 @@ class SpellList extends DBTypeList
if ($reagents)
{
$_ = Lang::spell('reagents').':<br/><div class="indent q1">';
while ($reagent = array_pop($reagents))
while ([$iId, $qty, $text, $exists] = array_pop($reagents))
{
$_ .= '<a href="?item='.$reagent[0].'">'.$reagent[2].'</a>';
if ($reagent[1] > 1)
$_ .= ' ('.$reagent[1].')';
$_ .= $exists ? '<a href="?item='.$iId.'">'.$text.'</a>' : $text;
if ($qty > 1)
$_ .= ' ('.$qty.')';
$_ .= empty($reagents) ? '<br />' : ', ';
}
@@ -2149,8 +2150,11 @@ class SpellList extends DBTypeList
{
$data[Type::SPELL][$id] = array(
'icon' => $this->curTpl['iconStringAlt'] ?: $this->curTpl['iconString'],
'name' => $this->getField('name', true),
'name' => $this->getField('name', true)
);
if (($_ = $this->curTpl['typeCat']) && in_array($_, [-5, -6, 9, 11]))
$data[Type::SPELL][$id]['completion_category'] = $_;
}
if ($addMask & GLOBALINFO_EXTRA)
@@ -2523,10 +2527,10 @@ class SpellListFilter extends Filter
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / text - only printable chars, no delimiter
'ex' => [parent::V_EQUAL, 'on', false], // extended name search
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
'minle' => [parent::V_RANGE, [1, 99], false], // spell level min
'maxle' => [parent::V_RANGE, [1, 99], false], // spell level max
'minrs' => [parent::V_RANGE, [1, 999], false], // required skill level min
'maxrs' => [parent::V_RANGE, [1, 999], false], // required skill level max
'minle' => [parent::V_RANGE, [0, 99], false], // spell level min
'maxle' => [parent::V_RANGE, [0, 99], false], // spell level max
'minrs' => [parent::V_RANGE, [0, 999], false], // required skill level min
'maxrs' => [parent::V_RANGE, [0, 999], false], // required skill level max
'ra' => [parent::V_LIST, [[1, 8], 10, 11], false], // races
'cl' => [parent::V_CALLBACK, 'cbClasses', true ], // classes
'gl' => [parent::V_CALLBACK, 'cbGlyphs', true ], // glyph type

View File

@@ -91,7 +91,7 @@ class WorldEventList extends DBTypeList
if ($rec < 0 || $date['lastDate'] < time())
return true;
$nIntervals = ceil((time() - $start) / $rec);
$nIntervals = (int)ceil((time() - $end) / $rec);
$start += $nIntervals * $rec;
$end += $nIntervals * $rec;
@@ -157,9 +157,9 @@ class WorldEventList extends DBTypeList
// use string-placeholder for dates
// start
$x .= Lang::event('start').Lang::main('colon').'%s<br />';
$x .= Lang::event('start').'%s<br />';
// end
$x .= Lang::event('end').Lang::main('colon').'%s';
$x .= Lang::event('end').'%s';
$x .= '</td></tr></table>';

View File

@@ -41,6 +41,8 @@ define('CACHE_TYPE_PAGE', 1);
define('CACHE_TYPE_TOOLTIP', 2);
define('CACHE_TYPE_SEARCH', 3);
define('CACHE_TYPE_XML', 4); // only used by items
define('CACHE_TYPE_LIST_PAGE', 5);
define('CACHE_TYPE_DETAIL_PAGE', 6);
define('CACHE_MODE_FILECACHE', 0x1);
define('CACHE_MODE_MEMCACHED', 0x2);
@@ -372,9 +374,9 @@ define('QUEST_CU_PART_OF_SERIES', 0x0200);
define('PROFILER_CU_PUBLISHED', 0x01);
define('PROFILER_CU_PINNED', 0x02);
define('PROFILER_CU_DELETED', 0x04);
define('PROFILER_CU_PROFILE', 0x08);
define('PROFILER_CU_NEEDS_RESYNC', 0x10);
// define('PROFILER_CU_DELETED', 0x04); // migrated to separate db cols
// define('PROFILER_CU_PROFILE', 0x08);
// define('PROFILER_CU_NEEDS_RESYNC', 0x10);
define('GUIDE_CU_NO_QUICKFACTS', 0x100); // merge with CC_FLAG_*
define('GUIDE_CU_NO_RATING', 0x200);
@@ -492,6 +494,15 @@ define('ITEM_MOD_SPELL_POWER', 45);
define('ITEM_MOD_HEALTH_REGEN', 46);
define('ITEM_MOD_SPELL_PENETRATION', 47);
define('ITEM_MOD_BLOCK_VALUE', 48);
// unknown by 335a client but still used by several item_templates
// define('ITEM_MOD_MASTERY_RATING', 49);
// define('ITEM_MOD_EXTRA_ARMOR', 50);
// define('ITEM_MOD_FIRE_RESISTANCE', 51);
// define('ITEM_MOD_FROST_RESISTANCE', 52);
// define('ITEM_MOD_HOLY_RESISTANCE', 53);
// define('ITEM_MOD_SHADOW_RESISTANCE', 54);
// define('ITEM_MOD_NATURE_RESISTANCE', 55);
// define('ITEM_MOD_ARCANE_RESISTANCE', 56);
// Combat Ratings
define('CR_WEAPON_SKILL', 0);
@@ -598,6 +609,7 @@ define('TEAM_NEUTRAL', 2);
// Lock Types
define('LOCK_TYPE_ITEM', 1);
define('LOCK_TYPE_SKILL', 2);
define('LOCK_TYPE_SPELL', 3);
// Lock-Properties (also categorizes GOs)
define('LOCK_PROPERTY_FOOTLOCKER', 1);
@@ -669,6 +681,7 @@ define('NPC_FLAG_STABLE_MASTER', 0x00400000);
define('NPC_FLAG_GUILD_BANK', 0x00800000);
define('NPC_FLAG_SPELLCLICK', 0x01000000);
define('NPC_FLAG_MAILBOX', 0x04000000);
define('NPC_FLAG_VALIDATE', 0x05FFFFF3);
define('CREATURE_FLAG_EXTRA_INSTANCE_BIND', 0x00000001); // creature kill binds instance to killer and killer's group
define('CREATURE_FLAG_EXTRA_CIVILIAN', 0x00000002); // creature does not aggro (ignore faction/reputation hostility)
@@ -728,6 +741,7 @@ define('UNIT_FLAG_UNK_28', 0x10000000); // (PreventKneelingW
define('UNIT_FLAG_UNK_29', 0x20000000); // Used in Feign Death spell or NPC will play dead. (PreventEmotes)
define('UNIT_FLAG_SHEATHE', 0x40000000); //
define('UNIT_FLAG_UNK_31', 0x80000000); //
define('UNIT_FLAG_VALIDATE', 0x7FFFFFFF); //
define('UNIT_FLAG2_FEIGN_DEATH', 0x00000001); //
define('UNIT_FLAG2_UNK1', 0x00000002); // Hide unit model (show only player equip)
@@ -747,6 +761,7 @@ define('UNIT_FLAG2_DISABLE_TURN', 0x00008000); //
define('UNIT_FLAG2_UNK2', 0x00010000); //
define('UNIT_FLAG2_PLAY_DEATH_ANIM', 0x00020000); // Plays special death animation upon death
define('UNIT_FLAG2_ALLOW_CHEAT_SPELLS', 0x00040000); // allows casting spells with AttributesEx7 & SPELL_ATTR7_IS_CHEAT_SPELL
define('UNIT_FLAG2_VALIDATE', 0x0006FDFF); //
// UNIT_FIELD_BYTES_1 - idx 0 (UnitStandStateType)
define('UNIT_STAND_STATE_STAND', 0);
@@ -782,6 +797,7 @@ define('UNIT_DYNFLAG_SPECIALINFO', 0x10); //
define('UNIT_DYNFLAG_DEAD', 0x20); // Makes the creature appear dead (this DOES NOT make the creature's name grey or not attack players).
define('UNIT_DYNFLAG_REFER_A_FRIEND', 0x40); //
define('UNIT_DYNFLAG_TAPPED_BY_ALL_THREAT_LIST', 0x80); // Lua_UnitIsTappedByAllThreatList
define('UNIT_DYNFLAG_VALIDATE', 0xFF); //
define('PET_TALENT_TYPE_FEROCITY', 0);
define('PET_TALENT_TYPE_TENACITY', 1);
@@ -851,9 +867,11 @@ define('GO_FLAG_INTERACT_COND', 0x0004); // Untargetable, can
define('GO_FLAG_TRANSPORT', 0x0008); // Gameobject can transport (boat, elevator, car)
define('GO_FLAG_NOT_SELECTABLE', 0x0010); // Not selectable (Not even in GM-mode)
define('GO_FLAG_NODESPAWN', 0x0020); // Never despawns. Typical for gameobjects with on/off state (doors for example)
define('GO_FLAG_TRIGGERED', 0x0040); // typically, summoned objects. Triggered by spell or other events
define('GO_FLAG_AI_OBSTACLE', 0x0040); // makes the client register the object in something called AIObstacleMgr, unknown what it does
define('GO_FLAG_FREEZE_ANIMATION',0x0080); //
define('GO_FLAG_DAMAGED', 0x0200); // Gameobject has been siege damaged
define('GO_FLAG_DESTROYED', 0x0400); // Gameobject has been destroyed
define('GO_FLAG_VALIDATE', 0x06FF); //
define('GO_STATE_ACTIVE', 0); // show in world as used and not reset (closed door open)
define('GO_STATE_READY', 1); // show in world as ready (closed door close)

View File

@@ -8,20 +8,31 @@ if (!defined('AOWOW_REVISION'))
enum ChrRace : int
{
case HUMAN = 1;
case ORC = 2;
case DWARF = 3;
case NIGHTELF = 4;
case UNDEAD = 5;
case TAUREN = 6;
case GNOME = 7;
case TROLL = 8;
case BLOODELF = 10;
case DRAENEI = 11;
case HUMAN = 1;
case ORC = 2;
case DWARF = 3;
case NIGHTELF = 4;
case UNDEAD = 5;
case TAUREN = 6;
case GNOME = 7;
case TROLL = 8;
case GOBLIN = 9;
case BLOODELF = 10;
case DRAENEI = 11;
case FEL_ORC = 12;
case NAGA = 13;
case BROKEN = 14;
case SKELETON = 15;
case VRYKUL = 16;
case TUSKARR = 17;
case FOREST_TROLL = 18;
case TAUNKA = 19;
case NORTHREND_SKELETON = 20;
case ICE_TROLL = 21;
public const MASK_ALLIANCE = 0x44D;
public const MASK_HORDE = 0x2B2;
public const MASK_ALL = 0x6FF;
public const MASK_ALLIANCE = 0x44D; // HUMAN, DWARF, NIGHTELF, GNOME, DRAENEI
public const MASK_HORDE = 0x2B2; // ORC, UNDEAD, TAUREN, TROLL, BLOODELF
public const MASK_ALL = self::MASK_ALLIANCE | self::MASK_HORDE;
public function matches(int $raceMask) : bool
{
@@ -80,7 +91,8 @@ enum ChrRace : int
self::GNOME => 'gnome',
self::TROLL => 'troll',
self::BLOODELF => 'bloodelf',
self::DRAENEI => 'draenei'
self::DRAENEI => 'draenei',
default => ''
};
}

Some files were not shown because too many files have changed in this diff Show More