refactor: split item type packages into definition/validator/actions
This commit is contained in:
14
docs/examples/item-type-sample/README.md
Normal file
14
docs/examples/item-type-sample/README.md
Normal 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>/`.
|
||||
19
docs/examples/item-type-sample/actions.py
Normal file
19
docs/examples/item-type-sample/actions.py
Normal 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},
|
||||
)
|
||||
21
docs/examples/item-type-sample/definition.py
Normal file
21
docs/examples/item-type-sample/definition.py
Normal 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}},
|
||||
}
|
||||
20
docs/examples/item-type-sample/module.py
Normal file
20
docs/examples/item-type-sample/module.py
Normal 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
|
||||
11
docs/examples/item-type-sample/plugin.py
Normal file
11
docs/examples/item-type-sample/plugin.py
Normal 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,
|
||||
}
|
||||
20
docs/examples/item-type-sample/validator.py
Normal file
20
docs/examples/item-type-sample/validator.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user