Skip to content

JavaScript 脚本

EmakiSkills 通过 CoreLib 的脚本系统注册脚本模块,模块 ID 为 skillsemaki.module("skills") 在不同上下文中返回不同对象:

  • 在普通触发脚本(runjs 动作、examples/ 等)中,返回查询门面 ScriptSkillsModuleApi,用于探针和查询已注册技能脚本动作。
  • 在 Skills 专属扩展脚本(extensions/skills/ 目录,通过 register() 加载)中,返回注册 API,用于注册自定义技能脚本动作。

本页只介绍 EmakiSkills 专属内容。通用脚本机制请阅读 CoreLib JavaScript 脚本系统

调用任何业务方法前,先用 available() 守卫。EmakiSkills 是 CoreLib 的 softdepend。

查询门面(触发脚本上下文)

在普通触发脚本中,emaki.module("skills") 返回查询门面。

方法返回说明
available()boolean模块是否可用。
hasScriptAction(actionId)boolean注册表中是否存在该技能脚本动作 id。
registeredScriptActions()list返回所有已注册技能脚本动作 id 列表。

查询门面只有上述方法,没有 ready() / apiVersion() / pluginName()。返回的列表在 JS 中用 .size() 取长度。

触发脚本可通过 emaki.context 读取技能上下文:

读取方式说明
emaki.context.placeholder("skills_skill_id")skills_skill_id技能 id(占位符形式)。
emaki.context.attribute("skill_id")skill_id技能 id(attribute 形式)。

触发示例

EmakiSkills 自带触发型示例脚本 scripts/examples/skills_upgrade_success.js

js
function main(ctx) {
  const skills = emaki.module("skills");
  const skillId = emaki.context.placeholder("skills_skill_id") || emaki.context.attribute("skill_id");
  if (emaki.player.exists()) {
    emaki.player.sendMessage("[EmakiJS] 技能升级成功脚本: " + skillId + " skills=" + skills.available());
  }
  return {
    success: true,
    output: {
      skill_id: String(skillId || ""),
      skills_available: skills.available(),
      registered_script_actions: skills.registeredScriptActions().size()
    }
  };
}

注册 API(扩展脚本上下文)

extensions/skills/ 目录下的扩展脚本中,CoreLib 调用脚本的 register() 函数并把注册 API 绑定为 emaki.module("skills")

方法返回说明
registerAction(definition)boolean注册一个自定义技能脚本动作。
unregisterAction(actionId)void按 id 注销技能脚本动作。

动作 definition 字段

字段类型必填默认说明
idstring动作 id,会被规范化;为空注册失败。
categorystringjavascript动作分类。
descriptionstring同 id描述。
parameterslist参数定义列表,见下表。
executionModestringSYNC执行模式,SYNCASYNC_IO(大小写不敏感,非法回退 SYNC)。
timeoutMillisnumber引擎默认超时单次脚本超时。
executestringexecute执行函数名。
validatestring空(不校验)校验函数名。

parameters 每个元素的字段:

字段类型必填默认说明
namestring参数名,会被规范化;为空跳过该参数。
typestringSTRING参数类型枚举(大小写不敏感,非法回退 STRING)。
requiredbool / stringfalse是否必填。
defaultValuestring默认值,仅可选参数使用。
descriptionstring描述。

执行与校验函数

  • execute(ctx, args):执行函数,ctx 是技能脚本上下文 API(见下),args 是参数 Map。
  • validate(args):校验函数,仅收到参数 Map。

执行模式 ASYNC_IO 在异步线程执行,SYNC 在调用线程同步执行。返回值映射为技能动作结果:{skipped:true} 跳过,成功返回 output / return / message{success:false} 失败。

技能脚本上下文 API(ctx)

方法返回说明
skillId()string当前技能 id。
triggerId()string触发器 id。
variable(key)string读取变量。
setVariable(key, value)void设置变量。
variables()map全部变量。
caster()entity施法者实体包装。
target()entity目标实体包装。
hasTarget()boolean是否有目标。
setTarget(entity)void设置目标。
targetLocation()map目标位置,键 world / x / y / z / yaw / pitch
runAction(actionId, arguments)boolean执行 CoreLib 动作。
runActionLine(line)boolean执行单行动作脚本。
castMythic(mythicSkillId)boolean释放 MythicMobs 技能。
castMythic(mythicSkillId, parameters)boolean带参数释放 MythicMobs 技能。
applyDamage(target, damageTypeId, baseDamage)boolean通过 EmakiAttribute 施加伤害。
applyDamage(target, damageTypeId, baseDamage, damageContext)boolean带伤害上下文施加伤害。

ctx.state 是跨调用共享状态访问器:get(key)set(key, value)has(key)remove(key)

注册示例

EmakiSkills 自带扩展型示例脚本 scripts/examples/js_lightning_strike.js

js
function register() {
  const skills = emaki.module("skills");
  skills.registerAction({
    id: "js_lightning_strike",
    category: "javascript",
    description: "攻击当前技能目标,并可选择通过EmakiAttribute传递伤害",
    executionMode: "SYNC",
    timeoutMillis: 1000,
    parameters: [
      { name: "damage", type: "DOUBLE", required: false, defaultValue: "10", description: "应用基础伤害" },
      { name: "damage_type", type: "STRING", required: false, defaultValue: "lightning", description: "Emaki属性伤害类型ID" }
    ],
    validate: "validateLightning",
    execute: "executeLightning"
  });
}

function validateLightning(args) {
  const damage = Number(read(args, "damage", "10"));
  if (!Number.isFinite(damage) || damage <= 0) {
    return { success: false, message: "伤害必须是一个正数" };
  }
  return true;
}

function executeLightning(ctx, args) {
  const caster = ctx.caster();
  const target = ctx.target();
  if (!target.exists()) {
    return { skipped: true, message: "未选择目标" };
  }

  const damage = Number(read(args, "damage", "10"));
  const damageType = String(read(args, "damage_type", "lightning"));

  let applied = false;
  const attribute = emaki.module("attribute");
  if (attribute.available()) {
    applied = attribute.applyDamage(caster, target, damageType, damage, {
      source: "js_lightning_strike",
      skill_id: ctx.skillId(),
      trigger_id: ctx.triggerId()
    });
  }
  if (!applied) {
    target.damage(damage);
  }

  return { success: true, message: "JavaScript闪电技能执行成功" };
}

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;
}

启用扩展脚本

Skills 扩展脚本加载自 CoreLib 脚本根目录下的 extensions/skills/

text
plugins/EmakiCoreLib/scripts/extensions/skills/js_lightning_strike.js
text
/corelib script reload

示例文件默认释放到 plugins/EmakiCoreLib/scripts/examples/,不会自动启用。需要启用时复制到 extensions/skills/ 再重载。

内置技能脚本动作

EmakiSkills 内置了一批 Java 技能脚本动作,技能配置可直接调用:

动作 id分类主要参数
messagefeedbacktext(必填);target(默认 caster)。
soundfeedbacksound(必填);volume=1;pitch=1;at=caster。
particlefeedbackparticle(必填);at=target;count=1;speed=0。
raytargetrange=16;width=1;save=target(保存到 state 的键)。
damagecombatamount(必填);target=target;damage_type=generic;element=空。
aoe_damagecombatamount(必填);radius=5;center=target;shape=sphere;filter=hostile;max_targets=20;exclude_caster=true。
projectilecombatspeed=1.5;gravity=0.05;lifetime=60;hit_radius=0.5;pierce=0;homing=false;particle=FLAME;damage=0;direction=look。
ignitecombatticks(必填);target=target。
healcombatamount(必填);target=caster。
mythicbridgeskill(必填,Mythic skill id)。

目标解析约定:caster / self / player 为施法者;look 为视线前方约 3 格;其它字符串先查 state 共享值,否则使用目标实体。

注意事项

  • 注册方法要求 CoreLib 脚本系统已启用,否则注册返回 false 或静默跳过。
  • applyDamage 经 EmakiAttribute 反射调用,EmakiAttribute 缺失时返回 false;target 须为生物实体。
  • CoreLib 缺失时模块自动降级。
  • 扩展脚本重载时会先注销旧注册动作,再重新执行 register(),避免热重载残留。