动作系统 (Action System)
动作系统是 EmakiCoreLib 里最常打交道的部分之一。你可以把它理解为一条执行管线:给它一组动作指令,它会按顺序解析、判断条件、执行,并把结果反馈回来。所有模块都通过这套系统来触发游戏内的行为。
动作行语法
每条动作写成一行字符串,格式如下:
[@if=<cond>] [@chance=<prob>] [@delay=<delay>] [@ignore_failure] <actionId> [key=value ...]| 部分 | 必填 | 说明 |
|---|---|---|
@if=<cond> | 否 | 条件前缀,条件为真时才执行 |
@chance=<prob> | 否 | 概率前缀,取值 0.0 ~ 1.0 |
@delay=<delay> | 否 | 延迟前缀,单位为 tick(20 tick = 1 秒) |
@ignore_failure | 否 | 忽略失败,即使该动作执行失败也继续后续动作 |
<actionId> | 是 | 动作标识符(见下方内置动作列表) |
key=value ... | 视动作而定 | 动作参数,以空格分隔的键值对 |
方括号里的都是可选的控制前缀,可以按需组合。核心就是 actionId 加上它需要的参数。
控制前缀
控制前缀写在动作 ID 前面,用来决定这条动作"要不要执行"以及"什么时候执行"。
@if — 条件执行
只有条件表达式求值为 true 时才执行。条件的写法详见 条件系统。
actions:
- "@if=%player_level%>=10 givemoney amount=500"
- "@if=%player_world%==world_nether sendmessage message=<red>你正在地狱中!"@chance — 概率执行
按指定概率执行,0.0 表示永不执行,1.0 表示必定执行。适合做随机奖励、稀有掉落之类的场景。
actions:
- "@chance=0.5 givemoney amount=1000" # 50% 概率给予 1000 金币
- "@chance=0.1 createitem source=vanilla-diamond_sword amount=1" # 10% 概率给予钻石剑@delay — 延迟执行
延迟指定 tick 数后再执行。20 tick = 1 秒,所以 @delay=60 就是 3 秒后执行。
actions:
- "sendmessage message=<yellow>3 秒后传送..."
- "@delay=60 teleport world=world x=0 y=100 z=0" # 延迟 3 秒后传送@ignore_failure — 忽略失败
默认情况下,一条动作执行失败会中断整个动作链。加上这个前缀后,即使当前动作失败也会继续往下走。
典型场景:扣款可能因余额不足而失败,但你不希望因此阻断后续流程。
actions:
- "@ignore_failure takemoney amount=500" # 即使扣款失败也继续
- "sendmessage message=<green>流程继续执行"组合使用
这些前缀可以自由叠加,系统会按固定顺序处理:
actions:
- "@if=%player_level%>=20 @chance=0.3 @delay=20 givemoney amount=2000"提示
求值顺序是 @if → @chance → @delay → 执行动作。也就是说,先看条件是否满足,再掷骰子,最后才安排延迟执行。如果条件不满足,后面的步骤直接跳过,不会白白消耗随机数。
通用参数格式
时间格式
部分参数支持时间格式,可以用以下写法:
| 写法 | 含义 | 示例 |
|---|---|---|
40 | 40 tick(无单位默认 tick) | duration=40 |
40t | 40 tick | duration=40t |
2s | 2 秒 = 40 tick | duration=2s |
100ms | 100 毫秒 ≈ 2 tick | duration=100ms |
坐标格式
坐标参数支持绝对值和相对偏移:
| 写法 | 含义 |
|---|---|
100.5 | 绝对坐标 100.5 |
~ | 玩家当前位置 |
~5 | 玩家当前位置 + 5 |
~-3 | 玩家当前位置 - 3 |
内置动作列表
消息类
sendmessage
向玩家发送聊天消息。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
text | 是 | string | — | 消息内容,支持 MiniMessage 格式 |
actions:
- "sendmessage text=<green>欢迎回来,%player_name%!"sendactionbar
向玩家发送 ActionBar 消息。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
text | 是 | string | — | 消息内容,支持 MiniMessage 格式 |
broadcastmessage
向全服所有玩家广播消息,不需要 player 上下文。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
text | 是 | string | — | 消息内容,支持 MiniMessage 格式 |
sendtitle
向玩家发送标题。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
title | 是 | string | — | 主标题,支持 MiniMessage |
subtitle | 否 | string | "" | 副标题,支持 MiniMessage |
fade_in | 否 | time | 10t | 淡入时间 |
stay | 否 | time | 40t | 停留时间 |
fade_out | 否 | time | 10t | 淡出时间 |
actions:
- "sendtitle title=<gold>恭喜 subtitle=<yellow>你升级了! fade_in=10 stay=40 fade_out=10"效果类
playsound
向玩家播放声音。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
sound | 是 | string | — | 声音 key(Bukkit Sound 枚举名,大小写不敏感) |
volume | 否 | double | 1.0 | 音量 |
pitch | 否 | double | 1.0 | 音调 |
actions:
- "playsound sound=entity.player.levelup volume=1.0 pitch=1.0"spawnparticle
在指定位置生成粒子。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
particle | 是 | string | — | 粒子 key(Bukkit Particle 枚举名,大小写不敏感) |
count | 否 | int | 1 | 粒子数量 |
target | 否 | string | "player" | 生成位置:player = 玩家位置,location = 指定坐标 |
world | 否 | string | "" | 世界名(仅 target=location 时有效) |
x | 否 | string | "" | X 坐标,支持相对格式(仅 target=location) |
y | 否 | string | "" | Y 坐标,支持相对格式(仅 target=location) |
z | 否 | string | "" | Z 坐标,支持相对格式(仅 target=location) |
offset_x | 否 | double | 0 | X 方向随机偏移 |
offset_y | 否 | double | 0 | Y 方向随机偏移 |
offset_z | 否 | double | 0 | Z 方向随机偏移 |
extra | 否 | double | 0 | 额外参数(速度/颜色等,取决于粒子类型) |
actions:
- "spawnparticle particle=HEART count=10 offset_x=0.5 offset_y=0.5 offset_z=0.5"
- "spawnparticle particle=FLAME count=20 target=location world=world x=~0 y=~2 z=~0"经济类
givemoney、takemoney、setmoney 三个动作参数完全相同,分别对应给予、扣除、设置余额。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
amount | 是 | double | — | 金额 |
provider | 否 | string | "auto" | 经济提供者,见下表 |
currency | 否 | string | "" | 货币 ID(ExcellentEconomy 时必须填) |
provider 可选值:
| 值 | 行为 |
|---|---|
auto(默认) | 若 currency 非空 → 使用 ExcellentEconomy;否则 → 使用 Vault |
vault | 强制使用 Vault |
excellenteconomy | 强制使用 ExcellentEconomy(必须同时填 currency) |
actions:
# 使用默认提供者(Vault)
- "givemoney amount=100"
# 指定 ExcellentEconomy 的特定货币
- "takemoney amount=50 provider=excellenteconomy currency=gems"
# 设置余额
- "setmoney amount=1000 provider=vault"注意
takemoney 在余额不足时会返回 ECONOMY_INSUFFICIENT 错误并中断动作链。如果不希望中断,加 @ignore_failure 前缀。
物品类
createitem
创建临时物品并存入上下文,后续通过 senditem 发送给玩家。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
id | 是 | string | — | 临时物品 ID(后续 senditem / clearitem 引用) |
source | 否 | string | "" | 物品来源,见下方来源前缀表。别名:item、item_source |
amount | 否 | int | 1 | 数量,最小为 1 |
senditem
将临时物品发送到玩家背包,背包满时掉落在脚下。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
id | 是 | string | — | 临时物品 ID(由 createitem 创建) |
keep | 否 | boolean | false | true = 发送后保留临时存储;false = 发送后移除 |
clearitem
清除玩家指定槽位的物品。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
slot | 是 | string | — | 槽位,见下方槽位表 |
source | 否 | string | "" | 若指定,仅当槽位物品匹配该来源时才清除。别名:item、item_source |
slot 可选值:
| 值 | 说明 |
|---|---|
mainhand / main_hand / hand | 主手 |
offhand / off_hand | 副手 |
helmet | 头盔 |
chestplate / chest | 胸甲 |
leggings / legs | 护腿 |
boots | 靴子 |
0 ~ 35 | 背包槽位索引 |
hotbar_0 ~ hotbar_8 | 快捷栏 |
物品来源前缀:
| 前缀 | 插件 | 示例 |
|---|---|---|
minecraft- / mc- / v- | 原版 | minecraft-diamond_sword |
mmoitems- / mi- | MMOItems | mmoitems-SWORD:my_sword |
itemsadder- / ia- | ItemsAdder | itemsadder-namespace:item_id |
neigeitems- / ni- | NeigeItems | neigeitems-item_id |
nexo- / no- | Nexo | nexo-item_id |
craftengine- / ce- | CraftEngine | craftengine-namespace:item_id |
| 无前缀 | 原版(回退) | diamond_sword |
actions:
- "createitem id=reward source=minecraft-diamond_sword amount=1"
- "senditem id=reward"
- "clearitem slot=offhand"注意
物品来源用 - 分隔,不是 :。写 minecraft-diamond_sword 而不是 minecraft:diamond_sword。详见 物品来源系统。
玩家状态类
teleport
传送玩家到指定位置。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
x | 是 | string | — | X 坐标,支持相对格式 ~ / ~N |
y | 是 | string | — | Y 坐标,支持相对格式 |
z | 是 | string | — | Z 坐标,支持相对格式 |
world | 否 | string | "" | 世界名,空 = 当前世界 |
yaw | 否 | double | 0 | 水平朝向(度) |
pitch | 否 | double | 0 | 垂直朝向(度) |
actions:
- "teleport world=world x=0 y=100 z=0 yaw=90 pitch=0"
- "teleport x=~0 y=~10 z=~0" # 向上传送 10 格heal / damage / sethealth
| 动作 ID | 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|---|
heal | amount | 是 | double | 恢复血量,不超过最大血量 |
damage | amount | 是 | double | 扣除血量,最低降至 0 |
sethealth | amount | 是 | double | 设置血量,范围 [0, maxHealth] |
giveexp / takeexp / setexp
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
amount | 是 | int | — | 数量,最小为 0 |
mode | 否 | string | "points" | points = 经验点数;levels = 等级 |
actions:
- "giveexp amount=100"
- "giveexp amount=5 mode=levels" # 给予 5 级
- "takeexp amount=50 mode=points"药水效果类
givepotioneffect
给予玩家药水效果。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
type | 是 | string | — | 药水效果 ID(如 speed、strength、regeneration) |
level | 是 | int | — | 等级(1 = amplifier 0,2 = amplifier 1,以此类推) |
duration | 是 | time | — | 持续时间,支持时间格式 |
ambient | 否 | boolean | false | 是否为环境效果(粒子更稀疏) |
particles | 否 | boolean | true | 是否显示粒子 |
icon | 否 | boolean | true | 是否显示 HUD 图标 |
type 格式:使用 Minecraft 原版效果 ID,如 speed、strength、minecraft:regeneration。不带命名空间时自动补 minecraft: 前缀,空格自动替换为 _。
removepotioneffect
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
type | 是 | string | — | 药水效果 ID(同上) |
clearpotioneffects
无参数,清除玩家所有药水效果。
actions:
- "givepotioneffect type=speed level=2 duration=10s ambient=false particles=true"
- "removepotioneffect type=poison"
- "clearpotioneffects"命令类
runcommandasplayer
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
command | 是 | string | — | 以玩家身份执行的命令(自动去除开头的 /) |
runcommandasop
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
command | 是 | string | — | 以临时 OP 权限执行,执行后恢复原状态 |
runcommandasconsole
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
command | 是 | string | — | 以控制台身份执行,无需 player 上下文 |
actions:
- "runcommandasplayer command=spawn"
- "runcommandasop command=gamemode creative %player_name%"
- "runcommandasconsole command=say 服务器公告:%player_name% 获得了成就!"注意
runcommandasop 会临时给玩家 OP 权限来执行命令,用的时候要小心。
模板类
usetemplate
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
name | 是 | string | — | 模板名称(在 config.yml 的 action.templates 中定义) |
actions:
- "usetemplate name=level_up_reward"属性类(EmakiAttribute 扩展)
attribute_add
叠加临时属性。同一个 effect_id 重复调用会累加数值并刷新过期时间。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
effect_id | 是 | string | — | 效果唯一标识,用于后续移除或覆盖 |
attribute | 是 | string | — | 属性 ID(如 physical_attack) |
value | 是 | double | — | 叠加的属性值 |
duration_ticks | 是 | time | — | 持续时间,必须 > 0,支持时间格式 |
attribute_set
覆盖临时属性。直接设定属性值,后写入的覆盖先写入的。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
effect_id | 是 | string | — | 效果唯一标识 |
attribute | 是 | string | — | 属性 ID |
value | 是 | double | — | 设置的属性值 |
duration_ticks | 是 | time | — | 持续时间,必须 > 0,支持时间格式 |
attribute_remove
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
effect_id | 是 | string | — | 要移除的临时效果 ID |
actions:
# 叠加 50 点物理攻击,持续 10 秒
- "attribute_add effect_id=buff_atk attribute=physical_attack value=50 duration_ticks=10s"
# 覆盖物理防御为 100,持续 200 tick
- "attribute_set effect_id=override_def attribute=physical_defense value=100 duration_ticks=200"
# 移除指定效果
- "attribute_remove effect_id=buff_atk"提示
临时属性数据只存在内存中,服务器重启后会丢失。如果需要跨重启的持久 Buff,应该用 PDC 属性。
attributedamage
对玩家造成属性伤害,走 EmakiAttribute 的伤害计算流程。
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
amount | 是 | double | — | 基础伤害值 |
type | 否 | string | "" | 伤害类型 ID,空 = 使用默认伤害类型 |
cause | 否 | string | "CUSTOM" | Bukkit DamageCause 枚举名(大写) |
cause 常用值:CUSTOM、MAGIC、FIRE、FALL、POISON、PROJECTILE、ENTITY_ATTACK
actions:
- "attributedamage amount=50 type=spell cause=MAGIC"ActionContext
每次执行动作时,系统会创建一个 ActionContext 来携带执行所需的上下文信息。你可以把它看作动作执行的"环境"。
| 字段 | 类型 | 说明 |
|---|---|---|
sourcePlugin | Plugin | 触发动作的插件实例 |
player | Player | 目标玩家 |
phase | String | 当前执行阶段标识(如 on_click, on_complete) |
silent | boolean | 是否静默模式(不发送反馈消息) |
placeholders | Map<String, String> | 自定义占位符映射 |
attributes | Map<String, Object> | 附加属性(可传递任意对象) |
sharedState | Map<String, Object> | 同一动作链中共享的状态 |
ActionContext context = ActionContext.builder()
.sourcePlugin(plugin)
.player(player)
.phase("on_reward")
.silent(false)
.placeholder("reward_amount", "500")
.placeholder("reward_type", "金币")
.attribute("source_event", event)
.build();
actionService.execute(actionLines, context);提示
sharedState 在同一动作链的所有动作之间共享。比如前一个动作算出了某个结果,可以存进 sharedState,后面的动作直接读取。这在需要多步骤协作的场景下很有用。
模板系统
如果你发现好几个地方都在写相同的动作序列,可以把它们抽成模板。定义一次,到处引用。
定义模板
在 config.yml 的 action.templates 下定义:
action:
templates:
level_up_reward:
- "playsound sound=entity.player.levelup volume=1.0 pitch=1.0"
- "spawnparticle particle=TOTEM count=30 offsetX=1 offsetY=1 offsetZ=1"
- "sendtitle title=<gold>升级! subtitle=<yellow>恭喜你升级了 fadeIn=10 stay=40 fadeOut=10"
- "givemoney amount=500"
- "giveexp amount=100"
error_feedback:
- "playsound sound=entity.villager.no volume=1.0 pitch=0.8"
- "sendmessage message=<red>操作失败,请稍后再试。"使用模板
在任何动作列表中用 usetemplate 引用即可:
actions:
- "sendmessage message=<green>你完成了任务!"
- "usetemplate template=level_up_reward"模板里的动作会被展开并按顺序执行。控制前缀也可以加在 usetemplate 上,这样整个模板的执行都会受到控制:
actions:
- "@if=%quest_completed%==true @chance=0.5 usetemplate template=level_up_reward"占位符系统
动作参数里可以用 %placeholder% 格式的占位符,执行时会被替换成实际值。
内置占位符
| 占位符 | 说明 |
|---|---|
%player_name% | 玩家名称 |
%player_uuid% | 玩家 UUID |
%player_world% | 玩家所在世界名称 |
%player_x% | 玩家 X 坐标 |
%player_y% | 玩家 Y 坐标 |
%player_z% | 玩家 Z 坐标 |
%phase% | 当前执行阶段 |
自定义占位符
通过 ActionContext 的 placeholders 传入的键值对会直接变成可用的占位符:
ActionContext context = ActionContext.builder()
.player(player)
.placeholder("item_name", "钻石剑")
.placeholder("item_count", "3")
.build();actions:
- "sendmessage message=<green>你获得了 %item_count% 个 %item_name%!"物品展示标签
用 <show_item> 标签可以在聊天消息里嵌入物品的悬浮展示,玩家把鼠标移上去就能看到物品的详细信息(名称、Lore、附魔等)。
actions:
- "sendmessage message=<green>你获得了 <show_item>!"提示
要让 <show_item> 正常工作,需要在 ActionContext 的 attributes 里放入对应的 ItemStack 对象。
PlaceholderAPI 集成
如果服务器装了 PlaceholderAPI,CoreLib 会自动识别并解析 PAPI 占位符。你可以在动作参数里直接用任何已注册的 PAPI 占位符:
actions:
- "sendmessage message=<gray>你的余额:%vault_eco_balance%"
- "@if=%statistic_play_one_minute%>=72000 sendmessage message=<gold>你已游玩超过 1 小时!"提示
CoreLib 内置占位符的优先级高于 PlaceholderAPI。如果两边有同名的占位符,会用 CoreLib 内置的值。
错误类型
动作执行过程中可能产生以下错误。其中 CONDITION_NOT_MET 和 CHANCE_NOT_MET 严格来说不算错误,只是正常的跳过行为。
| 错误枚举 | 说明 |
|---|---|
UNKNOWN_ACTION | 未知的动作 ID |
INVALID_SYNTAX | 动作行语法错误 |
MISSING_PARAMETER | 缺少必需参数 |
INVALID_PARAMETER | 参数值无效 |
CONDITION_NOT_MET | 条件不满足(正常跳过) |
CHANCE_NOT_MET | 概率未命中(正常跳过) |
EXECUTION_FAILED | 动作执行过程中发生异常 |
PLAYER_OFFLINE | 目标玩家不在线 |
ECONOMY_INSUFFICIENT | 经济余额不足 |
ITEM_SOURCE_NOT_FOUND | 物品来源无法解析 |
TEMPLATE_NOT_FOUND | 引用的模板不存在 |
actionService.execute(actionLines, context).whenComplete((result, ex) -> {
if (result.hasErrors()) {
for (ActionError error : result.getErrors()) {
logger.warning("动作执行错误: " + error.type() + " - " + error.message());
}
}
});