Skip to content

预览模式

预览槽位(preview 类型)用来向玩家实时展示某个操作完成后的效果,比如强化后的属性、洗练后的词条。

它的核心特性是沙箱执行:预览脚本对物品和变量的所有修改都在一份副本上进行,真实数据不受影响。

基本配置

yaml
slots:
  P: preview(id=preview, source="@input", script=preview_script)

scripts:
  preview_script: |
    # 预览脚本中直接操作(装备/lore/name 等)就是当前预览物品
    目标等级 = 当前等级 + 1
    lore.setCounter("强化等级", 目标等级, 最大等级)
    lore.attr("攻击力", "+5")
参数说明
id槽位 ID(必填)
source预览物品的来源,如 "@input""@material" 或变量名
script要执行的命名脚本块(在顶层 scripts: 下定义)

沙箱机制

执行预览时:

  1. 快照:系统为当前执行上下文创建一份副本。
  2. 隔离:脚本对槽位物品、所有变量、GUI 状态的修改都在副本里。
  3. 提取:执行完毕后,只把修改后的物品取出用于显示。
  4. 丢弃:副本被丢弃,原始数据保持不变。

被沙箱保护的对象:

  • 槽位物品(@input@material 等)
  • 所有变量(包括 variables 定义的)
  • GUI 会话状态

自动屏蔽的动作

以下 effect.* / gui.* 动作在预览模式下会被源码级拦截、直接跳过,不执行也不报错:

动作预览模式行为
effect.message()不发送消息
effect.sound()不播放音效
effect.title()不发送标题
effect.actionbar()不发送动作栏
effect.particle()不显示粒子
effect.potion()不应用药水
effect.command()不执行玩家命令
effect.console()不执行控制台命令
effect.delay()不执行延迟
gui.close()不关闭 GUI
gui.refresh()不刷新 GUI

effect.message 不能用于调试预览脚本

预览模式中 effect.message("调试信息") 不会输出任何东西。调试预览脚本只能靠"观察预览槽位的最终效果是否符合预期",或者暂时把相同逻辑在 execute 中跑一遍用 log()

不受保护的危险操作

以下动作/函数不在沙箱内,会真实执行:

yaml
# ✗ 会真实扣钱
money.take(100)
points.take(10)

# ✗ 会真实修改持久化变量
cx_set("key", value)
cxt_set("key", value, 86400000)

# ✗ 会真实写入仓库
wh_give(物品, 1)
vault.store(物品)

预览脚本里只做"改物品属性"这一件事,所有真实操作留给 execute

特殊变量

变量类型说明
isPreviewModeBoolean预览模式中始终为 true
previewSlotIdString当前预览槽位的 ID

可以在 execute 中检查 isPreviewMode 来避免真实副作用:

yaml
execute: |
  if isPreviewMode {
    return                     # 预览时直接返回
  }
  money.take(消耗金币)

但这种用法现在已不常见——更推荐在 preview 槽位直接指定 script 绑定专用脚本块,语义更清晰。

变量作用域陷阱

execute 中定义的变量在预览脚本中不可用。预览脚本只能访问 variables 块中定义的变量。

yaml
variables: |
  当前等级 = @input.counter("强化等级")      # ✓ 预览脚本能用
  强化加成 = 1 + 当前等级 * 0.05             # ✓ 预览脚本能用

scripts:
  preview_script: |
    # 新等级 = 当前等级 + 1  ← 如果这个变量是在 execute 中定义的,这里取不到
    新等级 = 当前等级 + 1              # ✓ 在预览脚本内重新算
    当前攻击 = @主装备.t("{*}物理攻击 +{#atk}", "atk")
    # ...

for 循环批量 lore.replace 的正确写法

多个属性联动预览时,用 for 代替复制粘贴。但有两个非常容易踩的坑:

陷阱 1:列表存的是值,不是名字

yaml
# ✗ 错误:变量名被求值成数值
物理攻击 = 1.5
属性列表 = [物理攻击, 生命力]         # 实际存的是 [1.5, 4.5]
for i in 1..属性列表.size() {
  属性 = list_get(属性列表, i-1)
  lore.replace("{属性}", ...)           # 变成 lore.replace("1.5", ...) → 找不到
}

要存属性名字符串,必须写字面量列表["物理攻击", "生命力"]

陷阱 2:复制粘贴把三个列表写成同一个

yaml
# ✗ 错误:旧值、新值都从属性名列表取
属性名列表 = ["物理攻击", "生命力"]
旧值列表 = [当前攻击, 当前生命]
新值列表 = [新攻击, 新生命]
for i in 1..属性名列表.size() {
  属性名 = list_get(属性名列表, i-1)
  旧值 = list_get(属性名列表, i-1)    # ✗ 应该是 旧值列表
  新值 = list_get(属性名列表, i-1)    # ✗ 应该是 新值列表
  lore.replace("{属性名}", "{旧值}", "{新值}")
}

正确写法

yaml
variables: |
  当前等级 = @主装备.t("{*}强化等级: {#current}/{#max}", "current")
  强化加成 = 1 + 当前等级 * 0.05

scripts:
  preview_script: |
    新等级 = 当前等级 + 1

    # 1. 预览脚本内重新计算所有属性
    当前攻击 = @主装备.t("{*}物理攻击 +{#atk}", "atk")
    新攻击 = round(当前攻击 * 强化加成)
    当前生命 = @主装备.t("{*}生命力 +{#atk}", "atk")
    新生命 = round(当前生命 * 强化加成)
    当前防御 = @主装备.t("{*}物理防御 +{#atk}", "atk")
    新防御 = round(当前防御 * 强化加成)

    # 2. 三个列表对齐
    属性名列表 = ["物理攻击", "生命力", "物理防御"]
    旧值列表 = [当前攻击, 当前生命, 当前防御]
    新值列表 = [新攻击, 新生命, 新防御]

    # 3. 各自从正确的列表取值
    for i in 1..属性名列表.size() {
      属性名 = list_get(属性名列表, i-1)
      旧值 = list_get(旧值列表, i-1)
      新值 = list_get(新值列表, i-1)
      lore.replace("{属性名}", "{旧值}", "{新值}")
    }

    # 4. 等级和名称单独处理
    lore.replace("强化等级", "{当前等级}", "{新等级}")
    原名 = @主装备.name.split("+", 0).trim()
    name.set("{原名} &a+{新等级}")

预览脚本三大注意事项

  1. 变量作用域:只能用 variables 中的变量,execute 中的变量必须在预览脚本内重新计算。
  2. 列表值类型[变量名] 存的是值而不是名字,属性名列表必须写字符串字面量 ["xxx"]
  3. 调试受限effect.message() 被屏蔽,无法用于调试预览脚本。

最佳实践

yaml
scripts:
  preview_script: |
    # ✓ 安全:只修改物品属性(预览的本职工作)
    lore.replace("攻击力", "{旧值}", "{新值}")
    name.set("{原名} +{新等级}")

    # ✓ 无害:以下动作会被静默跳过
    # effect.message("调试")        ← 被屏蔽
    # effect.sound("xxx")           ← 被屏蔽

    # ✗ 危险:下列操作不受保护,会真实执行
    # money.take(100)               ← 真实扣钱
    # cx_set("key", value)          ← 真实写持久化

TQ Minecraft Server Plugin Docs