From 085e5f20a90bfbfe2bade2dcd39887fe3adb9e30 Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Mon, 16 Mar 2026 12:37:24 +0100 Subject: [PATCH] feat(achievements): remove secret achievements and add new ones. also play sound only when notification comes up instead of instantly --- app/components/Achievements/Notification.vue | 30 ++++++++----- .../Contact/BottomScreen/Buttons.vue | 2 + .../BottomScreen/Menus/User/Color.vue | 7 --- app/pages/index.vue | 2 +- app/stores/achievements.ts | 44 ++++++++----------- app/stores/contact.ts | 4 -- i18n/locales/en.json | 4 +- i18n/locales/fr.json | 4 +- 8 files changed, 46 insertions(+), 51 deletions(-) diff --git a/app/components/Achievements/Notification.vue b/app/components/Achievements/Notification.vue index c4066b0..5cc6a8d 100644 --- a/app/components/Achievements/Notification.vue +++ b/app/components/Achievements/Notification.vue @@ -6,6 +6,8 @@ const { onRender } = useScreen(); const achievements = useAchievementsStore(); const { assets } = useAssets(); +const confetti = useConfetti(); + const queue = ref([]); const currentAchievement = ref(null); const x = ref(LOGICAL_WIDTH); @@ -20,16 +22,16 @@ const NOTIF_X_VISIBLE = LOGICAL_WIDTH - NOTIF_WIDTH; const TEXT_X_OFFSET = LOGO_SIZE + PADDING * 2; const LINE_HEIGHT = 8; -achievements.$onAction(({ name, args, after }) => { - if (name === "unlock") { - after((wasUnlocked) => { - if (wasUnlocked) { - queue.value.push(args[0]); - processQueue(); - } - }); - } -}); +watch( + () => achievements.unlocked, + (newVal, oldVal) => { + const added = newVal.filter((id) => !oldVal?.includes(id)); + for (const id of added) { + queue.value.push(id); + } + if (added.length > 0) processQueue(); + }, +); const processQueue = () => { if (isAnimating.value || queue.value.length === 0) return; @@ -40,6 +42,14 @@ const processQueue = () => { currentAchievement.value = next; isAnimating.value = true; + assets.audio.messageReceived.play(0.5); + + if (next === "all_achievements") { + confetti.spawn(); + } else { + confetti.spawn(50, 175); + } + gsap .timeline() .to(x, { diff --git a/app/components/Contact/BottomScreen/Buttons.vue b/app/components/Contact/BottomScreen/Buttons.vue index 0f6bc68..38bb6f6 100644 --- a/app/components/Contact/BottomScreen/Buttons.vue +++ b/app/components/Contact/BottomScreen/Buttons.vue @@ -100,6 +100,8 @@ const handleActivateA = async (button: typeof selected.value) => { if (button === "git") { achievements.unlock("contact_git_visit"); + } else if (button === "linkedin") { + achievements.unlock("contact_linkedin_visit"); } }, }); diff --git a/app/components/Settings/BottomScreen/Menus/User/Color.vue b/app/components/Settings/BottomScreen/Menus/User/Color.vue index e60880a..b1a0dee 100644 --- a/app/components/Settings/BottomScreen/Menus/User/Color.vue +++ b/app/components/Settings/BottomScreen/Menus/User/Color.vue @@ -291,13 +291,6 @@ const handleActivateA = () => { achievements.unlock("settings_color_change"); } - if (!achievements.advancement.colors.includes(app.color.hex)) { - achievements.advancement.colors.push(app.color.hex); - if (achievements.advancement.colors.length === APP_COLORS.flat().length) { - achievements.unlock("settings_color_try_all"); - } - } - confirmationModal.open({ text: $t("settings.user.color.confirmation"), onClosed: async () => { diff --git a/app/pages/index.vue b/app/pages/index.vue index cfdc67e..4690711 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -146,7 +146,7 @@ onMounted(async () => { } }); -useKeyDown(async ({ key, ndsButton, repeated }) => { +useKeyDown(async ({ key, repeated }) => { if (!repeated && key.toLocaleLowerCase() === "h") { if (app.hintsVisible) { hideHelpLabels(); diff --git a/app/stores/achievements.ts b/app/stores/achievements.ts index 5b6135e..20a0bdf 100644 --- a/app/stores/achievements.ts +++ b/app/stores/achievements.ts @@ -13,43 +13,34 @@ export const ACHIEVEMENTS = [ // contact { id: "contact_visit", secret: false }, { id: "contact_git_visit", secret: false }, + { id: "contact_linkedin_visit", secret: false }, // settings { id: "settings_color_change", secret: false }, + { id: "settings_visit_all", secret: false }, // snake { id: "snake_score_25", secret: false }, // 2048 { id: "2048_score_512", secret: false }, // taptap { id: "taptap_score_20", secret: false }, - // secrets - { id: "settings_color_try_all", secret: true }, - { id: "settings_visit_all", secret: true }, - { id: "contact_36_notifications", secret: true }, + // meta + { id: "all_achievements", secret: false }, ] as const; export type Achievement = (typeof ACHIEVEMENTS)[number]["id"]; export const useAchievementsStore = defineStore("achievements", () => { - const app = useAppStore(); - const storage = useLocalStorage( STORAGE_ID, { unlocked: [] as Achievement[], advancement: { - colors: [app.color.hex], visitedSettings: [] as string[], }, }, { mergeDefaults: true }, ); - if (!storage.value.advancement.colors.includes(app.color.hex)) { - storage.value.advancement.colors.push(app.color.hex); - } - - const confetti = useConfetti(); - const unlock = (name: Achievement) => { if (storage.value.unlocked.includes(name)) { return false; @@ -57,13 +48,14 @@ export const useAchievementsStore = defineStore("achievements", () => { storage.value.unlocked.push(name); - const { assets } = useAssets(); - assets.audio.messageReceived.play(0.5); - - if (storage.value.unlocked.length === ACHIEVEMENTS.length) { - confetti.spawn(); - } else { - confetti.spawn(50, 175); + if (name !== "all_achievements") { + const othersCount = ACHIEVEMENTS.length - 1; + const unlockedOthers = storage.value.unlocked.filter( + (id) => id !== "all_achievements" && validIds.has(id as Achievement), + ).length; + if (unlockedOthers === othersCount) { + unlock("all_achievements"); + } } return true; @@ -73,18 +65,20 @@ export const useAchievementsStore = defineStore("achievements", () => { storage.value = { unlocked: [], advancement: { - colors: [app.color.hex], visitedSettings: [], }, }; }; + const validIds = new Set(ACHIEVEMENTS.map((a) => a.id)); + const unlocked = computed(() => + storage.value.unlocked.filter((id) => validIds.has(id as Achievement)), + ); + return { - unlocked: computed(() => storage.value.unlocked), + unlocked, advancement: computed(() => storage.value.advancement), - allObtained: computed( - () => storage.value.unlocked.length === ACHIEVEMENTS.length, - ), + allObtained: computed(() => unlocked.value.length === ACHIEVEMENTS.length), unlock, reset, isUnlocked: computed( diff --git a/app/stores/contact.ts b/app/stores/contact.ts index 21ba67c..2125f45 100644 --- a/app/stores/contact.ts +++ b/app/stores/contact.ts @@ -86,10 +86,6 @@ export const useContactStore = defineStore("contact", { { notificationsYOffset: 0, duration: 0.075 }, ); - if (this.notifications.length === 36) { - const achievements = useAchievementsStore(); - achievements.unlock("contact_36_notifications"); - } }, animateOutro() { diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 877bd7f..382a860 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -64,9 +64,9 @@ "snake_score_25": "Score 25 points\nin Snake", "2048_score_512": "Reach the 512 tile\nin 2048", "taptap_score_20": "Score 20 points\nin TapTap", - "settings_color_try_all": "Try all colors", "settings_visit_all": "Visit all settings\nsubmenus", - "contact_36_notifications": "Trigger 36\nnotifications" + "contact_linkedin_visit": "Visit my LinkedIn\nprofile", + "all_achievements": "Unlock all\nachievements" }, "intro": { "copyright": "WARNING - COPYRIGHT", diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json index e599c29..f39aff7 100644 --- a/i18n/locales/fr.json +++ b/i18n/locales/fr.json @@ -64,9 +64,9 @@ "snake_score_25": "Marquer 25 points\nà Snake", "2048_score_512": "Atteindre la tuile 512\nà 2048", "taptap_score_20": "Marquer 20 points\nà TapTap", - "settings_color_try_all": "Essayer toutes les\ncouleurs", "settings_visit_all": "Visiter tous les\nparamètres", - "contact_36_notifications": "Déclencher 36\nnotifications" + "contact_linkedin_visit": "Visiter mon profil\nLinkedIn", + "all_achievements": "Débloquer tous\nles succès" }, "intro": { "copyright": "AVERTISSEMENT - COPYRIGHT",