mcpField
F
foundry-vtt-module-dev

foundry-vtt-module-dev

github.com/fcsouza开发2446 次安装1 周前更新
代码仓库

关于 foundry-vtt-module-dev

面向 Foundry Virtual Tabletop v14 及后续版本的模块开发技能,涵盖模块结构、生命周期、数据模型、界面、样式与迁移。

主要功能

  1. 01v14 模块结构与清单
  2. 02初始化生命周期指导
  3. 03命名空间 API 参考
  4. 04文档模型与子类型
  5. 05ApplicationV2 与 CSS 样式

适用场景

  1. 01创建新的 Foundry VTT 模块
  2. 02将模块迁移至 v14 API
  3. 03添加自定义文档类型
  4. 04实现主题适配的模块界面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/fcsouza/agent-skills --skill foundry-vtt-module-dev

安装后在 Claude Code 中输入/foundry-vtt-module-dev确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

boilerplate/
actor-sheet.mjs
main.mjs
module.json
package.json
rollup.config.mjs
sheet.hbs
socket-handler.mjs
type-data-model.mjs
vite.config.mjs
references/
accessibility.md
active-effects-v2.md
adventure-documents.md
application-v2.md
audio-and-macros.md
build-pipeline.md
canvas-and-pixi.md
chat-and-ui.md
combat-and-tokens.md
document-model.md
handlebars-and-templates.md
hooks-and-settings.md
measured-templates.md
migration-guide.md
module-subtypes.md
permissions-and-ownership.md
regions-and-grid.md
scene-levels.md
sockets-rolls-packs.md
testing-with-quench.md
v14-migration.md

详细介绍

Foundry VTT 模块开发

为 Foundry Virtual Tabletop(v14+)构建、扩展和维护模块。本技能涵盖模块的完整生命周期——从搭建新模块到在 Foundry 版本之间迁移。如果你正在使用 v13,请先阅读 references/v14-migration.md。

快速开始

模块结构
my-module/
├── module.json          ← manifest (required)
├── scripts/
│   └── main.mjs         ← ES module entry point
├── templates/            ← Handlebars HTML templates
├── styles/               ← CSS stylesheets
├── packs/                ← compendium data
└── lang/
    └── en.json           ← localization strings

请以 boilerplate/module.json 和 boilerplate/main.mjs 为起点。

模块清单(module.json)

每个模块都需要有效的 module.json。v14 中的关键字段:

{
  "id": "my-module",
  "title": "My Module",
  "description": "What this module does.",
  "version": "1.0.0",
  "compatibility": {
    "minimum": "14",
    "verified": "14"
  },
  "documentTypes": { "Actor": { "hero": {} } },
  "authors": [{ "name": "Your Name", "url": "https://github.com/you" }],
  "esmodules": ["scripts/main.mjs"],
  "styles": [{ "src": "styles/my-module.css", "layer": "modules" }],
  "languages": [{ "lang": "en", "name": "English", "path": "lang/en.json" }],
  "socket": true,
  "relationships": {
    "systems": [],
    "requires": [],
    "recommends": []
  }
}
字段用途
id唯一标识符,仅允许使用 [A-Za-z0-9_-] — 必须与文件夹名称一致
compatibilityminimum(低于此版本时不会加载)、verified(已测试的版本)。除非确认存在兼容性问题,否则省略 maximum
documentTypes声明模块注册的自定义 Actor/Item/ActiveEffect 子类型。键必须与 CONFIG.<Doc>.dataModels 中的键一致
esmodulesES 模块入口点 — 始终优先于旧版 scripts
styles{ src, layer? } 对象数组。layer 指定样式表加载到的 CSS 级联层。纯字符串仍会迁移,但请使用对象格式
socket设置为 true 可为模块启用 game.socket.emit/on
packscompendium 包定义数组。name 必须匹配 [A-Za-z0-9_-];重复的名称或路径会在加载时引发错误
relationships.requires对其他模块或系统的硬依赖
library如果此模块是共享库而非面向用户的模块,则设置为 true
quickstartv14 新增。{ adventures: { "<id>": { uuid } }, postImport, world: { background, cover, description } } — 将模块标记为 Quickstart,可从 Setup 屏幕创建世界并导入冒险

v14 中的变更: 对系统而言,template.json 也已弃用(直至 v16)——请使用 documentTypes 和 TypeDataModel 声明类型。静态 .html 文件会以 text/plain 提供;模板仍会渲染,但浏览器无法直接打开它们。

初始化生命周期

模块会依次经过三个钩子。在入口点中注册钩子:

// init — register settings, sheets, custom document types
// game.user is NOT available yet. Canvas is NOT ready.
Hooks.once("init", () => {
  console.log("my-module | Initializing");
  // Register settings, custom sheets, document types here
});

// setup — packages loaded, documents available, canvas not ready
Hooks.once("setup", () => {
  // Modify CONFIG, register additional features
});

// ready — everything available: game.actors, game.scenes, canvas
Hooks.once("ready", () => {
  console.log("my-module | Ready");
  // Safe to access game.actors, game.scenes, game.user
  // Run migrations, initialize socket listeners
});

请在 init 中注册 CONFIG 扩展项(CONFIG.statusEffects、CONFIG.ActiveEffect.changeTypes、CONFIG.Canvas.layers)。v14 起已移除 game.template — 请从 game.model 或 TypeDataModel 架构读取类型默认值。

样式(CSS 级联层)

Foundry 使用 CSS 级联层(@layer)。请将模块 CSS 放入某个层中,以避免特异性冲突,并支持 Foundry 的浅色/深色主题。Core 按以下顺序声明层:reset, variables, elements, blocks, applications, compatibility, layouts, system, modules, exceptions。在清单中指定 modules — styles: [{ "src": "...", "layer": "modules" }] — 这样你的规则会排在 Core 自身规则之后、exceptions 之前。

不要自行创建层名称。Core 从未声明的名称会排在 exceptions 之后,并覆盖所有 Core CSS。

在 modules 下加载的样式表中,内部的 @layer 会嵌套在其中,因此 @layer my-module { ... } 会变成 modules.my-module,并保持在级联顺序中的原有位置。可用它对自己的规则分组,但不要用它替代清单字段:

@layer my-module {
  .my-module .window-content {
    --accent-color: var(--color-warm-2);
    padding: 0.5rem;
  }
}

主题通过样式表上的 body.theme-light / body.theme-dark 和 .themed.theme-<x> 实现;@layer 名称和机制与 v13 相同。

CSS 变量(适配主题的样式)

Foundry 提供 CSS 自定义属性,以支持浅色/深色主题。请始终优先使用这些属性,而不是硬编码颜色:

@layer my-module {
  .my-module-panel {
    /* Text */
    color: var(--color-text-primary);
    border: 1px solid var(--color-border);

    /* Accent palette */
    --my-accent: var(--color-warm-2);
    --my-muted: var(--color-cool-4);

    /* Typography */
    font-family: var(--font-primary);
    font-size: var(--font-size-13);
  }
}

主要变量类别:

类别变量
文本--color-text-primary、--color-text-secondary、--color-text-emphatic、--color-text-subtle、--color-text-dark-primary、--color-text-dark-secondary、--color-text-hyperlink、--color-text-selection
边框--color-border、--color-border-dark、--color-border-light-1、--color-border-light-2
暖色强调色--color-warm-1、--color-warm-2、--color-warm-3
冷色强调色--color-cool-3、--color-cool-4、--color-cool-5
灰色--color-dark-1 … --color-dark-6、--color-light-1 … --color-light-6
字体--font-primary、--font-body、--font-sans、--font-h1、--font-size-13 至 --font-size-48
光标(v14 新增)--cursor-default、--cursor-pointer、--cursor-grab、--cursor-text(以及 -down 变体)
本地开发
  1. 在 Foundry 的数据路径中创建模块文件夹:{userData}/Data/modules/my-module/
  2. 或创建符号链接:ln -s /path/to/your/dev/folder {userData}/Data/modules/my-module
  3. 启动 Foundry,前往 附加组件模块,在一个世界中启用你的模块
  4. 打开浏览器控制台(F12)查看日志和错误

命名空间(foundry.*)

从 v13 起,几乎所有核心 API 都位于 foundry.* 命名空间中。旧版全局变量仍可通过弃用兼容层解析(将在 v15 移除;appv1 框架则在 v16 移除)。v14 已移除 v12 时代的兼容层:裸用的 mergeObject、getProperty、Die、DiceTerm、Math.clamped、CONST.DOCUMENT_TYPES 等都已不可用。在所有新文件中使用带命名空间的路径,并在修改旧文件时更新它们。

旧版全局变量(v12 及更早版本)带命名空间的路径
Application、FormApplicationfoundry.appv1.api.Application(请改用 V2)
Dialogfoundry.appv1.api.Dialog(请使用 DialogV2)
ApplicationV2foundry.applications.api.ApplicationV2
HandlebarsApplicationMixinfoundry.applications.api.HandlebarsApplicationMixin
DialogV2foundry.applications.api.DialogV2
DocumentSheetV2foundry.applications.api.DocumentSheetV2
ActorSheetV2、ItemSheetV2foundry.applications.sheets.ActorSheetV2 / .ItemSheetV2
Hooksfoundry.helpers.Hooks(全局 Hooks 仍保留别名)
Canvas、CanvasLayerfoundry.canvas.Canvas、foundry.canvas.layers.CanvasLayer
Token、Tile、Region(对象)foundry.canvas.placeables.*(MeasuredTemplate 自 v14 起已弃用,请使用 Region)
Document、DataModel、TypeDataModelfoundry.abstract.Document、foundry.abstract.DataModel、foundry.abstract.TypeDataModel
fields.*(NumberField 等)foundry.data.fields.*
Roll、DiceTerm、Diefoundry.dice.Roll、foundry.dice.terms.Die
Actors、Items(集合)foundry.documents.collections.Actors / .Items
loadTemplates、renderTemplatefoundry.applications.handlebars.loadTemplates / .renderTemplate
(无旧版别名)foundry.applications.fields.* — createFormGroup、createSelectInput、createCheckboxInput、createNumberInput、createTextInput、createTextareaInput、createMultiSelectInput、createEditorInput、setInputAttributes
(无旧版别名)foundry.applications.ux.* — Tabs、ContextMenu、DragDrop、Draggable、FormDataExtended、HTMLSecret、ProseMirrorEditor、SearchFilter、TextEditor
mergeObject、duplicate、debounce、isNewerVersionfoundry.utils.*(v14 已移除裸用的全局变量:mergeObject、deepClone、expandObject、flattenObject、getProperty、setProperty、hasProperty、diffObject、equals(替代 objectsEqual)、getType、isEmpty、isNewerVersion、randomID、debounce、throttle、benchmark、parseUuid、buildUuid、buildRelativeUuid、escapeHTML、formatFileSize 等)
(无旧版别名)foundry.data.operators.* — ForcedDeletion、ForcedReplacement(全局变量 _del、_replace)用于替代 -= / == 更新键

为什么要更新? 其余兼容层计划在 v15 移除(appv1 则在 v16 移除)。使用带命名空间的路径编写的代码具有向前兼容性;使用旧版全局变量编写的代码则时日无多。

快速解构模式:

const { ApplicationV2, HandlebarsApplicationMixin, DialogV2 } = foundry.applications.api;
const { ActorSheetV2 } = foundry.applications.sheets;
const fields = foundry.data.fields;

在每个文件顶部使用这些内容,文件其余部分的代码读起来就会更自然。


文档模型

Foundry 的数据层基于 DataModel 和 Document 构建。模块可扩展它们,以创建自定义的 Actor 类型、Item 类型,或存储结构化数据。

核心模式: 使用类型化字段定义架构 → 在 init 期间将其注册到 CONFIG → Foundry 负责持久化、验证和同步。

class HeroData extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const fields = foundry.data.fields;
    return {
      health: new fields.NumberField({ required: true, initial: 100, min: 0 }),
      class: new fields.StringField({ required: true, initial: "fighter" }),
      abilities: new fields.SchemaField({
        strength: new fields.NumberField({ initial: 10 }),
        dexterity: new fields.NumberField({ initial: 10 }),
      }),
      inventory: new fields.ArrayField(new fields.StringField()),
    };
  }

  prepareDerivedData() {
    this.maxHealth = this.health + this.abilities.strength * 2;
  }
}

// Register in init hook
Hooks.once("init", () => {
  CONFIG.Actor.dataModels.hero = HeroData;
});

标志是任何文档上的模块命名空间元数据——安全可靠,即使卸载模块也会保留:

await actor.setFlag("my-module", "customData", { tracked: true });
const data = actor.getFlag("my-module", "customData");
await actor.unsetFlag("my-module", "customData");

v14 中的变更——更新运算符。 "-=key": null 和 "==key": value 更新语法已弃用(弃用期持续至 v16)。请改用 foundry.data.operators(全局变量 _del 和 _replace):

await actor.update({ "system.inventory": _del });                 // delete the key
await actor.update({ "system.abilities": _replace({ str: 12 }) }); // replace without merging

ActiveEffect 现在是类型化文档(type + system,通过 documentTypes.ActiveEffect 定义子类型),其 changes 位于 system.changes 中——参见“Active Effects”部分。DataField#migrateSource 已变为 _migrate(value, options, _state),而 migrateData 必须返回数据。

有关完整字段类型参考、生命周期钩子(_preCreate、_onCreate、_preUpdate、_onUpdate、_preDelete、_onDelete)、嵌入式文档管理、用于原子性多文档写入的 foundry.documents.modifyBatch(),以及标志与模型字段的使用指南,请阅读 references/document-model.md。

模块子类型

模块可为任何运行任何系统的世界添加自定义 Actor、Item、JournalEntryPage 及其他文档子类型。这是自 v11 起官方支持的扩展机制。

需要完成三项工作:在 module.json 的 documentTypes 下声明(包含 htmlFields 和 filePathFields),在 CONFIG.<Doc>.dataModels 上注册带自动前缀键(my-module.vehicle)的 TypeDataModel,并为带前缀的类型注册一个表单。务必提供转换路径,避免用户卸载模块后陷入困境。

有关声明语法、停用行为、转换宏及注意事项,请阅读 references/module-subtypes.md。


Application Framework(v2)

所有 UI 均使用 ApplicationV2。旧版 Application 和 FormApplication 类(foundry.appv1)已弃用,并计划在 v16 中移除。

标准模式: 对模板驱动的窗口,继承 HandlebarsApplicationMixin(ApplicationV2)。对于 Actor/Item 表单,使用 foundry.applications.sheets 中的 ActorSheetV2 / ItemSheetV2 ——它们继承 DocumentSheetV2,并添加特定于文档的拖放和 Token 管理功能。

const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;

class MySheet extends HandlebarsApplicationMixin(ApplicationV2) {
  static DEFAULT_OPTIONS = {
    id: "my-sheet",
    classes: ["my-module"],
    window: { title: "My Sheet", resizable: true },
    position: { width: 500, height: 400 },
    actions: {
      rollDice: MySheet.#onRollDice,
    },
  };

  static PARTS = {
    main: { template: "modules/my-module/templates/sheet.hbs" },
  };

  async _prepareContext(options) {
    return { name: "Hello Foundry" };
  }

  static async #onRollDice(event, target) {
    const roll = new Roll("1d20");
    await roll.evaluate();
    await roll.toMessage({ flavor: "Ability Check" });
  }
}

v14 新增——独立窗口。 每个 ApplicationV2 默认都会获得“Detach”/“Attach”标题栏控件(DEFAULT_OPTIONS.window.controls)。app.detachWindow() 会将应用移入独立的浏览器窗口;app.attachWindow() 会将其移回;app.renderChild(child) 会在同一窗口中渲染另一个应用。每次切换时都会触发 openDetachedWindow(id, win) / closeDetachedWindow(id, win) 钩子。需要在文档之间移动后仍保持正常工作的自定义元素,应继承 foundry.applications.elements.AdoptableHTMLElement。

v14 新增——渲染钩子和框架按钮。 _preRender(context, options) 会在 _prepareContext 之后、框架和各个部分渲染之前运行;它会为继承链中的每个类触发 preRender<ClassName> 钩子。_getFrameButtons(options) 会返回额外的标题栏按钮({ icon, label, action, visible? };其形状与标题栏控件相同,后者现在使用 label/visible/onClick——name/condition/callback 已弃用)。DialogV2.wait({ renderOptions }) 会将选项传递给对话框的渲染调用。

关于 DocumentSheetV2、DialogV2、部分系统、操作处理程序和表单提交,请阅读 references/application-v2.md。关于拖放深入说明、异步/竞态模式和防抖策略,请阅读同一文件中的“Drag & Drop Deep Dive”和“Async Patterns”章节。

关于 Foundry 自定义 Handlebars 辅助函数({{localize}}、{{selectOptions}}、{{formInput}}、{{formGroup}}、{{editor}} 等——{{select}} 和 {{colorPicker}} 已在 v14 中移除)、自定义 HTML 元素(<prose-mirror>、<file-picker>、<color-picker>、<string-tags>,以及 v14 中新增的 <formula-input> 和 <autocomplete-tags>)及何时应优先使用它们而不是辅助函数、自定义辅助函数/部分注册、模板预加载和常见表单模式,请阅读 references/handlebars-and-templates.md。富文本仅支持 ProseMirror——TinyMCE 和 CONFIG.TinyMCE 已移除。

表单和对话框均为 HTML——请为其添加 ARIA 角色、键盘支持、焦点管理,并处理 prefers-reduced-motion。请阅读 references/accessibility.md。


钩子与设置

钩子是 Foundry 的事件系统。设置用于存储每个世界或每个客户端的模块配置。

// Document lifecycle hooks — fire for every Actor/Item/etc CRUD operation
Hooks.on("createActor", (actor, options, userId) => {
  console.log(`Actor ${actor.name} created by user ${userId}`);
});

Hooks.on("preUpdateItem", (item, changes, options, userId) => {
  // Return false to cancel the update
  if (changes.name === "forbidden") return false;
});

// Settings — register in init, use anywhere after
Hooks.once("init", () => {
  game.settings.register("my-module", "difficulty", {
    name: "Difficulty Level",
    hint: "Adjusts the challenge rating of encounters.",
    scope: "world",       // GM-set, all players see same value
    config: true,         // show in settings menu
    type: String,
    choices: { easy: "Easy", normal: "Normal", hard: "Hard" },
    default: "normal",
    onChange: (value) => console.log("Difficulty changed to", value),
  });
});

// Read a setting
const diff = game.settings.get("my-module", "difficulty");

v14 新增钩子: preRender<App>(app, context, options)、openDetachedWindow / closeDetachedWindow(id, win)、用于 Placeables 侧边栏标签页的 get<DocumentName>PlaceableContextOptions(app, menuItems)、dropItemSheetData(item, sheet, data)、planToken(document)。没有钩子被移除;renderChatMessage(jQuery)仍已弃用,将由 renderChatMessageHTML(message, html, context) 替代,并在 v15 中移除。

v14 变更——核心设置: core.rollMode 是一个已弃用的兼容接口;请读取 game.settings.get("core", "messageMode")(public | gm | blind | self | ic)。core.gridTemplates 和 core.coneTemplateType 已随 MeasuredTemplates 弃用。

关于完整的钩子生命周期、文档钩子的命名、画布钩子、Hooks.callAll 与 Hooks.call 的区别、设置子菜单,以及 scope: "world" 与 scope: "client",请阅读 references/hooks-and-settings.md。


高级模式

Sockets(GM 权威控制)

非 GM 客户端无法直接修改世界文档。模式如下:客户端发出请求 → GM 客户端拦截并执行修改 → 广播结果。

// Requires "socket": true in module.json
const SOCKET_NAME = "module.my-module";

// GM listens and executes
Hooks.once("ready", () => {
  game.socket.on(SOCKET_NAME, async (data) => {
    if (!game.user.isGM) return;
    if (data.type === "updateActor") {
      const actor = game.actors.get(data.actorId);
      await actor.update(data.changes);
    }
  });
});

// Any client requests
function requestActorUpdate(actorId, changes) {
  if (game.user.isGM) {
    return game.actors.get(actorId).update(changes);
  }
  game.socket.emit(SOCKET_NAME, { type: "updateActor", actorId, changes });
}
骰子掷骰
const roll = new Roll("2d6 + @mod", { mod: 3 });
await roll.evaluate();       // ALWAYS await — roll() is only an async alias of evaluate()
await roll.toMessage({ flavor: "Damage Roll" });
console.log(roll.total);     // e.g. 11

// Visibility: pass a messageMode (a key of CONFIG.ChatMessage.modes)
await roll.toMessage({ flavor: "Secret" }, { messageMode: "gm" });
await roll.toMessage({ flavor: "Uses the chat dropdown" },
  { messageMode: game.settings.get("core", "messageMode") });

// Use getRollData() on actors to expose system data to roll formulas
const rollData = actor.getRollData(); // { abilities: { str: 16, ... }, health: 50, ... }
const abilityRoll = new Roll("1d20 + @abilities.str", rollData);

v14 中的变更 — 掷骰模式属于消息模式。 Roll#toMessage、ChatMessage.create、RollTable#draw 和 Combat#rollInitiative 中的 rollMode(publicroll、gmroll、blindroll、selfroll)已弃用。请改用 messageMode,可选值为 public | gm | blind | self | ic;CONFIG.Dice.rollModes → CONFIG.ChatMessage.modes;ChatMessage#applyRollMode → applyMode。使用 Roll._mapLegacyRollMode(rollMode) 转换旧值。聊天命令在 ChatLog.CHAT_COMMANDS 中注册(MESSAGE_PATTERNS 已弃用)。

图鉴包
const pack = game.packs.get("my-module.monsters");
const docs = await pack.getDocuments();
const dragon = await pack.getDocument("some-id");
await game.actors.importFromCompendium(pack, "some-id");
本地化
// lang/en.json: { "MY_MODULE.greeting": "Hello, {name}!" }
game.i18n.localize("MY_MODULE.greeting");              // "Hello, {name}!"
game.i18n.format("MY_MODULE.greeting", { name: "GM" }); // "Hello, GM!"

v14 新增了绑定到 game.i18n.localize 的全局变量 _loc,现在 localize(id, data) 也会格式化占位符。game.i18n.localize 并未弃用,_loc 只是简写。

关于完整的 Socket 模式、自定义 DiceTerm、Roll.RESOLVERS、图鉴包查询、fromUuid() 和本地化设置,请参阅 references/sockets-rolls-packs.md。


画布扩展

使用 PIXI.js,通过自定义图层和可放置对象扩展 Foundry 画布:

const { CanvasLayer } = foundry.canvas.layers;

class MyLayer extends CanvasLayer {
  static get layerOptions() {
    return foundry.utils.mergeObject(super.layerOptions, { name: "myLayer" });
  }

  async _draw(options) {
    // Add PIXI children here
  }

  async _tearDown(options) {
    this.removeChildren().forEach(c => c.destroy());
  }
}

// Register in init
Hooks.once("init", () => {
  CONFIG.Canvas.layers.myLayer = { layerClass: MyLayer, group: "primary" };
});

替换图层类只能通过 CONFIG.Canvas.layers 进行。自 v14 起,CONFIG.<Doc>.layerClass(CONFIG.Token.layerClass 等)是已弃用的代理;请改为设置 CONFIG.Canvas.layers.tokens.layerClass = MyTokenLayer。

场景层级(v14 新增)。 场景包含一个嵌入式 Level 文档集合(scene.levels、scene.initialLevel,以及当前查看的层级 canvas.level)。背景、前景和迷雾纹理已从场景移至层级(level.background.src、level.foreground.src、level.elevation.{bottom,top});scene.background、scene.foreground、scene.foregroundElevation 和 scene.backgroundColor 是已弃用的 getter。每个可放置文档都有一个 levels 集合;Token 具有 level 和 depth。请遍历 layer.viewedDocuments()(支持 Level),而不要使用已弃用的 getDocuments()。请参阅 references/scene-levels.md。

现在由区域充当模板。 MeasuredTemplateDocument、TemplateLayer 和 Scene#templates 已弃用(弃用期限至 v16);RegionDocument#shapes 接受 circle、cone、ellipse、emanation、grid、line、polygon、rectangle、ring 和 token 形状。交互式放置使用 canvas.regions.placeRegion(data, { create }),并通过 canvas.regions.templateMode 切换:

const region = await canvas.regions.placeRegion({
  name: "Fireball",
  shapes: [{ type: "circle", x: 0, y: 0, radius: canvas.dimensions.distancePixels * 20 }],
  levels: [canvas.level.id],
  displayMeasurements: true,
});

RegionDocument.createTokenEmanation(token, range, regionData) 会创建附着在 Token 上的光环(attachment.token);region.spawnTokens() / region.teleportTokens() 会将 Token 移入区域。请参阅 references/measured-templates.md(“使用区域创建模板”)和 references/regions-and-grid.md。

可放置对象侧边栏与调色板。 v14 新增了可放置对象侧边栏标签页(ui.placeables、foundry.applications.sidebar.tabs.PlaceableDirectory),每种文档类型各有一个标签页(CONFIG.<Doc>.sidebar = { applicationClass, order }),并新增钩子 get<DocumentName>PlaceableContextOptions。可放置对象配置扩展 foundry.applications.sheets.PlaceableConfig。

VFX(实验性)。 foundry.canvas.vfx 提供 VFXEffect 和组件;只有设置 CONFIG.Canvas.vfx.enabled = true 后才会启用,且 API 仍可能变化。foundry.canvas.animation.ParticleGenerator 取代了 ParticleEffect / PrimaryParticleEffect(弃用期限至 v16);CanvasShakeEffect 用于震动屏幕,Scene#transition 用于驱动场景转换(CONFIG.Canvas.sceneTransitions)。

大多数模块都不需要画布扩展。关于自定义图层、PlaceableObject 子类、坐标转换、CanvasAnimation.animate() 和 PIXI 性能建议,请参阅 references/canvas-and-pixi.md。

随附的 JS 库

Foundry 已预加载以下库——无需安装或导入:

库全局变量用途阅读
HandlebarsHandlebars用于角色卡/对话框/聊天的 HTML 模板references/handlebars-and-templates.md
PixiJSPIXIWebGL 画布渲染——令牌、光照和场景背后的引擎(固定为 7.4.3)references/canvas-and-pixi.md
anime.js(v14 新增)animejs补间/时间线动画库,以 globalThis.animejs 的形式公开 animejs ^4.3.6—
GSAP (GreenSock)gsap用于超越 CSS 或 CanvasAnimation 的编排式 UI/画布动画references/canvas-and-pixi.md 的 “GSAP” 部分
jQuery$、jQuery仍随附(^3.7.1),仅供 appv1 和旧版 renderChatMessage hook 使用。已弃用——请使用原生 DOM APIreferences/migration-guide.md

在适用时使用这些库;只有在它们都无法满足需求时,才考虑 npm 依赖。


开发工具与生态系统

官方 CLI(@foundryvtt/foundryvtt-cli)
npm install -g @foundryvtt/foundryvtt-cli

官方 CLI(fvtt 命令)用于管理合辑——将 LevelDB 包提取为单独的 JSON/YAML 文件,并重新打包。这对于对合辑内容进行版本控制至关重要。

fvtt package workon my-module          # set active package context
fvtt package extract --type Module     # extract compendium to JSON files
fvtt package pack --type Module        # repackage JSON back to LevelDB
TypeScript(@league-of-foundry-developers/foundry-vtt-types)

由社区维护的 Foundry VTT 完整 API 类型定义。提供类型化的 game、CONFIG、Hooks 和所有核心类。

npm add -D fvtt-types@github:League-of-Foundry-Developers/foundry-vtt-types#main
// tsconfig.json
{
  "compilerOptions": {
    "types": ["fvtt-types"],
    "target": "esnext",
    "moduleResolution": "bundler",
    "strict": true
  }
}

依赖这些类型之前,请检查代码仓库是否支持 v14——v14 的架构变更(ActiveEffect system.changes、Scene Levels、Regions 结构、messageMode)可能会晚于核心版本更新。缺少类型定义时,进行类型转换后继续即可。

运行时:Node 24

v14 服务需要 Node >=24.13.1 <25.0.0(v13 需要 Node 20–22;二者不能同时使用)。构建工具也应使用相同的 Node 版本,以确保本地开发、CI 和 Foundry 主机环境一致。

构建工具(Vite / Rollup)

Foundry 模块是标准的 Web 应用——可使用 Vite(推荐)或 Rollup 编译 TypeScript、处理 SCSS、npm 依赖和打包。源文件位于 src/,输出文件从 dist/ 发布,且 dist/ 会符号链接到 Foundry 的 Data/modules/<id>/。

Foundry 唯一特有的限制是:不能对资源进行哈希处理。Foundry 通过 module.json 中的固定路径引用文件,因此在任何输出文件名中使用 [hash] 都会导致清单失效。请禁用 JS、CSS 和复制资源的哈希处理。

boilerplate/ 提供开箱即用的 vite.config.mjs、rollup.config.mjs 和 package.json,均已针对 Foundry 配置——开发代理指向端口 30000、固定名称输出、用于模板/lang/清单的复制插件、源映射和监视模式。

完整指南(项目布局、开发循环、flags.hotReload 集成、生产构建、构建前清单注入和常见问题)请参阅 references/build-pipeline.md。

响应式框架(Svelte / Lit)

ApplicationV2 使挂载响应式框架来替代 Handlebars 变得轻而易举——由于没有虚拟 DOM 开销,Svelte 是社区处理复杂模块 UI 的首选。重写 _renderHTML() 以挂载框架,并重写 _onClose() 以拆除框架。

模块模板

League of Foundry Developers 维护了一个预配置 Vite、TypeScript 和 ESM 的起始模板:GitHub 上的 League-of-Foundry-Developers/FoundryVTT-Module-Template。

发布到 Foundry

在 https://foundryvtt.com/packages/submit 提交模块。清单和下载 URL 必须遵循以下模式,才能用于 GitHub Releases:

manifest:  https://github.com/you/my-module/releases/latest/download/module.json
download:  https://github.com/you/my-module/releases/download/v1.0.0/module.zip

manifest URL 始终指向 latest,以便 Foundry 自动检测更新。download URL 则包含版本号——Foundry 使用它来安装特定版本。

用于自动发布的最简 GitHub Actions 工作流:

name: Release
on:
  push:
    tags: ["v*"]
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: zip -r module.zip module.json scripts/ templates/ styles/ lang/ packs/
      - uses: softprops/action-gh-release@v2
        with:
          files: module.zip
          generate_release_notes: true

使用 git tag v1.0.0 && git push --tags 标记版本以触发工作流。标记前请更新 module.json 中的版本号。

Tours API

引导式交互教程会突出显示 UI 元素,并逐步引导用户了解各项功能。教程定义在 JSON 文件中,并通过 game.tours.register() 注册。

在 modules/my-module/tours/welcome.json 创建一个教程 JSON 文件:

{
  "title": "MY_MODULE.Tour.welcome.title",
  "description": "MY_MODULE.Tour.welcome.description",
  "canBeResumed": false,
  "display": true,
  "steps": [
    {
      "id": "step1",
      "title": "MY_MODULE.Tour.welcome.step1",
      "content": "MY_MODULE.Tour.welcome.step1Content",
      "selector": ".my-module-panel .header"
    },
    {
      "id": "step2",
      "title": "MY_MODULE.Tour.welcome.step2",
      "content": "MY_MODULE.Tour.welcome.step2Content",
      "selector": ".my-module-panel button[data-action='roll']"
    },
    {
      "id": "step3",
      "title": "MY_MODULE.Tour.welcome.step3",
      "content": "MY_MODULE.Tour.welcome.step3Content",
      "selector": ".my-module-panel .inventory"
    }
  ]
}

在 setup 钩子中注册:

Hooks.once("setup", async () => {
  game.tours.register(
    "my-module",
    "welcome",
    await foundry.nue.Tour.fromJSON("/modules/my-module/tours/welcome.json")
  );
});

// Start programmatically (e.g., on first module load)
Hooks.once("ready", async () => {
  if (!game.settings.get("my-module", "tourCompleted")) {
    await game.tours.get("my-module.welcome").start();
    await game.settings.set("my-module", "tourCompleted", true);
  }
});

每个步骤都会通过 selector 突出显示一个 DOM 元素。sidebarTab 字段(可选)会在进入该步骤前自动切换到对应的侧边栏标签页。步骤可以通过 tooltipDirection(UP、DOWN、LEFT、RIGHT)定义提示框的位置。

测试(@ethaks/fvtt-quench)

一个使用 Mocha/Chai 的游戏内测试框架,可在 Foundry 环境中运行——这是必需的,因为 Foundry 的 API 需要在已初始化的游戏状态下使用。在 quenchReady 钩子中注册测试批次;仅在 Quench 启用时延迟导入测试文件,以免它们增大生产构建包。有关测试批次注册模式、文档/工作表/钩子/掷骰测试方案,以及将 Quench(手动运行)与纯辅助函数单元测试(在 GitHub Actions 中使用 Vitest)分开的 CI 策略,请阅读 references/testing-with-quench.md。

社区库
库用途使用时机
libWrapper安全地猴子补丁修改 Foundry 核心方法修改核心行为(例如 Token.prototype.draw)。可防止模块之间发生冲突
socketlib简化跨客户端通信比原始 game.socket 更简单的替代方案——支持从玩家客户端使用 await 等待 GM 响应
Developer Mode为每个模块提供统一的调试标志可通过 UI 按模块切换的结构化日志
模块 API 模式

公开一个 API,以便其他模块与你的模块交互:

Hooks.once("init", () => {
  game.modules.get("my-module").api = {
    getHeroData: (actorId) => game.actors.get(actorId)?.system,
    rollAbility: async (actorId, ability) => { /* ... */ },
  };
});

其他模块可通过 game.modules.get("my-module")?.api?.getHeroData(id) 访问它。

调试
  • 浏览器开发者工具(F12) — 主要调试工具
  • CONFIG.debug.hooks = true — 在控制台中记录每次钩子调用
  • ui.notifications.info/warn/error() — 用于测试的游戏内反馈
  • Developer Mode 模块 — 按模块切换调试标志

活跃效果

ActiveEffect 是 Foundry 用于临时修改文档数据的系统,例如增益、减益、状态和状态效果。它们作为嵌入式文档存在于 Actor 和 Item 上。

v14 中的变更 — ActiveEffect V2。 ActiveEffect 是一个类型化文档。变更已从根级别的 changes 数组移至 system.changes,其定义由 foundry.data.ActiveEffectTypeDataModel 提供。每项变更的格式为 { key, type, value, phase, priority }:

await actor.createEmbeddedDocuments("ActiveEffect", [{
  name: "Blessed",
  img: "icons/svg/angel.svg",
  type: "base",
  system: {
    changes: [{ key: "system.abilities.str", type: "add", value: 2, phase: "initial" }]
  },
  duration: { value: 10, units: "rounds", expiry: "turnStart" }
}]);

// Toggle an effect
const effect = Array.from(actor.allApplicableEffects()).find(e => e.name === "Blessed");
await effect.update({ disabled: !effect.disabled });
字段v13v14
变更根级别的 changes: [...]system.changes: [...]
变更操作数值型 mode(CONST.ACTIVE_EFFECT_MODES)字符串型 type(CONST.ACTIVE_EFFECT_CHANGE_TYPES)
图标iconimg
持续时间{ rounds, turns, seconds, startTime, startRound }{ value, units, expiry, expired },另加单独的 start 架构
来源StringFieldDocumentUUIDField({ relative: true })
图标显示overlay 布尔值showIcon(CONST.ACTIVE_EFFECT_SHOW_ICON.NEVER | CONDITIONAL | ALWAYS)

migrateData 会在加载时转换旧文档,因此 v13 数据仍可正常使用,但请写入新格式。

变更类型(CONST.ACTIVE_EFFECT_CHANGE_TYPES,括号内为默认优先级):custom(0)、multiply(10)、add(20)、subtract(20)、downgrade(30)、upgrade(40)、override(50)。add 和 subtract 还会拼接字符串,并向数组和集合中推入元素或从中删除元素。软件包可在 CONFIG.ActiveEffect.changeTypes 中注册自定义类型({ label, defaultPriority, handler, render }),也可注册任意 type 字符串;无法识别的类型会被忽略。

阶段。 CONST.ACTIVE_EFFECT_CHANGE_PHASES 的值为 ["initial", "final"]。Core 会在派生数据之前调用 actor.applyActiveEffects("initial"),之后调用 applyActiveEffects("final")。可在 CONFIG.ActiveEffect.phases 中注册额外阶段,并自行调用 applyActiveEffects("myPhase")——除此之外不会有任何操作。

持续时间和到期。 duration.units 是 CONST.ACTIVE_EFFECT_DURATION_UNITS 中的一个值(years、months、days、hours、minutes、seconds、rounds、turns)。duration.expiry 是 CONST.ACTIVE_EFFECT_EXPIRY_EVENTS 中的一个值(combatStart、roundStart、turnStart、combatEnd、roundEnd、turnEnd),也可以是你添加到 CONFIG.ActiveEffect.expiryEvents 中的键。ActiveEffect.registry 会跟踪待处理的到期效果;CONFIG.ActiveEffect.expiryAction(默认值为 "update")决定到期时是设置 duration.expired,还是删除效果。自定义到期事件通过 ActiveEffect.registry.refresh() 触发。

若要更改变更类型的应用方式,请在 ActiveEffect 子类中重写静态方法 _applyChangeUnguided、_applyChangeAdd、_applyChangeSubtract、_applyChangeMultiply、_applyChangeOverride、_applyChangeUpgrade、_applyChangeCustom。旧实例方法(apply、_applyAdd 等)已弃用,并将保留至 v16。

CONFIG.ActiveEffect.legacyTransferral 已移除——物品效果始终保留在物品上,并通过 allApplicableEffects() 读取。

有关子类型、注册表、存储在图鉴中的效果以及完整变更流程,请参阅 references/active-effects-v2.md。

全局状态效果

CONFIG.statusEffects 是一个按状态 ID 索引的 Proxy。请按 ID 赋值:

Hooks.once("init", () => {
  CONFIG.statusEffects["my-module.burning"] = {
    id: "my-module.burning",
    name: "MY_MODULE.Effect.burning",
    img: "modules/my-module/icons/burning.svg",
    system: {
      changes: [{ key: "system.abilities.dex", type: "subtract", value: 2 }]
    }
  };
});

push() 仍可使用,但请按 ID 赋值——这样可以干净地替换现有条目。替换整个状态效果列表的系统仍可赋值一个数组。

获取所有效果

actor.effects 只包含直接位于 Actor 上的效果。来自 Item 的转移效果需要通过 allApplicableEffects() 获取:

for (const effect of actor.allApplicableEffects()) {
  console.log(effect.name, effect.disabled, effect.isTemporary);
}

// Categorize for sheet display
function prepareActiveEffectCategories(effects) {
  const categories = {
    temporary: { label: "Temporary", effects: [] },
    passive:   { label: "Passive", effects: [] },
    inactive:  { label: "Inactive", effects: [] }
  };
  for (const e of effects) {
    if (e.disabled) categories.inactive.effects.push(e);
    else if (e.isTemporary) categories.temporary.effects.push(e);
    else categories.passive.effects.push(e);
  }
  return categories;
}

// Usage in _prepareContext
context.effects = prepareActiveEffectCategories(actor.allApplicableEffects());

重要属性:e.disabled(非活跃)、e.isTemporary(当设置了 duration.expiry 或 duration.value 为有限值时为 true——v14 扩大了此属性的适用范围)、e.showIcon(令牌图标显示)。


冒险文档

Adventure 是 Foundry 官方的文档类型,用于发布预制内容——战役模块、单次冒险、遭遇包。一个 Adventure 汇集场景、角色、物品、日志、表格、宏、播放列表、卡牌,以及用于组织这些内容的文件夹层级。用户只需单击一次即可导入全部内容。

// Programmatic import (selective)
const pack = game.packs.get("my-module.starter-adventure");
const adventure = await pack.getDocument("adventureDocId");

// Preview before committing
const data = await adventure.prepareImport();

// Import — all collections by default, opt out per collection
await adventure.import({ scenes: false });

在 module.json 中声明该包,设置 type: "Adventure"。在世界中创作内容,使用 fvtt package pack 构建,并将内容发布到 packs/。为确保重新导入时能正确更新,请在所有地方使用稳定 ID。

有关创作流程、以编程方式导入、版本更新、preImportAdventure 钩子,以及常见陷阱(重复 ID、场景缩略图、宏权限、播放列表路径),请阅读 references/adventure-documents.md。


权限与所有权

Foundry 的权限模型有两个相互独立的维度:用户的角色(游戏范围的能力——从 USER_ROLES.PLAYER 到 GAMEMASTER)和文档的所有权(针对单个文档的访问权限——从 OWNERSHIP_LEVELS.NONE 到 OWNER)。只有两项检查都通过,才能进行修改。

// UI-level check (gate a button)
if (actor.canUserModify(game.user, "update")) { /* show edit UI */ }
if (actor.testUserPermission(game.user, "OBSERVER")) { /* show sheet */ }

// Granular permission (GM-configurable thresholds per role)
if (game.user.hasPermission("ACTOR_CREATE")) { /* show "New" button */ }

// Set ownership programmatically
await actor.update({ "ownership.default": 2, [`ownership.${player.id}`]: 3 });

嵌入式文档默认使用 ownership: { default: -1 }(从父级继承)。玩家无法修改自己不拥有的世界文档——对于由玩家发起的更改,请使用由 GM 授权的 socket 模式。客户端检查仅用于改善用户体验;服务器会独立执行检查。

有关角色层级、细粒度的 USER_PERMISSIONS、所有权继承、由 GM 授权的模式、图鉴包所有权,以及静默失败的陷阱,请阅读 references/permissions-and-ownership.md。


迁移与维护

跨 Foundry 版本更新模块或更改数据架构时,请在世界加载时运行迁移:

const MIGRATIONS = [
  { version: 1, fn: migrateV1 },
  { version: 2, fn: migrateV2 },
];

Hooks.once("ready", async () => {
  if (!game.user.isGM) return;
  const current = game.settings.get("my-module", "schemaVersion") ?? 0;
  const target = MIGRATIONS[MIGRATIONS.length - 1].version;
  if (current >= target) return;

  for (const { version, fn } of MIGRATIONS) {
    if (current < version) await fn();
  }
  await game.settings.set("my-module", "schemaVersion", target);
  ui.notifications.info("my-module | Migration complete.");
});

要将 v13 模块迁移到 v14? 请从 references/v14-migration.md 开始阅读——其中列出了所有破坏性更改、每项弃用及其替代方案和移除版本,以及一份检查清单。主要变化包括:ActiveEffect 的变更移至 system.changes 并采用字符串类型;MeasuredTemplate 变为 Regions;场景的背景和前景移至 Level 文档;rollMode 变为 messageMode;-=/== 更新键变为 _del/_replace;v12 的全局 shim 已移除。

有关较早版本间的迁移(Application→ApplicationV2、移除同步掷骰)、数据迁移模式、兼容性标志、使用 migrateWorld 批量迁移,以及完整的迁移注册表模式,请阅读 references/migration-guide.md。


样板文件

复制以下文件,作为新模块的起点:

文件用途
boilerplate/module.json有效的 v14 清单——compatibility 14、styles 对象、documentTypes
boilerplate/main.mjsES 模块入口,包含 init/setup/ready、设置和角色卡注册
boilerplate/type-data-model.mjs使用 TypeDataModel 和 defineSchema 的自定义 Actor 类型
boilerplate/actor-sheet.mjsActorSheetV2 + HandlebarsApplicationMixin
boilerplate/sheet.hbs包含角色信息、属性和物品清单的 Handlebars 模板
boilerplate/socket-handler.mjs由 GM 授权的 socket 模式
boilerplate/vite.config.mjsVite 配置——开发代理、固定名称输出、静态文件复制插件
boilerplate/rollup.config.mjsRollup 替代方案——node-resolve、commonjs、postcss、copy、terser
boilerplate/package.json脚本(dev、build、link)、devDependencies,以及固定 Node 24 版本的 engines

常见陷阱

  1. 使用已弃用的 v1 类——Application、FormApplication、ActorSheet、ItemSheet 位于 foundry.appv1 下,并将在 v16 中移除。请使用 ApplicationV2、DocumentSheetV2。
  2. 忘记等待掷骰完成——roll.roll() 只是 evaluate() 的异步别名。请始终使用 await roll.evaluate()。Roll.evaluateSync() 仅适用于确定性掷骰(maximize/minimize)。
  3. 遗漏 socket 前缀——Socket 事件必须使用 "module.my-module" 格式。没有 module. 前缀,消息就无法路由。
  4. 在 init 中访问 game.user——init 阶段尚未设置 game.user。请使用 ready hook 执行依赖用户的逻辑。
  5. 非 GM 修改世界文档——只有 GM 客户端可以修改世界级文档。更新会返回成功,但不会持久化。尝试修改前请使用 document.canUserModify(user, "update"),并通过由 GM 主导的 socket 模式处理玩家发起的更改。请参阅 references/permissions-and-ownership.md。
  6. 使用 scripts 而非 esmodules——scripts 字段会将文件作为经典脚本加载(没有模块作用域)。请始终使用 esmodules 以正确支持 ES 模块。不存在单独的 worldScripts 字段——这是社区误传的说法。所有模块脚本都通过清单中的 esmodules/scripts 在客户端运行。使用 hooks(init、setup、ready)和条件检查来控制代码的执行时机和位置。
  7. 内部设置忘记设置 config: false——设置为 config: true 的设置会显示在模块设置菜单中。对于仅供程序使用的值(例如架构版本),请使用 config: false。
  8. 硬编码英文字符串——所有用户可见文本都应使用 game.i18n.localize(),即使你只支持英文也不例外。这样以后进行本地化会非常简单。
  9. 未清理 hooks——保存 Hooks.on() 返回的 hook ID,并在应用关闭时调用 Hooks.off()。泄漏的 hooks 会导致内存问题和重复行为。
  10. 过于激进地设置 compatibility.maximum——这会阻止用户在较新版本的 Foundry 上运行你的模块。只有确认存在不兼容问题时才设置此项。
  11. 在生命周期 hooks 中让所有客户端都执行副作用——_onCreate、_onUpdate、_onDelete 会在每个已连接客户端上触发。请使用 if (game.userId !== userId) return;,确保副作用只在发起操作的客户端上执行。
  12. 不使用 libWrapper 补丁修改核心方法——直接进行猴子补丁会在多个模块修改同一方法时造成问题。请使用 libWrapper 安全、无冲突地补丁修改 Foundry 核心函数。
  13. 自定义 Actor 类型缺少 getRollData()——如果没有实现 getRollData(),@abilities.str 之类的掷骰公式将无法解析。请返回掷骰所需的系统数据。
  14. 使用 jQuery——jQuery 仍随 appv1 和旧版 renderChatMessage hook 一起打包,但 hooks 和 _onRender 传递的是原生 HTMLElement。请使用 querySelector、addEventListener、classList,而不是 $()。
  15. CSS 未使用 @layer 或使用虚构的层——Foundry 使用 CSS 级联层。请在清单中将层命名为 modules:styles: [{ "src": "...", "layer": "modules" }]。核心从未声明过的层名会排在 exceptions 之后,并覆盖所有核心 CSS。
  16. 传入 rollMode(v14)——rollMode 在 toMessage、ChatMessage.create、RollTable#draw 和 rollInitiative 中已弃用。请传入 messageMode(public | gm | blind | self | ic),或不传任何值:toMessage 会自行回退到 core.messageMode 设置。
  17. 数字形式的 ActiveEffect 更改模式(v14)——CONST.ACTIVE_EFFECT_MODES 和根级 changes 数组已弃用。请使用字符串 type 值(add、subtract、multiply、override、upgrade、downgrade、custom)编写 system.changes。
  18. -= 和 == 更新键(v14)——这两种写法将在 v16 前弃用。请使用 foundry.data.operators 中的 _del 和 _replace(value),或使用 mergeObject(..., { applyOperators: true })。
  19. 构建 MeasuredTemplates(v14)——MeasuredTemplateDocument、TemplateLayer 和 Scene#templates 已弃用。请改为放置 Region:canvas.regions.placeRegion({ shapes: [...] })。请参阅 references/measured-templates.md。
  20. 读取 scene.background(v14)——背景、前景及其标高已移至 Level 文档。请读取 canvas.level / scene.levels;Scene getter 只是已弃用的兼容层。请参阅 references/scene-levels.md。
  21. 设置 CONFIG.<Doc>.layerClass(v14)——这是一个已弃用的代理。请改为赋值给 CONFIG.Canvas.layers.<name>.layerClass。
  22. 在浏览器中打开随包提供的 .html 文件(自 14.361 起)——静态 .html 文件会以 text/plain 类型提供。Handlebars 模板仍会通过 Foundry 渲染;只有直接在浏览器中打开的方式失效了。
  23. 随包提供 template.json(v14)——此做法将在 v16 前弃用。请在清单中使用 documentTypes 声明类型,并定义 TypeDataModel。game.template 已移除;请读取 game.model。

参考文件

如需深入了解 API 详情,请阅读这些文件——它们会按需加载:

文件何时阅读
references/v14-migration.md将模块从 v13 迁移到 v14 时,请从这里开始——破坏性变更、标明移除版本的弃用项、检查清单
references/document-model.md构建自定义 Actor/Item 类型、TypeDataModel、defineSchema、标记、生命周期钩子、日志页面、文件夹 API
references/application-v2.md构建表单、窗口、对话框——ApplicationV2、DocumentSheetV2、HandlebarsApplicationMixin、拖放
references/hooks-and-settings.md钩子生命周期、文档钩子、画布钩子、设置 API、子菜单、按键绑定、DataModel 设置
references/chat-and-ui.md聊天命令、消息渲染、上下文菜单、enrichHTML、FilePicker、安全/授权、ProseMirror 编辑器
references/sockets-rolls-packs.mdSocket 通信、骰子/Roll 扩展、合辑包、本地化
references/canvas-and-pixi.md自定义画布图层、PlaceableObject、PIXI.js 集成、坐标转换
references/combat-and-tokens.md战斗追踪器、先攻、Token HUD、场景控制、原型 Token 配置
references/regions-and-grid.md场景区域 API、RegionDocument、RegionBehavior、网格测量、坐标转换、网格高亮
references/scene-levels.md层级文档、canvas.level、各层级背景与战争迷雾、感知层级的可放置对象
references/active-effects-v2.mdActiveEffect V2——system.changes、变更类型与阶段、持续时间与到期、注册表、效果子类型
references/vision-and-lighting.mdVisionMode、检测模式、照明系统、AmbientLight、战争迷雾
references/measured-templates.md使用区域的模板——范围效果形状、placeRegion、Token 延伸范围、从 MeasuredTemplate 迁移
references/audio-and-macros.mdAudioHelper、播放列表、音效、Macro 创建、快捷栏集成
references/module-subtypes.md模块提供的 Actor/Item/JournalEntryPage 子类型、htmlFields/filePathFields、停用行为
references/accessibility.mdARIA 角色、键盘导航、焦点管理、颜色对比度、prefers-reduced-motion
references/testing-with-quench.md使用 Quench 编写游戏内测试(Mocha/Chai)、测试模式、CI 策略
references/build-pipeline.mdVite 与 Rollup 配置、开发代理、热重载、资源路径、构建前清单注入
references/handlebars-and-templates.mdFoundry 助手清单、自定义 HTML 元素、表单助手、自定义助手/局部模板、模板预加载、表单模式
references/adventure-documents.md冒险文档、打包场景/角色/物品/日志、以编程方式导入、版本更新
references/permissions-and-ownership.md用户角色与文档所有权、testUserPermission、canUserModify、USER_PERMISSIONS、由 GM 权威控制的模式
references/migration-guide.md旧版本迁移(v11→v12→v13)、弃用 API 检测、数据迁移脚本

评论

支持 Markdown