Skip to content

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

kotlin
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:

ValueMeaning
HANDLEDExecuted successfully (will trigger consumption deduction / cooldown recording)
NOT_HANDLEDHandler rejected (e.g. validation failed)
HANDLER_UNAVAILABLEDependency not loaded

2. Context QinhActionContext

kotlin
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

kotlin
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:

yaml
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.

kotlin
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

DSLTypeValidation
string(...)STRINGAny text
int(...)INTInteger
double(...)DOUBLEFloating point
bool(...)BOOLtrue/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:

kotlin
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)

kotlin
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