diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index fbfe4835..0f907ced 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -315,5 +315,8 @@ "report_reason_other": "Other", "profile_reported": "Profile reported", "your_friend_code": "Your friend code:" + }, + "achievement": { + "achievement_unlocked": "Achievement unlocked" } } diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index fcb18659..ca247eda 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -317,5 +317,8 @@ "report_reason_other": "Outro", "profile_reported": "Perfil reportado", "your_friend_code": "Seu código de amigo:" + }, + "achievement": { + "achievement_unlocked": "Conquista desbloqueada" } } diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 85c1a3c5..5a659945 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -277,5 +277,8 @@ "friend_code_copied": "Código de amigo copiado", "image_process_failure": "Falha ao processar a imagem", "your_friend_code": "Seu código de amigo:" + }, + "achievement": { + "achievement_unlocked": "Conquista desbloqueada" } } diff --git a/src/main/events/catalogue/get-game-achievements.ts b/src/main/events/catalogue/get-game-achievements.ts index ee882fd6..b2d8f103 100644 --- a/src/main/events/catalogue/get-game-achievements.ts +++ b/src/main/events/catalogue/get-game-achievements.ts @@ -7,23 +7,18 @@ import { userPreferencesRepository, } from "@main/repository"; import { UserNotLoggedInError } from "@shared"; +import { Game } from "@main/entity"; -const getGameAchievements = async ( - _event: Electron.IpcMainInvokeEvent, +const getAchievementsDataFromApi = async ( objectId: string, - shop: GameShop -): Promise => { - const [game, cachedAchievements, userPreferences] = await Promise.all([ - gameRepository.findOne({ - where: { objectID: objectId, shop }, - }), - gameAchievementRepository.findOne({ where: { objectId, shop } }), - userPreferencesRepository.findOne({ - where: { id: 1 }, - }), - ]); + shop: string, + game: Game | null +) => { + const userPreferences = await userPreferencesRepository.findOne({ + where: { id: 1 }, + }); - const apiAchievement = HydraApi.get("/games/achievements", { + return HydraApi.get("/games/achievements", { objectId, shop, language: userPreferences?.language || "en", @@ -46,10 +41,23 @@ const getGameAchievements = async ( if (err instanceof UserNotLoggedInError) throw err; return []; }); +}; + +const getGameAchievements = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop +): Promise => { + const [game, cachedAchievements] = await Promise.all([ + gameRepository.findOne({ + where: { objectID: objectId, shop }, + }), + gameAchievementRepository.findOne({ where: { objectId, shop } }), + ]); const gameAchievements = cachedAchievements?.achievements ? JSON.parse(cachedAchievements.achievements) - : await apiAchievement; + : await getAchievementsDataFromApi(objectId, shop, game); const unlockedAchievements = JSON.parse( cachedAchievements?.unlockedAchievements || "[]" diff --git a/src/main/events/catalogue/update-game-unlocked-achievements.ts b/src/main/events/catalogue/update-game-unlocked-achievements.ts new file mode 100644 index 00000000..f5e435b5 --- /dev/null +++ b/src/main/events/catalogue/update-game-unlocked-achievements.ts @@ -0,0 +1,11 @@ +import { registerEvent } from "../register-event"; +import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements"; + +const updateGameUnlockedAchievements = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string +) => { + return updateLocalUnlockedAchivements(false, objectId); +}; + +registerEvent("updateGameUnlockedAchievements", updateGameUnlockedAchievements); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 22eb341f..11861116 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -10,6 +10,7 @@ import "./catalogue/search-games"; import "./catalogue/get-game-stats"; import "./catalogue/get-trending-games"; import "./catalogue/get-game-achievements"; +import "./catalogue/update-game-unlocked-achievements"; import "./hardware/get-disk-free-space"; import "./library/add-game-to-library"; import "./library/create-game-shortcut"; diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 7dad6f9c..2ba251c2 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -62,29 +62,35 @@ export const mergeAchievements = async ( }; }); - if (newAchievements.length) { - const achievement = newAchievements.at(-1)!; - const achievementInfo = JSON.parse( - localGameAchievement?.achievements || "[]" - ).find((steamAchievement) => { - return achievement.name === steamAchievement.name; - }); + if (newAchievements.length && publishNotification) { + const achievementsInfo = newAchievements + .map((achievement) => { + return JSON.parse(localGameAchievement?.achievements || "[]").find( + (steamAchievement) => { + return achievement.name === steamAchievement.name; + } + ); + }) + .filter((achievement) => achievement) + .map((achievement) => { + return { + displayName: achievement.displayName, + iconUrl: achievement.icon, + }; + }); - if (publishNotification) { - WindowManager.notificationWindow?.webContents.send( - "on-achievement-unlocked", - objectId, - shop, - achievementInfo.displayName, - achievementInfo.icon - ); + WindowManager.notificationWindow?.webContents.send( + "on-achievement-unlocked", + objectId, + shop, + achievementsInfo + ); - WindowManager.notificationWindow?.setBounds({ y: 50 }); + WindowManager.notificationWindow?.setBounds({ y: 50 }); - setTimeout(() => { - WindowManager.notificationWindow?.setBounds({ y: -9999 }); - }, 4000); - } + setTimeout(() => { + WindowManager.notificationWindow?.setBounds({ y: -9999 }); + }, 4000); } const mergedLocalAchievements = unlockedAchievements.concat(newAchievements); diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index a44ea30b..162a3282 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -29,12 +29,12 @@ export const watchProcesses = async () => { if (games.length === 0) return; const processes = await PythonInstance.getProcessList(); + const processSet = new Set(processes.map((process) => process.exe)); + for (const game of games) { const executablePath = game.executablePath!; - const gameProcess = processes.find((runningProcess) => { - return executablePath == runningProcess.exe; - }); + const gameProcess = processSet.has(executablePath); if (gameProcess) { if (gamesPlaytime.has(game.id)) { diff --git a/src/preload/index.ts b/src/preload/index.ts index 91cfc2f7..9e847b28 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -51,21 +51,21 @@ contextBridge.exposeInMainWorld("electron", { getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), getGameAchievements: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameAchievements", objectId, shop), + updateGameUnlockedAchievements: (objectId: string) => + ipcRenderer.invoke("updateGameUnlockedAchievements", objectId), onAchievementUnlocked: ( cb: ( objectId: string, shop: GameShop, - displayName: string, - iconUrl: string + achievements?: { displayName: string; iconUrl: string }[] ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, objectId: string, shop: GameShop, - displayName: string, - iconUrl: string - ) => cb(objectId, shop, displayName, iconUrl); + achievements?: { displayName: string; iconUrl: string }[] + ) => cb(objectId, shop, achievements); ipcRenderer.on("on-achievement-unlocked", listener); return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); diff --git a/src/renderer/src/assets/audio/achievement.wav b/src/renderer/src/assets/audio/achievement.wav new file mode 100644 index 00000000..adf40d74 Binary files /dev/null and b/src/renderer/src/assets/audio/achievement.wav differ diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 883a80a5..393dcf17 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -179,6 +179,8 @@ export function GameDetailsContextProvider({ }, [game?.id, isGameRunning, updateGame]); useEffect(() => { + window.electron.updateGameUnlockedAchievements(objectID!).catch(() => {}); + const unsubscribe = window.electron.onAchievementUnlocked( (objectId, shop) => { if (objectID !== objectId || shop !== shop) return; diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 71909d41..e24567e8 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -70,12 +70,12 @@ declare global { objectId: string, shop: GameShop ) => Promise; + updateGameUnlockedAchievements: (objectId: string) => Promise; onAchievementUnlocked: ( cb: ( objectId: string, shop: GameShop, - displayName: string, - iconUrl: string + achievements?: { displayName: string; iconUrl: string }[] ) => void ) => () => Electron.IpcRenderer; diff --git a/src/renderer/src/pages/achievement/achievement.tsx b/src/renderer/src/pages/achievement/achievement.tsx index efb1374f..7bc625d3 100644 --- a/src/renderer/src/pages/achievement/achievement.tsx +++ b/src/renderer/src/pages/achievement/achievement.tsx @@ -1,27 +1,34 @@ import { useEffect, useMemo, useState } from "react"; +import achievementSound from "@renderer/assets/audio/achievement.wav"; +import { useTranslation } from "react-i18next"; export function Achievemnt() { + const { t } = useTranslation("achievement"); + const [achievementInfo, setAchievementInfo] = useState<{ displayName: string; icon: string; } | null>(null); const audio = useMemo(() => { - const audio = new Audio( - "https://cms-public-artifacts.artlist.io/content/sfx/aac/94201_690187_Classics_-_Achievement_Unlocked_-_MASTERED_-_2496.aac" - ); - + const audio = new Audio(achievementSound); + audio.volume = 0.2; audio.preload = "auto"; return audio; }, []); useEffect(() => { const unsubscribe = window.electron.onAchievementUnlocked( - (_object, _shop, displayName, icon) => { - setAchievementInfo({ - displayName, - icon, - }); + (_object, _shop, achievements) => { + if (!achievements) return; + + if (achievements.length) { + const achievement = achievements[0]; + setAchievementInfo({ + displayName: achievement.displayName, + icon: achievement.iconUrl, + }); + } audio.play(); } @@ -49,7 +56,7 @@ export function Achievemnt() { style={{ width: 60, height: 60 }} />
-

Achievement unlocked

+

{t("achievement_unlocked")}

{achievementInfo.displayName}