预览模式
预览槽位(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: 下定义) |
沙箱机制
执行预览时:
- 快照:系统为当前执行上下文创建一份副本。
- 隔离:脚本对槽位物品、所有变量、GUI 状态的修改都在副本里。
- 提取:执行完毕后,只把修改后的物品取出用于显示。
- 丢弃:副本被丢弃,原始数据保持不变。
被沙箱保护的对象:
- 槽位物品(
@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。
特殊变量
| 变量 | 类型 | 说明 |
|---|---|---|
isPreviewMode | Boolean | 预览模式中始终为 true |
previewSlotId | String | 当前预览槽位的 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+{新等级}")预览脚本三大注意事项
- 变量作用域:只能用
variables中的变量,execute中的变量必须在预览脚本内重新计算。 - 列表值类型:
[变量名]存的是值而不是名字,属性名列表必须写字符串字面量["xxx"]。 - 调试受限:
effect.message()被屏蔽,无法用于调试预览脚本。
最佳实践
yaml
scripts:
preview_script: |
# ✓ 安全:只修改物品属性(预览的本职工作)
lore.replace("攻击力", "{旧值}", "{新值}")
name.set("{原名} +{新等级}")
# ✓ 无害:以下动作会被静默跳过
# effect.message("调试") ← 被屏蔽
# effect.sound("xxx") ← 被屏蔽
# ✗ 危险:下列操作不受保护,会真实执行
# money.take(100) ← 真实扣钱
# cx_set("key", value) ← 真实写持久化