跳转至

KubeJS 进阶教程:完整实战案例

原文:原创教程(ぴよまる)

基础篇学会了「监听事件 + 操作对象」,这篇进阶教程带你把 KubeJS 用深入:数据持久化、自定义方块实体、定时任务、自定义命令、战利品表、世界生成、网络通信……每节都是能直接抄进 kubejs/ 就能用的完整案例


1. 事件高级技巧

1.1 取消事件(event.cancel())

可取消事件(事件列表里标 ✅ 的)调用 event.cancel() 就能阻止行为:

// server_scripts/anti_cheat.js
// 这个文件要放在 kubejs/server_scripts 文件夹里,服务器启动时会自动运行
// 禁止破坏基岩
BlockEvents.broken(event => {   // 监听"方块被破坏"事件;event 是装着这次事件所有信息的对象
  if (event.block.id === 'minecraft:bedrock') {   // 判断:如果被破坏的方块是基岩(bedrock)
    event.cancel()  // 破坏被取消(阻止这次破坏发生,基岩就挖不掉了)
    event.player.tell('你不能破坏基岩!')   // tell() 是给玩家发一条聊天框消息
  }
})

// 禁止苦力怕爆炸
EntityEvents.death(event => {   // 监听"实体死亡"事件
  if (event.entity.type === 'minecraft:creeper') {   // 判断:如果死掉的实体是苦力怕
    event.cancel()  // 苦力怕"死不掉"(先于爆炸)——取消死亡事件,它就不会爆炸了
  }
})

1.2 带条件的监听

事件回调里先判断,再决定做什么:

// server_scripts/player_rules.js
PlayerEvents.chat(event => {   // 监听"玩家在聊天框发消息"事件
  let msg = event.message.toLowerCase()   // 把玩家输入的消息转成小写,存进变量 msg(这样不管大写小写都能识别)

  // 屏蔽脏话(示例词)
  let badWords = ['fuck', 'shit', '傻逼']   // 定义一个"脏话清单",数组里放几个示例词
  if (badWords.some(word => msg.includes(word))) {   // some() 检查清单里有没有任何一个词出现在消息里,有就返回 true
    event.cancel()   // 有脏话:取消这条消息,不让它发出去
    event.player.tell('说话文明一点!')   // 私聊提醒玩家
    return   // 提前结束这个函数,后面的代码不再执行
  }

  // 自定义聊天前缀
  if (event.player.username === 'pyz') {   // 判断:发消息的人是不是叫 pyz
    event.message = '§c[服主]§r ' + event.message   // 是的话给消息前面加"服主"称号(§c 是红色颜色代码,§r 恢复默认色)
  }
})

1.3 同一事件多个监听器

同一个事件可以写多个 addEventListener,都会执行:

// server_scripts/welcome.js
PlayerEvents.loggedIn(event => {   // 监听"玩家登录进服"事件
  event.player.tell('§a欢迎回来,' + event.player.username + '!')   // 给登录的玩家发欢迎消息(+ 号是把文字和名字拼起来)
})

PlayerEvents.loggedIn(event => {   // 同一个事件可以再监听一次,两段代码都会执行
  if (event.player.username === 'pyz') {   // 判断:登录的人是不是 pyz
    event.player.tell('§d主人好~')   // 是的话额外说一句"主人好"(§d 是粉色)
  }
})

2. 玩家数据持久化(persistentData)

persistentData 是挂在实体/方块/玩家上的持久 NBT 数据,服务器重启也不会丢。最常用的就是它:

// server_scripts/kill_counter.js
// 统计每个玩家的击杀数
EntityEvents.death(event => {   // 监听"实体死亡"事件
  let player = event.source.player   // event.source 是"死亡原因",从中拿到凶手玩家;如果是摔死等非玩家原因,这里就是 null
  if (!player) return   // 判断:如果没有凶手玩家(不是玩家杀的),就提前结束,不统计

  let data = player.persistentData   // persistentData 是玩家的永久存档数据,服务器重启也不会丢
  data.kills = (data.kills || 0) + 1   // 击杀数 +1:没记录过就当 0,再加 1,存回去
})

// 用命令查看:/reload 后击杀另一个玩家,再执行任意命令看日志
PlayerEvents.chat(event => {   // 监听"玩家发消息"事件
  if (event.message === '!kills') {   // 判断:玩家输入的是不是 !kills
    let kills = event.player.persistentData.kills || 0   // 从存档里取出击杀数,没记录过就当 0
    event.player.tell('你的击杀数:' + kills)   // 把击杀数告诉玩家
  }
})

💡 persistentData 可以存数字、字符串、布尔、数组、对象,随意嵌套:data.stats = { kills: 1, deaths: 2 }


3. 自定义方块实体(BlockEntity)

方块实体 = 带「数据/逻辑」的方块(箱子、熔炉这类)。KubeJS 可以注册带 tick 逻辑的方块实体:

// startup_scripts/machine.js
// startup 脚本在游戏启动时运行一次,负责"注册"新东西
StartupEvents.registry('block', event => {   // 开始注册方块;event 提供注册方法
  event.create('example_machine')   // 创建一个叫 example_machine 的方块(完整 ID 是 kubejs:example_machine)
    .displayName('示例机器')   // 设置方块在游戏里显示的名字
    .material('metal')   // 设置材质为金属(影响声音、粒子效果等)
    .hardness(3.0)   // 设置挖掘硬度 3.0(数字越大越难挖)
    .blockEntity(entity => {   // 给方块绑定"方块实体"(让方块能存数据);entity 是配置对象
      // 方块实体类型注册(KubeJS 自动处理)
    })
})

// server_scripts/machine_tick.js
// 方块实体每 tick 逻辑:附近的玩家回血
BlockEvents.rightClicked(event => {   // 监听"右键点击方块"事件
  let block = event.block   // 取出被点击的方块
  if (block.id === 'kubejs:example_machine') {   // 判断:点的是不是我们做的示例机器
    let data = block.persistentData   // 拿到这个方块自己的永久存档数据
    data.clicks = (data.clicks || 0) + 1   // 点击次数 +1(没记录过就当 0)
    event.player.tell('你点击了机器 ' + data.clicks + ' 次!')   // 告诉玩家这是第几次点击
    event.cancel()  // 阻止默认行为(如打开合成界面)——不让方块执行它原本的默认动作
  }
})

⚠️ 方块实体的复杂 tick 逻辑(如容器 GUI)需要结合 Rhino 与 Java 类,KubeJS 原生支持有限,进阶玩家可配合 BEJS(Block Entity JS 插件)实现完整机器。

方块检测器(Detector)

KubeJS 自带检测器方块(kubejs:detector),配合事件做红石逻辑:

// server_scripts/detector.js
BlockEvents.detectorPowered(event => {   // 监听"检测器方块被激活(通电)"事件
  event.block.popItem('minecraft:diamond')  // 检测器被激活时掉钻石(popItem 是在方块位置丢出一个物品)
  event.server.runCommandSilent('say 检测器激活!')   // 悄悄执行命令:全服广播一句话(Silent 表示不输出命令回显)
})

BlockEvents.detectorUnpowered(event => {   // 监听"检测器断电"事件
  console.log('检测器断电')   // 在服务器日志里打印一行字,方便调试
})

4. 完整案例 A:会发光的「幸运方块」

一个完整的自定义方块案例(启动注册 + 服务端逻辑):

// startup_scripts/lucky_block.js
StartupEvents.registry('block', event => {   // 启动时注册方块
  event.create('lucky_block')   // 创建幸运方块
    .displayName('幸运方块')   // 显示名
    .material('metal')   // 金属材质
    .hardness(2.0)   // 硬度 2.0,不算难挖
    .lightLevel(0.5)          // 半亮度(0~1,1 是最亮)
    .requiresTool(true)       // 需要工具挖掘(空手挖不动)
    .tagBlock('mineable/pickaxe')  // 镐子可挖(打上"可以用镐挖"的标签)
})

// server_scripts/lucky_block.js
BlockEvents.broken(event => {   // 监听"方块被破坏"事件
  if (event.block.id !== 'kubejs:lucky_block') return   // 判断:如果破坏的不是幸运方块,就提前结束这个函数

  let player = event.player   // 拿到破坏方块的玩家
  let roll = Math.random()          // 0 ~ 1 随机数(生成一个 0 到 1 之间的小数,用来抽奖)

  if (roll < 0.3) {   // 判断:随机数小于 0.3,也就是 30% 概率
    player.tell('§a幸运!获得钻石!')   // 告诉玩家中奖了
    player.give('3x minecraft:diamond')   // 给玩家发物品,'3x minecraft:diamond' 表示 3 个钻石('数量x 物品ID' 格式)
  } else if (roll < 0.6) {   // 否则再判断:小于 0.6,也就是落在 30%~60% 这一档(又是 30% 概率)
    player.tell('§e还行,获得铁锭。')
    player.give('5x minecraft:iron_ingot')   // 发 5 个铁锭
  } else if (roll < 0.9) {   // 再否则:小于 0.9,即落在 60%~90% 这一档(30% 概率)
    player.tell('§c倒霉,召唤苦力怕!')
    player.server.runCommandSilent('summon minecraft:creeper ' + player.x + ' ' + player.y + ' ' + player.z)   // 在玩家所在坐标召唤一只苦力怕(summon 是游戏命令,player.x/y/z 是玩家坐标)
  } else {   // 剩下的情况:随机数 ≥ 0.9,也就是最后 10% 概率——超级大奖
    player.tell('§d超级大奖!100 经验!')
    player.give('10x minecraft:emerald')   // 发 10 个绿宝石
    player.server.runCommandSilent('experience add ' + player.username + ' 100')   // 用命令给玩家加 100 点经验
  }
})

💡 常用方法速记:player.give('物品id 数量')player.tell('消息')server.runCommandSilent('命令')event.block.popItem(...)


5. 定时任务(ScheduledEvent)

server.scheduleInTicks(ticks, callback) 做延时/周期逻辑(20 ticks = 1 秒):

// server_scripts/auto_tasks.js
// 玩家进服 5 秒后发提示
PlayerEvents.loggedIn(event => {   // 监听"玩家登录"事件
  let player = event.player   // 拿到登录的玩家
  player.server.scheduleInTicks(100, () => {   // 100 ticks = 5 秒(20 tick 等于 1 秒);把里面的代码推迟到 5 秒后执行
    if (player.health > 0) {   // 判断:玩家还活着(血量大于 0)
      player.tell('欢迎!记住规则:禁止作弊,友好交流。')   // 5 秒后才给玩家发这条提示
    }
  })
})

// 周期性广播(每 10 分钟)
ServerEvents.loaded(event => {   // 监听"服务器加载完成"事件(开服时触发一次)
  event.server.scheduleInTicks(12000, () => {   // 12000 tick = 10 分钟,先等 10 分钟
    event.server.runCommandSilent('say 服务器运行中,玩得开心!')   // 时间一到,全服广播一句话
    // 再次调度,形成循环
    event.server.scheduleInTicks(12000, arguments.callee)   // arguments.callee 指"当前这个函数自己"——每 10 分钟再安排一次自己,形成无限循环
  })
})

⚠️ 循环调度注意:arguments.callee 指当前函数(递归调度自己)。更稳妥的做法是写具名函数:

function broadcastLoop(server) {   // 定义一个"具名"函数:循环广播;参数 server 是服务器对象
  server.runCommandSilent('say 服务器运行中!')   // 全服广播一句话
  server.scheduleInTicks(12000, () => broadcastLoop(server))   // 10 分钟后再次调用自己,形成循环(比 arguments.callee 更清晰好懂)
}
ServerEvents.loaded(event => {   // 监听"服务器加载完成"事件
  broadcastLoop(event.server)   // 开服时调用一次,启动循环广播
})

6. 自定义命令

ServerEvents.commandRegistry 注册自己的命令(需要权限等级 2 = OP):

// server_scripts/my_commands.js
ServerEvents.commandRegistry(event => {   // 监听"注册命令"事件;event 提供 register 方法
  // 注册 /hello
  event.register('hello', command => {   // 注册一条叫 hello 的命令(游戏里输入 /hello 使用)
    command.permissionLevel(2)   // 设置权限等级 2 = OP(管理员)才能用
    command.executes(ctx => {   // 设置命令真正执行时要运行的代码;ctx 是"命令执行上下文"
      let player = ctx.source.player   // 从上下文里取出执行命令的玩家
      player.tell('你好,' + player.username + '!这是 KubeJS 命令。')   // 给玩家发一条问候消息
      return 1   // 返回 1 表示命令执行成功(游戏要求返回一个数字)
    })
  })

  // 注册 /giveall <物品> —— 全服发物品
  event.register('giveall', command => {   // 注册第二条命令 giveall
    command.permissionLevel(2)   // 同样需要 OP 权限
    command.argument('item', 'string', argument => {   // 给命令加一个参数 item,类型是字符串(玩家输入 /giveall 后面跟的物品 ID)
      argument.executes(ctx => {   // 参数填写完整后执行
        let item = ctx.arguments.item   // 从上下文里取出玩家输入的参数值(物品 ID)
        ctx.source.server.players.forEach(p => {   // 遍历服务器上的每一个玩家(forEach 就是"每个都做一遍")
          p.give(item)   // 给当前这个玩家发物品
        })
        ctx.source.server.runCommandSilent('say 全服发放:' + item)   // 全服广播发放了什么东西
        return 1   // 命令执行成功
      })
    })
  })
})

⚠️ 命令参数类型:'string''integer''float''player''blockpos' 等,参数值从 ctx.arguments.参数名 取。


7. 战利品表进阶

7.1 修改方块掉落

// server_scripts/loot.js
ServerEvents.blockLootTables(event => {   // 监听"方块战利品表"事件,用来修改方块掉落
  // 挖石头额外掉钻石(10%)
  event.modifyBlock('minecraft:stone', table => {   // 修改石头的掉落表;table 是掉落表对象
    table.addPool(pool => {   // 添加一个掉落池(相当于一次"抽奖机会")
      pool.addItem('minecraft:diamond', 1, 10)   // 1 个,权重 10(权重越大越容易掉;石头本身还有其他掉落,所以大约 10%)
    })
  })

  // 自定义方块掉落自己(幸运方块)
  event.modifyBlock('kubejs:lucky_block', table => {   // 修改幸运方块的掉落表
    table.addPool(pool => {
      pool.addItem('kubejs:lucky_block')   // 掉落自己,这样挖掉幸运方块还能捡回来
    })
  })
})

7.2 实体掉落

ServerEvents.entityLootTables(event => {   // 监听"实体战利品表"事件
  // 击杀僵尸必掉铁锭
  event.modifyEntity('minecraft:zombie', table => {   // 修改僵尸的掉落表
    table.addPool(pool => {
      pool.addItem('minecraft:iron_ingot', 1, 1)   // 必掉 1 个铁锭(池里只有这一项,权重多少都会掉)
    })
  })
})

7.3 箱子战利品(地牢/要塞)

ServerEvents.chestLootTables(event => {   // 监听"箱子战利品表"事件,修改地牢等箱子里的东西
  event.modify('minecraft:chests/simple_dungeon', table => {   // 修改"简易地牢"箱子的战利品表
    table.addPool(pool => {
      pool.addItem('minecraft:enchanted_golden_apple', 1, 2)   // 附魔金苹果 1 个,权重 2
      pool.addItem('minecraft:diamond_sword', 1, 5)   // 钻石剑 1 把,权重 5(比金苹果更容易出)
    })
  })
})

8. 世界生成进阶

8.1 添加矿石(addOre 快捷方法)

// startup_scripts/worldgen.js
WorldgenEvents.add(event => {   // 监听"世界生成-添加"事件,往世界里加新东西
  event.addOre(ore => {   // 添加一种矿石生成规则;ore 是配置对象
    ore.id = 'kubejs:ruby_ore'                    // 生成的矿石方块(要生成的是红宝石矿石)
    ore.addTarget('#minecraft:stone', 'kubejs:ruby_ore')  // 替换什么:把普通石头(# 开头表示标签,代表所有石头)替换成红宝石矿石
    ore.count(6)                                  // 每区块最多 6 团(一个区块里最多生成 6 堆)
    ore.squared()                                 // 随机散布(让生成位置在水平方向随机分布)
    ore.heightRange(0, 40)                        // Y 0~40(只在高度 0 到 40 之间生成)
    ore.biomeFilter(biome => {   // 设置生成的群系(生物群系)条件
      biome.include('minecraft:plains')           // 只在平原生成
    })
  })
})

8.2 移除默认地物

WorldgenEvents.remove(event => {   // 监听"世界生成-移除"事件,用来删掉默认生成的东西
  // 移除所有钻石矿石生成
  event.removeOres(ore => {   // 按矿石生成规则移除
    ore.addTarget('#minecraft:stone', 'minecraft:diamond_ore')   // 匹配"把石头替换成钻石矿石"这条规则,把它删掉
  })
})

9. 网络通信(客户端 ↔ 服务端)

KubeJS 支持自定义数据包在两端传数据:

// client_scripts/network.js
// 客户端收到服务端消息
NetworkEvents.fromServer(event => {   // 监听"从服务端收到数据"事件(写在这份客户端脚本里)
  console.log('收到服务端数据:', event.data)   // 把收到的数据打印到客户端日志
  if (event.data.action === 'notify') {   // 判断:数据里带的 action 是不是 notify(通知)
    // 弹提示(需要配合其他 UI 方式,这里只是示例)
    console.log('通知:' + event.data.text)   // 打印通知内容
  }
})

// server_scripts/network.js
// 服务端收到客户端消息
NetworkEvents.fromClient(event => {   // 监听"从客户端收到数据"事件(写在这份服务端脚本里)
  let player = event.player   // 拿到发来数据的玩家
  console.log(player.username + ' 发送了数据:', event.data)   // 在服务器日志里打印谁发了什么
  player.tell('已收到你的数据!')   // 回一条消息告诉玩家收到了
})

⚠️ 发送数据端需要频道注册,不同版本 API 有差异(player.sendData(...)Network.send(...)),以你所用 KubeJS 版本官方文档为准。


10. 完整案例 B:多功能「传送法杖」

综合运用:右键传送 + 冷却 + 数据持久化:

// startup_scripts/wand.js
StartupEvents.registry('item', event => {   // 启动时注册物品
  event.create('teleport_wand')   // 创建传送法杖
    .displayName('传送法杖')   // 显示名
    .maxStackSize(1)   // 一组最多 1 个(法杖不能堆叠)
    .tooltip('右键:向前传送 10 格(冷却 5 秒)')   // 鼠标放上去时显示的说明文字
})

// server_scripts/wand.js
const COOLDOWN_TICKS = 100  // 5 秒(定义常量:冷却时间 100 tick;20 tick 是 1 秒,所以是 5 秒)

ItemEvents.rightClicked(event => {   // 监听"右键使用物品"事件
  if (event.item.id !== 'kubejs:teleport_wand') return   // 判断:用的不是传送法杖就直接结束函数

  let player = event.player   // 拿到使用物品的玩家
  let data = player.persistentData   // 拿到玩家的永久存档数据

  // 冷却检查
  let now = player.server.tick   // 服务器当前的总 tick 数(相当于游戏世界的时间戳)
  if (data.lastWandUse && now - data.lastWandUse < COOLDOWN_TICKS) {   // 判断:上次使用过,而且距离现在还没到 100 tick(冷却没结束)
    let remain = Math.ceil((COOLDOWN_TICKS - (now - data.lastWandUse)) / 20)   // 算还剩几秒:剩余 tick 除以 20,再向上取整
    player.tell('§c冷却中,还有 ' + remain + ' 秒!')   // 告诉玩家还要等几秒
    return   // 提前结束,不执行传送
  }

  // 计算传送目标:玩家面朝方向 +10 格
  let dx = player.lookDirection.x * 10   // 玩家面朝方向的 x 分量乘 10,得到 x 方向要移动的距离
  let dz = player.lookDirection.z * 10   // 同理算出 z 方向要移动的距离(y 是上下,先不管)
  let target = { x: player.x + dx, y: player.y, z: player.z + dz }   // 目标点 = 当前位置 + 面朝方向 × 10

  // 传送到目标(保持 y 不变,简化处理)
  player.server.runCommandSilent(   // 执行一条游戏命令(反引号是模板字符串,里面 ${} 可以塞变量)
    `tp ${player.username} ${target.x.toFixed(1)} ${target.y.toFixed(1)} ${target.z.toFixed(1)}`   // tp 是传送命令;toFixed(1) 把坐标保留 1 位小数,命令更干净
  )

  data.lastWandUse = now   // 记录使用时间(把当前 tick 存进存档,作为下次冷却判断的依据)
  player.tell('§a传送完成!')
})

11. 全局脚本工具(server_scripts/utils.js)

把常用逻辑写成全局函数,所有脚本都能调用:

// server_scripts/utils.js —— 全局工具函数
function giveItem(player, itemId, count = 1) {   // 定义全局函数:给玩家发物品;count = 1 表示不传数量时默认发 1 个
  player.give((count > 1 ? count + 'x ' : '') + itemId)   // 数量大于 1 时拼成 "3x 物品ID" 的格式,否则直接发 1 个
}

function randomFrom(array) {   // 定义全局函数:从数组里随机抽一个元素
  return array[Math.floor(Math.random() * array.length)]   // Math.random() 生成 0~1 随机小数,乘数组长度再向下取整,得到随机下标
}

function isNight(level) {   // 定义全局函数:判断当前是不是晚上
  return level.dayTime % 24000 >= 13000   // 游戏一天是 24000 tick,13000 之后是夜晚;% 是取余数,把整天的部分去掉只留今天的时间
}

// 其他脚本直接调用:
// giveItem(player, 'minecraft:apple', 3)
// let gift = randomFrom(['minecraft:apple', 'minecraft:bread'])

12. 性能与调试技巧

12.1 别在 tick 事件里做重活

每 tick 触发的事件(ServerEvents.tickEntityEvents 高频事件)里不要频繁创建对象、不要循环大量实体:

// ❌ 不好:每 tick 遍历所有实体
ServerEvents.tick(event => {   // 监听"每 tick"事件(每秒触发 20 次,非常频繁)
  event.server.allLevels.forEach(level => {   // 遍历所有维度(主世界、地狱、末地等)
    level.getEntities().forEach(entity => { /* 卡! */ })   // 再遍历每个维度里的所有实体——每 tick 全服扫一遍,服务器会卡死
  })
})

// ✅ 推荐:低频检查(每 40 tick 一次)
ServerEvents.tick(event => {   // 同样监听每 tick 事件
  if (event.server.tick % 40 === 0) {   // 判断:当前 tick 能不能被 40 整除——能的话就每 40 tick(2 秒)才执行一次
    // 低频逻辑放这里
  }
})

12.2 调试三板斧

console.log('变量值:', myVar)                    // 看日志(把变量值打印到日志文件,适合查问题)
event.player.tell('调试信息:' + value)           // 游戏内看(直接在游戏聊天框显示)
event.server.runCommandSilent('say ...')          // 全服广播调试(让所有玩家看到调试信息)

日志位置:游戏目录/logs/kubejs/server.txt(服务端脚本)和 client.txt(客户端脚本)。

12.3 常见卡服/报错原因

症状 原因 解决
脚本不生效 放错文件夹 对照事件类型选 startup/server/client
报错 Cannot read properties of undefined 对象不存在 console.log(event) 看结构
服务器卡顿 tick 里重活 % N === 0 降频
物品没生成 ID 拼写错 /kubejs hand 或 JEI 查真实 ID

13. 完整案例 C:简易「签到系统」

把本篇所有知识串起来——玩家每天签到领奖:

// server_scripts/daily_signin.js
const REWARDS = [   // 定义奖励清单(一个数组,里面装 4 种奖励)
  '5x minecraft:iron_ingot',   // 5 个铁锭
  '3x minecraft:gold_ingot',   // 3 个金锭
  '1x minecraft:diamond',   // 1 个钻石
  '2x minecraft:emerald'   // 2 个绿宝石
]

PlayerEvents.chat(event => {   // 监听"玩家发消息"事件
  if (event.message !== '!签到') return   // 判断:玩家没输入"!签到"就提前结束

  let player = event.player   // 拿到签到的玩家
  let data = player.persistentData   // 拿到永久存档数据

  // 判断今天是否签过:记录上次签到日期(游戏天数)
  let today = player.server.overworld.dayTime / 24000  // 游戏天数(把总 tick 除以 24000,从第几天开始;可能带小数)

  if (data.lastSignDay === Math.floor(today)) {   // 判断:上次签到日期(取整后)是不是今天——是就说明今天已经签过了
    player.tell('§c你今天已经签过到啦,明天再来!')
    return   // 提前结束
  }

  data.lastSignDay = Math.floor(today)   // 把今天的日期存进存档(表示今天签过了)
  data.signCount = (data.signCount || 0) + 1   // 签到总次数 +1(第一次就按 0 算)

  // 随机奖励
  let reward = REWARDS[Math.floor(Math.random() * REWARDS.length)]   // 随机抽一个奖励:随机数乘数组长度再取整,得到数组下标
  player.give(reward)   // 把奖励发给玩家
  player.tell('§a签到成功!这是你第 ' + data.signCount + ' 次签到,获得:' + reward)   // 告诉玩家结果
})

14. 自定义物品进阶(食物 / 工具 / 剑 / 盔甲)

14.1 自定义食物

// startup_scripts/items.js
StartupEvents.registry('item', event => {   // 启动时注册物品
  event.create('magic_apple')   // 创建魔法苹果
    .displayName('魔法苹果')   // 显示名
    .food(food => {   // 配置它是食物;food 是配置对象
      food.hunger(6)          // 恢复 6 格饥饿值(一格是半个鸡腿)
      food.saturation(8)      // 饱和度(吃饱后的"隐藏饱腹",越高越耐饿)
      food.effect('minecraft:regeneration', 200, 1, 1)  // 回血效果 10 秒(200 tick = 10 秒;1 是效果等级,1 是概率 100%)
      food.effect('minecraft:absorption', 1200, 2, 1)   // 伤害吸收 60 秒(1200 tick = 60 秒;等级 2 多 2 颗金心)
      food.alwaysEdible()     // 饥饿值满也能吃(普通食物吃饱了就吃不了)
    })
})

14.2 自定义工具(镐子)

StartupEvents.registry('item', event => {   // 启动时注册物品
  event.create('ruby_pickaxe', 'pickaxe')   // 创建红宝石镐(第二个参数 'pickaxe' 指定物品类型是镐子)
    .displayName('红宝石镐')
    .tier(tier => {   // 配置工具的等级属性;tier 是配置对象
      tier.durability(1500)          // 耐久(能挖 1500 次)
      tier.speed(8)                  // 挖掘速度(数字越大挖得越快)
      tier.attackDamageBonus(4)      // 攻击伤害加成(用它打人额外 +4 伤害)
      tier.level(3)                  // 挖掘等级(3 = 钻石级,能挖黑曜石、钻石矿)
      tier.enchantmentValue(15)      // 附魔能力(越高越容易附出好属性)
    })
    .tag('minecraft:mineable/pickaxe')   // 打标签:属于"可以用镐挖"的工具
    .tag('minecraft:tools')   // 打标签:属于工具
})

14.3 自定义剑

StartupEvents.registry('item', event => {   // 启动时注册物品
  event.create('thunder_blade', 'sword')    // 创建雷霆之刃(第二个参数 'sword' 指定类型是剑)
    .displayName('雷霆之刃')
    .tier(tier => {   // 配置剑的等级属性
      tier.durability(1000)   // 耐久 1000
      tier.speed(2)   // 挖掘速度 2(剑本来就不擅长挖东西)
      tier.attackDamageBonus(7)      // 攻击伤害 +7
      tier.level(3)   // 等级 3(钻石级)
    })
    .tooltip('§e挥舞时释放闪电!')   // 物品说明文字
})

// 剑的效果:攻击时放电(server_scripts)
EntityEvents.hurt(event => {   // 监听"实体受伤"事件
  let source = event.source   // 拿到伤害来源
  if (source.player && source.player.mainHandItem.id === 'kubejs:thunder_blade') {   // 判断:是玩家打的,而且玩家手上拿的是雷霆之刃
    let pos = event.entity.blockPosition()   // 拿到被攻击实体的位置(blockPosition 返回坐标对象)
    source.player.server.runCommandSilent(   // 执行命令:summon 是召唤命令
      `summon minecraft:lightning_bolt ${pos.x} ${pos.y} ${pos.z}`   // 在被攻击实体的位置召唤一道闪电
    )
  }
})

14.4 自定义盔甲

StartupEvents.registry('item', event => {   // 启动时注册物品
  // 先注册盔甲等级
  ItemEvents.armorTierRegistry(event => {   // 监听"注册盔甲等级"事件(盔甲要先有等级才能做装备)
    event.register('ruby', tier => {   // 注册一个叫 ruby 的盔甲等级
      tier.durabilityMultiplier(33)   // 耐久倍率(33 倍基础耐久,数值越大越耐穿)
      tier.reduction(3, 6, 8, 3)      // 护甲值:靴/腿/胸/头(顺序是 靴子、护腿、胸甲、头盔的减伤值)
      tier.enchantmentValue(15)   // 附魔能力
      tier.toughness(2)               // 盔甲韧性(减少高伤害武器的额外伤害)
    })
  })

  // 注册四件套
  event.create('ruby_helmet', 'helmet').displayName('红宝石头盔').tier('ruby')   // 头盔,使用 ruby 等级
  event.create('ruby_chestplate', 'chestplate').displayName('红宝石胸甲').tier('ruby')   // 胸甲
  event.create('ruby_leggings', 'leggings').displayName('红宝石护腿').tier('ruby')   // 护腿
  event.create('ruby_boots', 'boots').displayName('红宝石靴子').tier('ruby')   // 靴子
})

15. 自定义方块进阶(更多类型)

KubeJS 可以注册多种类型的方块,不只是普通方块:

// startup_scripts/blocks.js
StartupEvents.registry('block', event => {   // 启动时注册方块
  // 按钮
  event.create('ruby_button', 'button')   // 创建红宝石按钮(第二个参数 'button' 指定方块类型是按钮)
    .displayName('红宝石按钮')
    .material('metal')   // 金属材质

  // 压力板
  event.create('ruby_pressure_plate', 'pressure_plate')   // 创建压力板(踩上去触发)
    .displayName('红宝石压力板')
    .material('metal')

  // 楼梯
  event.create('ruby_stairs', 'stairs')   // 创建楼梯
    .displayName('红宝石楼梯')
    .material('metal')

  // 栅栏
  event.create('ruby_fence', 'fence')   // 创建栅栏
    .displayName('红宝石栅栏')
    .material('metal')

  // 楼梯台阶
  event.create('ruby_slab', 'slab')   // 创建台阶
    .displayName('红宝石台阶')
    .material('metal')

  // 墙壁
  event.create('ruby_wall', 'wall')   // 创建墙
    .displayName('红宝石墙')
    .material('metal')

  // 门
  event.create('ruby_door', 'cardinal')   // 创建门(门要用 cardinal 类型,支持四个朝向)
    .displayName('红宝石门')
    .material('metal')
    .rightClickOpen()   // 右键开关(右键开门关门)
})

💡 方块类型:'button''pressure_plate''stairs''slab''fence''wall''door'cardinal)、'trapdoor''ladder''torch' 等。


16. 配方高级操作

16.1 带 NBT 的合成(精确匹配)

ServerEvents.recipes(event => {   // 监听"配方"事件,用来增删改合成配方
  // 移除指定 NBT 的物品(比如附魔书)
  event.remove({ output: { item: 'minecraft:enchanted_book' } })   // 删除所有"合成结果是附魔书"的配方(output 指合成产物)

  // 带 NBT 输出:合成出带附魔的剑
  event.shaped('minecraft:diamond_sword', [' A ', ' A ', ' B '], {   // 合成表:3×3 格子按字母排列,A 和 B 对应下面的材料
    A: 'minecraft:diamond',   // A = 钻石
    B: 'minecraft:stick'   // B = 木棍(这样摆出来合成一把钻石剑)
  }).id('kubejs:enchanted_sword')   // 给这个配方起个 ID,方便以后精准删除或覆盖
})

16.2 条件配方(只在某些情况下生效)

ServerEvents.recipes(event => {   // 监听"配方"事件
  // 给配方加 ID,方便之后精准移除
  event.shaped('minecraft:golden_apple', ['AAA', 'ABA', 'AAA'], {   // 合成表:周围一圈 A,中间是 B
    A: 'minecraft:gold_ingot',   // A = 金锭
    B: 'minecraft:apple'   // B = 苹果(结果:金苹果)
  }).id('kubejs:special_gapple')   // 配方 ID 命名为 special_gapple

  // 后续按 ID 移除
  event.remove({ id: 'kubejs:special_gapple' })   // 用 ID 精准删除刚才那个配方(示例:先建后删)
})

16.3 原料替代(用标签批量)

ServerEvents.recipes(event => {   // 监听"配方"事件
  // #forge:stone 代表所有石头类
  event.shaped('minecraft:stone_bricks', ['SS', 'SS'], {   // 合成表:2×2 全是 S
    S: '#forge:stone'   // S = 任意石头(# 开头是标签,一种标签代表一类物品)
  })

  // 任意木头都能合成
  event.shapeless('minecraft:stick', ['#minecraft:planks', '#minecraft:planks'])   // 无序合成:任意两个木板合成木棍(shapeless 不要求摆放位置)
})

16.4 熔炉配方与更多

ServerEvents.recipes(event => {   // 监听"配方"事件
  // 烧炼配方:红宝石矿石 → 红宝石(经验 1,时长 200 tick)
  event.smelting('kubejs:ruby', 'kubejs:ruby_ore').xp(1.0).cookingTime(200)   // 熔炉烧红宝石矿石得到红宝石;xp 是给的经验,cookingTime 200 tick = 10 秒

  // 切石配方
  event.stonecutting('minecraft:stone_slab', 'minecraft:stone')   // 切石机:石头切成石台阶

  // 锻造配方(下界合金升级样式)
  event.smithing('minecraft:netherite_ingot', 'minecraft:diamond', 'minecraft:netherite_upgrade_smithing_template')   // 锻造台:钻石 + 升级模板 = 下界合金锭
})

17. 标签(Tag)进阶用法

17.1 操作各种注册表标签

ServerEvents.tags(event => {   // 监听"标签"事件,用来给物品/方块等打标签
  // 物品标签
  event.add('forge:ingots/ruby', 'kubejs:ruby')   // 给红宝石打上"红宝石锭"标签(forge:ingots/ruby)
  event.add('forge:ores/ruby', 'kubejs:ruby_ore')   // 给红宝石矿石打上"红宝石矿"标签

  // 方块标签:让红宝石矿石能被镐子挖
  event.add('minecraft:mineable/pickaxe', 'kubejs:ruby_ore')   // 标记:红宝石矿石可以用镐挖
  event.add('minecraft:needs_iron_tool', 'kubejs:ruby_ore')   // 标记:需要铁镐及以上才能挖

  // 实体标签:僵尸和骷髅算敌对
  event.add('minecraft:hostile', 'minecraft:zombie')   // 把僵尸归入"敌对生物"标签
  event.add('minecraft:hostile', 'minecraft:skeleton')   // 把骷髅也归入敌对

  // 流体标签
  event.add('minecraft:water', 'minecraft:water')   // 给水打上"水"的标签(示例:把水归入水标签)
})

17.2 批量添加

ServerEvents.tags('item', event => {   // 监听"标签"事件,并指定只处理物品类型(item)的标签
  // 把一串物品都加到同一个标签
  let rubies = ['kubejs:ruby', 'kubejs:ruby_block', 'kubejs:ruby_ore']   // 定义一个数组,装 3 个红宝石相关物品的 ID
  rubies.forEach(id => event.add('forge:gems/ruby', id))   // 遍历数组,把每个物品都加到"宝石-红宝石"标签里
})

17.3 用标签做配方原料

ServerEvents.recipes(event => {   // 监听"配方"事件
  // 任何 #forge:gems/ruby 都能参与合成
  event.shaped('kubejs:ruby_block', ['RRR', 'RRR', 'RRR'], {   // 合成表:3×3 全是 R
    R: '#forge:gems/ruby'   // R = 任意带"红宝石宝石"标签的物品(9 个红宝石合成一个红宝石块)
  })
})

18. 实体玩法

18.1 召唤实体

// server_scripts/spawn.js
PlayerEvents.chat(event => {   // 监听"玩家发消息"事件
  if (event.message !== '!招宠物') return   // 判断:输入的不是"!招宠物"就结束
  let p = event.player   // 拿到玩家(起个短变量名 p 方便写)
  p.server.runCommandSilent(`summon minecraft:wolf ${p.x} ${p.y} ${p.z}`)   // 在玩家当前位置召唤一只狼(summon 是召唤命令)
  p.tell('召唤了一只狼!')   // 提示玩家
})

18.2 实体死亡掉落(进阶)

ServerEvents.entityLootTables(event => {   // 监听"实体战利品表"事件
  // 苦力怕额外掉火药 + 随机红宝石
  event.modifyEntity('minecraft:creeper', table => {   // 修改苦力怕的掉落表
    table.addPool(pool => {
      pool.addItem('minecraft:gunpowder', 2, 3)      // 2 个,权重 3(火药,权重高容易掉)
      pool.addItem('kubejs:ruby', 1, 1)              // 1 个,权重 1(红宝石,比火药难掉)
      pool.randomChance(0.5)                          // 50% 概率整个池生效(这个池有 50% 几率触发,触发后按权重掉落)
    })
  })
})

18.3 修改实体属性(自定义怪物)

// 让所有僵尸速度更快、血更厚
EntityEvents.spawned(event => {   // 监听"实体生成"事件
  let entity = event.entity   // 拿到刚生成的实体
  if (entity.type === 'minecraft:zombie') {   // 判断:是不是僵尸
    entity.setCustomName('强力僵尸')   // 给僵尸改名字(头顶显示"强力僵尸")
    entity.setGlowing(true)   // 让它发光(方便看到)
    entity.server.runCommandSilent(   // 执行命令修改属性:attribute 是属性命令,把移动速度基值设为 0.35(更快)
      `attribute ${entity.uuid} minecraft:movement_speed base set 0.35`   // uuid 是实体的唯一编号,用来指定是哪只僵尸
    )
  }
})

18.4 防止特定实体生成

EntityEvents.checkSpawn(event => {   // 监听"实体即将生成"事件(生成前一刻)
  // 禁止幻翼生成
  if (event.entity.type === 'minecraft:phantom') {   // 判断:要生成的实体是不是幻翼
    event.cancel()   // 取消生成,幻翼就刷不出来了
  }
})

19. 粒子与音效

19.1 播放粒子

// server_scripts/fx.js
BlockEvents.rightClicked(event => {   // 监听"右键点击方块"事件
  if (event.block.id !== 'minecraft:beacon') return   // 判断:点的不是信标就结束
  let p = event.player   // 拿到玩家
  let pos = event.block.getPos()   // 拿到信标方块的坐标

  // 生成火焰粒子(数量 50,扩散 0.5)
  event.server.runCommandSilent(   // 执行 particle 命令(生成粒子特效)
    `particle minecraft:flame ${pos.x + 0.5} ${pos.y + 1} ${pos.z + 0.5} 0.5 0.5 0.5 0.1 50`   // 在信标上方 1 格生成火焰粒子;0.5 是扩散范围,0.1 是速度,50 是数量
  )

  // 生成爱心粒子
  event.server.runCommandSilent(
    `particle minecraft:heart ${pos.x + 0.5} ${pos.y + 1.5} ${pos.z + 0.5} 0 0 0 0 10`   // 在更高一点的位置生成 10 个爱心粒子,0 表示不扩散
  )
})

19.2 播放音效

PlayerEvents.loggedIn(event => {   // 监听"玩家登录"事件
  let p = event.player   // 拿到玩家
  // 播放音效(音效ID,音量,音调)
  p.server.runCommandSilent(`playsound minecraft:block.note_block.harp master ${p.username} ~ ~ ~ 1 1`)   // 给玩家播放竖琴音效(playsound 命令;master 是音量分类,~ 表示当前位置,最后两个 1 是音量和音调)
})

// 自定义物品使用时播放
ItemEvents.rightClicked(event => {   // 监听"右键使用物品"事件
  if (event.item.id !== 'kubejs:magic_apple') return   // 判断:用的不是魔法苹果就结束
  let p = event.player   // 拿到玩家
  p.server.runCommandSilent(`playsound minecraft:entity.player.levelup master ${p.username} ~ ~ ~ 1 1`)   // 给玩家播放升级音效
})

20. 玩家操作大全

// server_scripts/player_ops.js
PlayerEvents.chat(event => {   // 监听"玩家发消息"事件
  let p = event.player   // 拿到玩家
  let msg = event.message   // 拿到玩家输入的消息

  if (msg === '!满血') p.setHealth(p.maxHealth)   // 输入"!满血":把血量设置成最大血量(maxHealth 是最大生命值)
  if (msg === '!吃饱') {   // 输入"!吃饱"
    p.setFoodLevel(20)   // 饥饿值设为 20(满格)
    p.setSaturation(20)   // 饱和度设为 20(更耐饿)
  }
  if (msg === '!经验') {   // 输入"!经验"
    p.server.runCommandSilent(`experience add ${p.username} 100`)   // 用命令给玩家加 100 经验
  }
  if (msg === '!回血道具') p.give('1x minecraft:golden_apple')   // 输入"!回血道具":发一个金苹果
  if (msg === '!清理背包') p.server.runCommandSilent(`clear ${p.username}`)   // 输入"!清理背包":用 clear 命令清空该玩家背包
  if (msg === '!飞') p.server.runCommandSilent(`effect give ${p.username} minecraft:levitation 10 1`)   // 输入"!飞":给漂浮效果 10 秒(levitation 会让玩家飘起来)
})

💡 常用:p.setHealth()p.setFoodLevel()p.give()p.tell()p.x/p.y/p.zp.usernamep.mainHandItem


21. 完整案例 D:简易商店系统

用聊天命令 + persistentData 做玩家间交易:

// server_scripts/shop.js
const SHOP = {   // 定义商店数据:一个对象,键是玩家输入的命令,值是商品信息
  '!买钻石': { item: 'minecraft:diamond', count: 1, price: 10 },   // 花 10 货币买 1 个钻石
  '!买红宝石': { item: 'kubejs:ruby', count: 1, price: 5 },   // 花 5 货币买 1 个红宝石
  '!买金苹果': { item: 'minecraft:golden_apple', count: 1, price: 20 }   // 花 20 货币买 1 个金苹果
}

PlayerEvents.chat(event => {   // 监听"玩家发消息"事件
  let p = event.player   // 拿到玩家
  let product = SHOP[event.message]   // 用玩家输入的消息去商店表里查商品;查不到就是 undefined
  if (!product) return   // 判断:输入的不是商店命令,直接结束

  let balance = p.persistentData.money || 0   // 从存档里拿余额(货币),没记录过就当 0
  if (balance < product.price) {   // 判断:余额不够付这个商品的价格
    p.tell(`§c余额不足!需要 ${product.price},你有 ${balance}`)   // 提示余额不足,显示价格和当前余额
    return   // 结束购买
  }

  p.persistentData.money = balance - product.price   // 扣钱:余额减去价格,存回存档
  p.give(`${product.count}x ${product.item}`)   // 把商品发给玩家(拼成"数量x 物品ID"的格式)
  p.tell(`§a购买成功!花费 ${product.price},余额 ${p.persistentData.money}`)   // 提示购买成功
})

// 发钱命令(OP 用):/reload 后控制台或游戏内
ServerEvents.commandRegistry(event => {   // 注册自定义命令
  event.register('money', cmd => {   // 注册 /money 命令
    cmd.permissionLevel(2)   // 需要 OP 权限
    cmd.argument('player', 'player', arg => {   // 第一个参数:玩家名字(类型 player)
      arg.argument('amount', 'integer', amountArg => {   // 第二个参数:金额(类型 integer 整数)
        amountArg.executes(ctx => {   // 两个参数都填好后执行
          let target = ctx.arguments.player   // 取出目标玩家
          let amount = ctx.arguments.amount   // 取出金额
          target.persistentData.money = (target.persistentData.money || 0) + amount   // 给目标玩家的余额加钱(没记录过当 0)
          ctx.source.server.runCommandSilent(`say ${target.username} 获得了 ${amount} 货币`)   // 全服广播谁获得了多少钱
          return 1   // 命令执行成功
        })
      })
    })
  })
})

22. 完整案例 E:玩家传送点(/sethome /home)

// server_scripts/homes.js
PlayerEvents.chat(event => {   // 监听"玩家发消息"事件
  let p = event.player   // 拿到玩家
  let msg = event.message   // 拿到消息

  // 设置家
  if (msg === '!sethome') {   // 判断:输入的是"!sethome"
    p.persistentData.home = { x: p.x, y: p.y, z: p.z, dim: p.level.dimension }   // 把当前坐标和所在维度存进存档(对象:x/y/z 坐标 + 维度)
    p.tell(`§a已设置家:(${p.x.toFixed(0)}, ${p.y.toFixed(0)}, ${p.z.toFixed(0)})`)   // 提示设置成功;toFixed(0) 是取整显示坐标
  }

  // 回家
  if (msg === '!home') {   // 判断:输入的是"!home"
    let home = p.persistentData.home   // 从存档里取出家的坐标
    if (!home) {   // 判断:还没设置过家
      p.tell('§c你还没设置家!用 !sethome')
      return   // 结束
    }
    p.server.runCommandSilent(`tp ${p.username} ${home.x} ${home.y} ${home.z}`)   // 用 tp 命令把玩家传送到家的坐标
    p.tell('§a已回家!')
  }
})

23. 完整案例 F:每日任务系统

// server_scripts/daily_quests.js
const QUESTS = {   // 定义任务表:键是任务名,值是任务的"完成判断"和"奖励"
  '挖 10 个石头': {
    check: (p, data) => (data.stoneMined || 0) >= 10,   // 判断函数:挖石头数量(没记录当 0)大于等于 10 就算完成
    reward: '5x minecraft:iron_ingot'   // 完成奖励:5 个铁锭
  },
  '击杀 5 只僵尸': {
    check: (p, data) => (data.zombieKills || 0) >= 5,   // 判断函数:击杀僵尸数量大于等于 5 就算完成
    reward: '3x minecraft:gold_ingot'   // 完成奖励:3 个金锭
  }
}

// 记录进度
BlockEvents.broken(event => {   // 监听"方块被破坏"事件
  if (event.block.id === 'minecraft:stone') {   // 判断:挖的是石头
    let data = event.player.persistentData   // 拿到玩家存档
    data.stoneMined = (data.stoneMined || 0) + 1   // 挖石头计数 +1
  }
})

EntityEvents.death(event => {   // 监听"实体死亡"事件
  let p = event.source.player   // 拿到凶手玩家
  if (p && event.entity.type === 'minecraft:zombie') {   // 判断:是玩家杀的,而且杀的是僵尸
    let data = p.persistentData   // 拿到存档
    data.zombieKills = (data.zombieKills || 0) + 1   // 击杀僵尸计数 +1
  }
})

// 查看/领取任务
PlayerEvents.chat(event => {   // 监听"玩家发消息"事件
  let p = event.player   // 拿到玩家
  if (event.message !== '!任务') return   // 判断:输入的不是"!任务"就结束

  let data = p.persistentData   // 拿到存档
  // 每天重置(简单版:记录游戏天数)
  let today = Math.floor(p.server.overworld.dayTime / 24000)   // 计算今天是第几天(总 tick ÷ 24000 再取整)
  if (data.questDay !== today) {   // 判断:记录的任务日期不是今天——说明是新的一天,要重置
    data.questDay = today   // 更新任务日期
    data.stoneMined = 0   // 清零挖石头进度
    data.zombieKills = 0   // 清零击杀进度
    data.questClaimed = {}   // 清空"已领取"记录
  }

  Object.keys(QUESTS).forEach(name => {   // Object.keys 取出所有任务名,遍历每个任务
    let q = QUESTS[name]   // 拿到当前这个任务
    let done = q.check(p, data)   // 调用任务的判断函数,看完成没有
    let claimed = data.questClaimed && data.questClaimed[name]   // 看这个任务今天领过奖没有
    p.tell(`${done ? '§a✓' : '§7○'} ${name} ${claimed ? '§e[已领取]' : ''}`)   // 显示任务状态:✓ 已完成 / ○ 未完成;领过就标"[已领取]"
  })
})

// 领取奖励:!领任务1 / !领任务2
PlayerEvents.chat(event => {   // 再写一个消息监听,专门处理领奖
  let p = event.player   // 拿到玩家
  let idx = ['!领任务1', '!领任务2'].indexOf(event.message)   // 在数组里找玩家输入的命令:找到返回下标 0 或 1,找不到返回 -1
  if (idx < 0) return   // 判断:输入的不是领奖命令就结束

  let name = Object.keys(QUESTS)[idx]   // 用下标取出对应的任务名(第 1 个任务或第 2 个)
  let q = QUESTS[name]   // 拿到任务
  let data = p.persistentData   // 拿到存档
  let today = Math.floor(p.server.overworld.dayTime / 24000)   // 计算今天是第几天

  if (data.questDay !== today) {   // 判断:任务日期不是今天(说明任务还没刷新)
    p.tell('§c任务还没刷新!')
    return
  }
  if (!q.check(p, data)) {   // 判断:任务还没完成
    p.tell('§c任务还没完成!')
    return
  }
  if (data.questClaimed && data.questClaimed[name]) {   // 判断:今天已经领过这个任务的奖
    p.tell('§c已经领过啦!')
    return
  }

  data.questClaimed = data.questClaimed || {}   // 确保"已领取记录"存在(没有就新建一个空对象)
  data.questClaimed[name] = true   // 标记这个任务今天领过了
  p.give(q.reward)   // 把奖励发给玩家
  p.tell(`§a领取奖励:${q.reward}`)   // 提示领取成功
})

结语

KubeJS 的进阶玩法 = 事件 × 数据 × 工具方法的组合

  1. 事件决定「什么时候触发」(查事件列表篇)
  2. persistentData 存「跨重启的数据」
  3. server 工具方法(give/tell/runCommandSilent/scheduleInTicks)做「具体动作」
  4. 复杂功能(容器 GUI、自定义实体 AI)可以配合 BEJS / ProbeJS 等插件扩展

多抄多改多调试,遇到问题先 console.log 看数据,再查事件列表——你就能写出自己的玩法系统了!