Skip to content

JavaScript Scripting

EmakiAttribute registers a scripting module through the CoreLib scripting system. The module ID is attribute. emaki.module("attribute") returns a different object depending on context:

  • In ordinary trigger scripts or Mythic mechanic scripts it returns the module facade ScriptAttributeModuleApi, used for source management, item attribute PDC read/write, and damage calculation and application.
  • In Attribute-specific extension scripts (the extensions/attribute/ directory, loaded through register()) it returns the registration API, used to register runtime attributes, damage types, damage pipelines, attribute providers and damage hooks.

This page covers only EmakiAttribute-specific content. For the generic scripting mechanism see CoreLib JavaScript Scripting.

Always guard with available() before calling any business method. EmakiAttribute is a CoreLib softdepend.

Module facade (trigger script context)

In ordinary trigger scripts, emaki.module("attribute") returns the module facade.

MethodReturnsDescription
available()booleanWhether the module is available.

The module facade only has the available() probe; there is no ready() / apiVersion() / pluginName().

Source management

MethodReturnsDescription
registerSource(sourceId)booleanRegister an attribute source.
unregisterSource(sourceId)voidUnregister a source.
isRegisteredSource(sourceId)booleanWhether the source is registered.
registeredSources()listReturn a copy of the registered source list.

Item attribute PDC read/write

The methods below take an itemKey, which resolves an ItemStack from the current action context by attribute key.

MethodReturnsDescription
read(itemKey, sourceId)mapRead the attribute payload of a source.
readAll(itemKey)mapRead the payload of all sources.
write(itemKey, sourceId, attributes, meta)booleanWrite attributes; attributes is converted to a double map, meta to a string map.
clear(itemKey, sourceId)booleanClear a source.
clearAll(itemKey)voidClear all sources.

The payload projection seen by scripts contains six fields — sourceId, attributes, meta, conditions, schemaVersion, and updatedAt — produced by EmakiAttribute's own script/ScriptAttributeDtoMapper.

Damage calculation

MethodReturnsDescription
applyDamage(attacker, target, damageTypeId, baseDamage, damageContext)booleanActually apply damage to the target; returns false when target is null.
calculateDamage(attacker, target, damageTypeId, baseDamage, damageContext)mapCalculate only without applying; returns a damage result Map.
setDamageTypeOverride(entity, damageTypeId)voidSet a damage-type override for the entity's next damage.

attacker may be null, but target must resolve to a living entity. A null damageContext is treated as an empty Map. These methods are invoked through the EmakiAttribute service by reflection and return false / empty Map / no-op respectively when the service is missing.

Mythic mechanic script example

EmakiAttribute ships the Mythic mechanic script scripts/mythic/mythic_js_damage.js, showing how to call applyDamage from MythicMobs:

js
function mythicDamage(meta, args) {
  const caster = meta.caster();
  const target = meta.firstTarget();
  if (!target.exists()) {
    return { skipped: true, message: "No MythicMobs target." };
  }

  const damage = Number(read(args, "damage", read(args, "base", meta.power())));
  const damageType = String(read(args, "damage_type", read(args, "type", "default")));
  if (!Number.isFinite(damage) || damage <= 0) {
    return { success: false, message: "damage must be a positive number" };
  }

  const attribute = emaki.module("attribute");
  const applied = attribute.applyDamage(caster, target, damageType, damage, {
    source: "mythic_js",
    mythic_mechanic: meta.mechanic(),
    mythic_power: meta.power(),
    mythic_cause: meta.cause()
  });

  return {
    success: applied,
    message: applied ? "Mythic JS damage applied." : "Attribute damage pipeline unavailable.",
    output: { target: target.uuid(), damage: damage, damage_type: damageType }
  };
}

function read(object, key, fallback) {
  if (object == null) { return fallback; }
  if (typeof object.get === "function") {
    const value = object.get(key);
    return value == null ? fallback : value;
  }
  const value = object[key];
  return value == null ? fallback : value;
}

Action script example

EmakiAttribute ships the trigger-style example script scripts/examples/attribute_buff.js:

js
function main(ctx) {
  const attribute = emaki.module("attribute");
  if (!attribute.available()) {
    return { success: false, message: "EmakiAttribute module is unavailable" };
  }
  if (!emaki.player.exists()) {
    return { success: false, message: "No player context" };
  }
  return emaki.action.run("attribute_add", {
    effect_id: "js_example_buff",
    attribute: "attack",
    value: "5",
    duration_ticks: "10s"
  });
}

Registration API (extension script context)

In extension scripts under the extensions/attribute/ directory, CoreLib invokes the script's register() function and binds the registration API as emaki.module("attribute").

MethodReturnsDescription
registerAttribute(definition)booleanRegister a runtime attribute definition.
registerDamageType(definition)booleanRegister a damage type.
registerDamagePipeline(definition)booleanRegister a damage pipeline.
registerProvider(definition)booleanRegister an attribute contribution provider.
onDamage(definition)booleanRegister a damage event hook.

registerAttribute definition fields

Every field accepts both camelCase and snake_case spellings.

FieldTypeRequiredDefaultDescription
idstringyesnoneAttribute id; registration fails when blank.
displayNamestringnoemptyDisplay name.
valueKindstringnoFLATValue kind enum (uppercase, invalid falls back to FLAT).
targetTypestringnoGENERICTarget type enum.
targetIdstringnoemptyTarget id.
mmoItemsStatstringnoemptyMMOItems stat mapping.
defaultValuenumberno0Default value.
minValuenumbernonullMinimum value (nullable).
maxValuenumbernonullMaximum value (nullable).
allowNegativebooleannotrueWhether negative values are allowed.
priorityintno0Priority.
loreFormatIdstringnoemptyLore format id.
lorePatternslistnoemptyLore parsing regex list.
descriptionstringnoemptyDescription.
attributePowernumberno1.0Attribute power.

registerDamagePipeline definition fields

FieldTypeRequiredDefaultDescription
idstringyesnonePipeline id, also the matching damage type id.
functionstringyesnoneCalculation function name; calculate also works; fails when both id and function are blank.
timeoutMillisnumbernoengine defaultPer-call script timeout.

At runtime the pipeline is matched by damage type id (one damage type maps to one pipeline).

registerProvider definition fields

FieldTypeRequiredDefaultDescription
idstringyesnoneProvider id; registration fails when blank.
priorityintno0Priority.
functionstringnocollectCollection function name; collect also works.

The collection function receives an entity and returns an attribute map (or a structure with attributes / sourceId, or an iterable list); items whose attribute value is not numeric are skipped.

onDamage definition fields

FieldTypeRequiredDefaultDescription
idstringyesnoneHook id; registration fails when blank.
functionstringyesnoneCallback function name; registration fails when blank.
priorityintno0Priority; runs in descending order (higher first).
damageTypeslistnoempty (all types)Damage types the hook applies to.

The registerDamageType definition is parsed by the damage type model and requires a non-blank id.

Damage hook callback event API

A damage hook callback (the onDamage function) receives the damage event API:

MethodReturnsDescription
attacker() / target() / projectile()entityRelated entities.
damageTypeId()stringDamage type id.
baseDamage() / finalDamage()doubleBase / final damage.
setFinalDamage(value)voidSet final damage (clamped non-negative).
multiplyDamage(multiplier)voidMultiply (floor 0).
cancel() / cancelled()void / booleanCancel the event / whether cancelled.
critical() / roll()boolean / doubleWhether critical / roll value.
context()mapEvent context (explicit variables injected by script or skill).
stageValues()mapPer-stage damage values.
attackerAttribute(id) / targetAttribute(id)doubleSingle attribute value.
attackerAttributes() / targetAttributes()mapAll attributes.
attackerSnapshot() / targetSnapshot()mapAttribute snapshots.
meta(key) / setMeta(key, value) / meta()object / void / mapHook custom metadata.

Damage pipeline callback ctx API

A damage pipeline callback (the registerDamagePipeline function) receives the damage context API; besides the read methods above it also provides:

MethodReturnsDescription
cause()stringDamage cause name.
sourceDamage() / baseDamage()doubleSource / base damage.
variable(key) / setVariable(key, value) / variables()object / void / mapVariable read/write.
setDamage(value) / damage()void / doubleSet / read damage.
setCritical(value) / critical()void / booleanCritical flag.
setRecovery(value) / recovery()void / doubleRecovery value.
healAttacker(amount) / healTarget(amount)voidDirect healing.

The pipeline callback return object may include: success, cancelled, damage or finalDamage, critical, recovery, stageValues (or stages).

Registration example

EmakiAttribute ships the extension-style example script scripts/examples/js_fire_mastery.js:

js
function register() {
  const attribute = emaki.module("attribute");
  attribute.registerAttribute({
    id: "js_fire_mastery",
    displayName: "Fire Mastery",
    valueKind: "FLAT",
    targetType: "GENERIC",
    defaultValue: 0,
    minValue: 0,
    allowNegative: false,
    priority: 100,
    lorePatterns: ["Fire Mastery: \\+(?<value>[0-9.]+)"],
    description: "Runtime attribute registered by the bundled JavaScript extension example",
    attributePower: 1.0
  });

  attribute.registerProvider({
    id: "js_fire_mastery_provider",
    priority: 100,
    function: "collectFireMastery"
  });

  attribute.onDamage({
    id: "js_fire_mastery_damage",
    priority: 100,
    damageTypes: ["fire", "magic", "lightning"],
    function: "boostFireDamage"
  });
}

function collectFireMastery(entity) {
  return [];
}

function boostFireDamage(event) {
  const context = event.context();
  const mastery = Number(read(context, "js_fire_mastery", 0));
  if (!Number.isFinite(mastery) || mastery <= 0) {
    return { skipped: true, message: "js_fire_mastery context value not found" };
  }

  const multiplier = 1 + mastery / 100;
  event.multiplyDamage(multiplier);
  event.setMeta("js_fire_mastery_boost", mastery);

  return {
    success: true,
    message: "JavaScript fire mastery damage hook applied",
    output: { mastery: mastery, multiplier: multiplier, final_damage: event.finalDamage() }
  };
}

function read(object, key, fallback) {
  if (object == null) { return fallback; }
  if (typeof object.get === "function") {
    const value = object.get(key);
    return value == null ? fallback : value;
  }
  const value = object[key];
  return value == null ? fallback : value;
}

Enabling extension scripts

Attribute extension scripts load from extensions/attribute/ under the CoreLib script root:

text
plugins/EmakiCoreLib/scripts/extensions/attribute/js_fire_mastery.js
text
/corelib script reload

The example file is released to plugins/EmakiCoreLib/scripts/examples/ by default (the Mythic script is released to scripts/mythic/) and is not auto-enabled. Copy it into extensions/attribute/ and reload to enable it.

Notes

  • All registrations / hooks / pipelines / providers are silently skipped when the CoreLib scripting system is not enabled.
  • Damage hooks run in descending priority order (higher first), the opposite of Forge / Cooking / Level rules (ascending). Keep this in mind when mixing them.
  • A damage hook that throws is caught and logged without interrupting other hooks; once a hook cancels the event, subsequent hooks stop.
  • On reload, extension scripts first unregister previous registrations (providers, runtime attributes, damage types, hooks, pipelines), then re-run register(), and finally refresh caches and resync all players.
  • When CoreLib is missing the module degrades gracefully.