diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 0ccac6db2..1971e5951 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -19,8 +19,8 @@ import { ContextMenuApi, Menu, NavigationRouter, RestAPI, useEffect, useState } import { JSX } from "react"; import { addIgnoredQuest, addRerenderCallback, autoFetchCompatible, fetchAndAlertQuests, maximumAutoFetchIntervalValue, minimumAutoFetchIntervalValue, questIsIgnored, removeIgnoredQuest, rerenderQuests, settings, startAutoFetchingQuests, stopAutoFetchingQuests, validateAndOverwriteIgnoredQuests } from "./settings"; -import { ExcludedQuestMap, GuildlessServerListItem, Quest, QuestIcon, QuestMap, QuestStatus, RGB } from "./utils/components"; -import { adjustRGB, decimalToRGB, fetchAndDispatchQuests, formatLowerBadge, getFormattedNow, getIgnoredQuestIDs, getQuestStatus, isDarkish, leftClick, middleClick, normalizeQuestName, q, QuestifyLogger, questPath, QuestsStore, refreshQuest, reportPlayGameQuestProgress, reportVideoQuestProgress, rightClick, setIgnoredQuestIDs, videoQuestLeeway, waitUntilEnrolled } from "./utils/misc"; +import { ExcludedQuestMap, GuildlessServerListItem, Quest, QuestIcon, QuestMap, QuestStatus, QuestTaskType, RGB } from "./utils/components"; +import { adjustRGB, decimalToRGB, fetchAndDispatchQuests, formatLowerBadge, getFormattedNow, getIgnoredQuestIDs, getQuestProgress, getQuestStatus, getQuestTarget, getQuestTask, isDarkish, leftClick, middleClick, normalizeQuestName, q, QuestifyLogger, questPath, QuestsStore, refreshQuest, reportPlayGameQuestProgress, reportVideoQuestProgress, rightClick, setIgnoredQuestIDs, videoQuestLeeway, waitUntilEnrolled } from "./utils/misc"; const QuestifyNative = VencordNative.pluginHelpers.Questify as PluginNative; const patchedMobileQuests = new Set(); @@ -206,8 +206,7 @@ function shouldHideBadgeOnUserProfiles(): boolean { function shouldHideQuestPopup(quest: Quest | null): boolean { const { disableQuestsPopupAboveAccountPanel, - disableQuestsEverything, - triggerQuestsRerender + disableQuestsEverything } = settings.use([ "disableQuestsPopupAboveAccountPanel", "disableQuestsEverything", @@ -291,8 +290,6 @@ function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any; export function getQuestTileClasses(originalClasses: string, quest: Quest, color: number | null | undefined, gradient: string | undefined): string { const { - ignoredQuestIDs, - ignoredQuestProfile, restyleQuestsUnclaimed, restyleQuestsClaimed, restyleQuestsIgnored, @@ -366,7 +363,7 @@ export function getQuestTileClasses(originalClasses: string, quest: Quest, color } function makeDesktopCompatible(quests: Quest[]): void { - const { makeMobileQuestsDesktopCompatible, triggerQuestsRerender } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]); + const { makeMobileQuestsDesktopCompatible } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]); if (makeMobileQuestsDesktopCompatible) { quests.forEach(quest => { @@ -378,7 +375,7 @@ function makeDesktopCompatible(quests: Quest[]): void { tasks.WATCH_VIDEO = { ...tasks.WATCH_VIDEO_ON_MOBILE, - type: "WATCH_VIDEO" + type: QuestTaskType.WATCH_VIDEO }; } }); @@ -398,17 +395,11 @@ function makeDesktopCompatible(quests: Quest[]): void { function sortQuests(quests: Quest[], skip?: boolean): Quest[] { const { - ignoredQuestIDs, - ignoredQuestProfile, reorderQuests, unclaimedSubsort, claimedSubsort, ignoredSubsort, expiredSubsort, - completeVideoQuestsInBackground, - completeGameQuestsInBackground, - completeAchievementQuestsInBackground, - triggerQuestsRerender } = settings.use([ "ignoredQuestIDs", "ignoredQuestProfile", @@ -510,11 +501,7 @@ function sortQuests(quests: Quest[], skip?: boolean): Quest[] { } export function getQuestTileStyle(quest: Quest | null): Record { - const { - restyleQuests, - ignoredQuestIDs, - ignoredQuestProfile - } = settings.use([ + settings.use([ "restyleQuests", "ignoredQuestIDs", "ignoredQuestProfile" @@ -684,8 +671,8 @@ async function startVideoProgressTracking(quest: Quest, questTarget: number): Pr async function startPlayGameProgressTracking(quest: Quest, questTarget: number): Promise { const questName = normalizeQuestName(quest.config.messages.questName); const questEnrolledAt = quest.userStatus?.enrolledAt ? new Date(quest.userStatus.enrolledAt) : null; - const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY; - const initialProgress = quest.userStatus?.progress?.[playType?.type || ""]?.value || 0; + const task = getQuestTask(quest); + const initialProgress = getQuestProgress(quest, task) || 0; const remaining = Math.max(0, questTarget - initialProgress); const heartbeatInterval = 20; // Heartbeats must be at most 2 minutes apart. activeQuestIntervals.set(quest.id, { progressTimeout: null as any, rerenderTimeout: null as any, progress: initialProgress, type: "play" }); @@ -872,19 +859,23 @@ async function startAchievementActivityProgressTracking(quest: Quest, questTarge function processQuestForAutoComplete(quest: Quest): boolean { const questName = normalizeQuestName(quest.config.messages.questName); - const { completeVideoQuestsInBackground, completeGameQuestsInBackground, completeAchievementQuestsInBackground, makeMobileQuestsDesktopCompatible } = settings.store; - const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY; - const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO; - const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY; - const questTarget = playType?.target || watchType?.target || achievementType?.target || 0; + const { completeVideoQuestsInBackground, completeGameQuestsInBackground, completeAchievementQuestsInBackground } = settings.store; + + const task = getQuestTask(quest); + const questTarget = getQuestTarget(task); const existingInterval = activeQuestIntervals.get(quest.id); - const watchTypeIncompatible = watchType && !completeVideoQuestsInBackground; - const playTypeIncompatible = playType && (!completeGameQuestsInBackground || !IS_DISCORD_DESKTOP); - const achievementTypeIncompatible = achievementType && (!completeAchievementQuestsInBackground || !IS_DISCORD_DESKTOP); + + const isWatch = task?.type === QuestTaskType.WATCH_VIDEO || task?.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE; + const isPlay = task?.type === QuestTaskType.PLAY_ON_DESKTOP || task?.type === QuestTaskType.PLAY_ON_XBOX || task?.type === QuestTaskType.PLAY_ON_PLAYSTATION || task?.type === QuestTaskType.PLAY_ACTIVITY; + const isAchievement = task?.type === QuestTaskType.ACHIEVEMENT_IN_ACTIVITY; + + const watchTypeIncompatible = isWatch && !completeVideoQuestsInBackground; + const playTypeIncompatible = isPlay && (!completeGameQuestsInBackground || !IS_DISCORD_DESKTOP); + const achievementTypeIncompatible = isAchievement && (!completeAchievementQuestsInBackground || !IS_DISCORD_DESKTOP); if (quest.userStatus?.completedAt || existingInterval) { return false; - } else if (!playType && !watchType && !achievementType) { + } else if (!task) { QuestifyLogger.warn(`[${getFormattedNow()}] Could not recognize the Quest type for ${questName}.`); return false; } else if (watchTypeIncompatible || playTypeIncompatible || achievementTypeIncompatible) { @@ -892,13 +883,13 @@ function processQuestForAutoComplete(quest: Quest): boolean { } else if (!questTarget) { QuestifyLogger.warn(`[${getFormattedNow()}] Could not find target for Quest ${questName}.`); return false; - } else if (watchType) { + } else if (isWatch) { startVideoProgressTracking(quest, questTarget); return true; - } else if (playType) { + } else if (isPlay) { startPlayGameProgressTracking(quest, questTarget); return true; - } else if (achievementType) { + } else if (isAchievement) { startAchievementActivityProgressTracking(quest, questTarget); return true; } @@ -921,17 +912,20 @@ function shouldDisableQuestAcceptedButton(quest: Quest): boolean | null { } function getQuestUnacceptedButtonText(quest: Quest): string | null { - const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground, makeMobileQuestsDesktopCompatible } = settings.store; - const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY; - const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO; - const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY; - const target = (playType?.target || (watchType?.target ? watchType.target - videoQuestLeeway : undefined) || achievementType?.target || 0); + const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground } = settings.store; + + const task = getQuestTask(quest); + const target = getQuestTarget(task); const targetFormatted = `${String(Math.floor(target / 60)).padStart(2, "0")}:${String(target % 60).padStart(2, "0")}`; + const isWatch = task?.type === QuestTaskType.WATCH_VIDEO || task?.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE; + const isPlay = task?.type === QuestTaskType.PLAY_ON_DESKTOP || task?.type === QuestTaskType.PLAY_ON_XBOX || task?.type === QuestTaskType.PLAY_ON_PLAYSTATION || task?.type === QuestTaskType.PLAY_ACTIVITY; + const isAchievement = task?.type === QuestTaskType.ACHIEVEMENT_IN_ACTIVITY; + if (target > 0) { - if ((playType && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (watchType && completeVideoQuestsInBackground)) { + if ((isPlay && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (isWatch && completeVideoQuestsInBackground)) { return `Complete (${targetFormatted})`; - } else if (achievementType && completeAchievementQuestsInBackground) { + } else if (isAchievement && completeAchievementQuestsInBackground) { return "Complete (Immediate)"; } } @@ -939,30 +933,36 @@ function getQuestUnacceptedButtonText(quest: Quest): string | null { return null; } -function getQuestAcceptedButtonText(quest: Quest): string | null { - const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground, makeMobileQuestsDesktopCompatible } = settings.store; +function getQuestAcceptedButtonText(quest: Quest, prepositional: boolean = false): string | null { + const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground } = settings.store; const questEnrolledAt = quest.userStatus?.enrolledAt ? new Date(quest.userStatus.enrolledAt) : null; - const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY; - const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO; - const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY; + const task = getQuestTask(quest); const intervalData = activeQuestIntervals.get(quest.id); + const isWatch = task?.type === QuestTaskType.WATCH_VIDEO || task?.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE; + const isPlay = task?.type === QuestTaskType.PLAY_ON_DESKTOP || task?.type === QuestTaskType.PLAY_ON_XBOX || task?.type === QuestTaskType.PLAY_ON_PLAYSTATION || task?.type === QuestTaskType.PLAY_ACTIVITY; + const isAchievement = task?.type === QuestTaskType.ACHIEVEMENT_IN_ACTIVITY; + if (questEnrolledAt) { - if (((playType && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (watchType && completeVideoQuestsInBackground))) { - const taskType = playType || watchType; - const duration = taskType?.target || 0; - const durationWithLeeway = watchType ? duration - videoQuestLeeway : duration; - const progress = Math.min((intervalData?.progress ?? (quest.userStatus?.progress?.[taskType?.type || ""]?.value || 0)), durationWithLeeway); + if (((isPlay && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (isWatch && completeVideoQuestsInBackground))) { + const durationWithLeeway = getQuestTarget(task); + const currentProgress = getQuestProgress(quest, task) || 0; + const progress = Math.min((intervalData?.progress ?? currentProgress), durationWithLeeway); const timeRemaining = Math.max(0, durationWithLeeway - progress); - const canCompleteImmediately = watchType && questEnrolledAt && ((new Date().getTime() - questEnrolledAt.getTime()) / 1000) >= durationWithLeeway; + const canCompleteImmediately = isWatch && questEnrolledAt && ((new Date().getTime() - questEnrolledAt.getTime()) / 1000) >= durationWithLeeway; const progressFormatted = `${String(Math.floor(timeRemaining / 60)).padStart(2, "0")}:${String(timeRemaining % 60).padStart(2, "0")}`; + const progressFormattedAsPreposition = timeRemaining >= 60 + ? `${Math.floor(timeRemaining / 60)}m ${timeRemaining % 60}s` + : `${timeRemaining % 60}s`; if (!!intervalData) { - return !canCompleteImmediately && timeRemaining ? `Completing (${progressFormatted})` : "Completing..."; - } else if (watchType || playType) { + return canCompleteImmediately || !timeRemaining + ? "Completing..." + : `Completing ${prepositional ? `in ${progressFormattedAsPreposition}` : `(${progressFormatted})`}`; + } else if (isWatch || isPlay) { return canCompleteImmediately ? "Complete (Immediate)" : timeRemaining === durationWithLeeway ? `Complete (${progressFormatted})` : `Resume (${progressFormatted})`; } - } else if (achievementType && completeAchievementQuestsInBackground) { + } else if (isAchievement && completeAchievementQuestsInBackground) { if (!!intervalData) { return "Completing..."; } else { @@ -974,8 +974,56 @@ function getQuestAcceptedButtonText(quest: Quest): string | null { return null; } +function getQuestPanelPercentComplete({ quest, percentCompleteText }: { quest: Quest; percentCompleteText?: string; }): { percentComplete: number; } | { percentComplete: number; percentCompleteText: string; } | null { + const task = getQuestTask(quest); + + if (!task) { return null; } + + const questTarget = getQuestTarget(task); + const questProgress = getQuestProgress(quest, task); + + if (!questTarget || questProgress === null) { return null; } + + const decimal = Math.min(1, questProgress / questTarget); + + return percentCompleteText ? { + percentComplete: decimal, + percentCompleteText: `${Math.floor(decimal * 100)}%` + } : { + percentComplete: decimal + }; +} + +function getQuestPanelSubtitleText(quest: Quest): string | null { + const intervalData = activeQuestIntervals.get(quest.id); + + if (!intervalData) { + return null; + } + + const rewardItem = quest.config.rewardsConfig.rewards[0] || null; + const completingText = getQuestAcceptedButtonText(quest, true) || "Completing"; + + if (rewardItem.orbQuantity) { + return `${completingText} for ${rewardItem.orbQuantity} Orbs.`; + } else if (rewardItem.messages?.nameWithArticle) { + return `${completingText} for ${rewardItem.messages.nameWithArticle}.`; + } else { + return `${completingText} for an unrecognized reward.`; + } +} + +function getQuestPanelTitleText(quest: Quest): string | null { + const intervalData = activeQuestIntervals.get(quest.id); + + if (!intervalData) { + return null; + } + + return normalizeQuestName(quest.config.messages.questName); +} + function getQuestPanelOverride(): Quest | null { - const { makeMobileQuestsDesktopCompatible } = settings.store; let closestQuest: Quest | null = null; let closestTimeRemaining = Infinity; @@ -986,10 +1034,8 @@ function getQuestPanelOverride(): Quest | null { return; } - const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY; - const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO; - const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY; - const duration = playType?.target || (watchType?.target ? watchType.target - videoQuestLeeway : undefined) || achievementType?.target || 0; + const task = getQuestTask(quest); + const duration = getQuestTarget(task); if (!duration) { return; @@ -1037,7 +1083,7 @@ function disguiseHomeButton(location: string): boolean { } function useQuestRerender(): number { - const { triggerQuestsRerender } = settings.use(["triggerQuestsRerender"]); + settings.use(["triggerQuestsRerender"]); const [renderTrigger, setRenderTrigger] = useState(0); useEffect(() => addRerenderCallback(() => setRenderTrigger(prev => prev + 1)), []); return renderTrigger; @@ -1064,17 +1110,17 @@ function setLastFilterChoices(filters: { group: string; filter: string; }[]): vo function getQuestAcceptedButtonProps(quest: Quest, text: string, disabled: boolean, onClick?: () => void) { const validTasks = [ - "WATCH_VIDEO", - "WATCH_VIDEO_ON_MOBILE", - "PLAY_ON_DESKTOP", - "PLAY_ON_XBOX", - "PLAY_ON_PLAYSTATION", - "PLAY_ACTIVITY", - "ACHIEVEMENT_IN_ACTIVITY" + QuestTaskType.WATCH_VIDEO, + QuestTaskType.WATCH_VIDEO_ON_MOBILE, + QuestTaskType.PLAY_ON_DESKTOP, + QuestTaskType.PLAY_ON_XBOX, + QuestTaskType.PLAY_ON_PLAYSTATION, + QuestTaskType.PLAY_ACTIVITY, + QuestTaskType.ACHIEVEMENT_IN_ACTIVITY ]; const validTask = Array.from(validTasks).some(taskType => Object.values(quest.config.taskConfigV2?.tasks || {}).some( - task => task.type === taskType && (task.type !== "WATCH_VIDEO_ON_MOBILE" || settings.store.makeMobileQuestsDesktopCompatible) + task => task.type === taskType && (task.type !== QuestTaskType.WATCH_VIDEO_ON_MOBILE || settings.store.makeMobileQuestsDesktopCompatible) )); if (!validTask) { @@ -1121,9 +1167,12 @@ export default definePlugin({ shouldHideFriendsListActiveNowPromotion, shouldHideMembersListActivelyPlayingIcon, getQuestUnacceptedButtonText, + getQuestPanelPercentComplete, processQuestForAutoComplete, getQuestAcceptedButtonProps, getQuestAcceptedButtonText, + getQuestPanelSubtitleText, + getQuestPanelTitleText, getQuestPanelOverride, setLastFilterChoices, getLastFilterChoices, @@ -1138,8 +1187,8 @@ export default definePlugin({ // Hides the notice in the gift inventory that Quests have been relocated to the Discovery tab. find: "quests-wumpus-hikes-mountain-transparent-background", replacement: { - match: /return(\(0,\i.\i\)\("div",{className:)/, - replace: "return $self.shouldHideGiftInventoryRelocationNotice()?null:$1" + match: /return(?=\(0,\i.\i\)\("div",{className:)/, + replace: "return $self.shouldHideGiftInventoryRelocationNotice()?null:" } }, { @@ -1217,6 +1266,23 @@ export default definePlugin({ replace: "return !$self.shouldHideQuestPopup($1)&&(" } }, + { + // Fixes the progress tracking for auto-completing Quests. + find: ",{progressTextAnimation:", + replacement: { + match: /(?<=children:\i}=)(\i)/, + replace: "Object.assign({},$1,$self.getQuestPanelPercentComplete($1))" + } + }, + { + // Overrides the title and subtitle to provide more + // useful information for Quests being auto-completed. + find: '"progress-title"', + replacement: { + match: /(?<=return.{0,150}?quest:(\i),percentComplete:\i.{0,280}?"progress-title",children.{0,115}?children:)(\i.{0,50}"progress-subtitle",isTextTransition:!0,children.{0,115}?children:)/, + replace: "$self.getQuestPanelTitleText($1)??$2$self.getQuestPanelSubtitleText($1)??" + } + }, { // Replaces the default displayed Quest with the soonest to // be completed Quest which is actively being auto-completed. @@ -1265,8 +1331,8 @@ export default definePlugin({ replacement: [ { // Extracts the custom dropdown prop before the variable is overwritten. - match: /(\((\i),\i\){)(var{options:\i,)/, - replace: "$1const vcDynamicDropdownFeedback=$2.feedback;$3" + match: /(?<=forwardRef\(function\((\i),\i\){)/, + replace: "const vcDynamicDropdownFeedback=$1.feedback;" }, { // Passes the custom prop to the dropdown's invalid handler. @@ -1287,7 +1353,7 @@ export default definePlugin({ replacement: [ { // Extracts the custom maxDigits prop. - match: /(=>{var{count:\i,)/, + match: /(=>{let{count:\i,)/, replace: "$1maxDigits," }, { @@ -1310,8 +1376,8 @@ export default definePlugin({ replacement: [ { // Restyles Quest tiles with colors. - match: /className:(\i\(\)\(\i.container,\i\)),/, - replace: "className:$self.getQuestTileClasses($1,arguments[0].quest),style:$self.getQuestTileStyle(arguments[0].quest)," + match: /(?<=\i.current=\i},className:)(\i\(\)\(\i.\i,\i\)),/, + replace: "$self.getQuestTileClasses($1,arguments[0].quest),style:$self.getQuestTileStyle(arguments[0].quest)," }, { // Encourages banners to load quicker if the setting is enabled. @@ -1383,7 +1449,7 @@ export default definePlugin({ { // Loads the last used sort method and filter choices. // Defaults to sorting by "Questify" and no filters. - find: "headingControls,children:", + find: "({resetSortingFiltering:()", group: true, replacement: [ { @@ -1417,7 +1483,7 @@ export default definePlugin({ // Whether preloading assets is enabled or not, the placeholders loading // before the assets causes a lot of element shifting, whereas if // the elements load immediately instead, it doesn't. - find: "rewardDescriptionContainer,children", + find: ",{rewardWithArticleHook:()", replacement: { match: /showPlaceholder:!\i/, replace: "showPlaceholder:false" @@ -1430,7 +1496,7 @@ export default definePlugin({ replacement: [ { // Resume Video Quest. - match: /(onClick:\(\)=>)(\(0,\i.openVideoQuestModal\)\({quest:(\i))/, + match: /(onClick:\(\)=>)(\(0,\i.\i\)\({quest:(\i))/, replace: "$1!$self.processQuestForAutoComplete($3)&&$2" }, { @@ -1473,7 +1539,7 @@ export default definePlugin({ { // Sets intervals to progress Play Game Quests in the background. // Triggers if a Quest has already been started but was interrupted, such as by a reload. - find: "platformSelectorPrimary,", + find: "WATCH_VIDEO,skipEnrollmentCheck:!0})", group: true, replacement: [ { diff --git a/src/equicordplugins/questify/settings.tsx b/src/equicordplugins/questify/settings.tsx index 06a4666aa..e64dbfdc1 100644 --- a/src/equicordplugins/questify/settings.tsx +++ b/src/equicordplugins/questify/settings.tsx @@ -364,7 +364,6 @@ function validateQuestButtonSetting() { function validateDisableQuestSetting() { const { - disableQuestsEverything, disableQuestsDiscoveryTab, disableQuestsFetchingQuests, disableQuestsDirectMessagesTab, diff --git a/src/equicordplugins/questify/utils/components.tsx b/src/equicordplugins/questify/utils/components.tsx index e885807a4..922335112 100644 --- a/src/equicordplugins/questify/utils/components.tsx +++ b/src/equicordplugins/questify/utils/components.tsx @@ -72,6 +72,21 @@ export interface ExcludedQuest { id: string; } +export enum QuestTaskType { + WATCH_VIDEO = "WATCH_VIDEO", + WATCH_VIDEO_ON_MOBILE = "WATCH_VIDEO_ON_MOBILE", + PLAY_ON_DESKTOP = "PLAY_ON_DESKTOP", + PLAY_ON_XBOX = "PLAY_ON_XBOX", + PLAY_ON_PLAYSTATION = "PLAY_ON_PLAYSTATION", + PLAY_ACTIVITY = "PLAY_ACTIVITY", + ACHIEVEMENT_IN_ACTIVITY = "ACHIEVEMENT_IN_ACTIVITY" +} + +export interface QuestTask { + type: QuestTaskType; + target: number; +} + export interface Quest { questifyNumber: number; id: string; @@ -88,36 +103,40 @@ export interface Quest { rewardsConfig: { rewards: { type: QuestRewardType; + orbQuantity?: number; + messages?: { + nameWithArticle: string; + }; }[]; }, taskConfigV2: { tasks: { WATCH_VIDEO?: { - type: "WATCH_VIDEO"; + type: QuestTaskType.WATCH_VIDEO; target: number; }, WATCH_VIDEO_ON_MOBILE?: { - type: "WATCH_VIDEO_ON_MOBILE"; + type: QuestTaskType.WATCH_VIDEO_ON_MOBILE; target: number; }, PLAY_ON_DESKTOP?: { - type: "PLAY_ON_DESKTOP"; + type: QuestTaskType.PLAY_ON_DESKTOP; target: number; }, PLAY_ON_XBOX?: { - type: "PLAY_ON_XBOX"; + type: QuestTaskType.PLAY_ON_XBOX; target: number; }, PLAY_ON_PLAYSTATION?: { - type: "PLAY_ON_PLAYSTATION"; + type: QuestTaskType.PLAY_ON_PLAYSTATION; target: number; }, PLAY_ACTIVITY?: { - type: "PLAY_ACTIVITY"; + type: QuestTaskType.PLAY_ACTIVITY; target: number; }; ACHIEVEMENT_IN_ACTIVITY?: { - type: "ACHIEVEMENT_IN_ACTIVITY"; + type: QuestTaskType.ACHIEVEMENT_IN_ACTIVITY; target: number; eventName: "progress"; applications: { @@ -134,27 +153,27 @@ export interface Quest { enrolledAt: string | null; progress: null | { WATCH_VIDEO?: { - eventName: "WATCH_VIDEO"; + eventName: QuestTaskType.WATCH_VIDEO; value: number; }, WATCH_VIDEO_ON_MOBILE?: { - eventName: "WATCH_VIDEO_ON_MOBILE"; + eventName: QuestTaskType.WATCH_VIDEO_ON_MOBILE; value: number; }, PLAY_ON_DESKTOP?: { - eventName: "PLAY_ON_DESKTOP"; + eventName: QuestTaskType.PLAY_ON_DESKTOP; value: number; }, PLAY_ON_XBOX?: { - eventName: "PLAY_ON_XBOX"; + eventName: QuestTaskType.PLAY_ON_XBOX; value: number; }, PLAY_ON_PLAYSTATION?: { - eventName: "PLAY_ON_PLAYSTATION"; + eventName: QuestTaskType.PLAY_ON_PLAYSTATION; value: number; }, PLAY_ACTIVITY?: { - eventName: "PLAY_ACTIVITY"; + eventName: QuestTaskType.PLAY_ACTIVITY; value: number; }, }; diff --git a/src/equicordplugins/questify/utils/misc.tsx b/src/equicordplugins/questify/utils/misc.tsx index 17a0811fd..917e11ba1 100644 --- a/src/equicordplugins/questify/utils/misc.tsx +++ b/src/equicordplugins/questify/utils/misc.tsx @@ -4,13 +4,14 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { questIsIgnored, settings } from "@equicordplugins/questify/settings"; import { classNameFactory } from "@utils/css"; import { Logger } from "@utils/Logger"; import { findByPropsLazy } from "@webpack"; import { FluxDispatcher, RestAPI, UserStore } from "@webpack/common"; -import { Quest, QuestStatus, RGB } from "./components"; +import { activeQuestIntervals } from ".."; +import { questIsIgnored, settings } from "../settings"; +import { Quest, QuestStatus, QuestTask, QuestTaskType, RGB } from "./components"; export const q = classNameFactory("questify-"); export const QuestifyLogger = new Logger("Questify"); @@ -38,6 +39,46 @@ export function getIgnoredQuestIDs(userId?: string): string[] { return ignoredQuestIDs[key]; } +export function getQuestTask(quest: Quest): QuestTask { + const tasks = quest.config.taskConfigV2?.tasks; + + return (tasks.PLAY_ON_DESKTOP || + tasks.PLAY_ON_XBOX || + tasks.PLAY_ON_PLAYSTATION || + tasks.PLAY_ACTIVITY || + tasks.WATCH_VIDEO || + tasks.WATCH_VIDEO_ON_MOBILE || + tasks.ACHIEVEMENT_IN_ACTIVITY) as QuestTask; +} + +export function getQuestProgress(quest: Quest, task: QuestTask) { + const intervalData = activeQuestIntervals.get(quest.id); + + if (intervalData) { return intervalData.progress; } + + const progressMap = quest.userStatus?.progress; + + if (!progressMap) { return null; } + + if (task.type === QuestTaskType.WATCH_VIDEO || task.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE) { + const watchProgress = progressMap.WATCH_VIDEO?.value; + const mobileProgress = progressMap.WATCH_VIDEO_ON_MOBILE?.value; + + if (watchProgress !== undefined || mobileProgress !== undefined) { + return Math.max(watchProgress ?? 0, mobileProgress ?? 0); + } + + return null; + } + + return progressMap[task.type]?.value ?? null; +} + +export function getQuestTarget(task: QuestTask) { + const isWatch = task.type === QuestTaskType.WATCH_VIDEO || task.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE; + return task.target - (isWatch ? videoQuestLeeway : 0); +} + export function getQuestStatus(quest: Quest, checkIgnored: boolean = true): QuestStatus { const completedQuest = quest.userStatus?.completedAt; const claimedQuest = quest.userStatus?.claimedAt; diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 13988b913..fc1fbc572 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -959,7 +959,7 @@ export default definePlugin({ }, { // Replace names in profile popouts. - find: "clickableUsername,\"aria-label\":", + find: "shouldWrap:!0,loop:!0,inProfile:!0", replacement: { match: /(tags:\i,)nickname:(\i)/, replace: "$1showMeYourNameNickname:$2=$self.getMemberListProfilesReactionsVoiceNameText({...arguments[0],type:\"profilesPopout\"})??(arguments[0].nickname)" @@ -968,7 +968,7 @@ export default definePlugin({ { // Replace names in the profile tooltip for switching between guild and global profiles. // You must open a profile modal before the code this is patching will be searchable. - find: "\"view-main-profile\",", + find: 'id:"view-server-profile",', group: true, replacement: [ { @@ -990,18 +990,18 @@ export default definePlugin({ } }, { - find: "discriminator,forceUsername", + find: "MESSAGE,userId:", group: true, replacement: [ { - // Replace names in reaction popouts. - match: /(?<=className:\i.reactorInfo,children:)(\[.{0,450}?}\)\])/, - replace: "($self.getMemberListProfilesReactionsVoiceNameElement({user:arguments[0].user,guildId:arguments[0].guildId,type:\"reactionsPopout\"}))??($1)" + // Track hovering over reaction popouts. + match: /(?<=return\(0,\i.\i\)\(\i.\i,{className:\i.\i,)(?=onContextMenu:\i=>)/, + replace: "onMouseEnter:()=>{$self.addHoveringReactionPopout(arguments[0].user.id)},onMouseLeave:()=>{$self.removeHoveringReactionPopout(arguments[0].user.id)},$2" }, { - // Track hovering over reaction popouts. - match: /(return\(0,\i.\i\)\(\i.\i,{className:\i.reactor,)(onContextMenu)/, - replace: "$1onMouseEnter:()=>{$self.addHoveringReactionPopout(arguments[0].user.id)},onMouseLeave:()=>{$self.removeHoveringReactionPopout(arguments[0].user.id)},$2" + // Replace names in reaction popouts. + match: /(?<=Child,{className:\i.\i,children:)/, + replace: "($self.getMemberListProfilesReactionsVoiceNameElement({user:arguments[0].user,guildId:arguments[0].guildId,type:\"reactionsPopout\"}))??" } ] }, diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index 25171131a..e1514c489 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -328,8 +328,8 @@ export default definePlugin({ replace: "const silentTypingShouldHideChatBarTypingIndicators=$self.shouldHideChatBarTypingIndicators();$1" }, { - match: /(typingDots,ref:\i,children:)(\[.{0,340}?}\)\])/, - replace: "$1silentTypingShouldHideChatBarTypingIndicators?[]:$2" + match: /("stop-animation".{0,80}?ref:\i,children:)/, + replace: "$1silentTypingShouldHideChatBarTypingIndicators?[]:" } ] },