# Foundry VTT + PF1e Module Development Environment > **Version**: 1.0.0 | **Last Updated**: 2025-01-29 | **Maintainer**: Development Team ## Project Overview ### Purpose This is a development environment for creating custom macros and debugging the Foundry VTT virtual tabletop with the Pathfinder 1e (PF1e) game system. The project combines the Foundry VTT core application (v11.315) with the PF1e system module (v10.8) to enable macro development, automation scripting, and troubleshooting of game mechanics. ### Key Objectives - Develop custom macros for Pathfinder 1e character automation - Debug and troubleshoot Foundry VTT and PF1e system issues - Automate complex character abilities (e.g., Magus Arcane Pool, Haste buff management) - Provide AI-assisted development via Claude Code with extensive MCP server integration - Maintain a professional development environment with linting, formatting, and type checking ### Project Type - [x] Virtual Tabletop Platform (Foundry VTT) - [x] Game System Module (Pathfinder 1e) - [x] Macro Development (JavaScript automation scripts) - [x] Electron Application (Desktop runtime) - [ ] Web Application - [ ] CLI Tool - [ ] Library/Package ### Target Users/Audience Game Masters (GMs) and players using Foundry VTT for Pathfinder 1e campaigns who need custom automation, particularly for complex character abilities and buff management. --- ## Technology Stack ### Core Technologies - **Language(s)**: JavaScript (ES6+), TypeScript definitions (.d.ts) - **Runtime**: Node.js v16-19 (via Electron) - **Platform**: Electron (cross-platform desktop app) - **Foundry VTT Version**: v11.315 (stable) - **Game System**: Pathfinder 1e v10.8 ### Frontend Technologies #### Rendering & UI - **Canvas Engine**: PIXI.js v7.2.4 (WebGL/Canvas2D rendering) - `@pixi/graphics-smooth` - Smooth graphics rendering - `@pixi/particle-emitter` - Particle effects - **DOM Manipulation**: jQuery v3.6.4 - **Template Engine**: Handlebars v4.7.7 - **Rich Text Editor**: ProseMirror v1.x (modular editor) - **Alternative Editor**: TinyMCE v6.7.1 - **Styling**: LESS → CSS compilation #### UI Framework ```javascript // Foundry VTT uses vanilla JavaScript with global objects game // Main game instance ui // UI managers (notifications, chat, etc.) canvas // Canvas rendering system CONFIG // Global configuration ``` ### Backend Technologies #### Server Framework - **Web Server**: Express.js v4.18.2 - **Session Management**: express-session with NeDB store - **File Upload**: express-fileupload - **Compression**: compression middleware - **Static Files**: serve-static #### Database & Storage - **Primary Database**: NeDB v1.8.0 (embedded NoSQL) - World data storage - Compendium packs - Session management - **Alternative**: classic-level (LevelDB abstraction) - **File Storage**: Local filesystem (fs-extra v11.1.1) - **Cloud Storage**: AWS S3 (@aws-sdk/client-s3 v3.312.0) #### Real-Time Communication - **Protocol**: Socket.io v4.6.1 (WebSocket + fallbacks) - **Features**: - Room-based communication (per world) - Real-time document synchronization - Audio/video signaling - Client-server data updates ### PF1 System Technologies #### Build & Development - **Module System**: ES Modules (.mjs files) - **Build Tool**: Vite v4.5.0 (with HMR support) - **Linting**: ESLint v8.53.0 - **Code Formatting**: Prettier v3.1.0 - **Testing Framework**: @ethaks/fvtt-quench (Foundry-specific testing) - **Documentation Generator**: TypeDoc v0.25.3 - **Git Hooks**: Husky v8.0.3 with lint-staged #### Development Scripts ```json { "build": "vite build", "build:watch": "vite build --mode development --watch", "packs:extract": "node ./tools/packs.mjs extract", "packs:compile": "node ./tools/packs.mjs compile", "lint": "eslint . --cache", "format": "prettier --write --cache .", "docs": "typedoc" } ``` ### Infrastructure & DevOps - **Version Control**: Git - **AI Development**: Claude Code with 8 MCP servers - serena (code navigation + memory) - sequential-thinking (complex reasoning) - context7 (library documentation) - memory (knowledge graph) - fetch (web content) - windows-mcp (desktop automation) - playwright (browser automation) - database-server (SQL access) ### Development Tools - **IDE**: Visual Studio Code (recommended) - **Package Manager**: npm - **Node Version**: 16-19 (Electron compatible) - **Module Format**: ES Modules (.mjs) for PF1 system, CommonJS for Foundry core --- ## Project Structure ### Directory Layout ``` c:\DEV\Foundry/ ├── src/ # Source code root │ ├── FoundryVTT-11.315/ # Foundry VTT core application │ │ └── resources/app/ # Electron app resources │ │ ├── main.js # Electron main process entry │ │ ├── package.json # Core dependencies (Node 16-19) │ │ ├── client/ # Client-side JavaScript │ │ │ ├── game.js # Main game client (Game class) │ │ │ ├── config.js # Configuration constants │ │ │ ├── apps/ # Application windows (FormApplication) │ │ │ ├── audio/ # Audio/sound management │ │ │ ├── av/ # Audio/video (WebRTC) │ │ │ ├── canvas/ # PIXI.js canvas rendering layers │ │ │ ├── core/ # Core utilities (hooks, settings) │ │ │ ├── dice/ # Dice rolling system (Roll, DiceTerm) │ │ │ ├── pixi/ # PIXI.js extensions │ │ │ ├── tours/ # User onboarding tours │ │ │ └── ui/ # UI components (notifications, dialogs) │ │ ├── common/ # Shared client/server code │ │ │ ├── documents/ # Document base classes │ │ │ ├── abstract/ # Abstract data models │ │ │ ├── config.mjs # Shared configuration │ │ │ ├── constants.mjs # Constants and enums │ │ │ ├── utils/ # Utility functions │ │ │ └── packages/ # Package management │ │ ├── dist/ # Compiled/bundled code │ │ ├── public/ # Public web assets │ │ │ ├── scripts/ # Bundled client scripts │ │ │ │ ├── foundry.js # Main bundled script (~2MB) │ │ │ │ └── worker.js # Web worker for background tasks │ │ │ ├── lang/en.json # English localization │ │ │ ├── tours/ # Tour definitions (JSON) │ │ │ └── nue/ # NUE engine assets │ │ ├── templates/ # Handlebars templates (.html) │ │ ├── prosemirror/ # ProseMirror rich text editor │ │ └── node_modules/ # Foundry core dependencies │ │ │ ├── foundryvtt-pathfinder1-v10.8/ # Pathfinder 1e System Module │ │ ├── pf1.js # System entry point (imports pf1.mjs) │ │ ├── pf1.mjs # Main system module (ES module) │ │ ├── package.json # PF1 dev dependencies & scripts │ │ ├── public/ # Public system files │ │ │ └── system.json # System manifest (metadata) │ │ ├── module/ # System source code (ES modules) │ │ │ ├── config.mjs # PF1 game configuration │ │ │ │ # Defines: abilities, skills, alignments, saves, etc. │ │ │ ├── config.d.ts # TypeScript definitions for config │ │ │ ├── modules.mjs # Module registry/loader │ │ │ ├── migration.mjs # Data migration system │ │ │ ├── patch-core.mjs # Foundry core monkey patches │ │ │ ├── socket.mjs # Socket event handlers │ │ │ ├── action-use/ # Action usage system │ │ │ │ ├── action-use.mjs # Main action resolution │ │ │ │ ├── chat-attack.mjs # Attack chat cards │ │ │ │ └── chat-context.mjs # Chat context menu │ │ │ ├── applications/ # UI applications │ │ │ │ ├── actor/ # Actor sheets by type │ │ │ │ │ ├── character-sheet.mjs │ │ │ │ │ ├── npc-sheet.mjs │ │ │ │ │ └── vehicle-sheet.mjs │ │ │ │ ├── item/ # Item sheets │ │ │ │ │ └── item-sheet.mjs │ │ │ │ ├── compendium-browser/ # Compendium browser │ │ │ │ ├── sidebar/ # Sidebar applications │ │ │ │ └── settings/ # Settings dialogs │ │ │ ├── canvas/ # Canvas layer extensions │ │ │ ├── chat/ # Chat message handlers │ │ │ ├── components/ # Reusable UI components │ │ │ ├── dice/ # Dice rolling extensions │ │ │ │ └── terms/ # Custom dice terms │ │ │ ├── documents/ # Document classes │ │ │ │ ├── actor/ # ActorPF class │ │ │ │ │ ├── entity.mjs # Main actor class │ │ │ │ │ ├── calculate.mjs # Stat calculation │ │ │ │ │ ├── rest.mjs # Rest mechanics │ │ │ │ │ └── spellcasting.mjs # Spellcasting logic │ │ │ │ ├── item/ # ItemPF class │ │ │ │ │ ├── entity.mjs # Main item class │ │ │ │ │ └── types/ # Item type handlers │ │ │ │ ├── chat-message.mjs # ChatMessagePF │ │ │ │ └── combat.mjs # CombatPF │ │ │ ├── migration/ # Version migration scripts │ │ │ ├── registry/ # Feature registries │ │ │ ├── test/ # Unit tests (Quench) │ │ │ ├── utils/ # Utility functions │ │ │ └── _module.mjs # Re-exports for easy import │ │ ├── lang/ # Localization files │ │ │ ├── en.json # English (primary language) │ │ │ ├── de.json # German │ │ │ ├── fr.json # French │ │ │ ├── es.json # Spanish │ │ │ ├── it.json # Italian │ │ │ ├── cn.json # Chinese │ │ │ └── pt_BR.json # Portuguese (Brazil) │ │ ├── less/ # LESS stylesheets │ │ │ ├── pf1.less # Main stylesheet │ │ │ └── [various component styles] │ │ ├── packs/ # Compendium packs (.db files) │ │ │ # Pre-packaged actors, items, spells, feats, etc. │ │ ├── help/ # Help documentation │ │ ├── changelogs/ # Version changelogs │ │ ├── tools/ # Build/dev tools │ │ │ └── packs.mjs # Compendium pack compiler │ │ ├── .eslintrc.js # ESLint configuration │ │ ├── .prettierrc.json # Prettier formatting config │ │ ├── vite.config.js # Vite build configuration │ │ ├── typedoc.json # TypeDoc documentation config │ │ └── hooks.d.ts # TypeScript hook definitions │ │ │ ├── macro.js # Custom Arcane Pool macro │ │ # Magus class feature automation with dialog UI │ ├── macro_haste.js # Haste buff automation macro │ │ # Intercepts attacks to apply Haste effects │ └── zeratal.json # Character data export │ # Example Magus character for testing │ ├── .claude/ # Claude Code configuration │ ├── settings.json # Shared Claude settings │ ├── settings.local.json # Local user settings │ ├── agents/ # Specialized AI agents (8 total) │ ├── commands/ # Slash commands (9 total) │ ├── output-styles/ # Output formatting (6 styles) │ ├── skills/ # Custom skills │ ├── hooks/ # Event hooks │ └── tools/ # Utility scripts │ ├── .mcp.json # MCP server configuration ├── .git/ # Git repository ├── .gitignore # Git ignore rules ├── CLAUDE.md # This file - project documentation ├── README.md # Project overview └── QUICKSTART.md # Quick start guide ``` ### Key Files - **Foundry Entry**: [main.js](src/FoundryVTT-11.315/resources/app/main.js) - Electron main process - **Client Entry**: [foundry.js](src/FoundryVTT-11.315/resources/app/public/scripts/foundry.js) - Bundled client - **PF1 Entry**: [pf1.js](src/foundryvtt-pathfinder1-v10.8/pf1.js) → [pf1.mjs](src/foundryvtt-pathfinder1-v10.8/pf1.mjs) - System initialization - **PF1 Config**: [config.mjs](src/foundryvtt-pathfinder1-v10.8/module/config.mjs) - Game system configuration - **System Manifest**: [system.json](src/foundryvtt-pathfinder1-v10.8/public/system.json) - System metadata - **Custom Macros**: [macro.js](src/macro.js), [macro_haste.js](src/macro_haste.js) - Automation scripts - **Character Data**: [zeratal.json](src/zeratal.json) - Test character export ### Module Organization The project follows a clear separation between: 1. **Foundry Core** - Generic VTT platform code 2. **PF1 System** - Pathfinder 1e game rules and mechanics 3. **Custom Macros** - User-specific automation scripts Each layer builds on the previous: ``` Custom Macros ↓ (uses) PF1 System API (ActorPF, ItemPF, CONFIG.PF1) ↓ (extends) Foundry Core API (Actor, Item, game, ui, canvas) ↓ (runs on) Electron + Node.js ``` --- ## Code Style & Standards ### Naming Conventions #### Variables & Functions - Use `camelCase` for local variables and function names - Use `_camelCase` for private/internal variables (PF1 system uses this convention) - Use descriptive names that reveal intent - Prefix boolean variables with `is`, `has`, `can`, or `should` ```javascript // Good const arcanePool = actor.system.resources.classFeat_arcanePool; let selectedBuffs = []; const isActive = buff.system.active; const hasHasteBuff = actor.items.find(i => i.name === "Haste"); // Avoid const ap = actor.system.resources.classFeat_arcanePool; let x = []; const flag = buff.system.active; ``` #### Classes & Interfaces - Use `PascalCase` for classes and constructors - Suffix with descriptive type (e.g., `Sheet`, `Application`, `Service`) - Foundry documents often use a suffix (e.g., `ActorPF`, `ItemPF`) ```javascript // Good class ActorPF extends Actor { } class CharacterSheetPF extends ActorSheet { } class DicePoolBuilder { } // Avoid class actor { } class sheet1 { } ``` #### Constants & Configuration - Use `UPPER_SNAKE_CASE` for true constants - Use `camelCase` for configuration objects - Global CONFIG uses `UPPER_CASE` keys with nested objects ```javascript // Good const MAX_ARCANE_POOL = 10; const config = { abilities: { str: "Strength" } }; CONFIG.PF1.abilities = { str: "STR", dex: "DEX" }; // Avoid const maxarcanepool = 10; const CONFIG = { abilities: { str: "Strength" } }; ``` #### Files & Folders - Use `kebab-case.mjs` for ES module files (PF1 system) - Use `kebab-case.js` for CommonJS files (Foundry core) - Use `descriptive_name.js` or `camelCase.js` for macro files - Use `_module.mjs` for re-export aggregation files ``` actor-sheet.mjs # PF1 system module game.js # Foundry core macro_arcane_pool.js # Custom macro _module.mjs # Re-export file ``` ### Code Organization #### File Size - Keep files under 500 lines when possible - Split large classes using mixins or composition - Use `_module.mjs` files to aggregate related exports #### Function Complexity - Functions should do one thing well - Keep functions under 50 lines when possible - Maximum 5-6 parameters; use options objects for more - Use async/await for all asynchronous operations ```javascript // Good - focused function async function applyArcanePoolBuff(actor, buffName, duration) { const buff = actor.items.find(i => i.name === buffName); if (!buff) return false; await buff.update({ "system.active": true, "system.duration.value": duration }); return true; } // Avoid - doing too much function processArcanePool(actor, buffName, duration, bonusType, bonusAmount, target, options, callback) { // 200+ lines of mixed concerns } ``` #### Module Import Organization Order imports in this sequence: 1. Foundry core imports 2. PF1 system imports 3. Utility imports 4. Local module imports ```javascript // Good import { ActorSheet } from "../../common/documents/actor.mjs"; import { CONFIG } from "../../common/config.mjs"; import { ActorPF } from "../documents/actor/entity.mjs"; import { applyBuff } from "../utils/buffs.mjs"; import { CharacterSheetHelper } from "./_helper.mjs"; // Avoid mixing import sources ``` ### Comments & Documentation #### When to Comment - **DO**: Explain *why* something is done, especially workarounds - **DO**: Document complex Pathfinder 1e rules implementation - **DO**: Add TODOs with context: `// TODO: Support metamagic feats` - **DO**: Explain Foundry API quirks or version-specific behavior - **DON'T**: State the obvious - **DON'T**: Leave commented-out code (use git history) ```javascript // Good - explains why // Using setTimeout to ensure Foundry's hook cycle completes // before updating the actor. See Foundry issue #7234 setTimeout(() => actor.update(data), 0); // Avoid - states the obvious // Set arcane pool to 5 arcanePool.value = 5; ``` #### JSDoc Documentation Document all public APIs, classes, and exported functions: ```javascript /** * Applies an Arcane Pool enhancement to a weapon * @param {ActorPF} actor - The character using Arcane Pool * @param {ItemPF} weapon - The weapon to enhance * @param {number} bonus - Enhancement bonus (1-5) * @param {string[]} abilities - Special abilities (e.g., "keen", "flaming") * @returns {Promise} True if successfully applied * @example * await applyArcanePoolEnhancement(actor, weapon, 2, ["keen"]); */ async function applyArcanePoolEnhancement(actor, weapon, bonus, abilities) { // Implementation } ``` ### Error Handling #### Strategy - Use try-catch blocks for Foundry API calls that may fail - Use `ui.notifications` for user-facing errors - Log errors to console for debugging - Never swallow errors silently - Validate inputs early (fail fast) ```javascript // Good try { if (!actor) { ui.notifications.warn("You must select a token!"); return; } const result = await actor.update({"system.hp.value": newHP}); ui.notifications.info(`HP updated to ${newHP}`); } catch (error) { console.error("Failed to update HP:", error); ui.notifications.error("Failed to update HP. Check console for details."); } // Avoid try { actor.update({"system.hp.value": newHP}); } catch (e) { // Silent failure } ``` ### Performance Considerations - Use `fromUuidSync()` instead of `fromUuid()` when possible (synchronous is faster) - Cache actor/item lookups instead of repeated searches - Batch document updates when updating multiple fields - Use `actor.updateSource()` for preparation phase (before rendering) - Avoid unnecessary re-renders in dialogs ```javascript // Good - batch update await actor.update({ "system.hp.value": newHP, "system.resources.arcanePool.value": newPool, "system.attributes.ac.total": newAC }); // Avoid - multiple updates await actor.update({"system.hp.value": newHP}); await actor.update({"system.resources.arcanePool.value": newPool}); await actor.update({"system.attributes.ac.total": newAC}); ``` --- ## Foundry VTT API Reference ### Official Documentation **Primary API Documentation**: [Foundry VTT API v11](https://foundryvtt.com/api/v11/) **Key API Modules**: - **Documents**: [foundry.documents](https://foundryvtt.com/api/v11/modules/foundry.documents.html) - Actor, Item, ChatMessage, etc. - **Client Classes**: [client](https://foundryvtt.com/api/v11/modules/client.html) - Game, Canvas, UI managers - **Applications**: [client.applications](https://foundryvtt.com/api/v11/modules/client.applications.html) - FormApplication, Dialog - **PIXI**: [pixi](https://foundryvtt.com/api/v11/modules/pixi.html) - Canvas rendering - **Dice**: [client.dice](https://foundryvtt.com/api/v11/modules/client.dice.html) - Roll, DiceTerm ### Global Objects Foundry VTT provides several global objects available in all scripts: ```javascript // Core game instance game // Main Game object game.actors // Actor collection game.items // Item collection game.scenes // Scene collection game.users // User collection game.macros // Macro collection game.settings // Settings registry game.i18n // Localization // UI managers ui // UI manager ui.notifications // Toast notifications ui.chat // Chat sidebar ui.combat // Combat tracker ui.actors // Actor directory ui.items // Item directory // Canvas rendering canvas // Canvas manager canvas.tokens // Token layer canvas.lighting // Lighting layer canvas.walls // Walls layer // Configuration CONFIG // Global configuration object CONFIG.Actor // Actor configuration CONFIG.Item // Item configuration CONFIG.PF1 // PF1 system configuration // Utilities Hooks // Hook system fromUuid() // Get document by UUID fromUuidSync() // Synchronous UUID lookup ``` ### Common API Patterns #### Document Access ```javascript // Get by ID const actor = game.actors.get(actorId); const item = game.items.get(itemId); // Get by name const actor = game.actors.getName("Character Name"); const macro = game.macros.getName("Macro Name"); // Get by UUID const doc = await fromUuid("Actor.abc123.Item.def456"); const doc = fromUuidSync("Actor.abc123.Item.def456"); // Faster, synchronous // Search collection const magus = game.actors.find(a => a.name === "Zeratal"); const hastes = game.items.filter(i => i.name.includes("Haste")); ``` #### Document Updates ```javascript // Update single field await actor.update({"system.hp.value": 50}); // Update multiple fields (preferred - single transaction) await actor.update({ "system.hp.value": 50, "system.attributes.ac.total": 25 }); // Update embedded documents (items) await actor.updateEmbeddedDocuments("Item", [ {_id: itemId, "system.active": true} ]); // Create embedded documents await actor.createEmbeddedDocuments("Item", [itemData]); // Delete embedded documents await actor.deleteEmbeddedDocuments("Item", [itemId]); ``` #### Hooks System ```javascript // One-time hooks (initialization) Hooks.once("init", () => { console.log("Foundry initializing..."); }); Hooks.once("ready", () => { console.log("Foundry ready!"); }); // Recurring hooks Hooks.on("updateActor", (actor, change, options, userId) => { console.log(`${actor.name} was updated`); }); Hooks.on("preCreateItem", (item, data, options, userId) => { // Can modify data or return false to prevent return true; }); // Call custom hooks Hooks.call("myCustomHook", arg1, arg2); Hooks.callAll("myCustomHook", arg1, arg2); ``` #### Dialog API ```javascript // Simple dialog new Dialog({ title: "Dialog Title", content: "

Dialog content HTML

", buttons: { yes: { icon: '', label: "Yes", callback: (html) => { console.log("Yes clicked"); } }, no: { icon: '', label: "No" } }, default: "yes", render: (html) => { // Setup event listeners html.find('.my-input').focus(); }, close: (html) => { console.log("Dialog closed"); } }).render(true); ``` #### Notifications ```javascript // Toast notifications ui.notifications.info("Information message"); ui.notifications.warn("Warning message"); ui.notifications.error("Error message"); // Persistent notification ui.notifications.notify("Message", "info", {permanent: true}); ``` #### Templates ```javascript // Render template with data const html = await renderTemplate( "systems/pf1/templates/actors/character-sheet.hbs", { actor: actor, data: actor.system } ); // Load template const template = await getTemplate("path/to/template.hbs"); ``` ### PF1 System API #### Actor API (ActorPF) ```javascript // Access actor data actor.system.attributes.hp.value // Current HP actor.system.attributes.hp.max // Max HP actor.system.attributes.ac.normal.total // AC actor.system.resources.classFeat_arcanePool.value // Class resource // Class information actor.classes // Object of class items actor.classes["magus"] // Specific class actor.classes["magus"].level // Class level // Buff management const buffs = actor.items.filter(i => i.type === "buff"); const activeBuff = actor.items.find(i => i.type === "buff" && i.system.active && i.name === "Haste" ); // Spellcasting const spellbook = actor.system.spells.spellbooks.primary; spellbook.spells.level1 // 1st level spells ``` #### Item API (ItemPF) ```javascript // Access item data item.system.active // Buff active state item.system.duration.value // Duration remaining item.system.enhancement // Enhancement bonus // Item usage await item.use(options); // Use item (attack, spell, etc.) await item.roll(); // Roll item // Item types item.type === "weapon" // Weapon item.type === "buff" // Buff/effect item.type === "spell" // Spell item.type === "feat" // Feat item.type === "class" // Class ``` #### Configuration (pf1.config) ```javascript // Access PF1 configuration pf1.config.abilities // Ability scores pf1.config.skills // Skills pf1.config.alignments // Alignments pf1.config.saves // Saving throws pf1.config.buffTargets // Buff target types pf1.config.bonusTypes // Bonus types // Via CONFIG global CONFIG.PF1.abilities // Same as pf1.config.abilities ``` --- ## Macro Development Patterns ### Basic Macro Structure All macros should follow this IIFE (Immediately Invoked Function Expression) pattern: ```javascript (async () => { // 1. Validation - check prerequisites if (!token) { ui.notifications.warn("You must select a token!"); return; } if (!actor) { ui.notifications.warn("Selected token has no actor!"); return; } // 2. Data access - get required data const arcanePool = actor.system.resources.classFeat_arcanePool; const magusLevel = actor.classes["magus"]?.level || 0; // 3. Business logic - main macro functionality async function doSomething() { // Implementation } // 4. Execution await doSomething(); // 5. User feedback ui.notifications.info("Macro completed successfully!"); })(); ``` ### Dialog-Based Macros For complex user interactions, use the Dialog API: ```javascript (async () => { // Validation if (!token) { ui.notifications.warn("You must select a token!"); return; } const actor = token.actor; // Define dialog function function showDialog() { // Build HTML content with inline styles const dialogContent = `

Select Options

`; // Create dialog new Dialog({ title: "Macro Dialog", content: dialogContent, buttons: { confirm: { icon: '', label: "Confirm", callback: async (html) => { // Extract user selections const selectedOptions = []; html.find('input[type="checkbox"]:checked').each(function() { selectedOptions.push($(this).val()); }); const bonus = parseInt(html.find('select[name="bonus"]').val()); // Process selections await processSelection(selectedOptions, bonus); ui.notifications.info("Macro applied successfully!"); } }, cancel: { icon: '', label: "Cancel" } }, render: (html) => { // Setup dynamic event handlers html.find('input[type="checkbox"]').on('change', function() { updatePreview(html); }); } }).render(true); } // Helper functions async function processSelection(options, bonus) { // Implementation } function updatePreview(html) { // Update UI based on selections } // Execute showDialog(); })(); ``` ### Macro Chaining Call other macros from within a macro: ```javascript (async () => { // Get macro by name const buffMacro = game.macros.getName("_applyBuff"); if (!buffMacro) { ui.notifications.error("Required macro '_applyBuff' not found!"); return; } // Execute macro with arguments await buffMacro.execute({ actorId: actor.id, buffName: "Haste", duration: 5, active: true }); // Continue with other logic ui.notifications.info("Buff applied!"); })(); ``` ### Buff Management Pattern ```javascript (async () => { const actor = token.actor; const buffName = "Haste"; // Find buff on actor const buff = actor.items.find(i => i.type === "buff" && i.name === buffName ); if (!buff) { ui.notifications.warn(`Buff "${buffName}" not found on ${actor.name}!`); return; } // Toggle buff const newState = !buff.system.active; await buff.update({ "system.active": newState, "system.duration.value": newState ? 5 : 0 }); ui.notifications.info( `${buffName} ${newState ? "activated" : "deactivated"} on ${actor.name}` ); })(); ``` ### Hook Interception Pattern For advanced macros that intercept game events: ```javascript (async () => { // Register a temporary hook const hookId = Hooks.on("pf1.itemRoll", (item, options) => { // Check if this is the right item if (item.type !== "weapon") return; // Check if actor has Haste buff const hasHaste = item.actor.items.find(i => i.type === "buff" && i.name === "Haste" && i.system.active ); if (!hasHaste) return; // Modify roll options options.extraAttacks = (options.extraAttacks || 0) + 1; ui.notifications.info("Haste: Added extra attack!"); }); // Clean up hook after some time or condition setTimeout(() => { Hooks.off("pf1.itemRoll", hookId); }, 60000); // Remove after 1 minute })(); ``` --- ## Configuration ### Foundry VTT Configuration #### System Manifest (system.json) Located at: [system.json](src/foundryvtt-pathfinder1-v10.8/public/system.json) ```json { "id": "pf1", "title": "Pathfinder 1e", "description": "The Pathfinder First Edition game system for Foundry VTT", "version": "10.8", "compatibility": { "minimum": "11", "verified": "11", "maximum": "11" }, "esmodules": ["pf1.js"], "styles": ["pf1.css"], "packs": [ { "name": "classes", "label": "Classes", "type": "Item", "system": "pf1" } // ... more compendium packs ], "languages": [ { "lang": "en", "name": "English", "path": "lang/en.json" } // ... more languages ], "socket": true, "initiative": "1d20 + @attributes.init.total + (@attributes.init.total / 100)" } ``` **Key Fields**: - `id`: System identifier (must be unique) - `esmodules`: JavaScript entry points (loaded as ES modules) - `styles`: CSS stylesheets to load - `packs`: Compendium packs (pre-packaged content) - `languages`: Localization files - `socket`: Enable socket communication for multiplayer - `initiative`: Formula for initiative rolls ### PF1 System Configuration #### Development Scripts ```bash # Build for production npm run build # Development build with watch mode npm run build:watch # Extract compendium packs to JSON npm run packs:extract # Compile JSON to compendium packs npm run packs:compile # Lint code npm run lint # Format code npm run format # Generate documentation npm run docs ``` #### ESLint Configuration (.eslintrc.js) ```javascript module.exports = { env: { browser: true, es2021: true, jquery: true }, extends: [ "eslint:recommended", "plugin:prettier/recommended" ], parserOptions: { ecmaVersion: 2022, sourceType: "module" }, globals: { game: "readonly", ui: "readonly", canvas: "readonly", CONFIG: "readonly", Hooks: "readonly", foundry: "readonly", Actor: "readonly", Item: "readonly" }, rules: { "no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }] } }; ``` #### Prettier Configuration (.prettierrc.json) ```json { "printWidth": 120, "tabWidth": 2, "useTabs": false, "semi": true, "singleQuote": false, "trailingComma": "es5", "bracketSpacing": true, "arrowParens": "always" } ``` ### Claude Code Configuration #### MCP Servers (.mcp.json) The project uses 8 MCP servers for AI-assisted development: ```json { "mcpServers": { "serena": { "description": "Code navigation, symbol search, and project memory", "capabilities": ["semantic search", "symbol manipulation", "memory"] }, "sequential-thinking": { "description": "Complex problem decomposition and reasoning" }, "context7": { "description": "Up-to-date library documentation retrieval" }, "memory": { "description": "Knowledge graph for persistent context" }, "fetch": { "description": "Web content retrieval and scraping" }, "windows-mcp": { "description": "Windows desktop automation and interaction" }, "playwright": { "description": "Browser automation and testing" }, "database-server": { "description": "SQL Server database access" } } } ``` #### Claude Settings - **Agents**: 8 specialized AI agents in [.claude/agents/](.claude/agents/) - **Commands**: 9 custom slash commands in [.claude/commands/](.claude/commands/) - **Output Styles**: 6 formatting styles in [.claude/output-styles/](.claude/output-styles/) --- ## Running the Application ### Prerequisites - Foundry VTT installed (v11.315 recommended) - Node.js v16-19 (for PF1 system development) - npm (Node Package Manager) - Visual Studio Code (recommended IDE) ### Initial Setup ```bash # Navigate to project root cd "C:\DEV\Foundry" # Install PF1 system dependencies (if developing system) cd src/foundryvtt-pathfinder1-v10.8 npm install # Build PF1 system npm run build # Or use watch mode for development npm run build:watch ``` ### Running Foundry VTT #### Option 1: Run Electron Application ```bash # Navigate to Foundry directory cd "C:\DEV\Foundry\src\FoundryVTT-11.315" # Run Foundry (Windows) .\foundryvtt.exe # Or run via Node.js node resources/app/main.js ``` #### Option 2: Launch from Start Menu/Desktop - Double-click Foundry VTT shortcut - Or launch from Windows Start Menu ### Accessing the Application Once Foundry VTT is running: 1. **Setup Screen**: First launch shows setup page 2. **Select World**: Choose or create a world 3. **Launch World**: Click "Launch World" 4. **Login**: Enter game master password (if set) ### Creating/Using Macros #### Import Macro from File 1. Open Foundry VTT 2. Click "Macro Directory" in sidebar 3. Click "Create Macro" button 4. Set macro type to "Script" 5. Copy content from [macro.js](src/macro.js) or [macro_haste.js](src/macro_haste.js) 6. Paste into macro editor 7. Save macro 8. Drag to hotbar for quick access #### Testing Macros 1. Select a token on the canvas 2. Click macro from hotbar 3. Follow dialog prompts 4. Check console (F12) for errors/logs ### Development Workflow ```bash # 1. Make changes to PF1 system or macros # Edit files in src/foundryvtt-pathfinder1-v10.8/module/ # 2. Build system (if modified system code) cd src/foundryvtt-pathfinder1-v10.8 npm run build:watch # Auto-rebuilds on changes # 3. Reload Foundry # Press F5 in Foundry VTT to reload # 4. Test changes # Execute macros or test system features # 5. Check for issues # Open browser console (F12) for errors # 6. Format code before committing npm run format npm run lint # 7. Commit changes git add . git commit -m "feat: add new macro feature" ``` --- ## Testing Requirements ### Manual Testing For macro development: 1. **Token Selection**: Ensure macro validates token selection 2. **Actor Data**: Verify correct actor data access 3. **Dialog UI**: Test all dialog buttons and inputs 4. **Error Handling**: Test with invalid inputs 5. **User Feedback**: Verify notifications display correctly 6. **Edge Cases**: Test with missing items, zero resources, etc. ### Console Debugging ```javascript // Add debug logging to macros (async () => { console.log("=== Macro Start ==="); console.log("Token:", token); console.log("Actor:", actor); console.log("Actor Data:", actor.system); try { // Macro logic } catch (error) { console.error("Macro Error:", error); ui.notifications.error(`Error: ${error.message}`); } console.log("=== Macro End ==="); })(); ``` ### PF1 System Testing The PF1 system uses Quench for unit testing: ```bash # Run tests (in Foundry VTT console) quench.runAll(); # Or access test UI # Game Settings > System Settings > Test Runner ``` ### Browser Console Access - Press **F12** to open browser developer tools - Use **Console** tab for logs and errors - Use **Network** tab for API call debugging - Use **Elements** tab for DOM inspection --- ## Git & Version Control ### Commit Message Format Follow the Conventional Commits specification: ``` ():