This skill should be used when working with the Combat tracker, Combatant documents, initiative rolling and sorting, turn/round management, or implementing combat hooks like combatStart, combatTurn, and combatRound.
Provides methods to manage combat encounters, roll initiative, and hook into combat lifecycle events for Foundry VTT systems. Use when implementing turn-based combat mechanics, initiative formulas, or combat automation.
/plugin marketplace add ImproperSubset/hh-agentics/plugin install fvtt-dev@hh-agenticsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Domain: Foundry VTT Module/System Development Status: Production-Ready Last Updated: 2026-01-05
Foundry's combat system manages turn-based encounters through Combat and Combatant documents. Understanding the combat lifecycle is essential for game system development.
game.combat // Active combat encounter
game.combats // All combat encounters
game.combats.active // Currently active combat
// Combat properties
combat.round // Current round number
combat.turn // Current turn index
combat.active // Is combat started?
combat.combatants // EmbeddedCollection of Combatants
combat.combatant // Current turn's Combatant
combat.scene // Linked Scene
// Start/end combat
await combat.startCombat();
await combat.endCombat();
// Navigation
await combat.nextTurn();
await combat.previousTurn();
await combat.nextRound();
await combat.previousRound();
// Initiative
await combat.rollInitiative(["combatantId"]);
await combat.rollAll(); // Roll for all
await combat.rollNPC(); // Roll for NPCs only
await combat.resetAll(); // Clear all initiative
combatant.actor // Associated Actor
combatant.token // Token data (not Token instance)
combatant.initiative // Initiative value
combatant.active // Is current turn?
combatant.defeated // Defeated status
combatant.hidden // Hidden from players?
combatant.players // Owning players
// Roll initiative for this combatant
await combatant.rollInitiative();
// Get Roll without rolling
const roll = combatant.getInitiativeRoll();
const customRoll = combatant.getInitiativeRoll("1d20+5");
// Single combatant
await combat.rollInitiative("combatantId");
// Multiple combatants
await combat.rollInitiative(["id1", "id2"]);
// With options
await combat.rollInitiative(["id1"], {
formula: "1d20 + @abilities.dex.mod",
messageOptions: { flavor: "Initiative" },
updateTurn: true
});
Override in your system's Combatant class:
class MyCombatant extends Combatant {
_getInitiativeFormula() {
const actor = this.actor;
if (!actor) return "1d20";
// System-specific formula
return `1d20 + @abilities.dex.mod + @initiative.bonus`;
}
}
// Register
CONFIG.Combatant.documentClass = MyCombatant;
class MyCombat extends Combat {
_sortCombatants(a, b) {
// Higher initiative first
const initA = a.initiative ?? -Infinity;
const initB = b.initiative ?? -Infinity;
if (initA !== initB) return initB - initA;
// Tie-breaker: alphabetical
return a.name.localeCompare(b.name);
}
}
CONFIG.Combat.documentClass = MyCombat;
Hooks.on("combatStart", (combat, updateData) => {
console.log(`Combat started: Round ${updateData.round}`);
});
Hooks.on("combatTurn", (combat, updateData, updateOptions) => {
const combatant = combat.combatants.contents[updateData.turn];
console.log(`${combatant.name}'s turn`);
// updateOptions.direction: 1 (forward) or -1 (backward)
});
Hooks.on("combatRound", (combat, updateData, updateOptions) => {
console.log(`Round ${updateData.round} started`);
});
Hooks.on("updateCombat", (combat, changes, options, userId) => {
if ("turn" in changes) {
console.log("Turn changed");
}
if ("round" in changes) {
console.log("Round changed");
}
});
Override these for system-specific behavior:
class MyCombat extends Combat {
// Called at start of each turn
async _onStartTurn(combatant) {
await super._onStartTurn(combatant);
// Decrement duration effects
const actor = combatant.actor;
if (actor) {
await this._decrementEffects(actor);
}
}
// Called at end of each turn
async _onEndTurn(combatant) {
await super._onEndTurn(combatant);
// Process end-of-turn effects
}
// Called at start of each round
async _onStartRound() {
await super._onStartRound();
// Reset per-round resources
for (const c of this.combatants) {
await c.actor?.resetRoundResources?.();
}
}
// Called at end of each round
async _onEndRound() {
await super._onEndRound();
}
async _decrementEffects(actor) {
for (const effect of actor.effects) {
if (effect.duration.rounds) {
const remaining = effect.duration.rounds - 1;
if (remaining <= 0) {
await effect.delete();
} else {
await effect.update({ "duration.rounds": remaining });
}
}
}
}
}
CONFIG.Combat.documentClass = MyCombat;
Workflow methods only run for one GM. Handle player-side logic with hooks:
// This runs for all clients
Hooks.on("combatTurn", (combat, update, options) => {
// Player-safe logic here
});
function getCurrentCombatant() {
const combat = game.combat;
if (!combat?.started) return null;
return combat.combatant;
}
function isActorsTurn(actor) {
const combat = game.combat;
if (!combat?.started) return false;
return combat.combatant?.actor?.id === actor.id;
}
async function addToCombat(token) {
let combat = game.combat;
// Create combat if none exists
if (!combat) {
combat = await Combat.create({ scene: token.scene.id });
}
// Add combatant
await combat.createEmbeddedDocuments("Combatant", [{
tokenId: token.id,
actorId: token.actor?.id,
sceneId: token.scene.id
}]);
}
class MyCombat extends Combat {
async nextTurn() {
let next = this.turn + 1;
let round = this.round;
// Skip defeated
while (this.combatants.contents[next]?.defeated) {
next++;
if (next >= this.combatants.size) {
next = 0;
round++;
}
}
return this.update({ turn: next, round });
}
}
// WRONG - errors with no combatants
const combat = await Combat.create({ scene: sceneId });
await combat.startCombat(); // Error!
// CORRECT - add combatants first
const combat = await Combat.create({
scene: sceneId,
combatants: [{ tokenId: token1.id }]
});
await combat.startCombat();
// combatant.token is TokenDocument data, not Token
const tokenData = combatant.token; // Plain object
// To get actual Token instance:
const token = canvas.tokens.get(combatant.tokenId);
// Initiative can be null before rolling
const init = combatant.initiative; // Could be null
// Handle null in comparisons
const sortedInit = init ?? -Infinity;
// _onStartTurn runs AFTER the turn changes
// Use hooks if you need to act BEFORE
Hooks.on("preUpdateCombat", (combat, changes) => {
// Runs before any combat update
});
// Be aware of token linking
if (combatant.token.actorLink) {
// Changes affect the base Actor
} else {
// Changes only affect this token's synthetic actor
}
// Workflow methods only run for one GM
// Don't rely on them for all-client logic
// Use hooks for all-client execution
Hooks.on("combatTurn", () => {
// Runs on all clients
});
_getInitiativeFormula() for system formula_sortCombatants() for custom orderingcombat.started before accessing turn dataLast Updated: 2026-01-05 Status: Production-Ready Maintainer: ImproperSubset
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.