Affix Scripts (JS)
In: Developer · Same group: Events · Provider & Bridges Related: Affix System · Realms & Keystones
Complex effects that declarative affixes (count / level / environment / state / loot) can't express can drop down to JS script affixes. The engine reuses QinhCoreLib's GraalJS (the same runtime as QS). This page covers the reference format, the ctx context, fallback behavior, and gives a complete example.
🔑 Architectural red line: scripts add "world / player interaction" effects to affixes (apply potions, give items, give money, add attributes) — they are not for QR to compute damage itself. Like other affix categories, they're bound by the danger budget.
1. Attaching a script in affixes.yml
Set an affix's effect.type to script, and use script to point at a function in the script:
affixes:
blood_ritual:
name: "§4Blood Ritual"
lore: ["§7A trial of blood descends when the realm activates"]
category: ENV
danger: 25 # Danger value (bound by realm.tiers.danger-budget)
reward: 20
min-tier: 3
effect:
type: script # ★ Use a script as the effect
script: "qinhruins:affix_example.js:onActivate" # namespace:file:functionReference format
qinhruins:path.js[:functionName]| Segment | Notes |
|---|---|
qinhruins: | Namespace prefix (QR registers it to this namespace via RuinScriptBridge.register at startup) |
path.js | File path relative to the QR script directory plugins/QinhRuins/scripts/ |
:functionName | Optional; specifies a function in the file to call. Omit it to call the conventional default function (main in the example file) |
Script files go in plugins/QinhRuins/scripts/; the example affix_example.js is released automatically on first startup.
2. When it fires
The script runs at the instant the realm activates, once for each player present at the time (the ctx.player() that onActivate(ctx) receives is the current player). It's fire-and-forget: a thrown exception only logs and doesn't affect the realm activation itself.
3. Two global objects: ctx and qcl
The GraalJS runtime binds two global objects to the script (provided by CoreLib's QinhScriptApi):
ctx— read-only context: get the player, read injected variables.qcl— action API: give items, apply potions, add attributes, give money, log, switch threads, etc.
Caution
Common pitfall: action methods are on qcl, not ctx. ctx only has player() / get() / set() / vars(). Writing ctx.itemGive(...) errors because the method doesn't exist.
ctx — context (reading variables)
The script can read only three variables (injected by the realm runtime):
| Call | Meaning |
|---|---|
ctx.player() | The current player (Bukkit Player, may be null, null-check needed) |
ctx.get("tier") | Realm tier (Int) |
ctx.get("affix") | The current affix id (String) |
ctx.get("danger") | The current affix's danger value (Int) |
Only these three keys (
tier/affix/danger). There is noskill,level,var_*, etc. — those are QinhSkills script keys, which QR doesn't inject.
qcl — action API
| Call | Effect |
|---|---|
qcl.itemGive(ref, amount) | Give the current player an item; ref supports prefixes like minecraft:, qinhitems:, mythicmobs:, qinhruins: |
qcl.addPotion(target, type, ticks, level) | Apply a potion effect to the target (type is a Bukkit PotionEffectType name, e.g. "SPEED") |
qcl.heal(n) | Heal the current player |
qcl.damage(target, n) | Deal damage to the target |
qcl.buff(target, key, value, op, ticks, source) | Add a CoreLib attribute buff (op like FLAT / RELATIVE / MULTIPLY, source used for per-source stacking) |
qcl.economyDeposit(n, provider, currency) | Give currency (also economyHas / economyWithdraw) |
qcl.runSyncLater(ticks, function) | Run the callback on the main thread after some ticks (also runSync / runSyncAndWait) |
qcl.logInfo(msg) | Log info (also logWarn / logError) |
Caution
Scripts may be invoked in an async context. Wrap any operation that touches the world / entities (teleport, modify blocks, spawn entities, etc.) in qcl.runSyncLater(0, ...) to switch back to the main thread, otherwise it may throw a threading exception. The qcl action methods for applying potions / giving items / giving money already handle threading themselves.
4. Complete example
On first startup QR releases an example affix_example.js into plugins/QinhRuins/scripts/. Below is a directly runnable form (read data with ctx, do actions with qcl):
function onActivate(ctx) {
var player = ctx.player();
if (!player) return;
var tier = ctx.get("tier") || 1;
var amp = Math.min(tier, 5) - 1; // The higher the tier, the stronger the buff
// Give the player "Speed" for 30s, level scaling with tier
qcl.addPotion(player, "SPEED", 20 * 30, amp);
// High tiers get a bonus golden apple
if (tier >= 5) {
qcl.itemGive("minecraft:golden_apple", 1);
}
player.sendMessage("§4[Blood Ritual] §7The realm infuses you with §cT" + tier + " §7power…");
qcl.logInfo("[QR affix] " + player.getName() + " triggered script affix T" + tier);
}
// Default function: called when the reference omits :functionName, i.e. main
function main(ctx) {
onActivate(ctx);
}Tip
Remember the prefix division of labor: read data with ctx (player() / get()), do actions with qcl (itemGive / addPotion / …). Writing ctx.itemGive(...) errors because the method doesn't exist.
Reference forms:
qinhruins:affix_example.js:onActivate— explicitly callonActivateqinhruins:affix_example.js— omit the function name, callmain
5. Fallback (when the engine is unavailable)
QR bridges QCL's QinhScriptBridge via reflection. When the GraalJS runtime isn't ready, script affixes are silently skipped (a warning is logged, the realm's other affixes still take effect):
[QR-JS] GraalJS not ready, skipping affix script ref=qinhruins:affix_example.js:onActivate
(needs CoreLib to pull GraalJS and javascript.enabled=true)GraalJS requires the Paper / Purpur runtime to pull in the GraalJS libraries with javascript.enabled=true. If your script affix doesn't take effect, check this log first.
6. When to use scripts, when to use declarative affixes
| Need | Use |
|---|---|
| Spawn ×N / mob level +N / no healing / time limit / slowness weakness / greedy gilding, etc. | Declarative affixes (no engine dependency, see Affix System) |
| Give different items by tier / give money / add temporary attributes / complex branching | type: script JS affix |
Prefer declarative — zero engine dependency, never falls back.
Next
- Affix System — the five affix categories and the danger budget
- Events — events are another extension point beyond scripts