Files
chat_grid/server/app/item_catalog.py

161 lines
4.8 KiB
Python
Raw Normal View History

"""Server-side catalog of global item type definitions and defaults."""
2026-02-20 08:16:43 -05:00
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
ItemType = Literal["radio_station", "dice", "wheel", "clock"]
ITEM_TYPE_SEQUENCE: tuple[ItemType, ...] = ("clock", "dice", "radio_station", "wheel")
ITEM_TYPE_LABELS: dict[ItemType, str] = {
"radio_station": "radio",
"dice": "dice",
"wheel": "wheel",
"clock": "clock",
}
RADIO_EFFECT_OPTIONS: tuple[str, ...] = ("reverb", "echo", "flanger", "high_pass", "low_pass", "off")
RADIO_CHANNEL_OPTIONS: tuple[str, ...] = ("stereo", "mono", "left", "right")
ITEM_TYPE_EDITABLE_PROPERTIES: dict[ItemType, tuple[str, ...]] = {
2026-02-21 20:31:34 -05:00
"radio_station": ("title", "streamUrl", "enabled", "channel", "volume", "effect", "effectValue", "facing", "emitRange"),
"dice": ("title", "sides", "number"),
"wheel": ("title", "spaces"),
"clock": ("title", "timeZone", "use24Hour"),
}
CLOCK_DEFAULT_TIME_ZONE = "America/Detroit"
CLOCK_TIME_ZONE_OPTIONS: tuple[str, ...] = (
"America/Anchorage",
"America/Argentina/Buenos_Aires",
"America/Chicago",
"America/Detroit",
"America/Halifax",
"America/Indiana/Indianapolis",
"America/Kentucky/Louisville",
"America/Los_Angeles",
"America/St_Johns",
"Asia/Bangkok",
"Asia/Dhaka",
"Asia/Dubai",
"Asia/Hong_Kong",
"Asia/Kabul",
"Asia/Karachi",
"Asia/Kathmandu",
"Asia/Kolkata",
"Asia/Seoul",
"Asia/Singapore",
"Asia/Tehran",
"Asia/Tokyo",
"Asia/Yangon",
"Atlantic/Azores",
"Atlantic/South_Georgia",
"Australia/Brisbane",
"Australia/Darwin",
"Australia/Eucla",
"Australia/Lord_Howe",
"Europe/Berlin",
"Europe/Helsinki",
"Europe/London",
"Europe/Moscow",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Chatham",
"Pacific/Honolulu",
"Pacific/Kiritimati",
"Pacific/Noumea",
"Pacific/Pago_Pago",
"UTC",
)
2026-02-20 08:16:43 -05:00
@dataclass(frozen=True)
class ItemDefinition:
"""Global behavior and defaults shared by all instances of one item type."""
2026-02-20 08:16:43 -05:00
default_title: str
capabilities: tuple[str, ...]
use_sound: str | None
emit_sound: str | None
2026-02-20 08:16:43 -05:00
default_params: dict
use_cooldown_ms: int = 1000
emit_range: int = 15
directional: bool = False
2026-02-20 08:16:43 -05:00
ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = {
"radio_station": ItemDefinition(
default_title="radio",
capabilities=("editable", "carryable", "deletable", "usable"),
use_sound=None,
emit_sound=None,
2026-02-21 20:31:34 -05:00
default_params={
"streamUrl": "",
"enabled": True,
"channel": "stereo",
"volume": 50,
"effect": "off",
"effectValue": 50,
"facing": 0,
"emitRange": 20,
},
emit_range=20,
directional=True,
2026-02-20 08:16:43 -05:00
),
"dice": ItemDefinition(
default_title="Dice",
capabilities=("editable", "carryable", "deletable", "usable"),
use_sound="sounds/roll.ogg",
emit_sound=None,
2026-02-20 08:16:43 -05:00
default_params={"sides": 6, "number": 2},
),
"wheel": ItemDefinition(
default_title="wheel",
capabilities=("editable", "carryable", "deletable", "usable"),
use_sound="sounds/spin.ogg",
emit_sound=None,
default_params={"spaces": "yes, no"},
use_cooldown_ms=4000,
),
"clock": ItemDefinition(
default_title="clock",
capabilities=("editable", "carryable", "deletable", "usable"),
use_sound=None,
emit_sound="sounds/clock.ogg",
default_params={"timeZone": CLOCK_DEFAULT_TIME_ZONE, "use24Hour": False},
emit_range=10,
),
2026-02-20 08:16:43 -05:00
}
ITEM_PROPERTY_OPTIONS: dict[str, tuple[str, ...]] = {
"effect": RADIO_EFFECT_OPTIONS,
"channel": RADIO_CHANNEL_OPTIONS,
"timeZone": CLOCK_TIME_ZONE_OPTIONS,
}
2026-02-20 08:16:43 -05:00
def get_item_definition(item_type: ItemType) -> ItemDefinition:
"""Return catalog definition for a known item type."""
2026-02-20 08:16:43 -05:00
return ITEM_DEFINITIONS[item_type]
def get_item_use_cooldown_ms(item_type: ItemType) -> int:
"""Return validated global use cooldown in milliseconds for an item type."""
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
def get_item_global_properties(item_type: ItemType) -> dict[str, str | int | bool]:
"""Return non-editable global properties exposed in UI metadata."""
definition = get_item_definition(item_type)
return {
"useSound": definition.use_sound or "none",
"emitSound": definition.emit_sound or "none",
"useCooldownMs": get_item_use_cooldown_ms(item_type),
"emitRange": definition.emit_range if isinstance(definition.emit_range, int) and definition.emit_range > 0 else 15,
"directional": bool(definition.directional),
}