refactor: split item type packages into definition/validator/actions

This commit is contained in:
Jage9
2026-02-24 03:08:30 -05:00
parent 7903bab131
commit 3718449156
39 changed files with 1216 additions and 862 deletions

View File

@@ -0,0 +1,14 @@
# Sample Item Type Folder
This is a reference layout for adding a new server item type plugin.
## Folder Layout
- `definition.py`: static metadata/defaults/schema constants.
- `validator.py`: `validate_update(item, next_params)` normalization and validation.
- `actions.py`: `use_item(item, nickname, clock_formatter)` runtime behavior.
- `module.py`: thin exported surface combining the three files.
- `plugin.py`: registration payload consumed by plugin auto-discovery.
Use this folder as a copy template when creating a real item under:
`server/app/items/types/<item_type>/`.

View File

@@ -0,0 +1,19 @@
"""Counter item use actions."""
from __future__ import annotations
from typing import Callable
from ....item_types import ItemUseResult
from ....models import WorldItem
def use_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict], str]) -> ItemUseResult:
"""Increment counter and return messages plus updated params."""
next_value = int(item.params.get("value", 0)) + 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},
)

View File

@@ -0,0 +1,21 @@
"""Counter item static metadata and defaults."""
from __future__ import annotations
LABEL = "counter"
TOOLTIP = "Simple incrementing counter."
EDITABLE_PROPERTIES: tuple[str, ...] = ("title", "value")
CAPABILITIES: tuple[str, ...] = ("editable", "carryable", "deletable", "usable")
USE_SOUND: str | None = None
EMIT_SOUND: str | None = None
USE_COOLDOWN_MS = 1000
EMIT_RANGE = 15
DIRECTIONAL = False
DEFAULT_TITLE = "counter"
DEFAULT_PARAMS: dict = {"value": 0}
PARAM_KEYS: tuple[str, ...] = ("value",)
PROPERTY_METADATA: dict[str, dict[str, object]] = {
"title": {"valueType": "text", "tooltip": "Display name.", "maxLength": 80},
"value": {"valueType": "number", "tooltip": "Current value.", "range": {"min": 0, "max": 9999, "step": 1}},
}

View File

@@ -0,0 +1,20 @@
"""Counter item plugin module surface."""
from __future__ import annotations
from .actions import use_item
from .definition import (
CAPABILITIES,
DEFAULT_PARAMS,
DEFAULT_TITLE,
DIRECTIONAL,
EDITABLE_PROPERTIES,
EMIT_RANGE,
EMIT_SOUND,
LABEL,
PROPERTY_METADATA,
TOOLTIP,
USE_COOLDOWN_MS,
USE_SOUND,
)
from .validator import validate_update

View File

@@ -0,0 +1,11 @@
"""Counter plugin registration sample."""
from __future__ import annotations
from . import module
ITEM_TYPE_PLUGIN = {
"type": "counter",
"order": 25,
"module": module,
}

View File

@@ -0,0 +1,20 @@
"""Counter item validation/normalization."""
from __future__ import annotations
from ....models import WorldItem
from ...helpers import keep_only_known_params
from .definition import PARAM_KEYS
def validate_update(_item: WorldItem, next_params: dict) -> dict:
"""Validate and normalize counter params."""
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 keep_only_known_params(next_params, PARAM_KEYS)

View File

@@ -6,19 +6,13 @@ This page is the practical template for the current plugin-driven item architect
When adding a new item type:
1. Server item module
- Add `server/app/items/types/<item_type>/module.py`.
- Define metadata/constants:
- `LABEL`, `TOOLTIP`
- `EDITABLE_PROPERTIES`
- `CAPABILITIES`
- `USE_SOUND`, `EMIT_SOUND`
- `USE_COOLDOWN_MS`, `EMIT_RANGE`, `DIRECTIONAL`
- `DEFAULT_TITLE`, `DEFAULT_PARAMS`
- `PROPERTY_METADATA`
- Implement behavior:
- `validate_update(item, next_params)`
- `use_item(item, nickname, clock_formatter)`
1. Server item package
- Add `server/app/items/types/<item_type>/` with:
- `definition.py` for metadata/constants
- `validator.py` for `validate_update(item, next_params)`
- `actions.py` for `use_item(item, nickname, clock_formatter)`
- `module.py` as thin exported surface
- `plugin.py` for registration
2. Server plugin file
- Add `server/app/items/types/<item_type>/plugin.py` exporting:
@@ -38,66 +32,16 @@ When adding a new item type:
That is enough for a first working item type.
## Minimal Server Module Example: `counter`
## Reference Sample Folder
`server/app/items/types/counter/module.py`:
See `docs/examples/item-type-sample/` for a complete copyable folder with all five files.
## Minimal `module.py` Example
```py
from __future__ import annotations
from typing import Callable
from ...item_types import ItemUseResult
from ...models import WorldItem
LABEL = "counter"
TOOLTIP = "Counts up each time you use it."
EDITABLE_PROPERTIES: tuple[str, ...] = ("title", "value")
CAPABILITIES: tuple[str, ...] = ("editable", "carryable", "deletable", "usable")
USE_SOUND: str | None = None
EMIT_SOUND: str | None = None
USE_COOLDOWN_MS = 1000
EMIT_RANGE = 15
DIRECTIONAL = False
DEFAULT_TITLE = "counter"
DEFAULT_PARAMS: dict = {"value": 0}
PROPERTY_METADATA: dict[str, dict[str, object]] = {
"title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item."},
"value": {"valueType": "number", "tooltip": "Current counter value.", "range": {"min": 0, "max": 9999, "step": 1}},
}
def validate_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_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict], str]) -> ItemUseResult:
next_value = int(item.params.get("value", 0)) + 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},
)
```
Then add plugin registration in `server/app/items/types/counter/plugin.py`:
```py
from . import module
ITEM_TYPE_PLUGIN = {
"type": "counter",
"order": 25,
"module": module,
}
from .actions import use_item
from .definition import LABEL, TOOLTIP, EDITABLE_PROPERTIES, CAPABILITIES, USE_SOUND, EMIT_SOUND, USE_COOLDOWN_MS, EMIT_RANGE, DIRECTIONAL, DEFAULT_TITLE, DEFAULT_PARAMS, PROPERTY_METADATA
from .validator import validate_update
```
## Checklist Before Commit

View File

@@ -193,10 +193,11 @@ Server is the source of truth for item type definitions and metadata. The client
For a full copy/paste example with plain-English explanation, see `docs/item-type-template.md`.
1. Server item module: add a new file under `server/app/items/types/<item_type>/module.py` with:
- defaults/capabilities
- property metadata/options
- `validate_update` and `use_item`
1. Server item package: add a new folder under `server/app/items/types/<item_type>/` with:
- `definition.py` (defaults/capabilities/metadata/options)
- `validator.py` (`validate_update`)
- `actions.py` (`use_item`)
- `module.py` (thin exported surface)
2. Server plugin: add `server/app/items/types/<item_type>/plugin.py` exporting `ITEM_TYPE_PLUGIN` with:
- `type`
- `order`