diff --git a/endpoints/achievement/achievement.php b/endpoints/achievement/achievement.php
index 7ab7fb86..1068fe42 100644
--- a/endpoints/achievement/achievement.php
+++ b/endpoints/achievement/achievement.php
@@ -124,10 +124,7 @@ class AchievementBaseResponse extends TemplateResponse implements ICache
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `realm` IS NOT NULL AND `realmGUID` IS NOT NULL AND `cuFlags` & ?d = 0', PROFILER_CU_NEEDS_RESYNC);
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
- // - js component missing;
- // - can't yet assign styles to li element
- // if (User::getPinnedCharacter())
- // $infobox[] = Lang::profiler('completion') . '[span class="compact-completion-display"][/span]'; // [li style="display:none"]...[/li]
+ // completion row added by InfoboxMarkup
}
// original name
@@ -135,7 +132,7 @@ class AchievementBaseResponse extends TemplateResponse implements ICache
$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));
/**********/
diff --git a/endpoints/faction/faction.php b/endpoints/faction/faction.php
index 84ba340f..2ca7ca92 100644
--- a/endpoints/faction/faction.php
+++ b/endpoints/faction/faction.php
@@ -106,18 +106,15 @@ class FactionBaseResponse extends TemplateResponse implements ICache
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `realm` IS NOT NULL AND `realmGUID` IS NOT NULL AND `cuFlags` & ?d = 0', PROFILER_CU_NEEDS_RESYNC);
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
- // - js component missing;
- // - can't yet assign styles to li element
- // if (User::getPinnedCharacter())
- // $infobox[] = Lang::profiler('completion') . '[span class="compact-completion-display"][/span]'; // [li style="display:none"]...[/li]
+ // 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');
+ 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);
/****************/
diff --git a/endpoints/item/item.php b/endpoints/item/item.php
index d6582962..34d94c1b 100644
--- a/endpoints/item/item.php
+++ b/endpoints/item/item.php
@@ -319,12 +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);
/****************/
diff --git a/endpoints/quest/quest.php b/endpoints/quest/quest.php
index 4c01b65a..6830704d 100644
--- a/endpoints/quest/quest.php
+++ b/endpoints/quest/quest.php
@@ -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);
/*************/
@@ -275,16 +276,13 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$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') && !($_flags & QUEST_FLAG_UNAVAILABLE || $this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW))
+ 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 `realm` IS NOT NULL AND `realmGUID` IS NOT NULL AND `cuFlags` & ?d = 0', PROFILER_CU_NEEDS_RESYNC);
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
- // - js component missing;
- // - can't yet assign styles to li element
- // if (User::getPinnedCharacter())
- // $infobox[] = Lang::profiler('completion') . '[span class="compact-completion-display"][/span]'; // [li style="display:none"]...[/li]
+ // completion row added by InfoboxMarkup
}
// original name
@@ -292,7 +290,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$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);
/*******************/
diff --git a/endpoints/spell/spell.php b/endpoints/spell/spell.php
index 28bafde2..5508beff 100644
--- a/endpoints/spell/spell.php
+++ b/endpoints/spell/spell.php
@@ -2352,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
@@ -2445,16 +2446,13 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// 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') && in_array($this->subject->getField('typeCat'), [-5, -6]) && !($this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW))
+ 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 `realm` IS NOT NULL AND `realmGUID` IS NOT NULL AND `cuFlags` & ?d = 0', PROFILER_CU_NEEDS_RESYNC);
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
- // - js component missing;
- // - can't yet assign styles to li element
- // if (User::getPinnedCharacter())
- // $infobox[] = Lang::profiler('completion') . '[span class="compact-completion-display"][/span]'; // [li style="display:none"]...[/li]
+ // completion row added by InfoboxMarkup
}
// original name
@@ -2484,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;
diff --git a/endpoints/title/title.php b/endpoints/title/title.php
index 7bc4ae01..1c935196 100644
--- a/endpoints/title/title.php
+++ b/endpoints/title/title.php
@@ -94,10 +94,7 @@ class TitleBaseResponse extends TemplateResponse implements ICache
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `realm` IS NOT NULL AND `realmGUID` IS NOT NULL AND `cuFlags` & ?d = 0', PROFILER_CU_NEEDS_RESYNC);
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
- // - js component missing;
- // - can't yet assign styles to li element
- // if (User::getPinnedCharacter())
- // $infobox[] = Lang::profiler('completion') . '[span class="compact-completion-display"][/span]'; // [li style="display:none"]...[/li]
+ // completion row added by InfoboxMarkup
}
// original name
@@ -105,7 +102,7 @@ class TitleBaseResponse extends TemplateResponse implements ICache
$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);
/****************/
diff --git a/includes/components/frontend/infoboxmarkup.class.php b/includes/components/frontend/infoboxmarkup.class.php
index bf6d5d18..dff22075 100644
--- a/includes/components/frontend/infoboxmarkup.class.php
+++ b/includes/components/frontend/infoboxmarkup.class.php
@@ -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);
}
@@ -31,6 +31,10 @@ class InfoboxMarkup extends Markup
public function __toString() : string
{
+ // 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($_);
diff --git a/includes/dbtypes/item.class.php b/includes/dbtypes/item.class.php
index 10c4fc1d..ceca727c 100644
--- a/includes/dbtypes/item.class.php
+++ b/includes/dbtypes/item.class.php
@@ -487,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)
diff --git a/includes/dbtypes/quest.class.php b/includes/dbtypes/quest.class.php
index f7798935..7754a173 100644
--- a/includes/dbtypes/quest.class.php
+++ b/includes/dbtypes/quest.class.php
@@ -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;
diff --git a/includes/dbtypes/spell.class.php b/includes/dbtypes/spell.class.php
index 70d6e46f..aabf72e0 100644
--- a/includes/dbtypes/spell.class.php
+++ b/includes/dbtypes/spell.class.php
@@ -2150,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)
diff --git a/includes/user.class.php b/includes/user.class.php
index 12928616..8ebd6261 100644
--- a/includes/user.class.php
+++ b/includes/user.class.php
@@ -569,6 +569,7 @@ class User
$gUser['downvoteRep'] = Cfg::get('REP_REQ_DOWNVOTE');
$gUser['upvoteRep'] = Cfg::get('REP_REQ_UPVOTE');
$gUser['characters'] = self::getCharacters();
+ $gUser['completion'] = self::getCompletion();
$gUser['excludegroups'] = self::$excludeGroups;
if (self::$debug)
@@ -723,6 +724,58 @@ class User
return $data;
}
+
+ public static function getCompletion() : array
+ {
+ $ids = [];
+ foreach (self::$profiles->iterate() as $_)
+ if (!self::$profiles->isCustom())
+ $ids[] = self::$profiles->id;
+
+ if (!$ids)
+ return [];
+
+ $completion = [];
+
+ $x = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `questId` AS ARRAY_KEY2, `questId` FROM ?_profiler_completion_quests WHERE `id` IN (?a)', $ids);
+ $completion[Type::QUEST] = $x ? array_map(array_values(...), $x) : [];
+
+ $x = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `achievementId` AS ARRAY_KEY2, `achievementId` FROM ?_profiler_completion_achievements WHERE `id` IN (?a)', $ids);
+ $completion[Type::ACHIEVEMENT] = $x ? array_map(array_values(...), $x) : [];
+
+ $x = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `titleId` AS ARRAY_KEY2, `titleId` FROM ?_profiler_completion_titles WHERE `id` IN (?a)', $ids);
+ $completion[Type::TITLE] = $x ? array_map(array_values(...), $x) : [];
+
+ $completion[Type::ITEM] = [];
+
+ $spells = DB::Aowow()->select(
+ 'SELECT pcs.`id` AS ARRAY_KEY, pcs.`spellId` AS ARRAY_KEY2, pcs.`spellId`, i.`id` AS "itemId"
+ FROM ?_spell s
+ JOIN ?_profiler_completion_spells pcs ON s.`id` = pcs.`spellId`
+ LEFT JOIN ?_items i ON i.spellId1 IN (?a) AND i.spellId2 = pcs.spellId
+ WHERE s.`typeCat` IN (?a) AND pcs.`id` IN (?a)',
+ LEARN_SPELLS, [-5, -6, 9, 11], $ids
+ );
+
+ if ($spells)
+ {
+ $completion[Type::SPELL] = array_map(fn($x) => array_column($x, 'spellId'), $spells);
+
+ if ($recipes = array_map(fn($x) => array_filter(array_column($x, 'itemId')), $spells))
+ foreach ($ids as $id) // array_merge_recursive does not respect numeric keys
+ $completion[Type::ITEM][$id] = array_merge($completion[Type::ITEM][$id] ?? [], $recipes[$id] ?? []);
+ }
+ else
+ $completion[Type::SPELL] = [];
+
+ // init empty result sets
+ foreach ($completion as &$c)
+ foreach ($ids as $id)
+ if (!isset($c[$id]))
+ $c[$id] = [];
+
+ return $completion;
+ }
}
?>
diff --git a/setup/sql/updates/1762199391_01.sql b/setup/sql/updates/1762199391_01.sql
new file mode 100644
index 00000000..168a5f1b
--- /dev/null
+++ b/setup/sql/updates/1762199391_01.sql
@@ -0,0 +1 @@
+UPDATE `aowow_dbversion` SET `build` = CONCAT(IFNULL(`build`, ''), ' globaljs tooltips');
diff --git a/setup/tools/filegen/templates/global.js/listview_templates.js b/setup/tools/filegen/templates/global.js/listview_templates.js
index 2dc8aca2..4f3b1d92 100644
--- a/setup/tools/filegen/templates/global.js/listview_templates.js
+++ b/setup/tools/filegen/templates/global.js/listview_templates.js
@@ -905,6 +905,43 @@ Listview.templates = {
var _ = Listview.funcBox.getItemType;
return $WH.strcmp(_(a.classs, a.subclass, a.subsubclass).text, _(b.classs, b.subclass, b.subsubclass).text);
}
+ },
+ {
+ id: 'completed', // Listview.COLUMN_ID_COMPLETION
+ name: LANG.completion, // WH.TERMS.completion
+ hidden: true,
+ compute: function (item, td)
+ {
+ var skip = !item.hasOwnProperty('classs') || !Listview.templates.item._validCompletionCategory(item.classs, item.subclass, item.quality);
+ $WH.addCompletionIcons(td, 3, item.id, skip);
+ },
+ sortFunc: function (a, b)
+ {
+ // return $WH.stringCompare(
+ return $WH.strcmp(
+ $WH.getCompletionFlags(3, a.id),
+ $WH.getCompletionFlags(3, b.id)
+ );
+ },
+ getValue: function (item) {
+ var value = 0;
+ var completionData = g_user.completion?.hasOwnProperty(3) ? g_user.completion[3] : {};
+
+ if (!item.hasOwnProperty('classs') || !Listview.templates.item._validCompletionCategory(item.classs, item.subclass, item.quality))
+ return -1;
+
+ for (var i in g_user.characters)
+ {
+ var profile = g_user.characters[i];
+ if (!(profile.id in completionData))
+ continue;
+
+ if ($WH.in_array(completionData[profile.id], item.id) != -1)
+ value++;
+ }
+
+ return value;
+ }
}
],
@@ -912,6 +949,12 @@ Listview.templates = {
return item.name.charAt(0) == '@' ? 'javascript:;' : '?item=' + item.id;
},
+ _validCompletionCategory: function (classs, subclass, quality) {
+ return $WH.in_array(g_completion_categories[3], classs) != -1 ||
+ $WH.in_array(g_completion_categories[3], '' + classs + '-' + subclass) != -1 ||
+ $WH.in_array(g_completion_categories[3], '' + classs + 'q' + quality) != -1
+ },
+
onBeforeCreate: function() {
var nComparable = false;
@@ -930,6 +973,23 @@ Listview.templates = {
this.mode = Listview.MODE_CHECKBOX;
this._nComparable = nComparable;
}
+
+ for (var i in this.columns)
+ {
+ if (this.columns[i].id == 'completed' && this.columns[i].hidden)
+ {
+ if ($WH.isset('g_user') && 'characters' in g_user && $WH.in_array(this.hiddenCols, this.columns[i].id) == -1)
+ {
+ var n = 0;
+ for (var j in this.data)
+ if (this.data[j].hasOwnProperty('classs') && Listview.templates.item._validCompletionCategory(this.data[j].classs, this.data[j].subclass, this.data[j].quality))
+ n++;
+
+ if (n > this.data.length * 0.1)
+ this.visibility.push(parseInt(i));
+ }
+ }
+ }
},
createCbControls: function(div, topBar) {
@@ -1148,7 +1208,7 @@ Listview.templates = {
}
}
else {
- return - 1;
+ return -1;
}
},
sortFunc: function(a, b, col) {
@@ -1902,11 +1962,69 @@ Listview.templates = {
var _ = Listview.funcBox.getQuestCategory;
return $WH.strcmp(_(a.category), _(b.category));
}
+ },
+ {
+ id: 'completed', // Listview.COLUMN_ID_COMPLETION
+ name: LANG.completion, // WH.TERMS.completion
+ hidden: true,
+ compute: function (quest, td)
+ {
+ if (quest.daily || quest.weekly)
+ return;
+
+ $WH.addCompletionIcons(td, 5, quest.id)
+ },
+ sortFunc: function (a, b)
+ {
+ // return $WH.stringCompare(
+ return $WH.strcmp(
+ $WH.getCompletionFlags(5, a.id),
+ $WH.getCompletionFlags(5, b.id)
+ );
+ },
+ getValue: function (quest) {
+ var value = 0;
+ var completionData = g_user.completion?.hasOwnProperty(5) ? g_user.completion[5] : {};
+
+ if (quest.daily || quest.weekly)
+ return -1;
+
+ for (var i in g_user.characters)
+ {
+ var profile = g_user.characters[i];
+ if (!(profile.id in completionData))
+ continue;
+
+ if ($WH.in_array(completionData[profile.id], quest.id) != -1)
+ value++;
+ }
+
+ return value;
+ }
}
],
getItemLink: function(quest) {
return '?quest=' + quest.id;
+ },
+
+ onBeforeCreate: function () {
+ for (var i in this.columns)
+ {
+ if (this.columns[i].id == 'completed' && this.columns[i].hidden)
+ {
+ if ($WH.isset('g_user') && 'characters' in g_user && $WH.in_array(this.hiddenCols, this.columns[i].id) == -1)
+ {
+ var n = 0;
+ for (var j in this.data)
+ if (!this.data[j].daily && !this.data[j].weekly)
+ n++;
+
+ if (n > this.data.length * 0.1)
+ this.visibility.push(parseInt(i));
+ }
+ }
+ }
}
},
@@ -3432,12 +3550,74 @@ Listview.templates = {
return 0;
}
- }
+ },
/* AoWoW: custom end */
+ {
+ id: 'completed', // Listview.COLUMN_ID_COMPLETION
+ name: LANG.completion, // WH.TERMS.completion
+ hidden: true,
+ compute: function (spell, td)
+ {
+ if (!spell.hasOwnProperty('cat') || !Listview.templates.spell._validCompletionCategory(spell.cat))
+ return;
+
+ $WH.addCompletionIcons(td, 6, spell.id)
+ },
+ sortFunc: function (a, b)
+ {
+ // return $WH.stringCompare(
+ return $WH.strcmp(
+ $WH.getCompletionFlags(6, a.id),
+ $WH.getCompletionFlags(6, b.id)
+ );
+ },
+ getValue: function (spell) {
+ var value = 0;
+ var completionData = g_user.completion?.hasOwnProperty(6) ? g_user.completion[6] : {};
+
+ if (!spell.hasOwnProperty('cat') || !Listview.templates.spell._validCompletionCategory(spell.cat))
+ return -1;
+
+ for (var i in g_user.characters)
+ {
+ var profile = g_user.characters[i];
+ if (!(profile.id in completionData))
+ continue;
+
+ if ($WH.in_array(completionData[profile.id], spell.id) != -1)
+ value++;
+ }
+
+ return value;
+ }
+ }
],
getItemLink: function(spell) {
return '?spell=' + spell.id;
+ },
+
+ _validCompletionCategory: function (category) {
+ return $WH.in_array(g_completion_categories[6], category) != -1;
+ },
+
+ onBeforeCreate: function () {
+ for (var i in this.columns)
+ {
+ if (this.columns[i].id == 'completed' && this.columns[i].hidden)
+ {
+ if ($WH.isset('g_user') && 'characters' in g_user && $WH.in_array(this.hiddenCols, this.columns[i].id) == -1)
+ {
+ var n = 0;
+ for (var j in this.data)
+ if (this.data[j].hasOwnProperty('cat') && Listview.templates.spell._validCompletionCategory(this.data[j].cat))
+ n++;
+
+ if (n > this.data.length * 0.1)
+ this.visibility.push(parseInt(i));
+ }
+ }
+ }
}
},
@@ -5834,11 +6014,69 @@ Listview.templates = {
return $WH.strcmp(g_achievement_categories[a.category], g_achievement_categories[b.category]);
},
hidden: true
+ },
+ {
+ id: 'completed', // Listview.COLUMN_ID_COMPLETION
+ name: LANG.completion, // WH.TERMS.completion
+ hidden: true,
+ compute: function (achievement, td)
+ {
+ if (achievement.type)
+ return;
+
+ $WH.addCompletionIcons(td, 10, achievement.id)
+ },
+ sortFunc: function (a, b)
+ {
+ // return $WH.stringCompare(
+ return $WH.strcmp(
+ $WH.getCompletionFlags(10, a.id),
+ $WH.getCompletionFlags(10, b.id)
+ );
+ },
+ getValue: function (achievement) {
+ var value = 0;
+ var completionData = g_user.completion?.hasOwnProperty(10) ? g_user.completion[10] : {};
+
+ if (achievement.type)
+ return -1;
+
+ for (var i in g_user.characters)
+ {
+ var profile = g_user.characters[i];
+ if (!(profile.id in completionData))
+ continue;
+
+ if ($WH.in_array(completionData[profile.id], achievement.id) != -1)
+ value++;
+ }
+
+ return value;
+ }
}
],
getItemLink: function(achievement) {
return '?achievement=' + achievement.id;
+ },
+
+ onBeforeCreate: function () {
+ for (var i in this.columns)
+ {
+ if (this.columns[i].id == 'completed' && this.columns[i].hidden)
+ {
+ if ($WH.isset('g_user') && 'characters' in g_user && $WH.in_array(this.hiddenCols, this.columns[i].id) == -1)
+ {
+ var n = 0;
+ for (var j in this.data)
+ if (!this.data[j].type)
+ n++;
+
+ if (n > this.data.length * 0.1)
+ this.visibility.push(parseInt(i));
+ }
+ }
+ }
}
},
@@ -6052,11 +6290,55 @@ Listview.templates = {
return $WH.strcmp(g_title_categories[a.category], g_title_categories[b.category]);
},
hidden: true
+ },
+ {
+ id: 'completed', // Listview.COLUMN_ID_COMPLETION
+ name: LANG.completion, // WH.TERMS.completion
+ hidden: true,
+ compute: function (title, td)
+ {
+ $WH.addCompletionIcons(td, 11, title.id)
+ },
+ sortFunc: function (a, b)
+ {
+ // return $WH.stringCompare(
+ return $WH.strcmp(
+ $WH.getCompletionFlags(11, a.id),
+ $WH.getCompletionFlags(11, b.id)
+ );
+ },
+ getValue: function (title) {
+ var value = 0;
+ var completionData = g_user.completion?.hasOwnProperty(11) ? g_user.completion[11] : {};
+
+ for (var i in g_user.characters)
+ {
+ var profile = g_user.characters[i];
+ if (!(profile.id in completionData))
+ continue;
+
+ if ($WH.in_array(completionData[profile.id], title.id) != -1)
+ value++;
+ }
+
+ return value;
+ }
}
],
getItemLink: function(title) {
return '?title=' + title.id;
+ },
+
+ onBeforeCreate: function () {
+ for (var i in this.columns)
+ {
+ if (this.columns[i].id == 'completed' && this.columns[i].hidden)
+ {
+ if ($WH.isset('g_user') && 'characters' in g_user && $WH.in_array(this.hiddenCols, this.columns[i].id) == -1)
+ this.visibility.push(parseInt(i));
+ }
+ }
}
},
diff --git a/setup/tools/filegen/templates/global.js/profiler.js b/setup/tools/filegen/templates/global.js/profiler.js
index 52a23ecc..273ea12f 100644
--- a/setup/tools/filegen/templates/global.js/profiler.js
+++ b/setup/tools/filegen/templates/global.js/profiler.js
@@ -17,3 +17,137 @@ function g_getProfileUrl(profile) {
function g_getProfileRealmUrl(profile) {
return '?profiles=' + profile.region + '.' + profile.realm;
}
+
+$WH.prepInfobox = function () {
+ $('.infobox').each(function ()
+ {
+ var row = $(this);
+ if (row.data('infobox-completion-info-added') !== true && g_user.characters &&
+ typeof g_pageInfo == 'object' && typeof g_pageInfo.type == 'number' && typeof g_pageInfo.typeId == 'number')
+ {
+ var wardrobe = $('.compact-completion-display', row);
+ if (wardrobe.length)
+ {
+ var completionIcon = $('span', wardrobe);
+ if (completionIcon.length)
+ {
+ var i = 0;
+ completionIcon.each(function ()
+ {
+ var e = $(this);
+ var t = e.html();
+
+ try { t = JSON.parse(t) }
+ catch (e) { return }
+
+ var a;
+ for (var n = 0, s; s = g_user.characters[n]; n++)
+ {
+ if (s.id == t.id)
+ {
+ a = s;
+ break
+ }
+ }
+
+ if (a)
+ {
+ e.parent().append($WH.createCompletionIcon(a, t.completed ? 1 : 0, t.rel));
+ e.remove();
+ i++
+ }
+ });
+
+ if (i)
+ wardrobe.parent().parent().show()
+ }
+ else if (wardrobe.is(':empty') && $WH.addCompletionIcons(wardrobe.get(0), g_pageInfo.type, g_pageInfo.typeId))
+ wardrobe.parent().parent().show()
+ }
+
+ row.data('infobox-completion-info-added', true)
+ }
+ });
+};
+
+$WH.addCompletionIcons = function (parent, type, typeIdOrData, skipIncomplete) {
+ var nComplete = 0;
+
+ for (var i in g_user.characters)
+ {
+ if (!g_user.characters.hasOwnProperty(i))
+ continue;
+
+ let profile = g_user.characters[i];
+
+ let completion = 0;
+ let completionData = g_user.completion?.hasOwnProperty(type) ? g_user.completion[type] : {};
+ if (!completionData.hasOwnProperty(profile.id))
+ continue;
+
+ completion = completionData[profile.id].includes(typeIdOrData) ? 1 : 0;
+
+ if (skipIncomplete && !completion)
+ continue;
+
+ $WH.ae(parent, $WH.createCompletionIcon(profile, completion));
+ nComplete++;
+ }
+
+ return nComplete;
+};
+
+$WH.createCompletionIcon = function (profile, completePct, rel) {
+ var icon = $WH.ce('a');
+ icon.href = '?profile=' + profile.region + '.' + profile.realm + '.' + profile.name;
+
+ // aowow - so the generic tooltips dont override our completion tooltip
+ icon.setAttribute('data-disable-wowhead-tooltip', true);
+
+ icon.className = 'progress-icon progress-' + (completePct ? Math.max(1, Math.floor(completePct * 8)) : 0);
+ $WH.Tooltip.simple(icon, $WH.getCompletionTooltip(profile, completePct), null, true);
+
+ if (rel)
+ icon.rel = rel;
+
+ return icon;
+};
+
+$WH.getCompletionTooltip = function (profile, completePct) {
+ let tooltip = $WH.ce('div');
+ $WH.ae(tooltip, $WH.ce('span', { className: 'q' }, $WH.ct((completePct >= 1 ? LANG.complete : LANG.incomplete) + LANG.colon)));
+ $WH.ae(tooltip, $WH.ce('br'));
+
+ let charRow = $WH.ce('span', { style: { whiteSpace: 'nowrap' } });
+ $WH.ae(tooltip, charRow);
+ $WH.ae(charRow, $WH.ce('b', { className: 'c' + profile.classs }, $WH.ct(profile.name)));
+
+ let server = [' ', profile.realmname];
+
+ if (profile.hasOwnProperty('region'))
+ server.push(profile.region.toUpperCase());
+
+ $WH.ae(charRow, $WH.ce('span', { className: 'q0' }, $WH.ct(server.join(' '))));
+
+ if (completePct > 0 && completePct < 1)
+ $WH.ae(charRow, $WH.ct( $WH.sprintf(LANG.parens_format, '', Math.round(completePct * 100) + '%') ));
+
+ return tooltip.innerHTML;
+};
+
+$WH.getCompletionFlags = function (type, typeId) {
+ var flags = 0;
+ var profiles = g_user.characters || [];
+ let completionData = g_user.completion?.hasOwnProperty(type) ? g_user.completion[type] : {};
+
+ for (var i = profiles.length - 1; i >= 0; i--)
+ {
+ var profile = profiles[i];
+ if (!(profile.id in completionData))
+ continue
+
+ flags = flags << 1 | (completionData[profile.id].includes(typeId) ? 1 : 0)
+ }
+
+ return flags;
+};
diff --git a/setup/tools/filegen/templates/global.js/wow.js b/setup/tools/filegen/templates/global.js/wow.js
index d482e5a7..99323c8b 100644
--- a/setup/tools/filegen/templates/global.js/wow.js
+++ b/setup/tools/filegen/templates/global.js/wow.js
@@ -269,6 +269,12 @@ var g_types = {
504: 'mail'
};
+var g_completion_categories = {
+ // 1: [12], // NPCs: Battle Pets
+ 3: [9, "15-2", "15-5", "15--7"], // Items: Recipes, Minipets, Mounts (Ground), Mounts (Flying)
+ 6: [-5, -6, 9, 11] // Spells: Mounts, Minipets, Sec. Skills, Prim. Skills
+};
+
// Items
$WH.cO(g_items, {
add: function(id, json)
diff --git a/setup/tools/filegen/templates/power.js.in b/setup/tools/filegen/templates/power.js.in
index 717345d7..f6e916f2 100644
--- a/setup/tools/filegen/templates/power.js.in
+++ b/setup/tools/filegen/templates/power.js.in
@@ -644,6 +644,49 @@ if (typeof $WowheadPower == "undefined") {
}
}
+ if (!isRemote && window.g_user && g_user.characters)
+ {
+ var completion = '';
+ let completionData = g_user.completion.hasOwnProperty(currentType) ? g_user.completion[currentType] : false;
+
+ let entity = {};
+ if (currentType == TYPE_QUEST && $WH.isset('g_quests'))
+ entity = g_quests[currentId] || {};
+ if (currentType == TYPE_ACHIEVEMENT && $WH.isset('g_achievements'))
+ entity = g_achievements[currentId] || {};
+ if (currentType == TYPE_ITEM && $WH.isset('g_items'))
+ entity = g_items[currentId] || {};
+ if (currentType == TYPE_SPELL && $WH.isset('g_spells'))
+ entity = g_spells[currentId] || {};
+
+ if ((!LOOKUPS[currentType][0] || LOOKUPS[currentType][0][currentId].status[currentLocale] !== STATUS_OK) ||
+ (currentType === TYPE_QUEST && (entity.daily || entity.weekly)) ||
+ (currentType === TYPE_ACHIEVEMENT && entity.type))
+ completionData = false;
+
+ let CompetionWithoutCatg = !(completionData && currentType in g_completion_categories && $WH.in_array(g_completion_categories[currentType], entity.completion_category) === -1);
+
+ if (completionData)
+ {
+ for (var i in g_user.characters)
+ {
+ var profile = g_user.characters[i];
+ if (!(profile.id in completionData))
+ continue;
+
+ let isComplete = $WH.in_array(completionData[profile.id], currentId) !== - 1;
+ if (!isComplete && !CompetionWithoutCatg)
+ continue;
+
+ completion += '
';
+ completion += profile.name + ' - ' + profile.realmname + ' ' + profile.region.toUpperCase();
+ }
+ }
+
+ if (completion !== '')
+ html += '
' + LANG.completion + ':' + completion;
+ }
+
if (currentParams.map && map && map.getMap) {
html2 = map.getMap();
}
diff --git a/static/css/aowow.css b/static/css/aowow.css
index 64c3fa45..4befa58b 100644
--- a/static/css/aowow.css
+++ b/static/css/aowow.css
@@ -4260,3 +4260,33 @@ a.button-red.fa-clipboard > em > span {
.fav-star-1 {
background-position: -65px center;
}
+
+/* imported from lists */
+.compact-completion-display a {
+ margin-left: 2px;
+ vertical-align: text-top; /* middle; */
+}
+
+.progress-icon {
+ /* background: url(/images/ListManager/completion.png?4) no-repeat; */
+ background: url(../images/ui/check.png) no-repeat -1px -15px;
+ border-radius: 99px;
+ display: inline-block;
+ height: 13px; /* 16px; */
+ line-height: 16px;
+ vertical-align: sub; /* bottom; */
+ width: 13px; /* 16px; */
+}
+
+.progress-icon:focus {
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
+}
+
+/* newer atlas has 9 icons */
+.progress-icon.progress-8 {
+ background-position: -1px 0px;
+}
+
+.progress-icon.with-text {
+ padding-left: 16px;
+}
diff --git a/static/js/Profiler.js b/static/js/Profiler.js
index ca60a8bf..2856b7e0 100644
--- a/static/js/Profiler.js
+++ b/static/js/Profiler.js
@@ -8538,10 +8538,15 @@ function ProfilerCompletion(_parent) {
_tabsListview.show((_subtotal[_category].complete[_subcategory] ? 2 : 3));
if (_opt.subname) {
- _listview.createIndicator($WH.sprintf(LANG['lvnote_' + _mode + 'ind'], _category, _subcategory, _opt.subname(_subcategory)), Listview.headerFilter.bind(_listview, _listview.columns[_listview.columns.length - 2], ''));
- _excluded.createIndicator($WH.sprintf(LANG['lvnote_' + _mode + 'ind'], _category, _subcategory, _opt.subname(_subcategory)), Listview.headerFilter.bind(_excluded, _listview.columns[_listview.columns.length - 2], ''));
- setTimeout(Listview.headerFilter.bind(_listview, _listview.columns[_listview.columns.length - 2], _opt.subname(_subcategory)), 1);
- setTimeout(Listview.headerFilter.bind(_excluded, _excluded.columns[_excluded.columns.length - 2], _opt.subname(_subcategory)), 1);
+ // aowow - adressing col by offset breaks everytime we add/remove cols in a listview template
+ // _listview.createIndicator($WH.sprintf(LANG['lvnote_' + _mode + 'ind'], _category, _subcategory, _opt.subname(_subcategory)), Listview.headerFilter.bind(_listview, _listview.columns[_listview.columns.length - 2], ''));
+ // _excluded.createIndicator($WH.sprintf(LANG['lvnote_' + _mode + 'ind'], _category, _subcategory, _opt.subname(_subcategory)), Listview.headerFilter.bind(_excluded, _listview.columns[_listview.columns.length - 2], ''));
+ // setTimeout(Listview.headerFilter.bind(_listview, _listview.columns[_listview.columns.length - 2], _opt.subname(_subcategory)), 1);
+ // setTimeout(Listview.headerFilter.bind(_excluded, _excluded.columns[_excluded.columns.length - 2], _opt.subname(_subcategory)), 1);
+ _listview.createIndicator($WH.sprintf(LANG['lvnote_' + _mode + 'ind'], _category, _subcategory, _opt.subname(_subcategory)), Listview.headerFilter.bind(_listview, _listview.columns.find((x) => x.id == (_opt.catgcol || 'category')), ''));
+ _excluded.createIndicator($WH.sprintf(LANG['lvnote_' + _mode + 'ind'], _category, _subcategory, _opt.subname(_subcategory)), Listview.headerFilter.bind(_excluded, _listview.columns.find((x) => x.id == (_opt.catgcol || 'category')), ''));
+ setTimeout(Listview.headerFilter.bind(_listview, _listview.columns.find((x) => x.id == (_opt.catgcol || 'category')), _opt.subname(_subcategory)), 1);
+ setTimeout(Listview.headerFilter.bind(_excluded, _excluded.columns.find((x) => x.id == (_opt.catgcol || 'category')), _opt.subname(_subcategory)), 1);
}
}
}
diff --git a/static/js/locale_dede.js b/static/js/locale_dede.js
index 12724635..67e6393b 100644
--- a/static/js/locale_dede.js
+++ b/static/js/locale_dede.js
@@ -4899,6 +4899,12 @@ var LANG = {
/* AoWoW: start custom */
+ // Profiler completions import
+ completion: 'Vervollständigung', // WH.TERMS.completion
+ complete: 'Vollständig', // WH.TERMS.complete
+ incomplete: 'Unvollständig', // WH.TERMS.incomplete
+ parens_format: '$1 ($2)', // WH.TERMS.parens_format
+
// click to copy fn
copied: 'Kopiert',
clickToCopy: 'Klicke zum Kopieren',
diff --git a/static/js/locale_enus.js b/static/js/locale_enus.js
index 65eac3b5..1255bfce 100644
--- a/static/js/locale_enus.js
+++ b/static/js/locale_enus.js
@@ -4947,6 +4947,12 @@ var LANG = {
/* AoWoW: start custom */
+ // Profiler completions import
+ completion: 'Completion', // WH.TERMS.completion
+ complete: 'Complete', // WH.TERMS.complete
+ incomplete: 'Incomplete', // WH.TERMS.incomplete
+ parens_format: '$1 ($2)', // WH.TERMS.parens_format
+
// click to copy fn
copied: 'Copied',
clickToCopy: 'Click to Copy',
diff --git a/static/js/locale_eses.js b/static/js/locale_eses.js
index 5ddeb9ce..a2ea7de4 100644
--- a/static/js/locale_eses.js
+++ b/static/js/locale_eses.js
@@ -4901,6 +4901,12 @@ var LANG = {
/* AoWoW: start custom */
+ // Profiler completions import
+ completion: 'Terminación', // WH.TERMS.completion
+ complete: 'Completo', // WH.TERMS.complete
+ incomplete: 'Incompleto', // WH.TERMS.incomplete
+ parens_format: '$1 ($2)', // WH.TERMS.parens_format
+
// click to copy fn
copied: 'Copiado',
clickToCopy: 'Click para copiar',
diff --git a/static/js/locale_frfr.js b/static/js/locale_frfr.js
index 234acbc0..37edaeb0 100644
--- a/static/js/locale_frfr.js
+++ b/static/js/locale_frfr.js
@@ -4901,6 +4901,12 @@ var LANG = {
/* AoWoW: start custom */
+ // Profiler completions import
+ completion: 'Achèvement', // WH.TERMS.completion
+ complete: 'Complète', // WH.TERMS.complete
+ incomplete: 'Incomplet', // WH.TERMS.incomplete
+ parens_format: '$1 ($2)', // WH.TERMS.parens_format
+
// click to copy fn
copied: 'Copié',
clickToCopy: 'Cliquer pour Copier',
diff --git a/static/js/locale_ruru.js b/static/js/locale_ruru.js
index a88b356f..a3f5c265 100644
--- a/static/js/locale_ruru.js
+++ b/static/js/locale_ruru.js
@@ -4903,6 +4903,12 @@ var LANG = {
/* AoWoW: start custom */
+ // Profiler completions import
+ completion: 'Завершено', // WH.TERMS.completion
+ complete: 'Завершено', // WH.TERMS.complete
+ incomplete: 'Не завершено', // WH.TERMS.incomplete
+ parens_format: '$1 ($2)', // WH.TERMS.parens_format
+
// click to copy fn
copied: 'Скопировано',
clickToCopy: 'Нажмите, чтобы скопировать',
diff --git a/static/js/locale_zhcn.js b/static/js/locale_zhcn.js
index 54f20940..065eb7e4 100644
--- a/static/js/locale_zhcn.js
+++ b/static/js/locale_zhcn.js
@@ -4927,6 +4927,12 @@ var LANG = {
/* AoWoW: start custom */
+ // Profiler completions import
+ completion: '达成', // WH.TERMS.completion
+ complete: '完成', // WH.TERMS.complete
+ incomplete: '未完成', // WH.TERMS.incomplete
+ parens_format: '$1($2)', // WH.TERMS.parens_format
+
// click to copy fn
copied: '已复制',
clickToCopy: '点击复制',
diff --git a/template/bricks/infobox.tpl.php b/template/bricks/infobox.tpl.php
index d9ca62ed..60252209 100644
--- a/template/bricks/infobox.tpl.php
+++ b/template/bricks/infobox.tpl.php
@@ -51,6 +51,7 @@ echo "
| =Lang::main('videos'); ?> |
|---|