Skip to content

Commit 0821bb2

Browse files
committed
feat: quest progress
1 parent ce4c015 commit 0821bb2

File tree

6 files changed

+381
-37
lines changed

6 files changed

+381
-37
lines changed

src/base/game/questProgress.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import Quest from '../../structures/quests/Quest.js'; // eslint-disable-line no-unused-vars
2+
import { toast } from '../../utils/2.toasts.js';
3+
import * as settings from '../../utils/settings/index.js';
4+
import onPage from '../../utils/onPage.js';
5+
import eventManager from '../../utils/eventManager.js';
6+
import { fetch } from '../../utils/quests.js';
7+
8+
const setting = settings.register({
9+
name: 'Disable Quest Toast',
10+
key: 'underscript.disable.questNotifications',
11+
page: 'Game',
12+
category: 'Notifications',
13+
});
14+
15+
onPage('Game', () => {
16+
if (setting.value()) return;
17+
18+
/**
19+
* @param {Quest[]} results
20+
*/
21+
function setup(results) {
22+
const cache = new Map(results.map((q) => [q.id, q.clone()]));
23+
24+
eventManager.once('questProgress', updateQuests);
25+
26+
/**
27+
* @param {Quest[]} quests
28+
*/
29+
function updateQuests(quests) {
30+
const changes = quests.filter((quest) => {
31+
if (quest.claimed) return false;
32+
33+
const previous = cache.get(quest.id);
34+
return previous && quest.progress.compare(previous.progress);
35+
}).sort((a, b) => b.claimable - a.claimable);
36+
37+
if (!changes.length) return;
38+
39+
const message = (() => {
40+
const lines = [];
41+
let completed = true;
42+
changes.forEach((e, i) => {
43+
if (!i && e.claimable) {
44+
lines.push('### Completed');
45+
} else if (completed && !e.claimable) {
46+
completed = false;
47+
if (i) lines.push('\n'); // Separate completed from progress
48+
lines.push('### Progression');
49+
}
50+
lines.push(`- ${e.name} (+${e.progress.compare(cache.get(e.id).progress)})`);
51+
});
52+
return lines.join('\n');
53+
})();
54+
55+
toast({
56+
title: 'Quest Progress!',
57+
text: `${message}Click to go to Quests page`,
58+
onClose: () => {
59+
location.href = '/Quests';
60+
},
61+
});
62+
}
63+
}
64+
65+
fetch(({ quests }) => quests && setup(quests), false);
66+
});

src/base/vanilla/quest.highlight.js

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,49 @@
1-
import axios from 'axios';
21
import eventManager from '../../utils/eventManager.js';
32
import * as settings from '../../utils/settings/index.js';
43
import wrap from '../../utils/2.pokemon.js';
5-
import { toast } from '../../utils/2.toasts.js';
64
import onPage from '../../utils/onPage.js';
7-
import translate from '../../utils/translate.js';
85
import style from '../../utils/style.js';
96
import $el from '../../utils/elementHelper.js';
10-
import { noop } from '../../utils/1.variables.js';
7+
import { fetch } from '../../utils/quests.js';
118

129
wrap(() => {
1310
const setting = settings.register({
14-
name: 'Disable Quest Completed Notifications',
11+
name: 'Disable Quest Highlight',
1512
key: 'underscript.disable.questHighlight',
1613
});
1714

18-
if (setting.value()) return; // TODO: Split into a setting to disable just the toast and a setting to disable highlighting.
15+
if (setting.value()) return;
1916
const questSelector = 'input[type="submit"][value="Claim"]:not(:disabled)';
2017

2118
eventManager.on(':loaded', () => $el.removeClass(document.querySelectorAll('.yellowLink[href="Quests"]'), 'yellowLink'));
2219
style.add('a.highlightQuest {color: gold !important;}');
2320

2421
function highlightQuest() {
25-
if (localStorage.getItem('underscript.quest.clear')) {
26-
$('a[href="Quests"]').addClass('highlightQuest');
27-
}
22+
$('a[href="Quests"]').toggleClass('highlightQuest', localStorage.getItem('underscript.quest.clear') !== null);
2823
}
2924

3025
function clearHighlight() {
3126
localStorage.removeItem('underscript.quest.clear');
3227
}
3328

34-
function checkHighlight() {
35-
axios.get('/Quests').then((response) => {
36-
const data = $(response.data);
37-
const quests = data.find(questSelector);
38-
if (quests.length) {
39-
localStorage.setItem('underscript.quest.clear', true);
40-
if (onPage('Game')) {
41-
let questsCleared = '';
42-
quests.each((i, e) => {
43-
questsCleared += `- ${translate($(e).parentsUntil('tbody', 'tr').find('span[data-i18n-custom]:first')).text()}\n`;
44-
});
45-
toast({
46-
title: 'Quest Completed!',
47-
text: `${questsCleared}Click to go to Quests page`,
48-
onClose: () => {
49-
location.href = '/Quests';
50-
},
51-
});
52-
} else {
53-
highlightQuest();
54-
}
55-
} else {
56-
// Perhaps another tab found a quest at some point...?
57-
clearHighlight();
58-
}
59-
}).catch(noop);
29+
function updateQuests(quests) {
30+
const completed = quests.filter((q) => q.claimable);
31+
if (completed.length) {
32+
localStorage.setItem('underscript.quest.clear', true);
33+
} else {
34+
clearHighlight();
35+
}
36+
highlightQuest();
6037
}
6138

6239
if (!localStorage.getItem('underscript.quest.clear')) {
6340
if (!localStorage.getItem('underscript.quest.skip')) { // TODO: If logged in
64-
onPage('', checkHighlight);
41+
onPage('', () => fetch(({ quests }) => quests && updateQuests(quests), false));
6542
}
66-
eventManager.on('getVictory getDefeat', checkHighlight);
6743
}
6844

45+
eventManager.on('questProgress', updateQuests);
46+
6947
eventManager.on('logout', clearHighlight);
7048

7149
eventManager.on('jQuery', function questHighlight() {

src/structures/quests/Progress.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export default class Progress {
2+
#max;
3+
#value;
4+
5+
constructor({
6+
max = 0,
7+
value = 0,
8+
} = {}) {
9+
this.#max = max;
10+
this.#value = value;
11+
}
12+
13+
get max() {
14+
return this.#max;
15+
}
16+
17+
get value() {
18+
return this.#value;
19+
}
20+
21+
get complete() {
22+
return this.max === this.value;
23+
}
24+
25+
compare(other) {
26+
if (!(other instanceof Progress)) throw new Error('invalid object');
27+
return Math.abs(this.value - other.value);
28+
}
29+
30+
toJSON() {
31+
return {
32+
complete: this.complete,
33+
max: this.max,
34+
value: this.value,
35+
};
36+
}
37+
}

src/structures/quests/Quest.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import Reward from './Reward.js';
2+
import Progress from './Progress.js';
3+
import { translateText } from '../../utils/translate.js';
4+
import Base from '../base.js';
5+
6+
export default class Quest extends Base {
7+
#args;
8+
#claimable = false;
9+
#key;
10+
#progress = new Progress();
11+
#reward = new Reward();
12+
13+
constructor(data) {
14+
const id = isQuest(data) ? data.id : getId(data);
15+
super({ id });
16+
if (isQuest(data)) {
17+
this.#args = data.#args;
18+
this.#key = data.key;
19+
} else if (data instanceof Element) {
20+
const el = data.querySelector('[data-i18n-custom^="quest"]');
21+
if (!el) throw new Error('Malformed quest');
22+
this.#args = el.dataset.i18nArgs?.split(',') || [];
23+
this.#key = el.dataset.i18nCustom;
24+
}
25+
this.update(data);
26+
}
27+
28+
update(data) {
29+
if (data instanceof Element) {
30+
this.#claimable = data.querySelector('input[type="submit"][value="Claim"]:not(:disabled)') !== null;
31+
this.#progress = new Progress(data.querySelector('progress.xpBar'));
32+
this.#reward = new Reward(data.querySelector('td:nth-last-child(2)'));
33+
} else if (isQuest(data)) {
34+
this.#claimable = data.claimable;
35+
this.#progress = data.progress;
36+
this.#reward = data.reward;
37+
}
38+
}
39+
40+
get name() {
41+
return translateText(this.key, {
42+
args: this.#args,
43+
});
44+
}
45+
46+
get key() {
47+
return this.#key;
48+
}
49+
50+
get reward() {
51+
return this.#reward;
52+
}
53+
54+
get progress() {
55+
return this.#progress;
56+
}
57+
58+
get claimable() {
59+
return this.#claimable;
60+
}
61+
62+
get claimed() {
63+
return !this.claimable && this.progress.complete;
64+
}
65+
66+
clone() {
67+
return new Quest(this);
68+
}
69+
70+
toJSON() {
71+
return {
72+
id: this.id,
73+
key: this.key,
74+
name: this.name,
75+
reward: this.reward,
76+
progress: this.progress,
77+
claimable: this.claimable,
78+
claimed: this.claimed,
79+
};
80+
}
81+
}
82+
83+
/**
84+
* @param {*} data
85+
* @returns {data is Quest}
86+
*/
87+
function isQuest(data) {
88+
return data instanceof Quest;
89+
}
90+
91+
export function getId(element) {
92+
return element.querySelector('[name="questId"]')?.value ?? element.querySelector('[data-i18n-custom^="quest"]')?.dataset.i18nCustom;
93+
}

0 commit comments

Comments
 (0)