From 0541418e2a8b73b48ae6ac34e461aa66696f3924 Mon Sep 17 00:00:00 2001 From: Jage9 Date: Sat, 21 Feb 2026 01:11:08 -0500 Subject: [PATCH] Make radios usable toggles and keep cooldown semantics --- client/public/version.js | 2 +- server/app/item_catalog.py | 2 +- server/app/server.py | 24 ++++++++++++++--- server/tests/test_item_use_cooldown.py | 36 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/client/public/version.js b/client/public/version.js index 97cf182..aacd18e 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,3 +1,3 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.21 R71"; +window.CHGRID_WEB_VERSION = "2026.02.21 R72"; diff --git a/server/app/item_catalog.py b/server/app/item_catalog.py index 1849aaa..031c277 100644 --- a/server/app/item_catalog.py +++ b/server/app/item_catalog.py @@ -18,7 +18,7 @@ class ItemDefinition: ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = { "radio_station": ItemDefinition( default_title="radio", - capabilities=("editable", "carryable", "deletable"), + capabilities=("editable", "carryable", "deletable", "usable"), use_sound=None, default_params={"streamUrl": "", "enabled": True, "volume": 50, "effect": "off", "effectValue": 50}, ), diff --git a/server/app/server.py b/server/app/server.py index 8b10e2d..dd07daf 100644 --- a/server/app/server.py +++ b/server/app/server.py @@ -392,7 +392,7 @@ class SignalingServer: if item.carrierId is None and (item.x != client.x or item.y != client.y): await self._send_item_result(client, False, "use", "Item is not on your square.", item.id) return - if item.type not in {"dice", "wheel"}: + if item.type not in {"radio_station", "dice", "wheel"}: await self._send_item_result(client, False, "use", "This item cannot be used yet.", item.id) return now_ms = self.item_service.now_ms() @@ -409,7 +409,26 @@ class SignalingServer: ) return self.item_last_use_ms[item.id] = now_ms - if item.type == "dice": + delayed_wheel_result: str | None = None + if item.type == "radio_station": + enabled_value = item.params.get("enabled", True) + if isinstance(enabled_value, bool): + currently_enabled = enabled_value + elif isinstance(enabled_value, (int, float)): + currently_enabled = bool(enabled_value) + elif isinstance(enabled_value, str): + currently_enabled = enabled_value.strip().lower() in {"on", "true", "1", "yes"} + else: + currently_enabled = True + next_enabled = not currently_enabled + item.params = {**item.params, "enabled": next_enabled} + item.updatedAt = now_ms + self.item_service.save_state() + await self._broadcast_item(item) + state_text = "on" if next_enabled else "off" + others_message = f"{client.nickname} turns {state_text} {item.title}." + self_message = others_message + elif item.type == "dice": try: sides = max(1, min(100, int(item.params.get("sides", 6)))) number = max(1, min(100, int(item.params.get("number", 2)))) @@ -422,7 +441,6 @@ class SignalingServer: f"{client.nickname} rolled {item.title}: {', '.join(str(value) for value in rolls)} (total {total})." ) self_message = f"You rolled {item.title}: {', '.join(str(value) for value in rolls)} (total {total})." - delayed_wheel_result: str | None = None else: spaces_raw = item.params.get("spaces", "") if isinstance(spaces_raw, str): diff --git a/server/tests/test_item_use_cooldown.py b/server/tests/test_item_use_cooldown.py index c24d721..3b6b873 100644 --- a/server/tests/test_item_use_cooldown.py +++ b/server/tests/test_item_use_cooldown.py @@ -46,3 +46,39 @@ async def test_item_use_has_global_cooldown(monkeypatch: pytest.MonkeyPatch) -> now_ms += 700 await server._handle_message(client, json.dumps({"type": "item_use", "itemId": item.id})) assert send_payloads[-1].ok is True + + +@pytest.mark.asyncio +async def test_radio_use_toggles_enabled(monkeypatch: pytest.MonkeyPatch) -> None: + server = SignalingServer("127.0.0.1", 8765, None, None) + ws = _fake_ws() + client = ClientConnection(websocket=ws, id="u1", nickname="tester", x=5, y=6) + server.clients[ws] = client + item = server.item_service.default_item(client, "radio_station") + server.item_service.add_item(item) + + send_payloads: list[object] = [] + broadcast_payloads: list[object] = [] + now_ms = 20_000 + + async def fake_send(websocket: ServerConnection, packet: object) -> None: + send_payloads.append(packet) + + async def fake_broadcast(packet: object, exclude: ServerConnection | None = None) -> None: + broadcast_payloads.append(packet) + + monkeypatch.setattr(server, "_send", fake_send) + monkeypatch.setattr(server, "_broadcast", fake_broadcast) + monkeypatch.setattr(server.item_service, "now_ms", lambda: now_ms) + + assert item.params.get("enabled") is True + await server._handle_message(client, json.dumps({"type": "item_use", "itemId": item.id})) + assert item.params.get("enabled") is False + assert send_payloads[-1].ok is True + + now_ms += 1200 + await server._handle_message(client, json.dumps({"type": "item_use", "itemId": item.id})) + assert item.params.get("enabled") is True + assert send_payloads[-1].ok is True + + assert any(getattr(packet, "type", "") == "item_upsert" for packet in broadcast_payloads)