Scripts
Previous: Costs, Conditions & Variables · Next: Configuration File
Declarative conditions handle the simple gates: is the level high enough, is there a target, is the player in a certain world. But some checks can't be expressed that way: reading data from other plugins, doing combined calculations, handing out rewards after a successful cast... That's where JS scripts come in: script.pre_js (the gate) and script.post_js (after-the-fact side effects).
📌 Use
conditionsfirst, reach for scripts only when you can't. Scripts are more powerful but more complex—don't pull in JS for anything a declarative condition can already solve.
🖼️ [Image placeholder] Where the pre_js gate / post_js after-the-fact hooks sit in the pipeline · suggested
assets/script-hooks.png
🪝 The two hooks
script:
pre_js: "qinhskills:demo.js:canCast" # Before the cast: return false to block it
post_js: "qinhskills:demo.js:onCast" # After a successful cast: run side effects| Hook | Timing | Return value | Blocks the cast? |
|---|---|---|---|
pre_js | Gate phase, before the cast goes out | Returning false → blocked, no resources spent, result code CONDITION_FAILED | Yes |
post_js | After a successful cast | Return value ignored | No (fire-and-forget) |
post_js is "fire-and-forget"—even if it fails, it doesn't undo the fact that the skill already went out.
🔖 Reference format
Scripts use a three-part "namespace : path : function name" reference:
namespace:path.js[:functionName]QS uses the qinhskills namespace, for example:
pre_js: "qinhskills:demo.js:canCast"
# └─ namespace └─ file └─ function nameScript files are managed centrally by QCL's script loader and live in QCL's conventional scripts directory. Scripts under the
qinhskillsnamespace belong to this QS set.
⚙️ Where the script engine comes from
The script engine is QinhCoreLib (QCL)'s GraalJS, not something QS ships itself. This means:
- You need Paper / Purpur to pull in the GraalJS runtime libraries (QCL config
javascript.enabled). - If the engine isn't ready, nothing crashes—QS degrades gracefully (see below).
📥 Injected objects
Two global objects are available inside scripts:
ctx — skill execution context
| Method | Purpose |
|---|---|
ctx.player() | Returns the Player (the caster) |
ctx.get(key) | Read a variable |
ctx.set(key, value) | Write a variable |
ctx.vars() | Get the entire variable table |
Variables readable via ctx.get(key) (the same set described in Costs, Conditions & Variables):
| Key | Source / meaning |
|---|---|
skill | Skill id (note: it's skill, not skillId) |
level | The player's level in this skill |
mode | Trigger mode (default if none) |
source | Trigger source (e.g. PLAYER / EVENT_LISTENER / COMMAND) |
player | Player name |
toggle_state | on / off (only present for toggle skills) |
has_target / target_type / target_uuid | Only present when there is a target (crosshair / target acquisition / passive-locked target) |
var_<name> | Skill variables, from both variables: and levels.params:, with a uniform var_ prefix (e.g. var_element, var_power) |
⚠️ Note: scripts do not have the keys
skillId/castMode/targetCount/slot/param_. Skill variables and level params both use thevar_prefix (notparam_); the key for the skill id isskill.
qcl — QCL global utilities
| Method | Purpose |
|---|---|
qcl.logInfo(msg) | Write a log line |
qcl.itemGive(player, item) | Give an item |
qcl.economy*(...) | Economy-related (withdraw / deposit / check balance, etc.) |
qcl.runSync(runnable) | Switch a piece of logic back to the main thread (use when touching the world / entities) |
🧪 Example 1: pre_js gate (only cast at level ≥ 2)
Skill yml:
script:
pre_js: "qinhskills:demo.js:canCast"In demo.js:
// Block if level < 2; returning false → QS spends no resources, result code CONDITION_FAILED
function canCast(ctx) {
var level = ctx.get("level"); // The player's level in this skill
if (level == null || level < 2) {
var p = ctx.player();
p.sendMessage("§c该技能需要 2 级才能施放");
return false; // ← block the cast
}
return true; // ← allow it through
}This simple example could actually be done with
conditions: ["player_level:>=2"]too. The real use forpre_jsis cases that declarative conditions can't express: "read another plugin's data," "score multiple conditions together," and the like.
🎁 Example 2: post_js after-the-fact side effects (send a message / give an item)
Skill yml:
script:
post_js: "qinhskills:demo.js:onCast"In demo.js:
// Runs after the skill is successfully cast: send a message, give a reward item
function onCast(ctx) {
var p = ctx.player();
p.sendMessage("§a技能命中!获得战利品 §e×1");
// Touching the world / giving items is safer on the main thread
qcl.runSync(function() {
qcl.itemGive(p, "DIAMOND");
});
qcl.logInfo("[demo] " + p.getName() + " 触发了 onCast 副作用");
}post_js has no bearing on whether the skill itself goes out—it just "does a little something afterward."
🛟 Degradation behavior (when the engine isn't ready)
When GraalJS isn't installed / isn't enabled, QS won't error out or hang—it degrades safely instead:
| Hook | Degraded behavior |
|---|---|
pre_js | Doesn't block (treated as returning true, casts as normal) |
post_js | No-op (does nothing) |
When degrading, it logs a diagnostic telling you the engine isn't ready, but does not interrupt the cast. So even if the script environment isn't set up, skills still cast—only the script logic is inert—which is safer in production.
📚 Further reading
- Script API — full method signatures for
ctx/qcland more usage - Costs, Conditions & Variables — simple gates with
conditions, plus the variable prefixes scripts can read - Configuration File — how to read script diagnostic logs when
debug: true