Skip to content

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:

yaml
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:function

Reference format

qinhruins:path.js[:functionName]
SegmentNotes
qinhruins:Namespace prefix (QR registers it to this namespace via RuinScriptBridge.register at startup)
path.jsFile path relative to the QR script directory plugins/QinhRuins/scripts/
:functionNameOptional; 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):

CallMeaning
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 no skill, level, var_*, etc. — those are QinhSkills script keys, which QR doesn't inject.

qcl — action API

CallEffect
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):

javascript
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 call onActivate
  • qinhruins:affix_example.js — omit the function name, call main

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

NeedUse
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 branchingtype: 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