Add Shift+Enter secondary item action with radio handler
This commit is contained in:
@@ -7,7 +7,11 @@ from .items.registry import ITEM_MODULES
|
||||
from .item_types import ItemTypeHandler
|
||||
|
||||
ITEM_TYPE_HANDLERS: dict[ItemType, ItemTypeHandler] = {
|
||||
item_type: ItemTypeHandler(validate_update=module.validate_update, use=module.use_item)
|
||||
item_type: ItemTypeHandler(
|
||||
validate_update=module.validate_update,
|
||||
use=module.use_item,
|
||||
secondary_use=getattr(module, "secondary_use_item", None),
|
||||
)
|
||||
for item_type, module in ITEM_MODULES.items()
|
||||
}
|
||||
|
||||
|
||||
@@ -25,3 +25,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
|
||||
|
||||
@@ -28,6 +28,7 @@ class ItemModule(Protocol):
|
||||
PROPERTY_METADATA: dict[str, dict[str, object]]
|
||||
validate_update: Callable[[WorldItem, dict], dict]
|
||||
use_item: Callable[[WorldItem, str, Callable[[dict], str]], ItemUseResult]
|
||||
secondary_use_item: Callable[[WorldItem, str, Callable[[dict], str]], ItemUseResult] | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -6,7 +6,7 @@ from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_item_module(definition: Any, *, validate_update: Any, use_item: Any) -> Any:
|
||||
def build_item_module(definition: Any, *, validate_update: Any, use_item: Any, secondary_use_item: Any = None) -> Any:
|
||||
"""Compose a plugin module-like object from split definition/validator/actions files."""
|
||||
|
||||
exports: dict[str, Any] = {
|
||||
@@ -16,4 +16,6 @@ def build_item_module(definition: Any, *, validate_update: Any, use_item: Any) -
|
||||
}
|
||||
exports["validate_update"] = validate_update
|
||||
exports["use_item"] = use_item
|
||||
if secondary_use_item is not None:
|
||||
exports["secondary_use_item"] = secondary_use_item
|
||||
return SimpleNamespace(**exports)
|
||||
|
||||
@@ -19,3 +19,25 @@ def use_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict],
|
||||
others_message=f"{nickname} turns {state_text} {item.title}.",
|
||||
updated_params={**item.params, "enabled": next_enabled},
|
||||
)
|
||||
|
||||
|
||||
def secondary_use_item(item: WorldItem, _nickname: str, _clock_formatter: Callable[[dict], str]) -> ItemUseResult:
|
||||
"""Speak now-playing metadata for this radio."""
|
||||
|
||||
if item.params.get("enabled") is False:
|
||||
return ItemUseResult(
|
||||
self_message=f"{item.title} is off.",
|
||||
others_message=f"{item.title} is off.",
|
||||
)
|
||||
|
||||
station_name = str(item.params.get("stationName", "")).strip()
|
||||
now_playing = str(item.params.get("nowPlaying", "")).strip()
|
||||
if now_playing and station_name:
|
||||
message = f"Playing {now_playing} from {station_name}."
|
||||
elif now_playing:
|
||||
message = f"Playing {now_playing}."
|
||||
elif station_name:
|
||||
message = f"Playing from {station_name}."
|
||||
else:
|
||||
message = "No now playing data."
|
||||
return ItemUseResult(self_message=message, others_message=message)
|
||||
|
||||
@@ -8,5 +8,10 @@ from . import actions, definition, validator
|
||||
ITEM_TYPE_PLUGIN = {
|
||||
"type": "radio_station",
|
||||
"order": 40,
|
||||
"module": build_item_module(definition, validate_update=validator.validate_update, use_item=actions.use_item),
|
||||
"module": build_item_module(
|
||||
definition,
|
||||
validate_update=validator.validate_update,
|
||||
use_item=actions.use_item,
|
||||
secondary_use_item=actions.secondary_use_item,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -95,6 +95,11 @@ class ItemUsePacket(BasePacket):
|
||||
itemId: str
|
||||
|
||||
|
||||
class ItemSecondaryUsePacket(BasePacket):
|
||||
type: Literal["item_secondary_use"]
|
||||
itemId: str
|
||||
|
||||
|
||||
class ItemPianoNotePacket(BasePacket):
|
||||
type: Literal["item_piano_note"]
|
||||
itemId: str
|
||||
@@ -132,6 +137,7 @@ ClientPacket = (
|
||||
| ItemDropPacket
|
||||
| ItemDeletePacket
|
||||
| ItemUsePacket
|
||||
| ItemSecondaryUsePacket
|
||||
| ItemPianoNotePacket
|
||||
| ItemPianoRecordingPacket
|
||||
| ItemUpdatePacket
|
||||
@@ -275,7 +281,7 @@ class ItemRemovePacket(BasePacket):
|
||||
class ItemActionResultPacket(BasePacket):
|
||||
type: Literal["item_action_result"]
|
||||
ok: bool
|
||||
action: Literal["add", "pickup", "drop", "delete", "use", "update"]
|
||||
action: Literal["add", "pickup", "drop", "delete", "use", "secondary_use", "update"]
|
||||
message: str
|
||||
itemId: str | None = None
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ from .models import (
|
||||
ItemPianoStatusPacket,
|
||||
ItemPickupPacket,
|
||||
ItemRemovePacket,
|
||||
ItemSecondaryUsePacket,
|
||||
ItemUpdatePacket,
|
||||
ItemUpsertPacket,
|
||||
ItemUsePacket,
|
||||
@@ -876,7 +877,7 @@ class SignalingServer:
|
||||
self,
|
||||
client: ClientConnection,
|
||||
ok: bool,
|
||||
action: Literal["add", "pickup", "drop", "delete", "use", "update"],
|
||||
action: Literal["add", "pickup", "drop", "delete", "use", "secondary_use", "update"],
|
||||
message: str,
|
||||
item_id: str | None = None,
|
||||
) -> None:
|
||||
@@ -1671,6 +1672,49 @@ class SignalingServer:
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemSecondaryUsePacket):
|
||||
item = self.items.get(packet.itemId)
|
||||
if not item:
|
||||
await self._send_item_result(client, False, "secondary_use", "Item not found.")
|
||||
return
|
||||
if item.carrierId not in (None, client.id):
|
||||
await self._send_item_result(client, False, "secondary_use", "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, "secondary_use", "Item is not on your square.", item.id)
|
||||
return
|
||||
handler = get_item_type_handler(item.type)
|
||||
if handler.secondary_use is None:
|
||||
await self._send_item_result(
|
||||
client,
|
||||
False,
|
||||
"secondary_use",
|
||||
f"No secondary action for {item.title}.",
|
||||
item.id,
|
||||
)
|
||||
return
|
||||
try:
|
||||
secondary_result = handler.secondary_use(item, client.nickname, self._format_clock_display_time)
|
||||
except ValueError as exc:
|
||||
await self._send_item_result(client, False, "secondary_use", str(exc), item.id)
|
||||
return
|
||||
if secondary_result.updated_params is not None:
|
||||
try:
|
||||
item.params = handler.validate_update(item, {**item.params, **secondary_result.updated_params})
|
||||
except ValueError as exc:
|
||||
await self._send_item_result(client, False, "secondary_use", str(exc), item.id)
|
||||
return
|
||||
item.updatedAt = self.item_service.now_ms()
|
||||
item.version += 1
|
||||
self._request_state_save()
|
||||
await self._broadcast_item(item)
|
||||
await self._broadcast(
|
||||
BroadcastChatMessagePacket(type="chat_message", message=secondary_result.others_message, system=True),
|
||||
exclude=client.websocket,
|
||||
)
|
||||
await self._send_item_result(client, True, "secondary_use", secondary_result.self_message, item.id)
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemPianoNotePacket):
|
||||
item = self.items.get(packet.itemId)
|
||||
if not item or item.type != "piano":
|
||||
|
||||
Reference in New Issue
Block a user