diff --git a/src/events/friend-active-doll.ts b/src/events/friend-active-doll.ts new file mode 100644 index 0000000..eea0416 --- /dev/null +++ b/src/events/friend-active-doll.ts @@ -0,0 +1,96 @@ +import { writable, get } from "svelte/store"; +import { events, type DollDto } from "$lib/bindings"; +import { createEventSource, removeFromStore } from "./listener-utils"; +import { getSpriteSheetUrl } from "$lib/utils/sprite-utils"; +import { appData } from "./app-data"; +import onekoGif from "../assets/oneko/oneko.gif"; + +export const friendsActiveDollSprites = writable>( + {}, +); + +async function getFriendSpriteUrl(doll: DollDto): Promise { + if (!doll.configuration?.colorScheme) { + return onekoGif; + } + + try { + return await getSpriteSheetUrl(doll.configuration.colorScheme); + } catch (e) { + console.error("Failed to generate friend sprite:", e); + return onekoGif; + } +} + +async function syncFromAppData() { + const data = get(appData); + if (!data?.friends) return; + + const currentSprites = get(friendsActiveDollSprites); + const newSprites: Record = {}; + + const friendUpdates = data.friends + .filter((f) => f.friend?.activeDoll) + .map(async (f) => { + const friendId = f.friend!.id; + if (currentSprites[friendId]) { + newSprites[friendId] = currentSprites[friendId]; + } else { + newSprites[friendId] = await getFriendSpriteUrl(f.friend!.activeDoll!); + } + }); + + await Promise.all(friendUpdates); + + Object.keys(currentSprites).forEach((friendId) => { + if (!newSprites[friendId] && data.friends) { + const friend = data.friends.find( + (f) => f.friend?.id === friendId && f.friend?.activeDoll, + ); + if (friend) { + getFriendSpriteUrl(friend.friend!.activeDoll!).then((spriteUrl) => { + friendsActiveDollSprites.update((current) => ({ + ...current, + [friendId]: spriteUrl, + })); + }); + } + } + }); + + friendsActiveDollSprites.set(newSprites); +} + +export const { + start: startFriendActiveDollTracking, + stop: stopFriendActiveDollTracking, +} = createEventSource(async (addEventListener) => { + await syncFromAppData(); + + addEventListener( + await events.friendActiveDollChanged.listen(async (event) => { + const { friendId, doll } = event.payload; + + if (doll) { + const spriteUrl = await getFriendSpriteUrl(doll); + friendsActiveDollSprites.update((current) => ({ + ...current, + [friendId]: spriteUrl, + })); + } else { + friendsActiveDollSprites.update((current) => + removeFromStore(current, friendId), + ); + } + }), + ); + + addEventListener( + await events.friendDisconnected.listen((event) => { + const { userId } = event.payload; + friendsActiveDollSprites.update((current) => + removeFromStore(current, userId), + ); + }), + ); +}); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b0d98aa..d158f8a 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -17,6 +17,10 @@ stopSceneInteractive, } from "../events/scene-interactive"; import { startUserStatus, stopUserStatus } from "../events/user-status"; + import { + startFriendActiveDollTracking, + stopFriendActiveDollTracking, + } from "../events/friend-active-doll"; let { children } = $props(); if (browser) { @@ -29,6 +33,7 @@ await startSceneInteractive(); await startInteraction(); await startUserStatus(); + await startFriendActiveDollTracking(); } catch (err) { console.error("Failed to initialize event listeners:", err); } @@ -41,6 +46,7 @@ stopSceneInteractive(); stopInteraction(); stopUserStatus(); + stopFriendActiveDollTracking(); }); } diff --git a/src/routes/scene/+page.svelte b/src/routes/scene/+page.svelte index 8923026..f0ca59f 100644 --- a/src/routes/scene/+page.svelte +++ b/src/routes/scene/+page.svelte @@ -3,6 +3,7 @@ import { friendsCursorPositions } from "../../events/friend-cursor"; import { appData } from "../../events/app-data"; import { activeDollSpriteUrl } from "../../events/active-doll-sprite"; + import { friendsActiveDollSprites } from "../../events/friend-active-doll"; import { sceneInteractive } from "../../events/scene-interactive"; import { friendsPresenceStates, @@ -26,6 +27,15 @@ targetY={$cursorPositionOnScreen.raw.y} spriteUrl={$activeDollSpriteUrl} /> + {#each Object.entries($friendsCursorPositions) as [friendId, position]} + {#if $friendsActiveDollSprites[friendId]} + + {/if} + {/each}