friend's neko in scene page

This commit is contained in:
2026-03-10 10:28:22 +08:00
parent a4d8601297
commit a0b0a7cdf3
3 changed files with 112 additions and 0 deletions

View File

@@ -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<Record<string, string>>(
{},
);
async function getFriendSpriteUrl(doll: DollDto): Promise<string> {
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<string, string> = {};
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),
);
}),
);
});

View File

@@ -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();
});
}
</script>

View File

@@ -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]}
<Neko
targetX={position.raw.x}
targetY={position.raw.y}
spriteUrl={$friendsActiveDollSprites[friendId]}
/>
{/if}
{/each}
<div id="debug-bar">
<DebugBar
isInteractive={$sceneInteractive}