From f000b4423d4eda2fdd9648a181f468ced061d228 Mon Sep 17 00:00:00 2001 From: Talon Date: Thu, 12 Mar 2026 13:46:54 +0100 Subject: [PATCH] Add standard deck of cards --- client/public/sounds/card_draw.ogg | Bin 0 -> 8651 bytes client/public/sounds/card_shuffle.ogg | Bin 0 -> 41670 bytes server/app/items/types/card_deck/__init__.py | 1 + server/app/items/types/card_deck/actions.py | 96 ++++++++++++++++++ .../app/items/types/card_deck/definition.py | 43 ++++++++ server/app/items/types/card_deck/plugin.py | 17 ++++ server/app/items/types/card_deck/validator.py | 53 ++++++++++ server/app/server.py | 14 +++ 8 files changed, 224 insertions(+) create mode 100644 client/public/sounds/card_draw.ogg create mode 100644 client/public/sounds/card_shuffle.ogg create mode 100644 server/app/items/types/card_deck/__init__.py create mode 100644 server/app/items/types/card_deck/actions.py create mode 100644 server/app/items/types/card_deck/definition.py create mode 100644 server/app/items/types/card_deck/plugin.py create mode 100644 server/app/items/types/card_deck/validator.py diff --git a/client/public/sounds/card_draw.ogg b/client/public/sounds/card_draw.ogg new file mode 100644 index 0000000000000000000000000000000000000000..cbeabd56ab2672b28e5b3178c28c4206dd77eb6e GIT binary patch literal 8651 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

X?WxcHSHRCljyb@ zD&8$`NVHSL&=t?F$8bz2$>re(N+lg}d{|B;P24v8=0xr$c&+6kX1kZ;y%>>7dSY7O zuORn1d5=X0-FZ&q^P4W~OX2!Tq2GtQU!FW%85voM{(MR#@yxu%115XcOfTZS*4Kp} zb2kGGn?i!5x-t98ajcOTv_v#kpoZBNw5R@L`d3=oqpLuSclg-}vfQKD4@ixeY|FbP zj*xaaVDq2zJ0Nwr*^BzXFJF~6GFqzCkQ*0wDQgT9boa{R>7S>_I!jeyGL`)?aCY=dY&GCDAZ;1+h8F zODQRjQG#`W;o3bDXcW3XTALcI<0&DbGn6N?%P)?t?K|)N>~p*jO_NFx&3rSIk^K5d zl30rOr&^@?GF5N&Wq9Z43*Ev5SxQ!H z`{hq@iM?}hf<3!iSIc$wk;mnarTky5g+#$~wIGh=;Hd_#;r?5vaSwU?^5v`NKY^YL zP=36Z<?<67J5d~T@Ca-7 z`yjC>-S@$Z@1gBd4?0?iw185QK@Eu({y^VxAs0?9mx0Tq3^n=Vku>U{wDH~Yp=hM; zNC~t?DZcE~a=OUfHWlOkeh0^Fe;4BPQw7{y{Z`KhkES zchtNXMOdN$L3OU8=F%8E!T`D~U;!})TJbK&FTIhN77l{J#=VXCPVrpu^m3b?o~9<} z=jFL_4=`DSOEq|c3DWNJKtE_v)UA5-1@Sp0t6b5LL}XKBD)5D@dUA4HxtCYGR~}hL zNS!u9673q|t7a&Hk<@38Z#_b#xQwkwyJfiuMKFe9_4%GikhU|{Nhq8YR#ZW>q2+a( z^W^ZcLkPt(U%$NleRJms!NsDuxs@|@GoS99>xs+w7RS-U_;@Ck22%{z2G7|7TIDC@ zAuZu&J>Gxfox6q1Y)l^2wjmMNeio)RFAzvJls5PdGL@&wVtwyL`doK($Q z>&eq3z0(8PdeEiC+wsa|uA?fb3inpc&&$vMtEz{zadvK`JvEy~oV0_g43pC+oqo2^ z1yxl6O~VqR5TQd~`+0pu1(jNobd9t-6Caa1<&xzwyyFKM#kERB!Py<;TQ6$FK!d&ZcyJ8}a++?9yS(p

&KWeK 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 literal 0 HcmV?d00001 diff --git a/server/app/items/types/card_deck/__init__.py b/server/app/items/types/card_deck/__init__.py new file mode 100644 index 0000000..4e9c7c4 --- /dev/null +++ b/server/app/items/types/card_deck/__init__.py @@ -0,0 +1 @@ +"""Card deck item type plugin package.""" diff --git a/server/app/items/types/card_deck/actions.py b/server/app/items/types/card_deck/actions.py new file mode 100644 index 0000000..55814b0 --- /dev/null +++ b/server/app/items/types/card_deck/actions.py @@ -0,0 +1,96 @@ +"""Card deck item use actions.""" + +from __future__ import annotations + +import random +from typing import Callable + +from ....item_types import ItemUseResult +from ....models import WorldItem + +RANK_NAMES: dict[str, str] = { + "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: dict[str, str] = { + "S": "Spades", + "H": "Hearts", + "D": "Diamonds", + "C": "Clubs", +} + + +def _card_name(code: str) -> str: + """Return the display name for a card code, e.g. '10H' → 'Ten of Hearts'.""" + if code in ("JO1", "JO2"): + return "Joker" + suit = code[-1] + rank = code[:-1] + return f"{RANK_NAMES[rank]} of {SUIT_NAMES[suit]}" + + +def _build_deck(include_jokers: bool) -> list[str]: + """Return a sorted list of 52 (or 54) card codes.""" + ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"] + suits = ["S", "H", "D", "C"] + deck = [f"{r}{s}" for s in suits for r in ranks] + if include_jokers: + deck += ["JO1", "JO2"] + return deck + + +def use_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict], str]) -> ItemUseResult: + """Draw one or more cards from the deck.""" + try: + draw_count = max(1, min(10, int(item.params.get("draw_count", 1)))) + except (TypeError, ValueError): + draw_count = 1 + + deck = item.params.get("deck", []) + if not isinstance(deck, list): + deck = [] + + if not deck: + return ItemUseResult( + self_message=f"{item.title} is empty. Shift+Use to shuffle.", + others_message="", + ) + + count = min(draw_count, len(deck)) + drawn = deck[:count] + remaining = deck[count:] + + card_names = ", ".join(_card_name(c) for c in drawn) + cards_left = len(remaining) + left_text = f"{cards_left} card{'s' if cards_left != 1 else ''} left" + + return ItemUseResult( + self_message=f"You draw from {item.title}: {card_names}. ({left_text})", + others_message=f"{nickname} draws {count} card{'s' if count != 1 else ''} from {item.title}. ({left_text})", + updated_params={"deck": remaining, "useSound": "sounds/card_draw.ogg"}, + ) + + +def secondary_use_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict], str]) -> ItemUseResult: + """Shuffle the deck.""" + include_jokers = bool(item.params.get("include_jokers", False)) + deck = _build_deck(include_jokers) + random.shuffle(deck) + total = len(deck) + + return ItemUseResult( + self_message=f"You shuffle {item.title}. {total} cards ready.", + others_message=f"{nickname} shuffles {item.title}.", + updated_params={"deck": deck, "useSound": "sounds/card_shuffle.ogg"}, + ) diff --git a/server/app/items/types/card_deck/definition.py b/server/app/items/types/card_deck/definition.py new file mode 100644 index 0000000..8ce05b5 --- /dev/null +++ b/server/app/items/types/card_deck/definition.py @@ -0,0 +1,43 @@ +"""Card deck item static metadata and defaults.""" + +from __future__ import annotations + +LABEL = "card deck" +TOOLTIP = "A standard 52-card deck. Use to draw cards, Shift+Use to shuffle." +EDITABLE_PROPERTIES: tuple[str, ...] = ("title", "draw_count", "include_jokers") +CAPABILITIES: tuple[str, ...] = ("editable", "carryable", "deletable", "usable") +USE_SOUND = None +EMIT_SOUND: str | None = None +EMIT_RANGE = 15 +DIRECTIONAL = False +USE_COOLDOWN_MS = 500 +DEFAULT_TITLE = "Card Deck" +PARAM_KEYS: tuple[str, ...] = ("deck", "draw_count", "include_jokers", "useSound") + +_RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"] +_SUITS = ["S", "H", "D", "C"] +_FULL_DECK: list[str] = [f"{r}{s}" for s in _SUITS for r in _RANKS] + +DEFAULT_PARAMS: dict = { + "deck": list(_FULL_DECK), + "draw_count": 1, + "include_jokers": False, + "useSound": "sounds/card_draw.ogg", +} + +PROPERTY_METADATA: dict[str, dict[str, object]] = { + "title": { + "valueType": "text", + "tooltip": "Display name spoken and shown for this item.", + "maxLength": 80, + }, + "draw_count": { + "valueType": "number", + "tooltip": "How many cards to draw per use.", + "range": {"min": 1, "max": 10, "step": 1}, + }, + "include_jokers": { + "valueType": "boolean", + "tooltip": "Include two Jokers when shuffled.", + }, +} diff --git a/server/app/items/types/card_deck/plugin.py b/server/app/items/types/card_deck/plugin.py new file mode 100644 index 0000000..b8f8a4e --- /dev/null +++ b/server/app/items/types/card_deck/plugin.py @@ -0,0 +1,17 @@ +"""Plugin registration for card deck item type.""" + +from __future__ import annotations + +from ..plugin_helpers import build_item_module +from . import actions, definition, validator + +ITEM_TYPE_PLUGIN = { + "type": "card_deck", + "order": 25, + "module": build_item_module( + definition, + validate_update=validator.validate_update, + use_item=actions.use_item, + secondary_use_item=actions.secondary_use_item, + ), +} diff --git a/server/app/items/types/card_deck/validator.py b/server/app/items/types/card_deck/validator.py new file mode 100644 index 0000000..6833123 --- /dev/null +++ b/server/app/items/types/card_deck/validator.py @@ -0,0 +1,53 @@ +"""Card deck item validation/normalization.""" + +from __future__ import annotations + +from ....models import WorldItem +from ...helpers import keep_only_known_params, parse_bool_like +from .definition import PARAM_KEYS + +_VALID_RANKS = frozenset(["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]) +_VALID_SUITS = frozenset(["S", "H", "D", "C"]) +_VALID_JOKERS = frozenset(["JO1", "JO2"]) +_ALLOWED_SOUNDS = frozenset(["sounds/card_draw.ogg", "sounds/card_shuffle.ogg", ""]) + + +def _is_valid_card(code: object) -> bool: + if not isinstance(code, str): + return False + if code in _VALID_JOKERS: + return True + if len(code) < 2: + return False + suit = code[-1] + rank = code[:-1] + return rank in _VALID_RANKS and suit in _VALID_SUITS + + +def validate_update(_item: WorldItem, next_params: dict) -> dict: + """Validate and normalize card deck params.""" + + try: + draw_count = int(next_params.get("draw_count", 1)) + except (TypeError, ValueError) as exc: + raise ValueError("draw_count must be a number.") from exc + if not (1 <= draw_count <= 10): + raise ValueError("draw_count must be between 1 and 10.") + next_params["draw_count"] = draw_count + + deck = next_params.get("deck", []) + if not isinstance(deck, list): + raise ValueError("deck must be a list.") + for card in deck: + if not _is_valid_card(card): + raise ValueError(f"Invalid card code: {card!r}") + next_params["deck"] = deck + + next_params["include_jokers"] = parse_bool_like(next_params.get("include_jokers", False), default=False) + + use_sound = str(next_params.get("useSound", "")).strip() + if use_sound not in _ALLOWED_SOUNDS: + use_sound = "sounds/card_draw.ogg" + next_params["useSound"] = use_sound + + return keep_only_known_params(next_params, PARAM_KEYS) diff --git a/server/app/server.py b/server/app/server.py index 42db18f..d4f7d6a 100644 --- a/server/app/server.py +++ b/server/app/server.py @@ -2955,6 +2955,20 @@ class SignalingServer: BroadcastChatMessagePacket(type="chat_message", message=secondary_result.others_message, system=True), exclude=client.websocket, ) + use_sound = self._resolve_item_use_sound(item) + if use_sound: + sound_x, sound_y = self._get_item_sound_source_position(item) + sound_range = self._get_item_emit_range(item) + await self._broadcast( + ItemUseSoundPacket( + type="item_use_sound", + itemId=item.id, + sound=use_sound, + x=sound_x, + y=sound_y, + range=sound_range, + ) + ) await self._send_item_result(client, True, "secondary_use", secondary_result.self_message, item.id) return