Add server-side item use cooldown and GitHub deploy update docs

This commit is contained in:
Jage9
2026-02-20 16:47:11 -05:00
parent 874b14afc9
commit bdd9bf8c77
5 changed files with 107 additions and 1 deletions

View File

@@ -1,3 +1,3 @@
// Maintainer-controlled web client version. // Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.20 R60"; window.CHGRID_WEB_VERSION = "2026.02.20 R61";

View File

@@ -114,3 +114,35 @@ sudo /usr/local/cpanel/scripts/restartsrv_httpd
Usage example in Chat Grid: Usage example in Chat Grid:
- `https://bestmidi.com/listen/8000/stream` - `https://bestmidi.com/listen/8000/stream`
## 8) GitHub-based update flow (`bestmidi`)
Initial clone (one time):
```bash
cd /home/bestmidi
git clone git@github.com:jage9/chat_grid.git chgrid
```
Update and redeploy:
```bash
cd /home/bestmidi/chgrid
git fetch origin
git checkout main
git pull --ff-only origin main
# Rebuild/publish web client
./deploy/scripts/deploy_client.sh /home/bestmidi/chgrid /home/bestmidi/public_html/chgrid /chgrid/
# Reconcile server env/deps (safe to rerun on updates)
./deploy/scripts/install_server.sh /home/bestmidi/chgrid
# Restart signaling service
sudo systemctl restart chgrid-signaling.service
journalctl -u chgrid-signaling.service -n 50 --no-pager
```
Notes:
- Run Apache install/reload steps again only if proxy config changed.
- If your checkout has local changes, stash or commit before `git pull`.

View File

@@ -12,6 +12,7 @@ class ItemDefinition:
capabilities: tuple[str, ...] capabilities: tuple[str, ...]
use_sound: str | None use_sound: str | None
default_params: dict default_params: dict
use_cooldown_ms: int = 1000
ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = { ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = {
@@ -32,3 +33,11 @@ ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = {
def get_item_definition(item_type: ItemType) -> ItemDefinition: def get_item_definition(item_type: ItemType) -> ItemDefinition:
return ITEM_DEFINITIONS[item_type] return ITEM_DEFINITIONS[item_type]
def get_item_use_cooldown_ms(item_type: ItemType) -> int:
definition = get_item_definition(item_type)
cooldown_ms = definition.use_cooldown_ms
if isinstance(cooldown_ms, int) and cooldown_ms > 0:
return cooldown_ms
return 1000

View File

@@ -15,6 +15,7 @@ from websockets.asyncio.server import ServerConnection, serve
from .client import ClientConnection from .client import ClientConnection
from .config import load_config from .config import load_config
from .item_catalog import get_item_use_cooldown_ms
from .item_service import ItemService from .item_service import ItemService
from .models import ( from .models import (
BroadcastChatMessagePacket, BroadcastChatMessagePacket,
@@ -66,6 +67,7 @@ class SignalingServer:
self._ssl_context = self._build_ssl_context(ssl_cert, ssl_key) self._ssl_context = self._build_ssl_context(ssl_cert, ssl_key)
self.clients: dict[ServerConnection, ClientConnection] = {} self.clients: dict[ServerConnection, ClientConnection] = {}
self.item_service = ItemService(state_file=state_file) self.item_service = ItemService(state_file=state_file)
self.item_last_use_ms: dict[str, int] = {}
@property @property
def items(self) -> dict[str, WorldItem]: def items(self) -> dict[str, WorldItem]:
@@ -369,6 +371,7 @@ class SignalingServer:
await self._send_item_result(client, False, "delete", "Item is not on your square.", item.id) await self._send_item_result(client, False, "delete", "Item is not on your square.", item.id)
return return
self.item_service.remove_item(item.id) self.item_service.remove_item(item.id)
self.item_last_use_ms.pop(item.id, None)
await self._broadcast(ItemRemovePacket(type="item_remove", itemId=item.id)) await self._broadcast(ItemRemovePacket(type="item_remove", itemId=item.id))
self.item_service.save_state() self.item_service.save_state()
await self._send_item_result(client, True, "delete", f"Deleted {item.title}.", item.id) await self._send_item_result(client, True, "delete", f"Deleted {item.title}.", item.id)
@@ -388,6 +391,20 @@ class SignalingServer:
if item.type != "dice": if item.type != "dice":
await self._send_item_result(client, False, "use", "This item cannot be used yet.", item.id) await self._send_item_result(client, False, "use", "This item cannot be used yet.", item.id)
return return
now_ms = self.item_service.now_ms()
cooldown_ms = get_item_use_cooldown_ms(item.type)
last_use_ms = self.item_last_use_ms.get(item.id)
if last_use_ms is not None and now_ms - last_use_ms < cooldown_ms:
remaining_ms = cooldown_ms - (now_ms - last_use_ms)
await self._send_item_result(
client,
False,
"use",
f"Item is on cooldown for {max(1, remaining_ms)} ms.",
item.id,
)
return
self.item_last_use_ms[item.id] = now_ms
try: try:
sides = max(1, min(100, int(item.params.get("sides", 6)))) sides = max(1, min(100, int(item.params.get("sides", 6))))
number = max(1, min(100, int(item.params.get("number", 2)))) number = max(1, min(100, int(item.params.get("number", 2))))

View File

@@ -0,0 +1,48 @@
from __future__ import annotations
import json
from typing import cast
import pytest
from websockets.asyncio.server import ServerConnection
from app.server import ClientConnection, SignalingServer
def _fake_ws() -> ServerConnection:
return cast(ServerConnection, object())
@pytest.mark.asyncio
async def test_item_use_has_global_cooldown(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, "dice")
server.item_service.add_item(item)
send_payloads: list[object] = []
now_ms = 10_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:
return
monkeypatch.setattr(server, "_send", fake_send)
monkeypatch.setattr(server, "_broadcast", fake_broadcast)
monkeypatch.setattr(server.item_service, "now_ms", lambda: now_ms)
await server._handle_message(client, json.dumps({"type": "item_use", "itemId": item.id}))
assert send_payloads[-1].ok is True
now_ms += 400
await server._handle_message(client, json.dumps({"type": "item_use", "itemId": item.id}))
assert send_payloads[-1].ok is False
assert "cooldown" in send_payloads[-1].message.lower()
now_ms += 700
await server._handle_message(client, json.dumps({"type": "item_use", "itemId": item.id}))
assert send_payloads[-1].ok is True