feat(achievements): implement unlocking, saving and notification

This commit is contained in:
2026-01-18 22:50:35 +01:00
parent d61a60132a
commit d7e626397a
13 changed files with 269 additions and 2 deletions

View File

@@ -0,0 +1,99 @@
<script setup lang="ts">
import gsap from "gsap";
import type { Achievement } from "~/stores/achievements";
const { onRender } = useScreen();
const achievements = useAchievementsStore();
const { assets } = useAssets();
const queue = ref<Achievement[]>([]);
const currentAchievement = ref<Achievement | null>(null);
const x = ref(LOGICAL_WIDTH);
const isAnimating = ref(false);
const PADDING = 4;
const LOGO_SIZE = assets.images.common.achievementNotificationLogo.rect.height;
const NOTIF_HEIGHT = LOGO_SIZE + PADDING * 2;
const NOTIF_WIDTH = 120;
const NOTIF_Y = 3;
const NOTIF_X_VISIBLE = LOGICAL_WIDTH - NOTIF_WIDTH;
achievements.$onAction(({ name, args, after }) => {
if (name === "unlock") {
after((wasUnlocked) => {
if (wasUnlocked) {
queue.value.push(args[0]);
processQueue();
}
});
}
});
const processQueue = () => {
if (isAnimating.value || queue.value.length === 0) return;
const next = queue.value.shift();
if (!next) return;
currentAchievement.value = next;
isAnimating.value = true;
gsap
.timeline()
.to(x, {
value: NOTIF_X_VISIBLE,
duration: 0.3,
ease: "power2.out",
})
.to(
x,
{
value: LOGICAL_WIDTH,
duration: 0.3,
ease: "power2.in",
},
"+=2.5",
)
.call(() => {
currentAchievement.value = null;
isAnimating.value = false;
processQueue();
});
};
onRender((ctx) => {
if (!currentAchievement.value) return;
const logo = assets.images.common.achievementNotificationLogo;
const textOffset = LOGO_SIZE + PADDING * 2;
// Shadow
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx.fillRect(x.value + 1, NOTIF_Y + 1, NOTIF_WIDTH, NOTIF_HEIGHT);
// Outer border (dark) - no right border
ctx.fillStyle = "#282828";
ctx.fillRect(x.value, NOTIF_Y, NOTIF_WIDTH, 1); // Top
ctx.fillRect(x.value, NOTIF_Y, 1, NOTIF_HEIGHT); // Left
ctx.fillRect(x.value, NOTIF_Y + NOTIF_HEIGHT - 1, NOTIF_WIDTH, 1); // Bottom
// Inner background (light)
ctx.fillStyle = "#fafafa";
ctx.fillRect(x.value + 1, NOTIF_Y + 1, NOTIF_WIDTH - 1, NOTIF_HEIGHT - 2);
// Logo
logo.draw(ctx, x.value + PADDING, NOTIF_Y + PADDING);
// Text
ctx.font = "8px NDS10";
ctx.textBaseline = "top";
ctx.fillStyle = "#282828";
ctx.fillText(
$t(`achievements.${currentAchievement.value}`),
x.value + textOffset,
NOTIF_Y + PADDING + (LOGO_SIZE - 8) / 2,
);
}, 200);
defineOptions({ render: () => null });
</script>