Support client interact action to allow updating of properties for non owning players
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -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' }
|
||||
| {
|
||||
|
||||
@@ -11,6 +11,7 @@ ITEM_TYPE_HANDLERS: dict[ItemType, ItemTypeHandler] = {
|
||||
validate_update=module.validate_update,
|
||||
use=module.use_item,
|
||||
secondary_use=getattr(module, "secondary_use_item", None),
|
||||
interact=getattr(module, "interact_item", None),
|
||||
)
|
||||
for item_type, module in ITEM_MODULES.items()
|
||||
}
|
||||
|
||||
@@ -26,3 +26,4 @@ class ItemTypeHandler:
|
||||
validate_update: Callable[[WorldItem, dict], dict]
|
||||
use: Callable[[WorldItem, str, Callable[[dict], str]], ItemUseResult]
|
||||
secondary_use: Callable[[WorldItem, str, Callable[[dict], str]], ItemUseResult] | None = None
|
||||
interact: Callable[[WorldItem, str, dict | None, str], ItemUseResult] | None = None
|
||||
|
||||
@@ -8,6 +8,26 @@ from typing import Callable
|
||||
from ....item_types import ItemUseResult
|
||||
from ....models import WorldItem
|
||||
|
||||
_VALID_RANKS = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}
|
||||
_VALID_SUITS = {"S", "H", "D", "C"}
|
||||
_RANK_NAMES = {
|
||||
"A": "Ace", "2": "Two", "3": "Three", "4": "Four", "5": "Five",
|
||||
"6": "Six", "7": "Seven", "8": "Eight", "9": "Nine", "10": "Ten",
|
||||
"J": "Jack", "Q": "Queen", "K": "King",
|
||||
}
|
||||
_SUIT_NAMES = {"S": "Spades", "H": "Hearts", "D": "Diamonds", "C": "Clubs"}
|
||||
|
||||
_CARD_TABLE_ACTIONS = frozenset(["draw", "draw_from_discard", "discard", "return_to_pile"])
|
||||
|
||||
|
||||
def _card_name(code: str) -> str:
|
||||
"""Human-readable card name."""
|
||||
if code in ("JO1", "JO2"):
|
||||
return "Joker"
|
||||
suit = code[-1]
|
||||
rank = code[:-1]
|
||||
return f"{_RANK_NAMES.get(rank, rank)} of {_SUIT_NAMES.get(suit, suit)}"
|
||||
|
||||
|
||||
def _build_deck(include_jokers: bool) -> list[str]:
|
||||
"""Return a sorted list of 52 (or 54) card codes."""
|
||||
@@ -62,3 +82,84 @@ def secondary_use_item(item: WorldItem, nickname: str, _clock_formatter: Callabl
|
||||
"hands": {},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def interact_item(
|
||||
item: WorldItem,
|
||||
action: str,
|
||||
params: dict | None,
|
||||
nickname: str,
|
||||
) -> ItemUseResult:
|
||||
"""Handle a card table interact action on behalf of any user."""
|
||||
if action not in _CARD_TABLE_ACTIONS:
|
||||
raise ValueError(f"Unknown card table action: {action!r}")
|
||||
|
||||
draw_pile = list(item.params.get("draw_pile", []))
|
||||
discard_pile = list(item.params.get("discard_pile", []))
|
||||
hands_raw = item.params.get("hands", {})
|
||||
hands: dict[str, list[str]] = dict(hands_raw) if isinstance(hands_raw, dict) else {}
|
||||
|
||||
if action == "draw":
|
||||
if not draw_pile:
|
||||
raise ValueError("Draw pile is empty.")
|
||||
card = draw_pile.pop(0)
|
||||
hand = list(hands.get(nickname, []))
|
||||
hand.append(card)
|
||||
hands[nickname] = hand
|
||||
return ItemUseResult(
|
||||
self_message=f"You drew {_card_name(card)}. {len(draw_pile)} remaining in draw pile.",
|
||||
others_message=f"{nickname} draws a card.",
|
||||
updated_params={"draw_pile": draw_pile, "hands": hands},
|
||||
)
|
||||
|
||||
if action == "draw_from_discard":
|
||||
if not discard_pile:
|
||||
raise ValueError("Discard pile is empty.")
|
||||
if not params or "card_index" not in params:
|
||||
raise ValueError("draw_from_discard requires params.card_index.")
|
||||
card_index = params["card_index"]
|
||||
if not isinstance(card_index, int) or card_index < 0 or card_index >= len(discard_pile):
|
||||
raise ValueError("Invalid card_index.")
|
||||
card = discard_pile.pop(card_index)
|
||||
hand = list(hands.get(nickname, []))
|
||||
hand.append(card)
|
||||
hands[nickname] = hand
|
||||
return ItemUseResult(
|
||||
self_message=f"You took {_card_name(card)} from the discard pile.",
|
||||
others_message=f"{nickname} takes a card from the discard pile.",
|
||||
updated_params={"discard_pile": discard_pile, "hands": hands},
|
||||
)
|
||||
|
||||
if action == "discard":
|
||||
if not params or "card_index" not in params:
|
||||
raise ValueError("discard requires params.card_index.")
|
||||
hand = list(hands.get(nickname, []))
|
||||
card_index = params["card_index"]
|
||||
if not isinstance(card_index, int) or card_index < 0 or card_index >= len(hand):
|
||||
raise ValueError("Invalid card_index.")
|
||||
card = hand.pop(card_index)
|
||||
discard_pile.insert(0, card)
|
||||
hands[nickname] = hand
|
||||
return ItemUseResult(
|
||||
self_message=f"You discarded {_card_name(card)}.",
|
||||
others_message=f"{nickname} discards a card.",
|
||||
updated_params={"discard_pile": discard_pile, "hands": hands},
|
||||
)
|
||||
|
||||
if action == "return_to_pile":
|
||||
if not params or "card_index" not in params:
|
||||
raise ValueError("return_to_pile requires params.card_index.")
|
||||
hand = list(hands.get(nickname, []))
|
||||
card_index = params["card_index"]
|
||||
if not isinstance(card_index, int) or card_index < 0 or card_index >= len(hand):
|
||||
raise ValueError("Invalid card_index.")
|
||||
card = hand.pop(card_index)
|
||||
draw_pile.append(card)
|
||||
hands[nickname] = hand
|
||||
return ItemUseResult(
|
||||
self_message=f"You returned {_card_name(card)} to the draw pile.",
|
||||
others_message=f"{nickname} returns a card to the draw pile.",
|
||||
updated_params={"draw_pile": draw_pile, "hands": hands},
|
||||
)
|
||||
|
||||
raise ValueError(f"Unhandled action: {action!r}") # unreachable guard
|
||||
|
||||
@@ -13,5 +13,6 @@ ITEM_TYPE_PLUGIN = {
|
||||
validate_update=validator.validate_update,
|
||||
use_item=actions.use_item,
|
||||
secondary_use_item=actions.secondary_use_item,
|
||||
interact_item=actions.interact_item,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -6,7 +6,14 @@ from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_item_module(definition: Any, *, validate_update: Any, use_item: Any, secondary_use_item: Any = None) -> Any:
|
||||
def build_item_module(
|
||||
definition: Any,
|
||||
*,
|
||||
validate_update: Any,
|
||||
use_item: Any,
|
||||
secondary_use_item: Any = None,
|
||||
interact_item: Any = None,
|
||||
) -> Any:
|
||||
"""Compose a plugin module-like object from split definition/validator/actions files."""
|
||||
|
||||
exports: dict[str, Any] = {
|
||||
@@ -18,4 +25,6 @@ def build_item_module(definition: Any, *, validate_update: Any, use_item: Any, s
|
||||
exports["use_item"] = use_item
|
||||
if secondary_use_item is not None:
|
||||
exports["secondary_use_item"] = secondary_use_item
|
||||
if interact_item is not None:
|
||||
exports["interact_item"] = interact_item
|
||||
return SimpleNamespace(**exports)
|
||||
|
||||
@@ -6,6 +6,9 @@ from typing import Callable
|
||||
|
||||
from ....item_types import ItemUseResult
|
||||
from ....models import WorldItem
|
||||
_WHITEBOARD_ACTIONS = frozenset(["add_line", "edit_line", "delete_line"])
|
||||
_MAX_LINES = 20
|
||||
_MAX_LINE_LENGTH = 200
|
||||
|
||||
|
||||
def use_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict], str]) -> ItemUseResult:
|
||||
@@ -21,3 +24,66 @@ def use_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict],
|
||||
self_message=f"You open {item.title}. {line_text}.",
|
||||
others_message=f"{nickname} opens {item.title}.",
|
||||
)
|
||||
|
||||
|
||||
def interact_item(
|
||||
item: WorldItem,
|
||||
action: str,
|
||||
params: dict | None,
|
||||
nickname: str,
|
||||
) -> ItemUseResult:
|
||||
"""Handle a whiteboard interact action on behalf of any user."""
|
||||
if action not in _WHITEBOARD_ACTIONS:
|
||||
raise ValueError(f"Unknown whiteboard action: {action!r}")
|
||||
|
||||
lines = list(item.params.get("lines", []))
|
||||
|
||||
if action == "add_line":
|
||||
if not params or not isinstance(params.get("text"), str):
|
||||
raise ValueError("add_line requires params.text.")
|
||||
text = params["text"].strip()
|
||||
if not text:
|
||||
raise ValueError("Line text cannot be empty.")
|
||||
if len(text) > _MAX_LINE_LENGTH:
|
||||
raise ValueError(f"Line text is too long (max {_MAX_LINE_LENGTH} characters).")
|
||||
if len(lines) >= _MAX_LINES:
|
||||
raise ValueError(f"Whiteboard is full (max {_MAX_LINES} lines).")
|
||||
lines.append(text)
|
||||
return ItemUseResult(
|
||||
self_message=f"Line added to {item.title}.",
|
||||
others_message=f"{nickname} adds a line to {item.title}.",
|
||||
updated_params={"lines": lines},
|
||||
)
|
||||
|
||||
if action == "edit_line":
|
||||
if not params or "line_index" not in params or not isinstance(params.get("text"), str):
|
||||
raise ValueError("edit_line requires params.line_index and params.text.")
|
||||
line_index = params["line_index"]
|
||||
if not isinstance(line_index, int) or line_index < 0 or line_index >= len(lines):
|
||||
raise ValueError("Invalid line_index.")
|
||||
text = params["text"].strip()
|
||||
if not text:
|
||||
raise ValueError("Line text cannot be empty.")
|
||||
if len(text) > _MAX_LINE_LENGTH:
|
||||
raise ValueError(f"Line text is too long (max {_MAX_LINE_LENGTH} characters).")
|
||||
lines[line_index] = text
|
||||
return ItemUseResult(
|
||||
self_message=f"Line updated on {item.title}.",
|
||||
others_message=f"{nickname} updates a line on {item.title}.",
|
||||
updated_params={"lines": lines},
|
||||
)
|
||||
|
||||
if action == "delete_line":
|
||||
if not params or "line_index" not in params:
|
||||
raise ValueError("delete_line requires params.line_index.")
|
||||
line_index = params["line_index"]
|
||||
if not isinstance(line_index, int) or line_index < 0 or line_index >= len(lines):
|
||||
raise ValueError("Invalid line_index.")
|
||||
lines.pop(line_index)
|
||||
return ItemUseResult(
|
||||
self_message=f"Line deleted from {item.title}.",
|
||||
others_message=f"{nickname} deletes a line from {item.title}.",
|
||||
updated_params={"lines": lines},
|
||||
)
|
||||
|
||||
raise ValueError(f"Unhandled action: {action!r}") # unreachable guard
|
||||
|
||||
@@ -12,5 +12,6 @@ ITEM_TYPE_PLUGIN = {
|
||||
definition,
|
||||
validate_update=validator.validate_update,
|
||||
use_item=actions.use_item,
|
||||
interact_item=actions.interact_item,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -176,6 +176,13 @@ class ItemUpdatePacket(BasePacket):
|
||||
params: dict | None = None
|
||||
|
||||
|
||||
class ItemInteractPacket(BasePacket):
|
||||
type: Literal["item_interact"]
|
||||
itemId: str
|
||||
action: str = Field(min_length=1, max_length=64)
|
||||
params: dict | None = None
|
||||
|
||||
|
||||
ClientPacket = (
|
||||
UpdatePositionPacket
|
||||
| TeleportCompletePacket
|
||||
@@ -204,6 +211,7 @@ ClientPacket = (
|
||||
| ItemTransferTargetsPacket
|
||||
| ItemUsePacket
|
||||
| ItemSecondaryUsePacket
|
||||
| ItemInteractPacket
|
||||
| ItemPianoNotePacket
|
||||
| ItemPianoRecordingPacket
|
||||
| ItemUpdatePacket
|
||||
@@ -366,7 +374,7 @@ class ItemRemovePacket(BasePacket):
|
||||
class ItemActionResultPacket(BasePacket):
|
||||
type: Literal["item_action_result"]
|
||||
ok: bool
|
||||
action: Literal["add", "pickup", "drop", "delete", "transfer", "use", "secondary_use", "update"]
|
||||
action: Literal["add", "pickup", "drop", "delete", "transfer", "use", "secondary_use", "update", "interact"]
|
||||
message: str
|
||||
itemId: str | None = None
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ from .models import (
|
||||
ItemClockAnnouncePacket,
|
||||
ItemDeletePacket,
|
||||
ItemDropPacket,
|
||||
ItemInteractPacket,
|
||||
ItemPianoNoteBroadcastPacket,
|
||||
ItemPianoNotePacket,
|
||||
ItemPianoRecordingPacket,
|
||||
@@ -2972,6 +2973,52 @@ class SignalingServer:
|
||||
await self._send_item_result(client, True, "secondary_use", secondary_result.self_message, item.id)
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemInteractPacket):
|
||||
if not self._client_has_permission(client, "item.use"):
|
||||
await self._send_item_result(client, False, "interact", "Not authorized to use items.")
|
||||
return
|
||||
item = self.items.get(packet.itemId)
|
||||
if not item:
|
||||
await self._send_item_result(client, False, "interact", "Item not found.")
|
||||
return
|
||||
if item.carrierId not in (None, client.id):
|
||||
await self._send_item_result(client, False, "interact", "Item is not available.", item.id)
|
||||
return
|
||||
if item.carrierId is None and (item.x != client.x or item.y != client.y):
|
||||
await self._send_item_result(client, False, "interact", "Item is not on your square.", item.id)
|
||||
return
|
||||
handler = get_item_type_handler(item.type)
|
||||
if handler.interact is None:
|
||||
await self._send_item_result(
|
||||
client, False, "interact", f"{item.title} does not support interact actions.", item.id
|
||||
)
|
||||
return
|
||||
try:
|
||||
interact_result = handler.interact(item, packet.action, packet.params, client.nickname)
|
||||
except ValueError as exc:
|
||||
await self._send_item_result(client, False, "interact", str(exc), item.id)
|
||||
return
|
||||
if interact_result.updated_params is not None:
|
||||
try:
|
||||
item.params = handler.validate_update(item, {**item.params, **interact_result.updated_params})
|
||||
except ValueError as exc:
|
||||
await self._send_item_result(client, False, "interact", str(exc), item.id)
|
||||
return
|
||||
item.updatedAt = self.item_service.now_ms()
|
||||
actor_id, actor_name = self._item_updated_actor(client)
|
||||
item.updatedBy = actor_id
|
||||
item.updatedByName = actor_name
|
||||
item.version += 1
|
||||
self._request_state_save()
|
||||
await self._broadcast_item(item)
|
||||
if interact_result.others_message.strip():
|
||||
await self._broadcast(
|
||||
BroadcastChatMessagePacket(type="chat_message", message=interact_result.others_message, system=True),
|
||||
exclude=client.websocket,
|
||||
)
|
||||
await self._send_item_result(client, True, "interact", interact_result.self_message, item.id)
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemPianoNotePacket):
|
||||
if not self._client_has_permission(client, "item.use"):
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user