feat(achievements): remove secret achievements and add new ones. also play sound only when notification comes up instead of instantly

This commit is contained in:
2026-03-16 12:37:24 +01:00
parent 0d580e7edf
commit 085e5f20a9
8 changed files with 46 additions and 51 deletions

View File

@@ -6,6 +6,8 @@ const { onRender } = useScreen();
const achievements = useAchievementsStore();
const { assets } = useAssets();
const confetti = useConfetti();
const queue = ref<Achievement[]>([]);
const currentAchievement = ref<Achievement | null>(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, {

View File

@@ -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");
}
},
});

View File

@@ -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 () => {

View File

@@ -146,7 +146,7 @@ onMounted(async () => {
}
});
useKeyDown(async ({ key, ndsButton, repeated }) => {
useKeyDown(async ({ key, repeated }) => {
if (!repeated && key.toLocaleLowerCase() === "h") {
if (app.hintsVisible) {
hideHelpLabels();

View File

@@ -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(

View File

@@ -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() {