Skip to content

JavaScript Scripting

CoreLib provides a GraalJS-based JavaScript scripting system. Scripts can be invoked from action lists through the runjs action.

This page summarizes the CoreLib JavaScript scripting system for the English documentation.

Basic Usage

Place scripts under:

text
plugins/EmakiCoreLib/scripts/

Common directories include:

text
scripts/
├── global/
├── mythic/
├── extensions/
│   └── global/
├── templates/
└── examples/

These folders come from the default value of script.paths.create_directories and can be extended as needed. The script system is disabled by default: set script.enabled: true in config.yml to turn it on.

Example action:

yaml
actions:
  - 'runjs script=examples/hello.js'

Example script:

js
function main(ctx) {
  emaki.logger.info("Hello from Emaki JavaScript.");

  if (emaki.player.exists()) {
    emaki.player.sendMessage("[EmakiJS] Hello, " + emaki.player.name() + "!");
  }

  return true;
}

Exposed APIs

Scripts can use the injected emaki object:

APIPurpose
emaki.contextRead action context, placeholders, attributes, and arguments.
emaki.playerRead player information and send messages.
emaki.itemRead contextual ItemStack information.
emaki.actionDispatch CoreLib actions from scripts.
emaki.loggerWrite script-prefixed logs.
emaki.randomGenerate random values.
emaki.stateStore temporary shared state in the current action context.
emaki.textText utility methods.

Emaki Series dynamic module entry points

CoreLib only provides a dynamic module registry; it does not hard-code business plugin fields. When a business plugin is enabled, it registers its scripting capabilities under emaki.module("moduleId"); when disabled or reloaded, it unregisters them.

Common rules:

  • emaki.module(id) is equivalent to emaki.modules.get(id).
  • emaki.modules.ids() lists all currently registered module IDs.
  • Missing, disabled, or not-yet-registered business plugins return an unavailable module with available() === false.
  • Script examples for business modules should always check available() first to avoid softdepend failures.
  • emaki.item is the context ItemStack helper, not the EmakiItem plugin API; the EmakiItem plugin API uses emaki.module("items").

Baseline probe example:

js
function main(ctx) {
  const ids = emaki.modules.ids();
  emaki.logger.info("registered modules=" + ids.join(","));

  const level = emaki.module("level");
  if (level.available()) {
    emaki.logger.info("Level module is available");
  }

  return true;
}

Common module IDs

Module IDRegistered byWhat it coversDetails
attributeEmakiAttributePDC attribute payloads, dynamic attributes, damage hooks, damage calculation and application.Attribute JavaScript
strengthenEmakiStrengthenStrengthenable checks, state summaries, chance rules, and result hooks.Strengthen JavaScript
skillsEmakiSkillsSkill script action registry queries and custom skill action registration.Skills JavaScript
itemsEmakiItemItem definition lookup, shared item/components snapshots, definition/factory registration, and context item identification.Item JavaScript
forgeEmakiForgeForge rule and result hook registration, plus readiness probes.Forge JavaScript
cookingEmakiCookingResult rule and complete hook registration, plus readiness probes.Cooking JavaScript
gemEmakiGemSocket rule and set bonus registration, plus readiness probes.Gem JavaScript
levelEmakiLevelLevel exp lookup, exp rule, and level-up hook registration.Level JavaScript

Why not list every business method here

Business plugin scripting modules are registered by the corresponding plugin, and their parameters, return values, and behavior evolve with that plugin’s API. To avoid turning the CoreLib page into a stale monolithic table, CoreLib documents only the dynamic module mechanism and shared safety rules. Plugin-specific methods, action IDs, configuration examples, and full script examples belong on the corresponding plugin pages.

emaki.server

emaki.server is a controlled server API facade for trusted JavaScript scripts. It exposes common Bukkit operations without enabling unrestricted Java class access by default.

MethodDescription
pluginEnabled(name)Check whether a plugin is enabled.
onlinePlayers()Return wrapped online players.
player(nameOrUuid)Return a wrapped player.
world(name)Return a wrapped world.
broadcast(message)Broadcast a message.
dispatchCommandAsConsole(command)Run a console command; requires server_api.allow_console_command: true.
runSync(fn) / runSyncAndWait(fn)Switch to the main thread.
type(className)Extreme mode Java class access; requires server_api.allow_type_access: true.
js
function main(ctx) {
  const player = emaki.server.player(emaki.player.uuid());
  if (player.exists()) {
    player.sendMessage("Hello from JavaScript server API");
  }
  return true;
}

type() example:

js
function main(ctx) {
  const Bukkit = emaki.server.type("org.bukkit.Bukkit");
  emaki.logger.info("online=" + Bukkit.getOnlinePlayers().size());
  return true;
}

type() is close to Java-plugin-level power. Only enable it for trusted scripts.

JavaScript extension scripts

Besides one-shot runjs, business plugins can load extension scripts:

text
plugins/EmakiCoreLib/scripts/extensions/
├── global/
│   └── *.js
├── skills/
│   └── *.js
└── attribute/
    └── *.js

Extension scripts usually export register(api):

js
function register(api) {
  // Register global CoreLib actions/placeholders, skill actions, dynamic attributes, providers, or damage hooks.
}

Business plugins clear old registrations and rerun register during reload.

CoreLib bundles default resource scripts and releases missing files into the script repository:

  • scripts/extensions/global/js_broadcast_action.js: registers the js_broadcast JavaScript global action for any CoreLib action list.
  • scripts/extensions/global/js_placeholders.js: registers the example %js_online_count% and %js_player_world% placeholders.
  • scripts/extensions/global/js_event_examples.js: bundles controlled event examples for player_join, player_interact, and entity_damage_by_entity; disabled by default until you edit the script switch.

Business plugins can also release their own example scripts into CoreLib's script repository. For example, Skills releases scripts/extensions/skills/js_lightning_strike.js, while Attribute releases scripts/extensions/attribute/js_fire_mastery.js and scripts/mythic/mythic_js_damage.js. Their registration source, parameters, and behavior belong to the corresponding business plugin pages.

Bundled scripts are only copied when the target file is missing; modified server-side files are not overwritten.

Actions registered from extensions/global are tracked by owner/source. CoreLib reload only clears registrations from the corresponding script source, preventing stale JavaScript actions or placeholders from remaining active.

Global placeholder example:

js
function register(api) {
  api.registerPlaceholder({
    id: "js_online_count",
    function: "onlineCount"
  });
}

function onlineCount(ctx, args) {
  return emaki.server.onlinePlayers().size();
}

After registration, use %js_online_count% in text rendered through CoreLib placeholders.

Controlled event listener example:

js
function register(api) {
  api.onEvent({
    id: "right_click_log",
    event: "player_interact",
    priority: "NORMAL",
    ignoreCancelled: true,
    function: "onInteract"
  });
}

function onInteract(event, args) {
  if (event.rightClick()) {
    emaki.logger.info(event.player().name() + " right clicked with " + event.itemType());
  }
  return true;
}

Initial whitelist:

  • player_interact
  • player_join
  • entity_damage_by_entity

The event wrapper exposes safe basic information by default. event.cancel(), event.setCancelled(...), event.setJoinMessage(...), and event.setDamage(...) only work outside MONITOR priority; MONITOR listeners are treated as read-only.

Security Notes

The default configuration disables host class lookup, IO, threads, native access, and environment access. Production servers should keep these safe defaults unless they fully understand the risks.