关于 foundry-vtt-module-dev
面向 Foundry Virtual Tabletop v14 及后续版本的模块开发技能,涵盖模块结构、生命周期、数据模型、界面、样式与迁移。
主要功能
- 01v14 模块结构与清单
- 02初始化生命周期指导
- 03命名空间 API 参考
- 04文档模型与子类型
- 05ApplicationV2 与 CSS 样式
适用场景
- 01创建新的 Foundry VTT 模块
- 02将模块迁移至 v14 API
- 03添加自定义文档类型
- 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.mjsmain.mjsmodule.jsonpackage.jsonrollup.config.mjssheet.hbssocket-handler.mjstype-data-model.mjsvite.config.mjsreferences/accessibility.mdactive-effects-v2.mdadventure-documents.mdapplication-v2.mdaudio-and-macros.mdbuild-pipeline.mdcanvas-and-pixi.mdchat-and-ui.mdcombat-and-tokens.mddocument-model.mdhandlebars-and-templates.mdhooks-and-settings.mdmeasured-templates.mdmigration-guide.mdmodule-subtypes.mdpermissions-and-ownership.mdregions-and-grid.mdscene-levels.mdsockets-rolls-packs.mdtesting-with-quench.mdv14-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_-] — 必须与文件夹名称一致 |
compatibility | minimum(低于此版本时不会加载)、verified(已测试的版本)。除非确认存在兼容性问题,否则省略 maximum |
documentTypes | 声明模块注册的自定义 Actor/Item/ActiveEffect 子类型。键必须与 CONFIG.<Doc>.dataModels 中的键一致 |
esmodules | ES 模块入口点 — 始终优先于旧版 scripts |
styles | { src, layer? } 对象数组。layer 指定样式表加载到的 CSS 级联层。纯字符串仍会迁移,但请使用对象格式 |
socket | 设置为 true 可为模块启用 game.socket.emit/on |
packs | compendium 包定义数组。name 必须匹配 [A-Za-z0-9_-];重复的名称或路径会在加载时引发错误 |
relationships.requires | 对其他模块或系统的硬依赖 |
library | 如果此模块是共享库而非面向用户的模块,则设置为 true |
quickstart | v14 新增。{ 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 变体) |
本地开发
- 在 Foundry 的数据路径中创建模块文件夹:
{userData}/Data/modules/my-module/ - 或创建符号链接:
ln -s /path/to/your/dev/folder {userData}/Data/modules/my-module - 启动 Foundry,前往 附加组件模块,在一个世界中启用你的模块
- 打开浏览器控制台(F12)查看日志和错误
命名空间(foundry.*)
从 v13 起,几乎所有核心 API 都位于 foundry.* 命名空间中。旧版全局变量仍可通过弃用兼容层解析(将在 v15 移除;appv1 框架则在 v16 移除)。v14 已移除 v12 时代的兼容层:裸用的 mergeObject、getProperty、Die、DiceTerm、Math.clamped、CONST.DOCUMENT_TYPES 等都已不可用。在所有新文件中使用带命名空间的路径,并在修改旧文件时更新它们。
| 旧版全局变量(v12 及更早版本) | 带命名空间的路径 |
|---|---|
Application、FormApplication | foundry.appv1.api.Application(请改用 V2) |
Dialog | foundry.appv1.api.Dialog(请使用 DialogV2) |
ApplicationV2 | foundry.applications.api.ApplicationV2 |
HandlebarsApplicationMixin | foundry.applications.api.HandlebarsApplicationMixin |
DialogV2 | foundry.applications.api.DialogV2 |
DocumentSheetV2 | foundry.applications.api.DocumentSheetV2 |
ActorSheetV2、ItemSheetV2 | foundry.applications.sheets.ActorSheetV2 / .ItemSheetV2 |
Hooks | foundry.helpers.Hooks(全局 Hooks 仍保留别名) |
Canvas、CanvasLayer | foundry.canvas.Canvas、foundry.canvas.layers.CanvasLayer |
Token、Tile、Region(对象) | foundry.canvas.placeables.*(MeasuredTemplate 自 v14 起已弃用,请使用 Region) |
Document、DataModel、TypeDataModel | foundry.abstract.Document、foundry.abstract.DataModel、foundry.abstract.TypeDataModel |
fields.*(NumberField 等) | foundry.data.fields.* |
Roll、DiceTerm、Die | foundry.dice.Roll、foundry.dice.terms.Die |
Actors、Items(集合) | foundry.documents.collections.Actors / .Items |
loadTemplates、renderTemplate | foundry.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、isNewerVersion | foundry.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 已预加载以下库——无需安装或导入:
| 库 | 全局变量 | 用途 | 阅读 |
|---|---|---|---|
| Handlebars | Handlebars | 用于角色卡/对话框/聊天的 HTML 模板 | references/handlebars-and-templates.md |
| PixiJS | PIXI | WebGL 画布渲染——令牌、光照和场景背后的引擎(固定为 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 API | references/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 });
| 字段 | v13 | v14 |
|---|---|---|
| 变更 | 根级别的 changes: [...] | system.changes: [...] |
| 变更操作 | 数值型 mode(CONST.ACTIVE_EFFECT_MODES) | 字符串型 type(CONST.ACTIVE_EFFECT_CHANGE_TYPES) |
| 图标 | icon | img |
| 持续时间 | { rounds, turns, seconds, startTime, startRound } | { value, units, expiry, expired },另加单独的 start 架构 |
| 来源 | StringField | DocumentUUIDField({ 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.mjs | ES 模块入口,包含 init/setup/ready、设置和角色卡注册 |
boilerplate/type-data-model.mjs | 使用 TypeDataModel 和 defineSchema 的自定义 Actor 类型 |
boilerplate/actor-sheet.mjs | ActorSheetV2 + HandlebarsApplicationMixin |
boilerplate/sheet.hbs | 包含角色信息、属性和物品清单的 Handlebars 模板 |
boilerplate/socket-handler.mjs | 由 GM 授权的 socket 模式 |
boilerplate/vite.config.mjs | Vite 配置——开发代理、固定名称输出、静态文件复制插件 |
boilerplate/rollup.config.mjs | Rollup 替代方案——node-resolve、commonjs、postcss、copy、terser |
boilerplate/package.json | 脚本(dev、build、link)、devDependencies,以及固定 Node 24 版本的 engines |
常见陷阱
- 使用已弃用的 v1 类——
Application、FormApplication、ActorSheet、ItemSheet位于foundry.appv1下,并将在 v16 中移除。请使用ApplicationV2、DocumentSheetV2。 - 忘记等待掷骰完成——
roll.roll()只是evaluate()的异步别名。请始终使用await roll.evaluate()。Roll.evaluateSync()仅适用于确定性掷骰(maximize/minimize)。 - 遗漏 socket 前缀——Socket 事件必须使用
"module.my-module"格式。没有module.前缀,消息就无法路由。 - 在 init 中访问 game.user——
init阶段尚未设置game.user。请使用readyhook 执行依赖用户的逻辑。 - 非 GM 修改世界文档——只有 GM 客户端可以修改世界级文档。更新会返回成功,但不会持久化。尝试修改前请使用
document.canUserModify(user, "update"),并通过由 GM 主导的 socket 模式处理玩家发起的更改。请参阅references/permissions-and-ownership.md。 - 使用
scripts而非esmodules——scripts字段会将文件作为经典脚本加载(没有模块作用域)。请始终使用esmodules以正确支持 ES 模块。不存在单独的worldScripts字段——这是社区误传的说法。所有模块脚本都通过清单中的esmodules/scripts在客户端运行。使用 hooks(init、setup、ready)和条件检查来控制代码的执行时机和位置。 - 内部设置忘记设置
config: false——设置为config: true的设置会显示在模块设置菜单中。对于仅供程序使用的值(例如架构版本),请使用config: false。 - 硬编码英文字符串——所有用户可见文本都应使用
game.i18n.localize(),即使你只支持英文也不例外。这样以后进行本地化会非常简单。 - 未清理 hooks——保存
Hooks.on()返回的 hook ID,并在应用关闭时调用Hooks.off()。泄漏的 hooks 会导致内存问题和重复行为。 - 过于激进地设置
compatibility.maximum——这会阻止用户在较新版本的 Foundry 上运行你的模块。只有确认存在不兼容问题时才设置此项。 - 在生命周期 hooks 中让所有客户端都执行副作用——
_onCreate、_onUpdate、_onDelete会在每个已连接客户端上触发。请使用if (game.userId !== userId) return;,确保副作用只在发起操作的客户端上执行。 - 不使用 libWrapper 补丁修改核心方法——直接进行猴子补丁会在多个模块修改同一方法时造成问题。请使用
libWrapper安全、无冲突地补丁修改 Foundry 核心函数。 - 自定义 Actor 类型缺少
getRollData()——如果没有实现getRollData(),@abilities.str之类的掷骰公式将无法解析。请返回掷骰所需的系统数据。 - 使用 jQuery——jQuery 仍随 appv1 和旧版
renderChatMessagehook 一起打包,但 hooks 和_onRender传递的是原生HTMLElement。请使用querySelector、addEventListener、classList,而不是$()。 - CSS 未使用
@layer或使用虚构的层——Foundry 使用 CSS 级联层。请在清单中将层命名为modules:styles: [{ "src": "...", "layer": "modules" }]。核心从未声明过的层名会排在exceptions之后,并覆盖所有核心 CSS。 - 传入
rollMode(v14)——rollMode在toMessage、ChatMessage.create、RollTable#draw和rollInitiative中已弃用。请传入messageMode(public | gm | blind | self | ic),或不传任何值:toMessage会自行回退到core.messageMode设置。 - 数字形式的 ActiveEffect 更改模式(v14)——
CONST.ACTIVE_EFFECT_MODES和根级changes数组已弃用。请使用字符串type值(add、subtract、multiply、override、upgrade、downgrade、custom)编写system.changes。 -=和==更新键(v14)——这两种写法将在 v16 前弃用。请使用foundry.data.operators中的_del和_replace(value),或使用mergeObject(..., { applyOperators: true })。- 构建 MeasuredTemplates(v14)——
MeasuredTemplateDocument、TemplateLayer和Scene#templates已弃用。请改为放置 Region:canvas.regions.placeRegion({ shapes: [...] })。请参阅references/measured-templates.md。 - 读取
scene.background(v14)——背景、前景及其标高已移至Level文档。请读取canvas.level/scene.levels;Scene getter 只是已弃用的兼容层。请参阅references/scene-levels.md。 - 设置
CONFIG.<Doc>.layerClass(v14)——这是一个已弃用的代理。请改为赋值给CONFIG.Canvas.layers.<name>.layerClass。 - 在浏览器中打开随包提供的
.html文件(自 14.361 起)——静态.html文件会以text/plain类型提供。Handlebars 模板仍会通过 Foundry 渲染;只有直接在浏览器中打开的方式失效了。 - 随包提供
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.md | Socket 通信、骰子/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.md | ActiveEffect V2——system.changes、变更类型与阶段、持续时间与到期、注册表、效果子类型 |
references/vision-and-lighting.md | VisionMode、检测模式、照明系统、AmbientLight、战争迷雾 |
references/measured-templates.md | 使用区域的模板——范围效果形状、placeRegion、Token 延伸范围、从 MeasuredTemplate 迁移 |
references/audio-and-macros.md | AudioHelper、播放列表、音效、Macro 创建、快捷栏集成 |
references/module-subtypes.md | 模块提供的 Actor/Item/JournalEntryPage 子类型、htmlFields/filePathFields、停用行为 |
references/accessibility.md | ARIA 角色、键盘导航、焦点管理、颜色对比度、prefers-reduced-motion |
references/testing-with-quench.md | 使用 Quench 编写游戏内测试(Mocha/Chai)、测试模式、CI 策略 |
references/build-pipeline.md | Vite 与 Rollup 配置、开发代理、热重载、资源路径、构建前清单注入 |
references/handlebars-and-templates.md | Foundry 助手清单、自定义 HTML 元素、表单助手、自定义助手/局部模板、模板预加载、表单模式 |
references/adventure-documents.md | 冒险文档、打包场景/角色/物品/日志、以编程方式导入、版本更新 |
references/permissions-and-ownership.md | 用户角色与文档所有权、testUserPermission、canUserModify、USER_PERMISSIONS、由 GM 权威控制的模式 |
references/migration-guide.md | 旧版本迁移(v11→v12→v13)、弃用 API 检测、数据迁移脚本 |