Action Handler Development
Belongs to: Developer · Related: Action System · Handlers
QI's action YAML cannot contain logic branches. To bring if/switch/state checks into the action system, the only proper way is to implement an action handler — write the logic in Kotlin/Java, and have the YAML merely reference it and pass a payload.
1. Interface QinhActionHandler
interface QinhActionHandler {
val handlerId: String // Unique ID, recommended "namespace:name"
fun dispatch(context: QinhActionContext): ActionDispatchResult
fun isAvailable(): Boolean = true // Return false when a required plugin is not enabled
}Return value ActionDispatchResult:
| Value | Meaning |
|---|---|
HANDLED | Executed successfully (will trigger consumption deduction / cooldown recording) |
NOT_HANDLED | Handler rejected (e.g. validation failed) |
HANDLER_UNAVAILABLE | Dependency not loaded |
2. Context QinhActionContext
data class QinhActionContext(
val trigger: String, // Trigger name (e.g. "left_click")
val player: Player,
val item: ItemStack,
val itemId: String,
val handlerId: String,
val payload: String, // The raw payload passed from YAML
val compileEpoch: Long?,
val providerSnapshot: ProviderSnapshot,
val triggerType: TriggerType?,
val rawContext: RawSkillContext, // Extended context (sneaking state, etc.)
)3. Registration
QinhItemsAPI.actions().registerHandler(object : QinhActionHandler {
override val handlerId = "myplugin:smart_cast"
override fun dispatch(ctx: QinhActionContext): ActionDispatchResult {
val payload = ctx.payload.trim()
if (payload.isEmpty()) return ActionDispatchResult.NOT_HANDLED
// Complex logic: if / switch / state checks all go here
if (ctx.player.level < 10) {
ctx.player.sendMessage("§cLevel too low")
return ActionDispatchResult.NOT_HANDLED
}
ctx.player.sendMessage("Casting: $payload")
return ActionDispatchResult.HANDLED
}
})Just register it in your plugin's onEnable(). After that, the YAML can reference it:
refs:
- handler: myplugin:smart_cast
payload: "fireball"4. payload schema
Optional: declare a field structure for the handler, letting the GUI editor automatically generate a form and perform validation.
QinhItemsAPI.actions().registerPayloadSchema("myplugin:smart_cast") {
serializeMode = PayloadSerializeMode.JSON // or PLAIN
string(key = "skill", required = true, label = "Skill ID")
int(key = "power", default = "1", label = "Power")
double(key = "multiplier", label = "Multiplier")
bool(key = "silent", default = "false", label = "Silent?")
}Field types
| DSL | Type | Validation |
|---|---|---|
string(...) | STRING | Any text |
int(...) | INT | Integer |
double(...) | DOUBLE | Floating point |
bool(...) | BOOL | true/false/1/0 |
Serialization modes
- PLAIN: single field, the payload is a bare string.
- JSON: multiple fields, the payload is a JSON object, e.g.
{"skill":"fireball","power":"2"}.
For the GUI editing flow see Action Editor. Codec operations:
PayloadSchemaCodec.defaults(schema) // Default values
PayloadSchemaCodec.deserializeForEditor(schema, payload)
PayloadSchemaCodec.serialize(schema, editorMap)
PayloadSchemaValidator.validate(schema, fieldMap) // Result(ok, errors)5. Direct dispatch (bypassing triggers)
val report = QinhItemsAPI.actions().dispatch(player, item, "left_click")Returns a DispatchReport, containing the result of each handler.
6. Boundary recap
| Who does what |
|---|
| Server owners write "what happens when": triggers + refs |
| Developers write "how it happens": logic inside the handler |
Server owners compose handlers in YAML, developers tuck the complexity inside handlers. This boundary keeps the configuration fully editable in the GUI — see Action System Overview → Design.
7. Validation: handler references
On save / load, ActionRefDeclarationValidator validates: handler is non-empty, contains no spaces, and recommends the namespace:id format; unregistered handlers will produce a warning (and HANDLER_UNAVAILABLE at runtime).
Next steps
- Handler Reference: reference for built-in handlers
- Integration: how QinhSkills plugs in as a handler