简体中文
动作管道扩展 SDK
第三方插件把自身的 source / gate / action 注册进 Emaki 动作管道后, 服主即可在任意动作行中调用这些段. 注册规范覆盖注册口, 域声明, 约定声明, Folia 注意事项与验证方式
同一份规范随 API jar 以 javadoc 形式发布 (emaki.jiuwu.craft.corelib.api.action 包的 package-info 与 CoreActionExecutionDomain 枚举), 在 IDE 中悬停即可查看 -- 页面内容与该 javadoc 保持同步
注册口一览
六个注册口全部位于 EmakiCoreLibApi 门面, 按 owner (调用方插件) 作用域划分, 均返回可关闭句柄:
| 注册口 | 段角色 | 管道里怎么用 |
|---|---|---|
registerActionSource(Plugin, CoreActionSource) | 产生目标流 | 行首, 如 <你的段> radius=8 |
registerActionGate(Plugin, CoreActionGate) | 过滤/重排/控制流 | ... | <你的段> ... |
registerActionStage(Plugin, CoreActionStage) | 产生效果 | ... | <你的段> amount=3 |
registerActionTrigger(Plugin, CoreActionTrigger) | 触发入口 | 由触发器机制调用 |
registerTargetCondition(Plugin, CoreTargetCondition) | 目标条件类型 | filter '<你的条件>=值' |
registerTargetIdentityProvider(Plugin, CoreTargetIdentityProvider) | 外部系统身份 | 身份识别与 <系统 id>_level 条件 |
无 source 参数, 无按 id 注销
段只能经返回句柄的 close() 撤销, 或在 owner 插件被禁用时由 CoreLib 自动撤销. 插件无法注销其他插件注册的段. 以相同 id 重复注册 (同 owner) 会替换旧段
域声明清单 (必须)
每个 source/action 都必须实现 executionTarget(CoreStagePlanningContext), 返回声明了调度域的 CoreActionExecutionTarget.未声明域 (UNDECLARED) 的段在注册期即被拒绝 -- 该类段在 Folia 上会直接导致崩溃, 而非抛出可处理错误
CoreActionExecutionDomain | 含义 | 适用场景 |
|---|---|---|
SERVER_GLOBAL | 服务器全局 (主全局线程) | 查全服玩家, 改游戏规则, 写配置 |
CONTEXT_ENTITY | 当前目标实体的 owner 线程 | 改实体背包/传送/发光, 发消息给玩家 |
LOCATION_REGION | 位置/区域域 | 附近实体搜索, 在原点生成粒子/方块 |
ASYNC_COMPUTE | 纯异步计算 | 不允许同时要求目标 (targetRequirement 必须为 NONE, 否则注册被拒) |
UNDECLARED | 未声明 | 禁止 |
gate 经 CoreGateThread 调度: PURE (不触碰任何 Bukkit 状态, 在 Folia 上始终安全, 亦为默认值) 或 NEEDS_ENTITY_READ (读取实体状态)
约定声明清单
注册前对照这份清单, 每一项都在注册期校验, 写错会被拒绝并返回 reasonKey:
- id: 非空, 小写, 无
=; 与既有段同 id 注册会返回duplicate_id(含既有 owner 名) - 域: 见上节; 异步域 + 要求目标 =
async_needs_target - 参数表:
CoreStageParameter.required(name, type, desc)/optional(name, type, default, desc)/positional(name, type, desc). 编译期对未声明参数报unknown_argument, 缺必填报missing_required_argument - 目标需求:
targetRequirement()取NONE(不要目标, 如广播) /OPTIONAL/REQUIRED_ENTITY/REQUIRED_LOCATION/REQUIRED_ANY. 声明REQUIRED_*后无目标的调用会被跳过 (action.run.no_target), 不必自己判空 - 上下文键:
requiredContext()声明本段读取的CoreActionKey, 编译期对照触发阶段的PhaseContract检查, 缺键在加载期报错而非运行期 NPE - 参数类型:
CoreStageParameterType(STRING/INTEGER/DOUBLE/BOOLEAN/DURATION/ENTITY_TYPE/MATERIAL/PERCENTAGE/EXPRESSION/SOUND). 非占位符字面量在编译期按类型校验 (invalid_argument_type)
Folia 注意事项
- 15 个 Emaki runtime 全部声明
folia-supported: true. 段的执行域由声明决定, 不得假定始终位于主线程: 声明CONTEXT_ENTITY的段在实体 owner 线程执行, 声明LOCATION_REGION的段在位置域线程执行 - 调度域是收紧而非放宽: 声明越窄, 可调用的 Bukkit API 越受限; 声明过宽 (如把修改实体背包的段声明为
SERVER_GLOBAL) 在 Folia 上会抛错 - 需要跨域转移时使用
EmakiCoreLibApi.scheduling()提供的 global/entity/location/async 四类 owner-aware 调度; 异步 body 内不得访问 Bukkit - 段内部发起的异步任务不受 CoreLib 管理, owner 插件禁用时须由注册方自行取消
reload 后必须重放注册
动作段表在 CoreLib reload 时重建, 已注册的段随之失效. 必须在注册后登记重建回调:
java
EmakiCoreLibApi.onStageRegistryRebuilt(this, () -> {
registerMyStages();
});同一插件需要注册多个相互独立的回调时, 使用 addStageRegistryRebuildListener(owner, callback) (各回调独立追加, 返回可关闭句柄). 动画定义注册表不随 reload 重建, 无需重建回调
onEnable 的注册时序: 先检查 EmakiCoreLibApi.status().usable(); CoreLib 不可用时各注册口返回 inactive 句柄, 不抛异常
验证方式
- 列出已注册段:
<任意插件根命令> debug action registry(如/emakicorelib debug action registry) -- 按 source/gate/action 分组列出全部段与 owner 插件名, 第三方段可直接识别 - 静态体检:
debug action check "<你的段> 参数"-- parse + 校验, 输出全部诊断 (含列号与候选建议) - 干跑:
debug action run shadow "<管道行>"-- source/gate 真实求值, action 只回显不落地, 逐步观察该段在管道中的行为
完整示例
最小第三方扩展插件骨架 (注册 1 source + 1 gate + 1 action):
java
public final class ExampleExtensionPlugin extends JavaPlugin {
private CoreStageRegistration sourceHandle;
private CoreStageRegistration gateHandle;
private CoreStageRegistration actionHandle;
@Override
public void onEnable() {
if (!EmakiCoreLibApi.status().usable()) {
getLogger().warning("EmakiCoreLib 不可用,跳过动作段注册");
return;
}
registerStages();
EmakiCoreLibApi.onStageRegistryRebuilt(this, this::registerStages);
}
private void registerStages() {
sourceHandle = EmakiCoreLibApi.registerActionSource(this, new HighLevelPlayersSource());
gateHandle = EmakiCoreLibApi.registerActionGate(this, new FreshSpawnGate());
actionHandle = EmakiCoreLibApi.registerActionStage(this, new BroadcastConsoleAction());
}
@Override
public void onDisable() {
close(sourceHandle);
close(gateHandle);
close(actionHandle);
}
private static void close(@Nullable CoreStageRegistration handle) {
if (handle != null) {
handle.close();
}
}
}三段实现的要点:
java
final class HighLevelPlayersSource implements CoreActionSource {
@Override public String id() { return "example_high_level_players"; }
@Override public String description() { return "等级不低于 level 的在线玩家"; }
@Override public List<CoreStageParameter> parameters() {
return List.of(CoreStageParameter.optional("level", CoreStageParameterType.INTEGER, "10", "最低等级"));
}
@Override public CoreActionExecutionTarget executionTarget(CoreStagePlanningContext context) {
return CoreActionExecutionTarget.global();
}
@Override public CoreSourceResult select(CoreStageContext context, CoreResolvedArguments arguments) {
int min = arguments.getInt("level", 10);
List<CoreActionSubject> matched = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.getLevel() >= min) matched.add(CoreActionSubject.of(player));
}
return matched.isEmpty()
? CoreSourceResult.empty("example.source.no_match")
: CoreSourceResult.selected(matched);
}
}- gate: 实现
apply(context, inbound, arguments), 返回CoreGateResult.passed(保留的流)/halted(reasonKey)/invalid(reasonKey);threadNeed()按是否读实体选PURE/NEEDS_ENTITY_READ - action: 实现
execute(context, arguments), 操作context.currentTarget(), 返回CoreActionOutcome.success()/skipped(reasonKey)/failure(kind, reasonKey);executionTarget按实际改动的 Bukkit 状态声明域 - 自定义 reasonKey (如
example.source.no_match) 的文案在注册方插件自身的lang/中配置; 未配置时DiagnosticRenderer回退输出键本身, 不抛异常
参数类型速查
| 类型 | 编译期校验 (无占位符时) |
|---|---|
STRING / SOUND | 恒通过 |
INTEGER / DOUBLE | 数字解析 |
BOOLEAN | true / false |
DURATION | tick/时间格式 (1s, 20t...) |
ENTITY_TYPE / MATERIAL | 原版枚举/材质存在 |
PERCENTAGE | 0~1 小数或 x/y 分数 |
EXPRESSION | 表达式引擎可求值 |
读取参数经 CoreResolvedArguments: getString / getInt / getDouble / getBoolean / getDurationTicks / getChance / getEntityType / getMaterial / getExpression, 各方法均带 fallback; 声明了 defaultValue 的可选参数在省略时回落到默认值