跳到主要内容

脚本系统

Baikiruto 当前默认内置的脚本类型是 Fluxon,绝大多数服主日常仍然只需要写 Fluxon。 不过这次源码已经把脚本处理改成“脚本类型注册表 + 脚本源”结构,所以配置层现在既兼容旧的纯字符串写法,也支持显式声明 type 的结构化写法。

脚本类型

写法 1:直接写脚本文本(兼容旧配置)

scripts:
build: |
return item

这种写法等价于“type: fluxon + 脚本内容”,适合继续沿用旧配置。

写法 2:结构化脚本源(推荐跟着新默认示例写)

scripts:
build:
type: fluxon
script: |
return item

event:
on_use:
type: fluxon
script: |
&ops.setData("last_trigger", "use")
return item

data-mapper:
durability_line:
type: fluxon
script: |
def current = &data["durability_current"] ?: &data["durability"] ?: 0
def max = &data["durability"] ?: 0
return current.toString() + "/" + max.toString()

这套结构当前可用于 scriptseventmetas.<id>.scriptsi18n.<locale>.scriptsi18n.<locale>.eventdata-mapper

实际规则:

  • type 默认就是 fluxon
  • engine 可以当成 type 的别名
  • scriptsourcecontent 三个字段都能放脚本文本
  • 如果你把脚本写成字符串列表,loader 会按换行拼成一段完整脚本
  • 脚本类型会先规范化:转小写,并把 - 变成 _
  • 如果配置里写了未注册的脚本类型,这条脚本会被跳过,并在控制台输出 log-script-type-missing

生命周期脚本

在物品数据流的特定阶段执行:

scripts:
build:
type: fluxon
script: |
return item

release:
type: fluxon
script: |
&ops.setData("last_release", "release")
return item

release_display: |
return item

drop: |
return item

上面故意混用了两种写法:build / release 展示新结构,release_display / drop 保留旧写法,实际两种都能正常加载。

事件脚本

响应玩家交互和游戏事件:

event:
on_right_click:
type: fluxon
script: |
&ops.setCooldown(80)
&ops.setData("last_trigger", "right_click")
return item

on_attack:
type: fluxon
script: |
&ops.setData("last_trigger", "attack")
return item

on_damage:
type: fluxon
script: |
&ops.damage(1)
&ops.setData("last_trigger", "damage")
return item

如果你不打算区分脚本类型,继续写成旧的 on_right_click: | 这种形式也完全兼容。

触发器完整列表

源码中定义了 31 个触发器。每个触发器都支持别名写法,所以你既可以写标准 key,也可以沿用 on_xxx 这类老习惯。

生命周期触发器

触发器配置写法说明
BUILDbuild / on_build物品构建时执行
RELEASErelease / on_release转换为 ItemStack 时执行
RELEASE_DISPLAYrelease_display / on_release_display显示渲染后执行
DROPdrop / on_drop物品销毁时执行(逆序)

常用交互触发器

触发器配置写法说明
USEon_use / onuse使用物品;右键使用和消耗流程都会命中
INTERACTon_interact / oninteract玩家交互(左右键空气 / 方块的总入口)
LEFT_CLICKon_left_click / onleftclick左键点击
RIGHT_CLICKon_right_click / onrightclick右键点击
RIGHT_CLICK_ENTITYon_right_click_entity / onrightclickentity右键点击实体
CONSUMEon_consume / onconsume消耗物品(吃下 / 喝下)
PICKUPon_pickup / on_pick / onpickup / onpick拾取物品
SELECTon_select / onselect快捷栏切换到该物品;玩家进服、重生、切世界后的刷新流程也会补触发
ASYNC_TICKon_async_tick / on_tick / onasynctick / ontick异步周期轮询,可配间隔与条件过滤

注意:ASYNC_TICK 在异步线程执行。全局默认间隔来自 config.ymloperations.async-tick.default-interval(默认 100 tick),单个物品还能用 meta.async-tick 覆盖;不要直接在里面操作 Bukkit API。

战斗与场景触发器

触发器配置写法说明
ATTACKon_attack / on_sword / onattack / onsword攻击实体
DAMAGEon_damage / ondamage物品耐久受损时触发
BLOCK_BREAKon_block_break / onblockbreak破坏方块
ITEM_BREAKon_item_break / onitembreak物品耐久归零损坏
DEATHon_death / ondeath玩家死亡时,对身上受追踪的 Baikiruto 物品逐个触发
KILLon_kill / onkill玩家击杀实体时触发
HURTon_hurt / onhurt玩家受伤时,对身上受追踪的 Baikiruto 物品逐个触发
SHOOTon_shoot / onshoot发射弹射物时触发
PROJECTILE_HITon_projectile_hit / onprojectilehit自己发出的弹射物命中时触发
SNEAKon_sneak / onsneak潜行切换时,对身上受追踪的 Baikiruto 物品逐个触发
SPRINTon_sprint / onsprint疾跑切换时,对身上受追踪的 Baikiruto 物品逐个触发
JUMPon_jump / onjump跳跃检测命中时,对身上受追踪的 Baikiruto 物品逐个触发
RESPAWNon_respawn / onrespawn玩家重生后,对身上受追踪的 Baikiruto 物品逐个触发

背包与装备触发器

触发器配置写法说明
SWAP_TO_MAINHANDon_swap_to_mainhand / onswaptomainhand主副手交换后进入主手
SWAP_TO_OFFHANDon_swap_to_offhand / onswaptooffhand主副手交换后进入副手
INVENTORY_CLICKon_inventory_click / on_click / oninventoryclick / onclick背包点击,包括数字键热键切换
EQUIPon_equip / onequip通过背包装备槽点击 / Shift 点击穿戴时触发
UNEQUIPon_unequip / onunequip通过背包装备槽卸下时触发

槽位上下文的实际用法

DEATHHURTSNEAKSPRINTJUMPRESPAWNEQUIPUNEQUIPASYNC_TICK 这类触发器,脚本上下文里会附带 slot,可直接按槽位做分流。

常见槽位值:

  • MAINHAND
  • OFFHAND
  • HEAD
  • CHEST
  • LEGS
  • FEET
event:
on_equip: |
if (slot == "CHEST") {
&ops.setData("last_equip_slot", slot)
}
return item

on_hurt: |
if (slot == "OFFHAND") {
&ops.setData("last_guard_slot", slot)
}
return item

ASYNC_TICK 还会额外注入 slot_index,方便你区分背包格位。主手会给 MAINHAND,其他快捷栏格会给 HOTBAR,主背包区会给 INVENTORY,护甲与副手则会给具体槽位。

脚本上下文变量

所有脚本都可以访问以下变量:

变量类型说明
&playerPlayer当前玩家
&senderCommandSender命令发送者
&itemItemStack物品实例(可直接修改)
&streamItemStream数据流对象
&opsItemScriptOps操作 API
&eventEvent原始 Bukkit 事件对象
&ctxMap完整上下文数据
&itemIdString物品 ID
&triggerString触发器名称(枚举名小写,如 left_click
&slotString槽位上下文(可选;如 MAINHANDOFFHANDCHEST
slot_indexInt背包槽位索引(可选;主要用于 ASYNC_TICK
&localeString玩家客户端语言(可选,仅在玩家有 locale 时存在)

此外,&ctx 中的所有键值对也会被展开注入到脚本变量中。

ItemScriptOps API

&ops 提供了便捷的物品操作方法。

数据操作

// 读取数据
def value = &ops.data("key")

// 设置数据(单键值对)
&ops.setData("key", "value")
&ops.setData("player.level", 10)

耐久系统

// 扣除耐久(返回 Boolean,true 表示物品已被销毁)
def destroyed = &ops.damage(1)

// 获取当前耐久
def current = &ops.durability()

// 获取最大耐久
def max = &ops.durabilityMax()

// 设置耐久
&ops.setDurability(100)

冷却系统

// 获取剩余冷却(tick)
def remaining = &ops.cooldown()

// 设置冷却(tick)
&ops.setCooldown(80)

// 检查是否在冷却中
if (&ops.cooldown() > 0) {
&player.sendMessage("§c冷却中...")
return
}

绑定系统

// 获取物品所有者
def owner = &ops.owner()

// 检查是否为所有者
if (!&ops.isOwner()) {
&player.sendMessage("§c此物品不属于你!")
return
}

// 绑定所有者(不传参时绑定当前玩家)
&ops.bindOwner(&player.name)
&ops.bindOwner()

信号系统

// 标记信号
&ops.signal("CUSTOM_FLAG")

// 检查信号
if (&ops.hasSignal("DATA_MAPPED")) {
// 数据已映射
}

重建物品

// 使用最新配置重建物品
&ops.rebuild()

脚本返回值

脚本的返回值会影响物品行为。源码会先检查 &item 是否被直接修改,再检查脚本返回值。

返回 ItemStack

scripts:
build: |
def newItem = new ItemStack(Material.DIAMOND)
return newItem

替换当前物品,并执行显示锁定保护。

返回 ItemStream

scripts:
build: |
&stream.setDisplayName("§6新名称")
return &stream

用返回的 stream 的 snapshot 替换当前物品,同时合并其 runtimeData

返回 Map

scripts:
build: |
return ["key1": "value1", "key2": 123]

将 map 中的每个键值对写入 runtimeData

直接修改 &item

除了通过返回值,你也可以直接修改 &item 引用。源码会在脚本执行后检查 &item 是否发生了变化,如果变化了也会同步回物品。

scripts:
build: |
def meta = &item.getItemMeta()
meta.setDisplayName("§6直接修改")
&item.setItemMeta(meta)

事件取消 (!!)

!! 是配置层面的声明式事件取消,写在触发器 key 的末尾。

event:
on_right_click!!: |
// 执行脚本,同时取消底层 Bukkit 右键事件
&player.sendMessage("已拦截")

真实行为:

  • ItemScriptHooks 解析阶段,如果触发器 key 以 !! 结尾,该触发器会被标记为 cancelEvent = true
  • 脚本执行后,ItemScriptActionDispatcher 会将原始 Bukkit 事件设为 isCancelled = true
  • 这是配置层声明,不是脚本返回值机制

注意:!! 只对底层有 Cancellable 接口的 Bukkit 事件生效。对 build / release / release_display 这类生命周期脚本,不要理解成"取消 BUILD/RELEASE 阶段"。

数据映射器

data-mappertoItemStack() 早期执行,用于动态计算运行时数据。它现在和 scripts / event 共用同一套脚本源解析规则:

data:
durability: 240

data-mapper:
durability_line:
type: fluxon
script: |
def current = &data["durability_current"] ?: &data["durability"] ?: 0
def max = &data["durability"] ?: 0
return current.toString() + "/" + max.toString()

lore:
item_description:
- "耐久: {durability_line}"

你也可以继续把 mapper 直接写成字符串或字符串列表;当前 loader 会统一整理成“脚本类型 + 脚本文本”的内部格式。

注意时机:

  • data-mapperItemReleaseEvent 之前运行
  • durability_barcooldown_remaining 这类显示期变量尚未注入
  • 推荐在 mapper 中使用原始运行时数据

Fluxon 内置函数

Baikiruto 会在 Fluxon 运行时注册一些内置函数,让你在脚本中直接调用第三方插件的能力。目前支持 MythicMobs。

MythicMobs

前提:服务端安装了 MythicMobs 插件。如果 MythicMobs 没有加载,这组函数不会被注册,脚本中调用会报错。

通过 mythic()mythicmobs() 获取 API 对象,然后调用其方法:

mm = mythic()

MythicApi 方法一览

状态查询
方法返回值说明
isLoaded()BooleanMythicMobs 插件是否已加载
怪物操作
方法返回值说明
getMob(entity)Mob / null通过实体获取 MM 怪物对象
getMobByUUID(uuid)Mob / null通过 UUID 字符串获取 MM 怪物对象
getMobType(id)MobType / null通过怪物类型 ID 获取怪物类型定义
getMobIds()List<String>获取所有已注册的怪物类型 ID 列表
spawnMob(id, location, level)Mob / null在指定位置生成指定等级的 MM 怪物
isMythicMob(entity)Boolean判断实体是否为 MM 怪物
物品获取
方法返回值说明
getItem(id)ItemStack / null通过 MM 物品 ID 获取物品
getItemWithPlayer(id, player)ItemStack / null通过 MM 物品 ID 获取物品(带玩家上下文)
getItemId(itemStack)String / null获取物品的 MM 物品 ID
getItemIds()List<String>获取所有已注册的 MM 物品 ID 列表
技能释放
方法返回值说明
castSkill(caster, skillName, power)void以实体为施法者释放技能
castSkillAt(caster, skillName, target, power)void以实体为施法者,对目标释放技能

参数说明:

  • caster:施法者实体(Entity)
  • skillName:MythicMobs 技能名(String)
  • power:技能强度(Float)
  • target:目标生物(LivingEntity)
仇恨管理
方法返回值说明
addThreat(entity, target, amount)void增加 MM 怪物对目标的仇恨值
reduceThreat(entity, target, amount)void减少 MM 怪物对目标的仇恨值

参数说明:

  • entity:MM 怪物实体(Entity)
  • target:仇恨目标(LivingEntity)
  • amount:仇恨值变化量(Double)

Mob 扩展函数

当你通过 getMob() 拿到一个 Mob 对象后,可以调用以下扩展函数:

方法返回值说明
getId()String怪物类型 ID
getDisplayName()String怪物显示名称
getLevel()Double怪物等级
getEntity()Entity底层 Bukkit 实体
getFaction()String怪物阵营
getStance()String怪物姿态

MobType 扩展函数

当你通过 getMobType() 拿到一个 MobType 对象后,可以调用以下扩展函数:

方法返回值说明
getId()String怪物类型 ID
getDisplayName()String怪物类型显示名称
getEntityType()String底层实体类型
spawn(location, level)Entity在指定位置以指定等级生成该类型怪物

完整示例

event:
on_attack: |
mm = mythic()

# 判断被攻击的实体是否为 MM 怪物
if &mm :: isMythicMob(&event.getEntity()) {
mob = &mm :: getMob(&event.getEntity())
&player.sendMessage("§e你攻击了 MM 怪物: " + &mob :: getDisplayName() + " (Lv." + &mob :: getLevel() + ")")
}
return item

on_right_click: |
mm = mythic()

# 右键释放 MythicMobs 技能
&mm :: castSkill(&player, "Fireball", 1.0)
&player.sendMessage("§a释放了火球术!")
return item

on_kill: |
mm = mythic()

# 击杀时检查是否为 MM 怪物,并读取信息
if &mm :: isMythicMob(&event.getEntity()) {
mob = &mm :: getMob(&event.getEntity())
&player.sendMessage("§6击杀了 " + &mob :: getId() + ",阵营: " + &mob :: getFaction())
}
return item
event:
on_right_click: |
mm = mythic()

# 在玩家位置生成一个 5 级的 SkeletonKing
spawned = &mm :: spawnMob("SkeletonKing", &player.getLocation(), 5.0)
if spawned != null {
&player.sendMessage("§a召唤成功!")
}
return item
event:
on_right_click: |
mm = mythic()

# 获取 MythicMobs 物品并给玩家
mmItem = &mm :: getItemWithPlayer("CustomSword", &player)
if mmItem != null {
&player.getInventory().addItem(mmItem)
&player.sendMessage("§a获得了 MythicMobs 物品!")
}
return item

注意事项

  • 所有 API 调用内部都做了 runCatching 保护,如果 MythicMobs 内部抛异常,方法会返回 null 而不是让脚本崩溃
  • mythic()mythicmobs() 是等价的,用哪个都行
  • 仇恨操作(addThreat / reduceThreat)的第一个参数必须是 MM 怪物实体,不能传玩家
  • 如果你的服务端没装 MythicMobs,这些函数不会被注册;脚本中调用 mythic() 会直接报运行时错误

性能优化

脚本预热

启动时预编译所有脚本,减少首次执行延迟:

# config.yml
script:
preheat:
enabled: true
strategy: "ON_ENABLE"
batch-size: 64

配置项:

  • enabled:是否启用预热;默认 true
  • strategy:预热策略;ON_ENABLE(启动时预热)或 ON_DEMAND(按需预热)
  • batch-size:每批预编译的脚本数量;默认 64,推荐 1-256

物品重载时会自动清除脚本缓存。

异步执行

on_async_tick 事件在异步线程执行,适合做轻量状态刷新。当前实现要点:

  • 调度器会持续扫描在线玩家背包,但只会对真正写了 on_async_tick 的物品派发脚本
  • 全局默认频率来自 operations.async-tick.default-interval,默认 100 tick
  • 单个物品可用 meta.async-tick.interval / ticks / period 覆盖
  • 还能通过 meta.async-tick.conditions 限制潜行、疾跑、游泳、滑翔、飞行、着地、载具、燃烧和格挡等状态
  • 也能按槽位、世界、游戏模式和权限过滤;permissions 写多个值时命中任意一个即可
  • 仍然不要在里面直接操作 Bukkit API
meta:
async-tick:
interval: 20
conditions:
sprinting: true
worlds:
- world
game-mode: survival
permissions:
- baikiruto.async.fire
- baikiruto.async.admin
slots:
- mainhand
- offhand

event:
on_async_tick: |
&ops.setData("last_async_slot", slot)
&ops.setData("last_async_slot_index", slot_index)
return item

常见槽位值包括 MAINHANDHOTBARINVENTORYOFFHANDHEADCHESTLEGSFEET。如果你只想扫装备中的物品,可以在 meta.async-tick.conditions.slots 里写 equippedarmor;如果你只想让主世界生效、只给生存模式玩家,或要求玩家带某个权限,也都可以直接写在 conditions 里。

下一步