3.5 KiB
3.5 KiB
Item Type Template
This page is a practical, copy/paste template for adding a new item type with the current registry-based system.
Use this when you want a new item type without editing one huge if/elif chain.
Plain-English Flow
When a new item type is added, you wire it in four places:
- Server catalog (
item_catalog.py)
- Defines global defaults shared by all instances of that type:
- default title
- default params
- use sound / emit sound
- cooldown
- Server behavior (
item_type_handlers.py)
- Defines what happens when users edit params (
validate_update). - Defines what happens when users press
use(use).
- Shared type unions (
models.py, protocol/state types)
- Adds the new type name to type literals/unions so packets validate.
- Client item registry (
itemRegistry.ts)
- Defines add-menu order, editable properties, and optional property dropdown choices.
That is enough for a first working item type.
Example: counter
This sample item increments a number each time it is used.
1. Server Catalog (server/app/item_catalog.py)
ItemType = Literal["radio_station", "dice", "wheel", "clock", "counter"]
ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = {
# ...existing...
"counter": ItemDefinition(
default_title="counter",
capabilities=("editable", "carryable", "deletable", "usable"),
use_sound=None,
emit_sound=None,
default_params={"value": 0},
use_cooldown_ms=1000,
),
}
2. Server Handler (server/app/item_type_handlers.py)
def _validate_counter_update(_item: WorldItem, next_params: dict) -> dict:
try:
value = int(next_params.get("value", 0))
except (TypeError, ValueError) as exc:
raise ValueError("value must be a number.") from exc
if value < 0:
raise ValueError("value must be 0 or greater.")
next_params["value"] = value
return next_params
def _use_counter(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict], str]) -> ItemUseResult:
current = int(item.params.get("value", 0))
next_value = current + 1
return ItemUseResult(
self_message=f"{item.title}: {next_value}",
others_message=f"{nickname} uses {item.title}: {next_value}",
updated_params={**item.params, "value": next_value},
)
ITEM_TYPE_HANDLERS: dict[ItemType, ItemTypeHandler] = {
# ...existing...
"counter": ItemTypeHandler(
validate_update=_validate_counter_update,
use=_use_counter,
),
}
3. Type Unions
Update item-type unions/literals in:
server/app/models.pyclient/src/network/protocol.tsclient/src/state/gameState.ts
Add "counter" anywhere item types are enumerated.
4. Client Registry (client/src/items/itemRegistry.ts)
export const ITEM_TYPE_SEQUENCE: ItemType[] = ['clock', 'counter', 'dice', 'radio_station', 'wheel'];
const ITEM_TYPE_EDITABLE_PROPERTIES: Record<ItemType, string[]> = {
// ...existing...
counter: ['title', 'value'],
};
export const ITEM_TYPE_GLOBAL_PROPERTIES: Record<ItemType, Record<string, string | number | boolean>> = {
// ...existing...
counter: { useSound: 'none', emitSound: 'none', useCooldownMs: 1000 },
};
No dropdown options are needed here because value is a numeric text field.
Checklist Before Commit
- Add/adjust server tests for
useandupdatevalidation. - Run
cd server && uv run --extra dev pytest. - Run
cd client && npm run lint && npm run build. - Update
docs/item-types.mdanddocs/item-schema.mdif behavior/defaults changed.