Spyglass 插件相关使用说明

Spyglass (Datapack Helper Plus)是一个强大的 Minecraft 开发工具, 提供语法高亮, 智能提示等功能

https://spyglassmc.com/这里有 Spyglass 的官方文档, 提供了完整的使用说明

下载此页面的 Markdown 版本

Spyglass 插件

安装 Spyglass

在 VS Code 插件页面搜索 Datapack Helper Plus by Spyglass 进行安装

配置 Spyglass

VS Code 插件设置

VS Code 插件设置中可以调整:

配置文件

Spyglass 官方配置文件说明

在工作区根目录(指用 VS Code 打开文件夹时的文件夹下) 创建文件名为下列文件名其中一种的文本文件:

配置文件使用 JSON 格式, 未指定的配置项将不启用或使用默认设置
配置主要包含以下几个部分:

env 配置

env 对象用于配置 Spyglass 的运行环境, 包括:

format 配置

format 对象控制代码格式化选项, 包括:

lint 配置

lint 对象用于配置代码检查规则, 包括:

snippet 配置
此功能可能尚未开发完成, 实际尝试也没有成功触发补全, 见 Spyglass #1392

snippet对象定义代码片段, 用户可以通过快捷方式快速插入常用代码

Snippet(代码片段)是一种可以快速插入预定义代码模板的功能
在 Spyglass 中, 你可以自定义代码片段, 通过特定的触发词快速插入常用的命令或代码结构

每个snippet由一个键值对组成:

snippet模板支持使用占位符来创建可编辑区域, 占位符格式:

示例:

"snippet": {
    "executeIfScoreSet": "execute if score ${1:score_holder} ${2:objective} = ${1:score_holder} ${2:objective} $0",
    "summonAec": "summon minecraft:area_effect_cloud ~ ~ ~ {Age: -2147483648, Duration: -1, WaitTime: -2147483648, Tags: [\"${1:tag}\"]}"
}
配置文件示例:
此部分内容过长, 点击展开
{
    "env": {
        "dataSource": "GitHub",
        "dependencies": [
            "@vanilla-mcdoc",
            "@vanilla-datapack",
            "@vanilla-resourcepack"
        ],
        "feature": {
            "codeActions": true,
            "colors": true,
            "completions": true,
            "documentHighlighting": true,
            "documentLinks": true,
            "foldingRanges": true,
            "formatting": true,
            "hover": true,
            "inlayHint": {
                "enabledNodes": [
                    "mcfunction:command_child/unknown"
                ]
            },
            "semanticColoring": true,
            "selectionRanges": true,
            "signatures": true
        },
        "gameVersion": "Auto",
        "language": "Default",
        "permissionLevel": 2,
        "plugins": [],
        "mcmetaSummaryOverrides": {},
        "useFilePolling": false
    },
    "format": {
        "blockStateBracketSpacing": {
            "inside": 0
        },
        "blockStateCommaSpacing": {
            "before": 0,
            "after": 1
        },
        "blockStateEqualSpacing": {
            "before": 0,
            "after": 0
        },
        "blockStateTrailingComma": false,
        "eol": "auto",
        "nbtArrayBracketSpacing": {
            "inside": 0
        },
        "nbtArrayCommaSpacing": {
            "before": 0,
            "after": 1
        },
        "nbtArraySemicolonSpacing": {
            "after": 1
        },
        "nbtArrayTrailingComma": false,
        "nbtByteSuffix": "b",
        "nbtCompoundBracketSpacing": {
            "inside": 0
        },
        "nbtCompoundColonSpacing": {
            "before": 0,
            "after": 1
        },
        "nbtCompoundCommaSpacing": {
            "before": 0,
            "after": 1
        },
        "nbtCompoundTrailingComma": false,
        "nbtDoubleOmitSuffix": false,
        "nbtDoubleSuffix": "d",
        "nbtFloatSuffix": "f",
        "nbtListBracketSpacing": {
            "inside": 0
        },
        "nbtListCommaSpacing": {
            "before": 0,
            "after": 1
        },
        "nbtListTrailingComma": false,
        "nbtLongSuffix": "L",
        "nbtShortSuffix": "s",
        "selectorBracketSpacing": {
            "inside": 0
        },
        "selectorCommaSpacing": {
            "before": 0,
            "after": 1
        },
        "selectorEqualSpacing": {
            "before": 0,
            "after": 0
        },
        "selectorTrailingComma": false,
        "timeOmitTickUnit": false
    },
    "lint": {
        "blockStateSortKeys": null,
        "nbtCompoundSortKeys": null,
        "selectorSortKeys": null,

        "commandStringQuote": null,
        "nbtKeyQuote": null,
        "nbtPathQuote": null,
        "nbtStringQuote": null,
        "selectorKeyQuote": null,

        "idOmitDefaultNamespace": null,

        "nameOfNbtKey": null,
        "nameOfObjective": null,
        "nameOfScoreHolder": null,
        "nameOfTag": null,
        "nameOfTeam": null,

        "nbtArrayLengthCheck": true,
        "nbtBoolean": null,
        "nbtListLengthCheck": null,
        "nbtTypeCheck": "loosely",

        "undeclaredSymbol": [
            {
                "then": {
                    "declare": "block"
                }
            }
        ]
    },
    "snippet": {
        "executeIfScoreSet": "execute if score ${1:score_holder} ${2:objective} = ${1:score_holder} ${2:objective} $0",
        "summonAec": "summon minecraft:area_effect_cloud ~ ~ ~ {Age: -2147483648, Duration: -1, WaitTime: -2147483648, Tags: [\"${1:tag}\"]}"
    }
}

命令树

Spyglass 使用命令树(Command Tree)来解析和验证 Minecraft 命令 命令树定义了命令的语法结构, 包括参数类型、子命令等

命令树的结构

命令树由节点组成, 每个节点代表命令中的一个元素:

示例: execute 命令树结构

execute
├── run <command>
├── as <entity>
├── at <entity>
├── if
│   ├── block <pos> <block>
│   ├── blocks <start> <end> <destination> <mode>
│   ├── data <target>
│   ├── entity <entity>
│   └── score <target> <objective> <operation> <source> <sourceObjective>
└── unless
    ├── block <pos> <block>
    ├── blocks <start> <end> <destination> <mode>
    ├── data <target>
    ├── entity <entity>
    ├── score <target> <objective> <operation> <source> <sourceObjective>
    └── loaded <pos>

自定义命令树

Spyglass 支持自定义命令树, 允许用户添加原版命令以外的命令, 如模组命令和服务端插件命令

配置自定义命令树

可以通过 spyglass.json 配置文件中的 mcmetaSummaryOverrides.commands 选项来配置自定义命令树

见 env 配置 下的 mcmetaSummaryOverrides.commands 配置项

生成命令树文件

  1. 手动编写: 根据 Minecraft 命令树格式手动编写 commands.json 文件, 命令树格式相关内容可以查看 数据生成器 - 中文 Minecraft Wiki #命令报告
  2. 使用 Command Extractor 模组: 使用模组从游戏中提取命令树, 模组 Modrinth 链接: Command Extractor - Minecraft Mod
  3. 使用原版 Minecraft 数据生成器获取命令报告: 见 数据生成器 - 中文 Minecraft Wiki, 使用数据生成器获取命令报告

Mcdoc 的使用

Mcdoc 是一种用于描述 Minecraft 数据包中数据结构的文档格式, 主要用于为 Minecraft 的 storage, entity, block 等数据提供类型定义和自动补全支持

在工作区内的任何 .mcdoc 文件都可以被 Spyglass 识别并解析, 但还是建议把项目的 .mcdoc 文件放在 mcdoc 文件夹内, 文件夹与数据包的 data 文件夹同级, 此时 mcdoc 文件夹所在的目录会被视为根目录

官方文档中的 Mcdoc 用法见: Mcdoc

基本语法

Spyglass 官方文档中的 语法介绍

官方文档的汉化版本见: Mcdoc格式 - 雪狐小窝

原版 Minecraft 数据的 Mcdco 文档见: vanilla-mcdoc

注释

普通注释

Mcdoc 使用 // 来表示普通注释
// 与后面的内容间应有一个空格

// 这是一个文档注释

可以使用 2 个或者 4 个及以上的 / 表示此行后续的内容为注释 但 3 个 / 的///保留用作Doc注释

Doc注释

使用 /// 表示Doc 注释, 此类注释会作为在自动补全和悬停交互时字段的描述

/// 字段的描述

类型化数字

类型化数字类似 Minecraft SNBT 的数字写法, 在数字后有表示数值类型的后缀

数字范围

使用与 Minecraft 相同的数字范围表示方法, 即使用 .. 来表示, 额外可以使用 <表示排他性结束

// 等于 0
0

// 不大于 0
..0

// 不小于 0
0..

// 不小于 0 且不大于 10
0..10

// 大于 0.5 且小于 10
0.5<..<10

// 大于 0.5 且不大于 10
0.5<..10

字符串语法

字符串必须用双引号包围, 部分字符需要转义:

路径

路径用于在项目中定位类型定义, 使用 :: 用作路径分隔符 路径中的文件不包括.mcdoc扩展名

标识符

标识符区分大小写, 可以包含任何 Unicode 字母、数字和下划线, 但不能以数字开头

结构体定义

使用 struct 关键字来定义结构体:

struct MyStructure {
    field_name: field_type
}

其中 MyStructure 为结构体的标识符, field_name 为字段的标识符, field_type 为字段的类型

结构体可以嵌套匿名结构体

struct OuterStruct {
    inner_struct: struct {
        field: int
    }
}

可以在 键和 : 之间添加 ? 来表示可选字段

struct OuterStruct {
    field?: int
}

扩展操作符...后跟结构体类型, 可以用于重用另一个结构体的字段

struct InnerStruct {
    count: int
}

struct OuterStruct {
    ...InnerStruct
    field: int
}

类型映射

使用 dispatch 关键字将 Minecraft 中的实际存储映射到定义的结构体:

dispatch minecraft:storage["storage_name"] to StructName

这条 dipatch 语句说明 storage storage_name 应具有 StructName 类型

也可以直接在映射时定义结构体

dispatch minecraft:storage["storage_name"] to struct StructName {
    field_name: field_type
}

字段的类型

在 Mcdoc 中, 字段可以有多种不同的类型, 每种类型都有其特定的用途和语法规则
以下是完整的字段类型说明:

any 类型

any 类型是 Mcdoc 类型系统中的顶级类型, 任何其他类型(包括 any 本身)都可以赋值给 any 类型, 但 any 类型不能赋值给除 any 之外的其他类型

value: any
boolean 类型

boolean 类型表示期望一个布尔值(false 或 true)

is_active: boolean
string 类型

string 类型表示期望一个字符串值, 可以使用可选的范围来定义字符串的长度范围

name: string
description: string @1..100
数值类型

Mcdoc 支持多种数值类型, 包括:

这些类型可以配合范围使用, 定义数值的有效区间

health: int
damage: float @ 0.0..10.0
count: byte @ 1..64
字面量类型

字面量类型包括 字面量布尔类型、字面量字符串类型 和 字面量数值类型 , 数据必须完全匹配这些字面量值才是有效的

// 字面量布尔类型
is_enabled: true

// 字面量字符串类型
type: "player"

// 字面量数值类型
version: 1
distance: 1.2f
原始数组类型

原始数组类型表示数据必须是某种数值类型的集合, 第一个可选范围定义值的范围, 第二个可选范围定义集合大小的范围

// 字节集合
bytes: byte[]

// 仅包含0或1的字节集合
limited_values: byte#0..1[]

// 包含4个整数的集合
fixed_ints: int[] # 4

// 包含至少3个非负长整数的集合
non_negative_longs: long#0..[] # 3..
列表类型

列表类型表示数据必须是另一种类型的集合, 可选范围定义集合大小的范围

// 字符串集合
tags: [string]

// 字符串集合的集合
nested_lists: [[string]]

// 结构体Foo的集合
objects: [struct Foo {}]
元组类型

元组类型表示数据必须是按指定顺序排列的某些其他类型的集合, 使用[ ]和,分隔不同类型

// 一个字节的元组
[byte,]

// 一个字符串后跟一个布尔值的元组
[string, boolean]

为了区分只包含一个元素的元组类型与列表类型, 需要在类型后面添加一个,, 或者可以使用大小为1的列表类型来替代包含一个元素的元组, 例如[byte] @ 1

元组类型通常对NBT结构没有用, 因为NBT没有混合类型的集合

枚举类型

枚举类型用于定义一组命名的常量值, Mcdoc支持基于不同基础类型的枚举, 最常见的是字符串枚举

// 字符串枚举
enum(string) DamageScaling {
    Never = "never",
    Always = "always",
    LivingNonPlayer = "when_caused_by_living_non_player",
}

enum(string) NarrationPriority {
    Chat = "chat",
    System = "system",
}

// 结构体中直接使用枚举名称
struct Foo {
    damage_scaling: NarrationPriority
}
分派器类型

分派器类型用于根据不同条件动态确定具体类型, 常用于根据某个字段的值来决定整个对象的结构

struct Foo {
    id: string,

    // 静态分派到cow实体类型
    cow_data: minecraft:entity[cow],

    // 动态根据id值分派实体类型
    dynamic_entity_data: minecraft:entity[[id]],

    // 嵌套分派
    command: minecraft:block[command_block][Command],

    // 多级分派
    dynamic_memories: minecraft:entity[[id]][Brain][memories]
}
联合类型

联合类型表示值可以是几种类型中的任意一种, 使用管道符|分隔不同的类型选项

id: string | int
类型索引

类型索引允许从复杂类型结构中提取特定部分, 类似于从对象中获取指定属性

// 基本用法: 从实体类型中获取特定实体的信息
// 获取猪的相关数据结构
minecraft:entity[pig]

可以同时获取多个类型的索引:

// 同时获取末影龙和凋零的类型信息
minecraft:entity[ender_dragon, wither]

类型索引分为两种形式:

struct MobData {
    id: string,  // 存储实体ID, 比如 "pig" 或 "cow"
    // 静态索引: 总是指向猪的数据
    static_pig_data: minecraft:entity[pig],

    // 动态索引: 根据id字段的值来决定指向哪种实体
    dynamic_mob_data: minecraft:entity[[id]],
}

Mcdoc提供了几个特殊关键字来处理特殊情况:

// 使用 %fallback 处理未明确指定的实体类型
type AnyEntity = minecraft:entity[%fallback]

// 结合动态索引和 %none 处理可选字段
struct RandomIntGenerator {
    type?: ("uniform" | "binomial" | "constant"),
    // 当 type 有值时使用对应的生成器, 当 type 为空时使用 %none 对应的默认结构
    ...minecraft:random_int_generator[[type]]
}
dispatch minecraft:random_int_generator[uniform, %none] to struct {
    min?: int,
    max?: int
}

还有一些高级关键字:

// 使用 %key 来动态确定类型
struct DebugStick {
    DebugProperty: struct {
        // 根据键名来确定应该使用哪种方块状态类型
        [#[id=block] string]: mcdoc:block_state_name[[%key]]
    }
}

// 使用 %parent 来关联相关信息
struct Item {
    id: #[id=item] string,  // 物品ID
    tag: struct ItemTag {
        // 根据物品ID来确定适用的方块状态
        BlockStateTag: mcdoc:block_item_states[[%parent.id]]
    }
}

类型别名语句

使用 type 关键字创建类型别名以引用另一个类型, 提高代码的可读性和可重用性

// 定义Integer类型为byte、short、int或long的联合类型
type Integer = (byte | short | int | long)

// 定义Float类型为float或double的联合类型
type Float = (float | double)

// 定义Number类型为Integer或Float的联合类型
type Number = (Integer | Float)

如果要创建具有大致相同结构但在某些小方面有所不同的不同类型定义, 可以创建一个带有类型参数的模板类型别名
类型别名语句的右侧可以引用这些类型参数, 当类型别名在其他地方实例化时, 这些参数将被实际类型替换

// 定义带类型参数T的NumericRange模板
type NumericRange<T> = (
    // 单个T类型的值
    T |
    // 包含两个T类型值的元组(表示范围的最小值和最大值)
    [T, T] |
    // 包含min和max字段的结构体
    struct {
        min: T,
        max: T
    }
)

// 实例化NumericRange为float类型
type FloatRange = NumericRange<float>

// 实例化NumericRange为int类型
type IntegerRange = NumericRange<int>

// 实例化NumericRange为非负int类型
type NaturalRange = NumericRange<int @ 0..>
绑定类型参数

所有路径引用通过路径中描述的规则解析, 类型参数引用也不例外
当在类型别名语句中声明类型参数时, 它会被暂时绑定到当前模块, 直到语句结束
因此, 就像其他类型定义一样, 类型参数在模块范围内应该是唯一的

// 文件 '/example.mcdoc'
// 定义一个名为T的结构体
struct T {}

// 尝试声明类型参数T, 但与上面的结构体T冲突
type List<T> = [T] 
//        ^
//        WARNING: Duplicated declaration for "::example::T"

T 的声明被警告并忽略, 右侧的 T 实际上引用的是上面定义的结构体 T

// 在此语句范围内声明类型参数 T
type List<T> = [T] 

// 这是另一个独立的类型别名, 其 T 参数不会与上面的冲突
type Struct<T> = struct {
    value: T
}

这是可以的, 虽然 T 也在 List 类型别名语句中声明, 但该声明的效果仅在该语句结束前有效

使用语句

使用语句用于引入其他文件中定义的类型, 并可以给它起一个别名
使用语句使你能够在当前文件中方便地引用其他模块中定义的类型, 而无需每次都使用完整的路径, 通常在 .mcdoc 文件的最开头使用

// 直接使用SomeType
use ::foo::bar::SomeType

// 将AnotherType引入重命名为AT
use ::foo::bar::AnotherType as AT

// 将Config重命名为AppConfig
use ::utils::common::Config as AppConfig

注入

注入语句用于向已存在的类型定义中添加额外的字段或枚举值, 而无需修改原始定义, 可以用于扩展原版或其他库定义的类型, 注入的字段或值会被合并到原始类型定义中

枚举注入

枚举注入允许你向现有的枚举类型添加新的枚举值:

inject enum(string) ::existing::enum::Path {
    NEW_VALUE = "new_value_name",
    ANOTHER_VALUE = "another_value"
}
结构体注入

结构体注入允许你向现有的结构体类型添加新的字段:

inject struct ::existing::struct::Path {
    new_field: string,
    another_field: int
}

属性

Mcdoc 支持使用属性来为类型和字段添加元数据信息 属性使用 #[attribute_name] 语法, 可以应用于类型、字段或其他语言结构

此部分所有示例选项解释均为根据属性选项的命名猜测, 可能与实际语法不同, 极为不确定的解释标有 "(?)"
canonical

特殊属性, 标记联合类型中的规范成员, 表示优先使用此类型 如果联合类型有规范成员, 读取该联合类型时将始终期望规范成员

type RGB = (
    #[canonical] int |
    [float] @ 3 |
)
color

表示颜色 此属性有一个必需的字符串选项, 指定颜色格式

// 十进制 RGB
#[color="dec_rgb"] [float] @ 3

// 组合 RGB
#[color="composite_rgb"] int

// 十六进制 RGB
#[color="hex_rgb"] string
command

指定字符串包含命令 此属性有多个选项

#[command(slash="allowed")] string

#[command(slash="allowed", empty="allowed", max_length=32500)] string

#[command(macro="implicit")] string
deprecated

标记字段为已弃用, 编辑器在使用时可能会对字段添加删除线 参数为指定弃用版本的字符串

#[deprecated="1.16"]
value?: Text,
dispatcher_key

指定字符串应匹配给定分派器的键

#[dispatcher_key="mcdoc:custom_data"] string
divisible_by

指定数值必须能被给定数字整除

#[divisible_by=16] int
entity

表示选择器、玩家名或UUID 指定分数持有者请使用 score_holder 属性 此属性有两个选项:

#[entity(amount="single",type="entities")] string
game_rule

指定字符串应包含游戏规则名称 此属性有一个选项:

#[game_rule(type="int")] string
id

指定字符串应包含资源位置 有一个必需选项 registry, 可以写成简写形式或带更多选项:

#[id="item"] string

#[id(registry="item",tags="allowed")] string
match_regex

指定字符串需要匹配给定的正则表达式模式 此属性仅匹配正则表达式而不是字符串包含正则表达式, 包含正则表达式使用 regex_pattern 属性

#[match_regex="^[a-z_]+$"] string
nbt

包含 SNBT 的字符串 有一个选项, 指定字符串化 SNBT 需要匹配的类型

#[nbt=ItemStack] string
nbt_path

包含 NBT 路径的字符串

#[nbt_path] string
objective

包含记分项名称的字符串

#[objective] string
regex_pattern

包含正则表达式模式的字符串 不要与 match_regex 混淆

#[regex_pattern] string
score_holder

允许使用*通配符、实体选择器和玩家名 不要与 entity 混淆

#[score_holder] string
since

使字段或联合成员仅从给定版本开始可用 只允许正式版本(不包括快照) 从给定版本不可用参见 until

struct Example1 {
   #[since="1.19"]
   entity: string,
}

type Example2 = (
   string |
   #[since="1.20.5"] int |
)

无效语法:

struct Example {
   entity: #[since="1.19"] string,
}
tag

包含命令标签名称的字符串(通过 /tag 获得或在 Tags 实体字段中)

#[tag] string
team

包含队伍名称的字符串

#[team] string
text_component

包含表示文本组件的字符串化 JSON 的字符串 注意在1.21.5及更高版本文本组件更改后, 应使用 ::java::util::text::Text 类型而不是字符串化 JSON

#[text_component] string
until

使字段或联合成员从给定版本开始不可用 只允许正式版本(不包括快照) 注意给定版本不包含在该字段或成员被接受的版本范围内 从给定版本开始可用参见 since

struct Example1 {
   #[until="1.19"]
   entity: string,
}

type Example2 = (
   string |
   #[until="1.20.5"] int |
)

无效语法:

struct Example {
   entity: #[until="1.19"] string,
}