petpet headpat init

This commit is contained in:
2026-02-20 02:23:01 +08:00
parent 62a1c1b672
commit ea8f41f852
12 changed files with 162 additions and 78 deletions

View File

@@ -153,6 +153,7 @@
userStatus={getFriendStatus(userId)}
{doll}
{isInteractive}
senderDoll={getUserDoll()}
/>
{/if}
{/each}

View File

@@ -14,6 +14,7 @@
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto";
import type { PresenceStatus } from "../../../types/bindings/PresenceStatus";
import type { UserStatus } from "../../../events/user-status";
import type { InteractionPayloadDto } from "../../../types/bindings/InteractionPayloadDto";
export let id = "";
export let targetX = 0;
@@ -22,6 +23,7 @@
export let userStatus: UserStatus | undefined = undefined;
export let doll: DollDto | undefined = undefined;
export let isInteractive = false;
export let senderDoll: DollDto | undefined = undefined;
const { position, currentSprite, updatePosition, setPosition } = usePetState(
32,
@@ -33,17 +35,14 @@
let spriteSheetUrl = onekoGif;
let isPetMenuOpen = false;
let receivedMessage: string | undefined = undefined;
let receivedInteraction: InteractionPayloadDto | undefined = undefined;
let messageTimer: number | undefined = undefined;
// Watch for received interactions for this user
$: {
const interaction = $receivedInteractions.get(user.id);
if (interaction && interaction.content !== receivedMessage) {
console.log(
`Received interaction for ${user.id}: ${interaction.content}`,
);
receivedMessage = interaction.content;
if (interaction && interaction !== receivedInteraction) {
receivedInteraction = interaction;
isPetMenuOpen = true;
// Make scene interactive so user can see it
@@ -58,7 +57,7 @@
// Auto-close and clear after 8 seconds
messageTimer = setTimeout(() => {
isPetMenuOpen = false;
receivedMessage = undefined;
receivedInteraction = undefined;
clearInteraction(user.id);
// We probably shouldn't disable interactivity globally here as other pets might be active,
// but 'set_pet_menu_state' in backend handles the window transparency logic per pet/menu.
@@ -85,7 +84,7 @@
// Actually, `isInteractive` is a prop passed from +page.svelte probably based on hover state.
// If we want the menu to stay open during the message, we should probably ignore this auto-close behavior if a message is present.
$: if (!receivedMessage && !isInteractive) {
$: if (!receivedInteraction && !isInteractive) {
isPetMenuOpen = false;
}
@@ -154,7 +153,13 @@
aria-label="Pet Menu"
>
{#if doll}
<PetMenu {doll} {user} {userStatus} {receivedMessage} />
<PetMenu
{doll}
{user}
{userStatus}
{receivedInteraction}
{senderDoll}
/>
{/if}
</div>
{/if}
@@ -176,7 +181,7 @@
isPetMenuOpen = !isPetMenuOpen;
if (!isPetMenuOpen) {
// Clear message when closing menu manually
receivedMessage = undefined;
receivedInteraction = undefined;
clearInteraction(user.id);
if (messageTimer) clearTimeout(messageTimer);
}

View File

@@ -4,11 +4,13 @@
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto";
import type { SendInteractionDto } from "../../../types/bindings/SendInteractionDto";
import type { UserStatus } from "../../../events/user-status";
import type { InteractionPayloadDto } from "../../../types/bindings/InteractionPayloadDto";
export let doll: DollDto;
export let user: UserBasicDto;
export let userStatus: UserStatus | undefined = undefined;
export let receivedMessage: string | undefined = undefined;
export let receivedInteraction: InteractionPayloadDto | undefined = undefined;
export let senderDoll: DollDto | undefined = undefined;
let showMessageInput = false;
let messageContent = "";
@@ -32,6 +34,26 @@
}
}
async function sendHeadpat() {
if (!senderDoll) return;
try {
const gifBase64 = await invoke("encode_pet_doll_gif_base64", { doll: senderDoll }) as string;
const dto: SendInteractionDto = {
recipientUserId: user.id,
content: gifBase64,
type: "headpat",
};
await invoke("send_interaction_cmd", { dto });
messageContent = "";
showMessageInput = false;
} catch (e) {
console.error("Failed to send interaction:", e);
alert("Failed to send headpat: " + e);
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === "Enter") {
sendMessage();
@@ -62,11 +84,19 @@
</div>
{/if}
{#if receivedMessage}
{#if receivedInteraction}
<div class="">
<div class="text-sm max-w-[140px]">
{receivedMessage}
</div>
{#if receivedInteraction.type === "headpat"}
<img
src={`data:image/gif;base64,${receivedInteraction.content}`}
alt="Headpat GIF"
class="max-w-[140px] h-auto"
/>
{:else}
<div class="text-sm max-w-[140px]">
{receivedInteraction.content}
</div>
{/if}
</div>
{:else if showMessageInput}
<div class="flex flex-col gap-1">
@@ -89,7 +119,7 @@
</div>
{:else}
<div class="flex flex-row gap-1 w-full *:flex-1 *:btn *:btn-sm">
<button disabled>Headpat</button>
<button onclick={sendHeadpat}>Headpat</button>
<button onclick={() => (showMessageInput = true)}>Message</button>
</div>
<div class="flex flex-row gap-1 w-full *:flex-1 *:btn *:btn-sm">