Support client interact action to allow updating of properties for non owning players

This commit is contained in:
2026-04-07 11:00:25 +01:00
parent 1d5723788e
commit 2c8ab41fe3
12 changed files with 274 additions and 89 deletions

View File

@@ -142,22 +142,8 @@ export function createCardTableController(deps: CardTableControllerDeps): {
deps.sfxUiCancel();
return;
}
const card = drawPile[0];
const newDrawPile = drawPile.slice(1);
const hands = item.params['hands'];
const handsObj: Record<string, string[]> =
hands && typeof hands === 'object' && !Array.isArray(hands)
? (hands as Record<string, string[]>)
: {};
const currentHand = Array.isArray(handsObj[nickname]) ? [...handsObj[nickname]] : [];
currentHand.push(card);
const newHands = { ...handsObj, [nickname]: currentHand };
deps.signalingSend({
type: 'item_update',
itemId: item.id,
params: { draw_pile: newDrawPile, hands: newHands },
});
deps.updateStatus(`Drew ${cardName(card)}. ${newDrawPile.length} cards remaining in pile.`);
deps.signalingSend({ type: 'item_interact', itemId: item.id, action: 'draw' });
deps.updateStatus('Drawing a card.');
deps.sfxUiBlip();
return;
}
@@ -292,58 +278,30 @@ export function createCardTableController(deps: CardTableControllerDeps): {
if (control.type === 'select') {
const actionIdx = deps.state.cardTableCardActionIndex;
const card = hand[cardIndex];
const newHand = [...hand];
newHand.splice(cardIndex, 1);
const hands = item.params['hands'];
const handsObj: Record<string, string[]> =
hands && typeof hands === 'object' && !Array.isArray(hands)
? (hands as Record<string, string[]>)
: {};
if (actionIdx === 0) {
// Discard
const discardPile = getDiscardPile(item);
const newDiscard = [card, ...discardPile];
const newHands = { ...handsObj, [nickname]: newHand };
deps.signalingSend({
type: 'item_update',
type: 'item_interact',
itemId: item.id,
params: { discard_pile: newDiscard, hands: newHands },
action: 'discard',
params: { card_index: cardIndex },
});
deps.state.cardTableHandIndex = Math.min(cardIndex, Math.max(0, newHand.length - 1));
if (newHand.length === 0) {
deps.state.mode = 'cardTableMenu';
deps.state.cardTableMenuIndex = 2; // view hand entry
deps.updateStatus(`${cardName(card)} discarded. Hand is empty.`);
} else {
deps.state.mode = 'cardTableHand';
deps.updateStatus(`${cardName(card)} discarded.`);
}
deps.state.mode = 'cardTableHand';
deps.updateStatus('Discarding card.');
deps.sfxUiBlip();
return;
}
if (actionIdx === 1) {
// Return to draw pile
const drawPile = getDrawPile(item);
const newDrawPile = [...drawPile, card];
const newHands = { ...handsObj, [nickname]: newHand };
deps.signalingSend({
type: 'item_update',
type: 'item_interact',
itemId: item.id,
params: { draw_pile: newDrawPile, hands: newHands },
action: 'return_to_pile',
params: { card_index: cardIndex },
});
deps.state.cardTableHandIndex = Math.min(cardIndex, Math.max(0, newHand.length - 1));
if (newHand.length === 0) {
deps.state.mode = 'cardTableMenu';
deps.state.cardTableMenuIndex = 2;
deps.updateStatus(`${cardName(card)} returned to draw pile. Hand is empty.`);
} else {
deps.state.mode = 'cardTableHand';
deps.updateStatus(`${cardName(card)} returned to draw pile.`);
}
deps.state.mode = 'cardTableHand';
deps.updateStatus('Returning card to draw pile.');
deps.sfxUiBlip();
return;
}
@@ -395,28 +353,15 @@ export function createCardTableController(deps: CardTableControllerDeps): {
// Take card from discard into hand
const cardIdx = deps.state.cardTableDiscardIndex;
const card = discardPile[cardIdx];
const newDiscard = [...discardPile];
newDiscard.splice(cardIdx, 1);
const hands = item.params['hands'];
const handsObj: Record<string, string[]> =
hands && typeof hands === 'object' && !Array.isArray(hands)
? (hands as Record<string, string[]>)
: {};
const currentHand = Array.isArray(handsObj[nickname]) ? [...handsObj[nickname]] : [];
currentHand.push(card);
const newHands = { ...handsObj, [nickname]: currentHand };
deps.signalingSend({
type: 'item_update',
type: 'item_interact',
itemId: item.id,
params: { discard_pile: newDiscard, hands: newHands },
action: 'draw_from_discard',
params: { card_index: cardIdx },
});
deps.state.mode = 'cardTableMenu';
deps.state.cardTableMenuIndex = 0;
deps.updateStatus(`Took ${cardName(card)} from discard pile.`);
deps.updateStatus('Taking card from discard pile.');
deps.sfxUiBlip();
return;
}

View File

@@ -141,10 +141,13 @@ export function createWhiteboardController(deps: WhiteboardControllerDeps): {
deps.sfxUiBlip();
} else {
// Delete
const newLines = [...lines];
newLines.splice(lineIndex, 1);
deps.signalingSend({ type: 'item_update', itemId: item.id, params: { lines: newLines } });
deps.state.whiteboardLineIndex = Math.min(deps.state.whiteboardLineIndex, newLines.length);
deps.signalingSend({
type: 'item_interact',
itemId: item.id,
action: 'delete_line',
params: { line_index: lineIndex },
});
deps.state.whiteboardLineIndex = Math.min(deps.state.whiteboardLineIndex, Math.max(0, lines.length - 1));
deps.state.mode = 'whiteboardLines';
deps.updateStatus('Line deleted.');
deps.sfxUiBlip();
@@ -175,20 +178,21 @@ export function createWhiteboardController(deps: WhiteboardControllerDeps): {
return;
}
const lines = getLines(item);
const editIndex = deps.state.whiteboardEditingLineIndex;
let newLines: string[];
if (editIndex !== null) {
newLines = [...lines];
newLines[editIndex] = text;
deps.signalingSend({
type: 'item_interact',
itemId: item.id,
action: 'edit_line',
params: { line_index: editIndex, text },
});
} else {
newLines = [...lines, text];
}
deps.signalingSend({ type: 'item_update', itemId: item.id, params: { lines: newLines } });
if (editIndex === null) {
deps.state.whiteboardLineIndex = newLines.length - 1;
deps.signalingSend({
type: 'item_interact',
itemId: item.id,
action: 'add_line',
params: { text },
});
}
deps.state.nicknameInput = '';

View File

@@ -241,7 +241,7 @@ export const itemRemoveSchema = z.object({
export const itemActionResultSchema = z.object({
type: z.literal('item_action_result'),
ok: z.boolean(),
action: z.enum(['add', 'pickup', 'drop', 'delete', 'transfer', 'use', 'secondary_use', 'update']),
action: z.enum(['add', 'pickup', 'drop', 'delete', 'transfer', 'use', 'secondary_use', 'update', 'interact']),
message: z.string(),
itemId: z.string().optional(),
});
@@ -415,6 +415,7 @@ export type OutgoingMessage =
| { type: 'item_transfer'; itemId: string; targetUserId: string }
| { type: 'item_use'; itemId: string }
| { type: 'item_secondary_use'; itemId: string }
| { type: 'item_interact'; itemId: string; action: string; params?: Record<string, unknown> }
| { type: 'item_piano_note'; itemId: string; keyId: string; midi: number; on: boolean }
| { type: 'item_piano_recording'; itemId: string; action: 'toggle_record' | 'playback' | 'stop_playback' | 'stop_record' }
| {