2026-02-21 16:51:07 -05:00
|
|
|
"""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
|
2026-02-21 22:02:48 -05:00
|
|
|
from typing import Literal, cast
|
2026-02-20 08:16:43 -05:00
|
|
|
|
2026-02-21 22:02:48 -05:00
|
|
|
from .items import clock, radio
|
|
|
|
|
from .items.registry import ITEM_MODULES, ITEM_TYPE_ORDER
|
2026-02-21 21:58:35 -05:00
|
|
|
|
2026-02-21 22:20:15 -05:00
|
|
|
ItemType = Literal["radio_station", "dice", "wheel", "clock", "widget"]
|
2026-02-21 22:02:48 -05:00
|
|
|
ITEM_TYPE_SEQUENCE: tuple[ItemType, ...] = cast(tuple[ItemType, ...], ITEM_TYPE_ORDER)
|
|
|
|
|
ITEM_TYPE_LABELS: dict[ItemType, str] = {item_type: ITEM_MODULES[item_type].LABEL for item_type in ITEM_TYPE_SEQUENCE}
|
2026-02-21 19:12:58 -05:00
|
|
|
ITEM_TYPE_EDITABLE_PROPERTIES: dict[ItemType, tuple[str, ...]] = {
|
2026-02-21 22:02:48 -05:00
|
|
|
item_type: ITEM_MODULES[item_type].EDITABLE_PROPERTIES for item_type in ITEM_TYPE_SEQUENCE
|
2026-02-21 19:12:58 -05:00
|
|
|
}
|
2026-02-21 21:58:35 -05:00
|
|
|
|
|
|
|
|
CLOCK_DEFAULT_TIME_ZONE = clock.DEFAULT_TIME_ZONE
|
|
|
|
|
CLOCK_TIME_ZONE_OPTIONS = clock.TIME_ZONE_OPTIONS
|
|
|
|
|
RADIO_EFFECT_OPTIONS = radio.EFFECT_OPTIONS
|
|
|
|
|
RADIO_CHANNEL_OPTIONS = radio.CHANNEL_OPTIONS
|
2026-02-20 08:16:43 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class ItemDefinition:
|
2026-02-21 16:51:07 -05:00
|
|
|
"""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, ...]
|
2026-02-21 16:13:48 -05:00
|
|
|
use_sound: str | None
|
2026-02-21 16:01:40 -05:00
|
|
|
emit_sound: str | None
|
2026-02-20 08:16:43 -05:00
|
|
|
default_params: dict
|
2026-02-20 16:47:11 -05:00
|
|
|
use_cooldown_ms: int = 1000
|
2026-02-21 19:37:08 -05:00
|
|
|
emit_range: int = 15
|
|
|
|
|
directional: bool = False
|
2026-02-20 08:16:43 -05:00
|
|
|
|
|
|
|
|
|
2026-02-21 21:58:35 -05:00
|
|
|
def _build_definition(
|
|
|
|
|
*,
|
|
|
|
|
default_title: str,
|
|
|
|
|
capabilities: tuple[str, ...],
|
|
|
|
|
use_sound: str | None,
|
|
|
|
|
emit_sound: str | None,
|
|
|
|
|
default_params: dict,
|
|
|
|
|
use_cooldown_ms: int,
|
|
|
|
|
emit_range: int,
|
|
|
|
|
directional: bool,
|
|
|
|
|
) -> ItemDefinition:
|
|
|
|
|
"""Build one immutable catalog definition from an item module."""
|
|
|
|
|
|
|
|
|
|
return ItemDefinition(
|
|
|
|
|
default_title=default_title,
|
|
|
|
|
capabilities=capabilities,
|
|
|
|
|
use_sound=use_sound,
|
|
|
|
|
emit_sound=emit_sound,
|
|
|
|
|
default_params=default_params,
|
|
|
|
|
use_cooldown_ms=use_cooldown_ms,
|
|
|
|
|
emit_range=emit_range,
|
|
|
|
|
directional=directional,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-02-20 08:16:43 -05:00
|
|
|
ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = {
|
2026-02-21 22:02:48 -05:00
|
|
|
item_type: _build_definition(
|
|
|
|
|
default_title=ITEM_MODULES[item_type].DEFAULT_TITLE,
|
|
|
|
|
capabilities=ITEM_MODULES[item_type].CAPABILITIES,
|
|
|
|
|
use_sound=ITEM_MODULES[item_type].USE_SOUND,
|
|
|
|
|
emit_sound=ITEM_MODULES[item_type].EMIT_SOUND,
|
|
|
|
|
default_params=ITEM_MODULES[item_type].DEFAULT_PARAMS,
|
|
|
|
|
use_cooldown_ms=ITEM_MODULES[item_type].USE_COOLDOWN_MS,
|
|
|
|
|
emit_range=ITEM_MODULES[item_type].EMIT_RANGE,
|
|
|
|
|
directional=ITEM_MODULES[item_type].DIRECTIONAL,
|
|
|
|
|
)
|
|
|
|
|
for item_type in ITEM_TYPE_SEQUENCE
|
2026-02-20 08:16:43 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:12:58 -05:00
|
|
|
ITEM_PROPERTY_OPTIONS: dict[str, tuple[str, ...]] = {
|
2026-02-21 22:55:20 -05:00
|
|
|
"mediaEffect": RADIO_EFFECT_OPTIONS,
|
|
|
|
|
"emitEffect": RADIO_EFFECT_OPTIONS,
|
|
|
|
|
"mediaChannel": RADIO_CHANNEL_OPTIONS,
|
2026-02-21 19:12:58 -05:00
|
|
|
"timeZone": CLOCK_TIME_ZONE_OPTIONS,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 20:47:02 -05:00
|
|
|
ITEM_TYPE_TOOLTIPS: dict[ItemType, str] = {
|
2026-02-21 22:02:48 -05:00
|
|
|
item_type: ITEM_MODULES[item_type].TOOLTIP for item_type in ITEM_TYPE_SEQUENCE
|
2026-02-21 20:54:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GLOBAL_ITEM_PROPERTY_METADATA: dict[str, dict[str, object]] = {
|
|
|
|
|
"useSound": {"valueType": "sound", "tooltip": "One-shot sound played when this item is used successfully."},
|
|
|
|
|
"emitSound": {"valueType": "sound", "tooltip": "Looping sound emitted from this item on the grid."},
|
|
|
|
|
"useCooldownMs": {"valueType": "number", "tooltip": "Global cooldown in milliseconds between uses for this item type."},
|
|
|
|
|
"emitRange": {"valueType": "number", "tooltip": "Maximum distance in squares where emitted audio can be heard."},
|
|
|
|
|
"directional": {"valueType": "boolean", "tooltip": "Whether emitted audio favors the item's facing direction."},
|
2026-02-21 23:17:18 -05:00
|
|
|
"emitSoundSpeed": {
|
|
|
|
|
"valueType": "number",
|
|
|
|
|
"tooltip": "Global emitted sound speed/pitch percent. 50 is normal.",
|
|
|
|
|
"range": {"min": 0, "max": 100, "step": 1},
|
|
|
|
|
},
|
|
|
|
|
"emitSoundTempo": {
|
|
|
|
|
"valueType": "number",
|
|
|
|
|
"tooltip": "Global emitted sound tempo percent. 50 is normal.",
|
|
|
|
|
"range": {"min": 0, "max": 100, "step": 1},
|
|
|
|
|
},
|
2026-02-22 01:57:52 -05:00
|
|
|
"emitSoundReverse": {
|
|
|
|
|
"valueType": "boolean",
|
|
|
|
|
"tooltip": "Global emitted sound reverse flag.",
|
|
|
|
|
},
|
2026-02-21 20:47:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ITEM_TYPE_PROPERTY_METADATA: dict[ItemType, dict[str, dict[str, object]]] = {
|
2026-02-21 22:02:48 -05:00
|
|
|
item_type: {**GLOBAL_ITEM_PROPERTY_METADATA, **ITEM_MODULES[item_type].PROPERTY_METADATA} for item_type in ITEM_TYPE_SEQUENCE
|
2026-02-21 20:47:02 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 08:16:43 -05:00
|
|
|
|
|
|
|
|
def get_item_definition(item_type: ItemType) -> ItemDefinition:
|
2026-02-21 16:51:07 -05:00
|
|
|
"""Return catalog definition for a known item type."""
|
|
|
|
|
|
2026-02-20 08:16:43 -05:00
|
|
|
return ITEM_DEFINITIONS[item_type]
|
2026-02-20 16:47:11 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_item_use_cooldown_ms(item_type: ItemType) -> int:
|
2026-02-21 16:51:07 -05:00
|
|
|
"""Return validated global use cooldown in milliseconds for an item type."""
|
|
|
|
|
|
2026-02-20 16:47:11 -05:00
|
|
|
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
|
2026-02-21 19:12:58 -05:00
|
|
|
|
|
|
|
|
|
2026-02-21 19:37:08 -05:00
|
|
|
def get_item_global_properties(item_type: ItemType) -> dict[str, str | int | bool]:
|
2026-02-21 19:12:58 -05:00
|
|
|
"""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),
|
2026-02-21 19:37:08 -05:00
|
|
|
"emitRange": definition.emit_range if isinstance(definition.emit_range, int) and definition.emit_range > 0 else 15,
|
|
|
|
|
"directional": bool(definition.directional),
|
2026-02-21 23:17:18 -05:00
|
|
|
"emitSoundSpeed": 50,
|
|
|
|
|
"emitSoundTempo": 50,
|
2026-02-22 01:57:52 -05:00
|
|
|
"emitSoundReverse": False,
|
2026-02-21 19:12:58 -05:00
|
|
|
}
|