Add Status item system and separate attack/damage rolls

Status System:
- Add StatusData model with mechanical modifiers (damageDealt, healingReceived)
- Add status item sheet with modifier configuration
- Add status-bar.hbs for displaying status chips on actor sheets
- Status chips show tooltip on hover, can be removed via click
- Add 17 status items to compendium (Blinded, Burning, Charmed, etc.)
- Frightened applies -2 damage dealt, Sickened applies -2 healing received

Attack Roll Changes:
- Separate attack and damage into two discrete rolls
- Attack hit now shows "Roll Damage" button instead of auto-rolling
- Button click rolls damage and updates the chat message in-place
- Store weapon/attack data in message flags for later damage rolling
- Fix favor/hinder and modifier preset buttons in attack dialog
- Show individual damage dice results in chat card breakdown

Mechanical Integration:
- Add _applyStatusModifiers() to VagabondActor for aggregating status effects
- Update getRollData() to include statusModifiers for roll formulas
- Update damageRoll() to automatically apply damageDealt modifier
- Update applyHealing() to respect healingReceived modifier

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-12-17 14:36:57 -06:00
parent 8d44b06f40
commit bf2cd92e93
84 changed files with 2594 additions and 97 deletions

View File

@ -237,6 +237,16 @@
"priority": "low",
"dependencies": ["1.11", "1.12"],
"notes": "Investigate what materials exist in Vagabond RPG and their mechanical effects. May need to update compendium items (6.6, 6.7) after implementation."
},
{
"id": "1.17",
"name": "Create Status item data model",
"description": "Status conditions as items: name, description, mechanical effects via embedded Active Effect data. 17 statuses from Core Rulebook: Berserk, Blinded, Burning, Charmed, Confused, Dazed, Fatigued, Frightened, Incapacitated, Invisible, Paralyzed, Prone, Restrained, Sickened, Suffocating, Unconscious, Vulnerable.",
"completed": true,
"tested": false,
"priority": "high",
"dependencies": ["1.6", "1.15"],
"notes": "Implemented StatusData with: changes array for Active Effects, includesStatuses for composites, flags for rule hints (cantMove, cantFocus, isVulnerable, etc.), favorHinder for roll modifiers, modifiers for damage/healing penalties, periodic for turn-based effects, duration tracking, stacking support."
}
]
},
@ -553,6 +563,16 @@
"priority": "medium",
"dependencies": ["3.13", "2.13"],
"notes": "Morale roll button in header, disabled when morale broken."
},
{
"id": "3.17",
"name": "Implement Status display section on Actor sheets",
"description": "Add status condition display to both Character and NPC sheets. Show active statuses as icons/badges with tooltips. Allow adding/removing statuses via drag-drop or context menu. Display on Main tab for quick visibility during combat.",
"completed": true,
"tested": false,
"priority": "high",
"dependencies": ["3.2", "3.13", "1.17"],
"notes": "Consider icon-based display similar to token status effects. Statuses should be visible at a glance. May integrate with Foundry's built-in status effect system for token overlay icons."
}
]
},
@ -641,6 +661,16 @@
"tested": false,
"priority": "high",
"dependencies": ["4.1", "1.14"]
},
{
"id": "4.10",
"name": "Implement Status item sheet",
"description": "Status name, icon selector, description editor, embedded Active Effect configuration for mechanical effects. Show which effects the status applies (e.g., Vulnerable adds Hinder to saves).",
"completed": true,
"tested": false,
"priority": "high",
"dependencies": ["4.1", "1.17"],
"notes": "Implemented with: icon selector with preview, flags section (movement/action/sense/combat/morale restrictions), favor/hinder modifiers, damage/healing modifiers, periodic effects (trigger/type/value), duration tracking, stacking config, included statuses for composites, reference field. Uses existing base item sheet infrastructure with type-specific status.hbs template."
}
]
},
@ -849,6 +879,16 @@
"priority": "medium",
"dependencies": ["6.1", "1.3"],
"notes": "In progress: 21/289+ creatures. Humanlike category complete (21 NPCs). Remaining: Artificials (23), Beasts (75+), Cryptids (75+), Fae (16), Outers (32), Primordials (28), Undead (21). Source: Core Rulebook pp.112-181."
},
{
"id": "6.10",
"name": "Populate Statuses compendium",
"description": "All 17 status conditions from Core Rulebook with icons and Active Effects: Berserk, Blinded, Burning, Charmed, Confused, Dazed, Fatigued, Frightened, Incapacitated, Invisible, Paralyzed, Prone, Restrained, Sickened, Suffocating, Unconscious, Vulnerable.",
"completed": true,
"tested": false,
"priority": "high",
"dependencies": ["6.1", "1.17"],
"notes": "Each status needs: icon, description from rulebook, Active Effects for mechanical changes. Key effects: Vulnerable (Hinder on saves, Favor for attackers), Confused (Hinder on checks/saves), Frightened (-2 damage), Sickened (-2 healing), Incapacitated (fail Might/Dex checks, is Vulnerable). Composite statuses: Unconscious (Blinded+Incapacitated+Prone), Paralyzed (Incapacitated+Speed 0), Restrained (Vulnerable+Speed 0)."
}
]
},
@ -1279,7 +1319,7 @@
}
],
"summary": {
"total_tasks": 125,
"total_tasks": 129,
"phases": 12,
"critical_path": [
"0.1 -> 1.1 -> 2.1 -> 2.2 -> 3.1 -> 3.2 -> 7.1 -> 9.2",

View File

@ -169,6 +169,7 @@
"VAGABOND.Fumble": "Fumble!",
"VAGABOND.Damage": "Damage",
"VAGABOND.RollDamage": "Roll Damage",
"VAGABOND.DamageType": "Damage Type",
"VAGABOND.Effect": "Effect",
"VAGABOND.CritEffect": "Crit Effect",
@ -395,6 +396,7 @@
"VAGABOND.ItemNew": "New {type}",
"VAGABOND.ItemDeleteTitle": "Delete {name}",
"VAGABOND.ItemDeleteConfirm": "Are you sure you want to delete {name}?",
"VAGABOND.RemoveStatus": "Remove Status",
"VAGABOND.ItemName": "Item Name",
"VAGABOND.DamageTypeBlunt": "Blunt",
@ -533,5 +535,72 @@
"VAGABOND.ClassFeatures": "Class Features",
"VAGABOND.NoProgression": "No progression entries",
"VAGABOND.FeatureName": "Feature Name",
"VAGABOND.FeatureDescription": "Description"
"VAGABOND.FeatureDescription": "Description",
"VAGABOND.ItemTypeStatus": "Status",
"VAGABOND.StatusIcon": "Status Icon",
"VAGABOND.StatusRestrictions": "Status Restrictions",
"VAGABOND.MovementRestrictions": "Movement",
"VAGABOND.CantMove": "Cannot Move",
"VAGABOND.CantRush": "Cannot Rush",
"VAGABOND.SpeedZero": "Speed is 0",
"VAGABOND.CrawlOnly": "Crawl Only",
"VAGABOND.ActionRestrictions": "Actions",
"VAGABOND.CantFocus": "Cannot Focus",
"VAGABOND.CantUseActions": "Cannot Use Actions",
"VAGABOND.OnlyAttackMoveRush": "Only Attack, Move, Rush",
"VAGABOND.SenseRestrictions": "Senses",
"VAGABOND.CantSee": "Cannot See",
"VAGABOND.CombatModifiers": "Combat",
"VAGABOND.IsVulnerable": "Is Vulnerable",
"VAGABOND.CloseAttacksAutoCrit": "Close Attacks Auto-Crit",
"VAGABOND.FailsMightDexChecks": "Fails MIT/DEX Checks",
"VAGABOND.MoraleSpecial": "Morale & Special",
"VAGABOND.NoMoraleChecks": "No Morale Checks",
"VAGABOND.ImmuneToFrightened": "Immune to Frightened",
"VAGABOND.CantAttackCharmer": "Cannot Attack Charmer",
"VAGABOND.ReducesItemSlots": "Reduces Item Slots",
"VAGABOND.FavorHinderModifiers": "Favor/Hinder Modifiers",
"VAGABOND.HinderOnAfflicted": "Afflicted Has Hinder On",
"VAGABOND.FavorAgainstAfflicted": "Others Have Favor Against",
"VAGABOND.Checks": "Checks",
"VAGABOND.FavorHinderContext": "Context (optional)",
"VAGABOND.DamageHealingModifiers": "Damage/Healing Modifiers",
"VAGABOND.DamageDealtModifier": "Damage Dealt",
"VAGABOND.HealingReceivedModifier": "Healing Received",
"VAGABOND.PeriodicEffects": "Periodic Effects",
"VAGABOND.Trigger": "Trigger",
"VAGABOND.StartOfTurn": "Start of Turn",
"VAGABOND.EndOfTurn": "End of Turn",
"VAGABOND.EachRound": "Each Round",
"VAGABOND.EffectType": "Effect Type",
"VAGABOND.Healing": "Healing",
"VAGABOND.EffectDescription": "Effect Description",
"VAGABOND.None": "None",
"VAGABOND.DurationType": "Duration Type",
"VAGABOND.DurationValue": "Duration Value",
"VAGABOND.Rounds": "Rounds",
"VAGABOND.Minutes": "Minutes",
"VAGABOND.Hours": "Hours",
"VAGABOND.UntilRest": "Until Rest",
"VAGABOND.UntilRemoved": "Until Removed",
"VAGABOND.Stacking": "Stacking",
"VAGABOND.Stackable": "Stackable",
"VAGABOND.MaxStacks": "Max Stacks",
"VAGABOND.IncludedStatuses": "Included Statuses",
"VAGABOND.IncludedStatusesHint": "For composite statuses (e.g., Unconscious includes Blinded, Incapacitated, Prone)",
"VAGABOND.AddIncludedStatus": "Add Included Status",
"VAGABOND.Reference": "Reference"
}

View File

@ -204,6 +204,29 @@ export default class AttackRollDialog extends VagabondRollDialog {
return weapon.system.getDamageFormula?.(this.twoHanded) || weapon.system.damage || "1d6";
}
/**
* Extract dice results from a Roll for display.
* @param {Roll|null} roll - The roll to extract results from
* @returns {Array<{faces: number, result: number}>} Array of dice results
* @private
*/
_extractDiceResults(roll) {
if (!roll) return [];
const results = [];
for (const term of roll.terms) {
if (term instanceof foundry.dice.terms.Die) {
for (const r of term.results) {
results.push({
faces: term.faces,
result: r.result,
});
}
}
}
return results;
}
/* -------------------------------------------- */
/* Data Preparation */
/* -------------------------------------------- */
@ -318,18 +341,8 @@ export default class AttackRollDialog extends VagabondRollDialog {
modifier: this.rollConfig.modifier,
});
// Roll damage if the attack hit
let damageResult = null;
if (result.success) {
const damageFormula = this._getDamageFormula();
damageResult = await damageRoll(damageFormula, {
isCrit: result.isCrit,
rollData: this.actor.getRollData(),
});
}
// Send to chat with custom template
await this._sendToChat(result, damageResult);
// Send to chat (damage is rolled separately via button click)
await this._sendToChat(result);
}
/**
@ -340,9 +353,10 @@ export default class AttackRollDialog extends VagabondRollDialog {
* @returns {Promise<ChatMessage>}
* @private
*/
async _sendToChat(result, damageResult) {
async _sendToChat(result, damageResult = null) {
const weapon = this.weapon;
const attackData = this.attackData;
const damageFormula = this._getDamageFormula();
// Prepare template data
const templateData = {
@ -372,11 +386,15 @@ export default class AttackRollDialog extends VagabondRollDialog {
netFavorHinder: this.netFavorHinder,
favorSources: this.rollConfig.autoFavorHinder.favorSources,
hinderSources: this.rollConfig.autoFavorHinder.hinderSources,
// Damage info
// Damage info (only present if damage was rolled)
hasDamage: !!damageResult,
damageTotal: damageResult?.total,
damageFormula: damageResult?.formula,
damageDiceResults: this._extractDiceResults(damageResult),
twoHanded: this.twoHanded,
// Show damage button if hit but damage not yet rolled
showDamageButton: result.success && !damageResult,
pendingDamageFormula: damageFormula,
};
// Render the chat card template
@ -389,13 +407,30 @@ export default class AttackRollDialog extends VagabondRollDialog {
const rolls = [result.roll];
if (damageResult) rolls.push(damageResult);
// Create the chat message
// Create the chat message with flags for later damage rolling
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
content,
rolls,
sound: CONFIG.sounds.dice,
flags: {
vagabond: {
type: "attack-roll",
actorId: this.actor.id,
weaponId: weapon.id,
weaponName: weapon.name,
damageFormula,
damageType: weapon.system.damageType,
damageTypeLabel: game.i18n.localize(
CONFIG.VAGABOND?.damageTypes?.[weapon.system.damageType] || weapon.system.damageType
),
twoHanded: this.twoHanded,
isCrit: result.isCrit,
success: result.success,
damageRolled: !!damageResult,
},
},
};
return ChatMessage.create(chatData);

View File

@ -24,4 +24,5 @@ export {
ArmorData,
EquipmentData,
FeatureData,
StatusData,
} from "./item/_module.mjs";

View File

@ -13,3 +13,4 @@ export { default as WeaponData } from "./weapon.mjs";
export { default as ArmorData } from "./armor.mjs";
export { default as EquipmentData } from "./equipment.mjs";
export { default as FeatureData } from "./feature.mjs";
export { default as StatusData } from "./status.mjs";

264
module/data/item/status.mjs Normal file
View File

@ -0,0 +1,264 @@
/**
* Status Item Data Model
*
* Defines the data schema for status conditions in Vagabond RPG.
* Statuses represent temporary conditions like Blinded, Prone, Frightened, etc.
* that can be applied to actors and modify their capabilities.
*
* Core Rulebook Reference: Status conditions (p.36)
*
* @extends VagabondItemBase
*/
import VagabondItemBase from "./base-item.mjs";
export default class StatusData extends VagabondItemBase {
/**
* Define the schema for status items.
*
* @returns {Object} The schema definition
*/
static defineSchema() {
const fields = foundry.data.fields;
const baseSchema = super.defineSchema();
return {
...baseSchema,
// Icon for display on actor sheets and tokens
// Uses Foundry's built-in icon path format
icon: new fields.StringField({
required: false,
blank: true,
initial: "icons/svg/hazard.svg",
}),
// Active Effect changes this status applies when added to an actor
// These are the mechanical effects that can be automated
changes: new fields.ArrayField(
new fields.SchemaField({
key: new fields.StringField({ required: true }),
mode: new fields.NumberField({
integer: true,
initial: 2, // CONST.ACTIVE_EFFECT_MODES.ADD
}),
value: new fields.StringField({ required: true }),
priority: new fields.NumberField({ integer: true, nullable: true }),
}),
{ initial: [] }
),
// Statuses that this status includes (composite statuses)
// e.g., Unconscious includes Blinded, Incapacitated, Prone
// Store as array of status names for lookup
includesStatuses: new fields.ArrayField(new fields.StringField(), { initial: [] }),
// Special flags for rules that can't be expressed as Active Effects
// These serve as hints for the GM and can be checked programmatically
flags: new fields.SchemaField({
// Movement restrictions
cantMove: new fields.BooleanField({ initial: false }),
cantRush: new fields.BooleanField({ initial: false }),
speedZero: new fields.BooleanField({ initial: false }),
crawlOnly: new fields.BooleanField({ initial: false }),
// Action restrictions
cantFocus: new fields.BooleanField({ initial: false }),
cantUseActions: new fields.BooleanField({ initial: false }),
onlyAttackMoveRush: new fields.BooleanField({ initial: false }),
// Sense restrictions
cantSee: new fields.BooleanField({ initial: false }),
// Combat modifiers
isVulnerable: new fields.BooleanField({ initial: false }),
closeAttacksAutoCrit: new fields.BooleanField({ initial: false }),
failsMightDexChecks: new fields.BooleanField({ initial: false }),
// Morale
noMoraleChecks: new fields.BooleanField({ initial: false }),
immuneToFrightened: new fields.BooleanField({ initial: false }),
// Other
cantAttackCharmer: new fields.BooleanField({ initial: false }),
reducesItemSlots: new fields.BooleanField({ initial: false }),
}),
// Favor/Hinder modifiers (for roll dialogs to check)
favorHinder: new fields.SchemaField({
// Applies Hinder to the afflicted creature's rolls
hinderChecks: new fields.BooleanField({ initial: false }),
hinderSaves: new fields.BooleanField({ initial: false }),
hinderAttacks: new fields.BooleanField({ initial: false }),
// Applies Favor to rolls against the afflicted creature
favorAgainstChecks: new fields.BooleanField({ initial: false }),
favorAgainstSaves: new fields.BooleanField({ initial: false }),
favorAgainstAttacks: new fields.BooleanField({ initial: false }),
// Specific contexts (for conditional application)
context: new fields.StringField({
required: false,
blank: true,
initial: "",
}),
}),
// Damage or healing modifiers (flat bonuses/penalties)
modifiers: new fields.SchemaField({
damageDealt: new fields.NumberField({ integer: true, initial: 0 }),
healingReceived: new fields.NumberField({ integer: true, initial: 0 }),
}),
// Periodic effects (for statuses like Burning, Suffocating)
periodic: new fields.SchemaField({
// When does the effect trigger?
trigger: new fields.StringField({
required: false,
nullable: true,
blank: false,
initial: null,
choices: ["startOfTurn", "endOfTurn", "eachRound"],
}),
// What type of effect?
type: new fields.StringField({
required: false,
nullable: true,
blank: false,
initial: null,
choices: ["damage", "fatigue", "healing", "check"],
}),
// Dice formula or flat value
value: new fields.StringField({ required: false, blank: true }),
// Description of what happens
effectDescription: new fields.StringField({ required: false, blank: true }),
}),
// Duration tracking (for time-limited statuses)
duration: new fields.SchemaField({
type: new fields.StringField({
required: false,
nullable: true,
blank: false,
initial: null,
choices: ["rounds", "minutes", "hours", "untilRest", "untilRemoved", "special"],
}),
value: new fields.NumberField({ integer: true, nullable: true }),
remaining: new fields.NumberField({ integer: true, nullable: true }),
}),
// Stacking behavior
stackable: new fields.BooleanField({ initial: false }),
maxStacks: new fields.NumberField({ integer: true, initial: 1, min: 1 }),
// Reference to the rulebook page for quick lookup
reference: new fields.StringField({
required: false,
blank: true,
initial: "Core Rulebook p.36",
}),
};
}
/**
* Get a summary of this status's effects for display.
*
* @returns {string[]} Array of effect descriptions
*/
getEffectSummary() {
const effects = [];
// Movement effects
if (this.flags.speedZero) effects.push("Speed is 0");
if (this.flags.cantMove) effects.push("Cannot move");
if (this.flags.cantRush) effects.push("Cannot Rush");
if (this.flags.crawlOnly) effects.push("Can only crawl");
// Action effects
if (this.flags.cantFocus) effects.push("Cannot Focus");
if (this.flags.cantUseActions) effects.push("Cannot use Actions");
if (this.flags.onlyAttackMoveRush) effects.push("Can only Attack, Move, and Rush");
// Sense effects
if (this.flags.cantSee) effects.push("Cannot see");
// Combat effects
if (this.flags.isVulnerable) effects.push("Is Vulnerable");
if (this.flags.closeAttacksAutoCrit) effects.push("Close Attacks auto-crit");
if (this.flags.failsMightDexChecks) effects.push("Fails Might and Dexterity Checks");
// Favor/Hinder
if (this.favorHinder.hinderChecks) effects.push("Hinder on Checks");
if (this.favorHinder.hinderSaves) effects.push("Hinder on Saves");
if (this.favorHinder.favorAgainstAttacks) effects.push("Attacks against have Favor");
if (this.favorHinder.favorAgainstSaves) effects.push("Saves against have Favor");
// Modifiers
if (this.modifiers.damageDealt !== 0) {
effects.push(`${this.modifiers.damageDealt} to damage dealt`);
}
if (this.modifiers.healingReceived !== 0) {
effects.push(`${this.modifiers.healingReceived} to healing received`);
}
// Periodic effects
if (this.periodic.trigger && this.periodic.effectDescription) {
effects.push(this.periodic.effectDescription);
}
// Included statuses
if (this.includesStatuses.length > 0) {
effects.push(`Includes: ${this.includesStatuses.join(", ")}`);
}
return effects;
}
/**
* Check if this status has any Active Effect changes.
*
* @returns {boolean} True if status has mechanical effects
*/
hasActiveEffects() {
return this.changes.length > 0;
}
/**
* Check if this status is a composite (includes other statuses).
*
* @returns {boolean} True if this status includes other statuses
*/
isComposite() {
return this.includesStatuses.length > 0;
}
/**
* Check if this status has periodic effects.
*
* @returns {boolean} True if status has periodic effects
*/
hasPeriodic() {
return this.periodic.trigger !== null && this.periodic.type !== null;
}
/**
* Get chat card data for displaying status information.
*
* @returns {Object} Chat card data
*/
getChatData() {
const data = super.getChatData();
data.icon = this.icon;
data.effects = this.getEffectSummary();
data.reference = this.reference;
if (this.duration.type) {
data.duration =
this.duration.type === "special"
? "Special"
: `${this.duration.value} ${this.duration.type}`;
}
return data;
}
}

View File

@ -229,15 +229,17 @@ export async function saveRoll(actor, saveType, difficulty, options = {}) {
/**
* Roll damage dice.
* Automatically applies status modifiers (e.g., Frightened's -2 damage dealt).
*
* @param {string} formula - The damage formula (e.g., "2d6", "1d8+3")
* @param {Object} options - Roll options
* @param {boolean} [options.isCrit=false] - Double the dice on crit
* @param {Object} [options.rollData={}] - Data for roll formula evaluation
* @param {boolean} [options.applyStatusModifiers=true] - Apply status damage modifiers
* @returns {Promise<Roll>} The evaluated roll
*/
export async function damageRoll(formula, options = {}) {
const { isCrit = false, rollData = {} } = options;
const { isCrit = false, rollData = {}, applyStatusModifiers = true } = options;
let rollFormula = formula;
@ -246,6 +248,14 @@ export async function damageRoll(formula, options = {}) {
rollFormula = doubleDice(formula);
}
// Apply status modifiers to damage dealt (e.g., Frightened gives -2)
if (applyStatusModifiers) {
const damageModifier = rollData.statusModifiers?.damageDealt || 0;
if (damageModifier !== 0) {
rollFormula += damageModifier > 0 ? ` + ${damageModifier}` : ` - ${Math.abs(damageModifier)}`;
}
}
const roll = new Roll(rollFormula, rollData);
await roll.evaluate();

View File

@ -204,6 +204,9 @@ export default class VagabondActor extends Actor {
}
system.armor = totalArmor;
// Apply status modifiers
this._applyStatusModifiers();
// Calculate used item slots from inventory
// Each item type implements getTotalSlots() with its own logic
let usedSlots = 0;
@ -225,7 +228,39 @@ export default class VagabondActor extends Actor {
*/
_prepareNPCDerivedData() {
// NPC derived data is handled by NPCData.prepareDerivedData()
// Add any document-level NPC calculations here if needed
// Apply status modifiers to NPCs as well
this._applyStatusModifiers();
}
/**
* Apply modifiers from status items to the actor.
* Aggregates damageDealt and healingReceived modifiers from all active statuses.
*
* @private
*/
_applyStatusModifiers() {
const system = this.system;
// Initialize modifiers if not present
if (!system.statusModifiers) {
system.statusModifiers = {
damageDealt: 0,
healingReceived: 0,
};
}
// Reset to base values
system.statusModifiers.damageDealt = 0;
system.statusModifiers.healingReceived = 0;
// Aggregate modifiers from all status items
for (const item of this.items) {
if (item.type === "status") {
const modifiers = item.system.modifiers || {};
system.statusModifiers.damageDealt += modifiers.damageDealt || 0;
system.statusModifiers.healingReceived += modifiers.healingReceived || 0;
}
}
}
/* -------------------------------------------- */
@ -246,6 +281,12 @@ export default class VagabondActor extends Actor {
// Add actor-level conveniences
data.name = this.name;
// Add status modifiers for use in damage/healing formulas
data.statusModifiers = this.system.statusModifiers || {
damageDealt: 0,
healingReceived: 0,
};
// Type-specific roll data is added by the TypeDataModel's getRollData()
return data;
@ -417,16 +458,21 @@ export default class VagabondActor extends Actor {
/**
* Heal this actor.
* Applies status modifiers (e.g., Sickened reduces healing received).
*
* @param {number} amount - The amount to heal
* @returns {Promise<VagabondActor>} The updated actor
*/
async applyHealing(amount) {
// Apply status modifiers to healing (e.g., Sickened gives -2)
const healingModifier = this.system.statusModifiers?.healingReceived || 0;
const finalAmount = Math.max(0, amount + healingModifier);
if (this.type === "character") {
return this.modifyResource("hp", amount);
return this.modifyResource("hp", finalAmount);
}
const max = this.system.hp.max;
const newHP = Math.min(max, this.system.hp.value + amount);
const newHP = Math.min(max, this.system.hp.value + finalAmount);
return this.update({ "system.hp.value": newHP });
}
@ -693,7 +739,7 @@ export default class VagabondActor extends Actor {
// Find the lowest morale score (weakest link)
const actors = tokens.map((t) => t.actor);
const lowestMorale = Math.min(...actors.map((a) => a.system.morale));
const groupName =
const _groupName =
tokens.length === 1
? tokens[0].name
: `${tokens.length} enemies (lowest morale: ${lowestMorale})`;

View File

@ -110,6 +110,10 @@ export const EFFECT_KEYS = {
// Focus tracking
"focus.maxConcurrent": "system.focus.maxConcurrent",
// Status-related modifiers
"damage.dealt.bonus": "system.modifiers.damageDealt",
"healing.received.bonus": "system.modifiers.healingReceived",
};
/**
@ -273,6 +277,7 @@ export function getEffectsBySource(actor) {
perk: [],
feature: [],
equipment: [],
status: [],
temporary: [],
other: [],
};

View File

@ -66,6 +66,7 @@ export default class VagabondActorSheet extends HandlebarsApplicationMixin(Actor
changeTab: VagabondActorSheet.#onChangeTab,
modifyResource: VagabondActorSheet.#onModifyResource,
toggleTrained: VagabondActorSheet.#onToggleTrained,
removeStatus: VagabondActorSheet.#onRemoveStatus,
},
// Drag-drop configuration - use Foundry's built-in system
// Setting to empty array disables ActorSheetV2's default handling
@ -184,6 +185,7 @@ export default class VagabondActorSheet extends HandlebarsApplicationMixin(Actor
features: [],
perks: [],
classes: [],
statuses: [],
ancestry: null,
};
@ -220,6 +222,9 @@ export default class VagabondActorSheet extends HandlebarsApplicationMixin(Actor
case "ancestry":
items.ancestry = item;
break;
case "status":
items.statuses.push(item);
break;
}
}
@ -821,4 +826,20 @@ export default class VagabondActorSheet extends HandlebarsApplicationMixin(Actor
const currentValue = this.actor.system.skills[skillId]?.trained ?? false;
await this.actor.update({ [`system.skills.${skillId}.trained`]: !currentValue });
}
/**
* Handle status removal action.
* @param {PointerEvent} event
* @param {HTMLElement} target
*/
static async #onRemoveStatus(event, target) {
event.preventDefault();
const itemId = target.closest("[data-item-id]")?.dataset.itemId;
if (!itemId) return;
const item = this.actor.items.get(itemId);
if (!item || item.type !== "status") return;
await item.delete();
}
}

View File

@ -3,7 +3,7 @@
* @module vagabond
*/
/* global Actors, Items */
/* global Actors, Items, AudioHelper */
// Import configuration
import { VAGABOND } from "./helpers/config.mjs";
@ -19,6 +19,7 @@ import {
ArmorData,
EquipmentData,
FeatureData,
StatusData,
} from "./data/item/_module.mjs";
// Import document classes
@ -63,6 +64,7 @@ async function preloadHandlebarsTemplates() {
"systems/vagabond/templates/actor/character-magic.hbs",
"systems/vagabond/templates/actor/character-biography.hbs",
"systems/vagabond/templates/actor/parts/tabs.hbs",
"systems/vagabond/templates/actor/parts/status-bar.hbs",
// NPC sheet parts
"systems/vagabond/templates/actor/npc-header.hbs",
"systems/vagabond/templates/actor/npc-stats.hbs",
@ -83,6 +85,7 @@ async function preloadHandlebarsTemplates() {
"systems/vagabond/templates/item/types/spell.hbs",
"systems/vagabond/templates/item/types/perk.hbs",
"systems/vagabond/templates/item/types/feature.hbs",
"systems/vagabond/templates/item/types/status.hbs",
];
return loadTemplates(templatePaths);
@ -132,6 +135,7 @@ Hooks.once("init", async () => {
armor: ArmorData,
equipment: EquipmentData,
feature: FeatureData,
status: StatusData,
};
// Define custom Document classes
@ -473,6 +477,182 @@ Hooks.on("renderChatMessage", (message, html) => {
});
});
/* -------------------------------------------- */
/* Attack Damage Roll Handling */
/* -------------------------------------------- */
/**
* Handle clicks on "Roll Damage" buttons in attack chat cards.
* Rolls damage using the stored weapon data and updates the message.
*/
Hooks.on("renderChatMessage", (message, html) => {
// Find damage roll buttons in this message
html.find(".roll-damage-btn").on("click", async (event) => {
event.preventDefault();
const button = event.currentTarget;
const flags = message.flags?.vagabond;
// Verify this is an attack roll message with pending damage
if (!flags || flags.type !== "attack-roll" || flags.damageRolled) {
ui.notifications.warn("Cannot roll damage for this message");
return;
}
// Disable button immediately to prevent double-clicks
button.disabled = true;
button.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Rolling...';
try {
// Get the actor for roll data
const actor = game.actors.get(flags.actorId);
if (!actor) {
ui.notifications.error("Could not find actor for damage roll");
return;
}
// Import damageRoll function
const { damageRoll } = await import("./dice/rolls.mjs");
// Roll damage
const roll = await damageRoll(flags.damageFormula, {
isCrit: flags.isCrit,
rollData: actor.getRollData(),
});
// Extract dice results for display
const damageDiceResults = [];
for (const term of roll.terms) {
if (term instanceof foundry.dice.terms.Die) {
for (const r of term.results) {
damageDiceResults.push({
faces: term.faces,
result: r.result,
});
}
}
}
// Render updated content with damage
const content = await renderTemplate("systems/vagabond/templates/chat/attack-roll.hbs", {
// Original attack data from message content
...(await _extractAttackDataFromMessage(message)),
// Damage data
hasDamage: true,
damageTotal: roll.total,
damageFormula: roll.formula,
damageDiceResults,
twoHanded: flags.twoHanded,
isCrit: flags.isCrit,
showDamageButton: false,
});
// Update the message with damage rolled
await message.update({
content,
rolls: [...message.rolls, roll],
"flags.vagabond.damageRolled": true,
});
// Play dice sound
AudioHelper.play({ src: CONFIG.sounds.dice }, true);
} catch (error) {
console.error("Vagabond RPG | Error rolling damage:", error);
ui.notifications.error("Failed to roll damage");
button.disabled = false;
button.innerHTML = '<i class="fa-solid fa-burst"></i> Roll Damage';
}
});
});
/**
* Extract attack data from an existing chat message for re-rendering.
* @param {ChatMessage} message - The chat message
* @returns {Promise<Object>} Template data extracted from the message
* @private
*/
async function _extractAttackDataFromMessage(message) {
const flags = message.flags?.vagabond || {};
const content = message.content;
// Parse values from the message HTML
const parser = new DOMParser();
const doc = parser.parseFromString(content, "text/html");
// Extract weapon info
const weaponImg = doc.querySelector(".weapon-icon")?.getAttribute("src") || "";
const weaponName =
doc.querySelector(".weapon-name")?.textContent || flags.weaponName || "Unknown";
const attackLabel = doc.querySelector(".attack-type-badge")?.textContent || "";
// Extract roll result
const total = doc.querySelector(".roll-total")?.textContent || "0";
const status = doc.querySelector(".roll-status .status")?.classList;
const isCrit = status?.contains("critical") || false;
const isFumble = status?.contains("fumble") || false;
const success = status?.contains("success") || status?.contains("critical") || false;
// Extract formula and breakdown
const formula = doc.querySelector(".roll-formula .value")?.textContent || "";
const d20Result = doc.querySelector(".d20-result")?.textContent?.replace(/[^\d]/g, "") || "0";
const favorDieEl = doc.querySelector(".favor-die");
const favorDie = favorDieEl?.textContent?.replace(/[^\d-]/g, "") || null;
const netFavorHinder = favorDieEl?.classList.contains("favor")
? 1
: favorDieEl?.classList.contains("hinder")
? -1
: 0;
const modifier = doc.querySelector(".modifier")?.textContent?.replace(/[^\d-]/g, "") || null;
// Extract difficulty info
const difficulty = doc.querySelector(".difficulty .value")?.textContent || "10";
const critThreshold =
doc.querySelector(".crit-threshold .value")?.textContent?.replace(/[^\d]/g, "") || "20";
// Extract weapon properties
const propertyTags = doc.querySelectorAll(".weapon-properties .property-tag");
const properties = Array.from(propertyTags).map((el) => el.textContent);
// Extract favor/hinder sources
const favorSourcesEl = doc.querySelector(".favor-sources span");
const hinderSourcesEl = doc.querySelector(".hinder-sources span");
const favorSources =
favorSourcesEl?.textContent
?.replace(/^[^:]+:\s*/, "")
.split(", ")
.filter(Boolean) || [];
const hinderSources =
hinderSourcesEl?.textContent
?.replace(/^[^:]+:\s*/, "")
.split(", ")
.filter(Boolean) || [];
return {
weapon: {
id: flags.weaponId,
name: weaponName,
img: weaponImg,
damageType: flags.damageType,
damageTypeLabel: flags.damageTypeLabel,
properties,
},
attackLabel,
total,
d20Result,
favorDie: favorDie ? parseInt(favorDie) : null,
modifier: modifier ? parseInt(modifier) : null,
formula,
difficulty,
critThreshold,
success,
isCrit,
isFumble,
netFavorHinder,
favorSources,
hinderSources,
};
}
/* -------------------------------------------- */
/* Quench Test Registration */
/* -------------------------------------------- */

View File

@ -5,7 +5,7 @@
"scripts": {
"build": "npm run build:css && npm run build:packs",
"build:css": "sass styles/scss/vagabond.scss styles/vagabond.css --style=compressed",
"build:packs": "fvtt package workon vagabond --type System && npm run build:pack:ancestries && npm run build:pack:classes && npm run build:pack:spells && npm run build:pack:perks && npm run build:pack:weapons && npm run build:pack:armor && npm run build:pack:equipment",
"build:packs": "fvtt package workon vagabond --type System && npm run build:pack:ancestries && npm run build:pack:classes && npm run build:pack:spells && npm run build:pack:perks && npm run build:pack:weapons && npm run build:pack:armor && npm run build:pack:equipment && npm run build:pack:statuses && npm run build:pack:bestiary",
"build:pack:ancestries": "fvtt package pack -n ancestries -t Item --in packs/_source/ancestries --out packs",
"build:pack:classes": "fvtt package pack -n classes -t Item --in packs/_source/classes --out packs",
"build:pack:spells": "fvtt package pack -n spells -t Item --in packs/_source/spells --out packs",
@ -13,6 +13,8 @@
"build:pack:weapons": "fvtt package pack -n weapons -t Item --in packs/_source/weapons --out packs",
"build:pack:armor": "fvtt package pack -n armor -t Item --in packs/_source/armor --out packs",
"build:pack:equipment": "fvtt package pack -n equipment -t Item --in packs/_source/equipment --out packs",
"build:pack:statuses": "fvtt package pack -n statuses -t Item --in packs/_source/statuses --out packs",
"build:pack:bestiary": "fvtt package pack -n bestiary -t Actor --in packs/_source/bestiary --out packs",
"watch": "sass styles/scss/vagabond.scss styles/vagabond.css --watch --style=expanded --source-map",
"lint": "eslint module/",
"lint:fix": "eslint module/ --fix",

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusBerserk",
"name": "Berserk",
"type": "status",
"img": "icons/svg/terror.svg",
"system": {
"description": "<p>It can't Focus, and it can only Attack, Move, and Rush. It doesn't make Morale Checks, and it can't be Frightened.</p>",
"icon": "icons/svg/terror.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": true,
"cantUseActions": false,
"onlyAttackMoveRush": true,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": true,
"immuneToFrightened": true,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusBerserk"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusBlinded",
"name": "Blinded",
"type": "status",
"img": "icons/svg/blind.svg",
"system": {
"description": "<p>It can't see and is Vulnerable.</p>",
"icon": "icons/svg/blind.svg",
"changes": [],
"includesStatuses": ["Vulnerable"],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": true,
"isVulnerable": true,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusBlinded"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusBurning",
"name": "Burning",
"type": "status",
"img": "icons/svg/fire.svg",
"system": {
"description": "<p>A die roll indicates damage it takes at the start of its Turns, unless an appropriate action is taken to end it (such as dousing fire).</p>",
"icon": "icons/svg/fire.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": "startOfTurn",
"type": "damage",
"value": "varies",
"effectDescription": "Takes damage at the start of each turn (die roll indicates amount)"
},
"duration": {
"type": "special",
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusBurning"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusCharmed",
"name": "Charmed",
"type": "status",
"img": "icons/svg/silenced.svg",
"system": {
"description": "<p>It can't willingly attack the charmer.</p>",
"icon": "icons/svg/silenced.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": true,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusCharmed"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusConfused",
"name": "Confused",
"type": "status",
"img": "icons/svg/daze.svg",
"system": {
"description": "<p>Its Checks and Saves have Hinder, and Saves against its Actions have Favor.</p>",
"icon": "icons/svg/daze.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": true,
"hinderSaves": true,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": true,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusConfused"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusDazed",
"name": "Dazed",
"type": "status",
"img": "icons/svg/stoned.svg",
"system": {
"description": "<p>It can't Focus or Move unless it uses an Action to do so.</p>",
"icon": "icons/svg/stoned.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": true,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusDazed"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusFatigued",
"name": "Fatigued",
"type": "status",
"img": "icons/svg/unconscious.svg",
"system": {
"description": "<p>Fatigue is measured from 0 to 5. With 1 or more Fatigue, the following rules apply:</p><ul><li>Each Fatigue occupies an Item Slot.</li><li>At 3 or more Fatigue, it can't Rush.</li><li>At 5 Fatigue, it dies.</li></ul>",
"icon": "icons/svg/unconscious.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": true
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": "untilRest",
"value": null,
"remaining": null
},
"stackable": true,
"maxStacks": 5,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusFatigued"
}

View File

@ -0,0 +1,65 @@
{
"_id": "vagabondStatusFrightened",
"name": "Frightened",
"type": "status",
"img": "icons/svg/terror.svg",
"system": {
"description": "<p>It has a -2 penalty to damage it deals.</p>",
"icon": "icons/svg/terror.svg",
"changes": [
{
"key": "system.modifiers.damageDealt",
"mode": 2,
"value": "-2",
"priority": null
}
],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": -2,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusFrightened"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusIncapacitated",
"name": "Incapacitated",
"type": "status",
"img": "icons/svg/paralysis.svg",
"system": {
"description": "<p>The following rules apply:</p><ul><li>It can't Focus or use its Actions or Move.</li><li>It fails all Might and Dexterity Checks.</li><li>It is Vulnerable.</li></ul>",
"icon": "icons/svg/paralysis.svg",
"changes": [],
"includesStatuses": ["Vulnerable"],
"flags": {
"cantMove": true,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": true,
"cantUseActions": true,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": true,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": true,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusIncapacitated"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusInvisible",
"name": "Invisible",
"type": "status",
"img": "icons/svg/invisible.svg",
"system": {
"description": "<p>It can't be seen by normal senses and those that can't see it (p. 9) act as Blinded for Checks and Saves involving it.</p>",
"icon": "icons/svg/invisible.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": "Others act as Blinded against this creature"
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusInvisible"
}

View File

@ -0,0 +1,65 @@
{
"_id": "vagabondStatusParalyzed",
"name": "Paralyzed",
"type": "status",
"img": "icons/svg/frozen.svg",
"system": {
"description": "<p>It is Incapacitated and its Speed is 0.</p>",
"icon": "icons/svg/frozen.svg",
"changes": [
{
"key": "system.speed.walk",
"mode": 5,
"value": "0",
"priority": null
}
],
"includesStatuses": ["Incapacitated"],
"flags": {
"cantMove": true,
"cantRush": false,
"speedZero": true,
"crawlOnly": false,
"cantFocus": true,
"cantUseActions": true,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": true,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": true,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusParalyzed"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusProne",
"name": "Prone",
"type": "status",
"img": "icons/svg/falling.svg",
"system": {
"description": "<p>You can either drop or stand from Prone by using 10 feet of Speed. While Prone, the following rules apply:</p><ul><li>It can only move by crawling (2 feet of Speed for 1 foot of movement) and it can't Rush.</li><li>It is Vulnerable for the purposes of Melee Attacks and Dodge Checks.</li></ul>",
"icon": "icons/svg/falling.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": true,
"speedZero": false,
"crawlOnly": true,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": "Vulnerable for Melee Attacks and Dodge Checks"
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusProne"
}

View File

@ -0,0 +1,65 @@
{
"_id": "vagabondStatusRestrained",
"name": "Restrained",
"type": "status",
"img": "icons/svg/net.svg",
"system": {
"description": "<p>It is Vulnerable, and its Speed is 0.</p>",
"icon": "icons/svg/net.svg",
"changes": [
{
"key": "system.speed.walk",
"mode": 5,
"value": "0",
"priority": null
}
],
"includesStatuses": ["Vulnerable"],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": true,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": true,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusRestrained"
}

View File

@ -0,0 +1,65 @@
{
"_id": "vagabondStatusSickened",
"name": "Sickened",
"type": "status",
"img": "icons/svg/poison.svg",
"system": {
"description": "<p>The following rules apply:</p><ul><li>It has a -2 penalty to any healing it receives.</li><li>Additional effects may be indicated in parenthesis, which apply while it is Sickened.</li></ul>",
"icon": "icons/svg/poison.svg",
"changes": [
{
"key": "system.modifiers.healingReceived",
"mode": 2,
"value": "-2",
"priority": null
}
],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": -2
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusSickened"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusSuffocating",
"name": "Suffocating",
"type": "status",
"img": "icons/svg/unconscious.svg",
"system": {
"description": "<p>A creature can hold its breath for a number of rounds equal to its Might. After that, it begins Suffocating and takes 1d6 damage at the start of each of its turns until it can breathe again or dies.</p>",
"icon": "icons/svg/unconscious.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": false,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": "turnStart",
"type": "damage",
"value": "1d6",
"effectDescription": "Takes 1d6 damage from suffocation"
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusSuffocating"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusUnconscious",
"name": "Unconscious",
"type": "status",
"img": "icons/svg/sleep.svg",
"system": {
"description": "<p>It is Blinded, Incapacitated, and Prone. In addition, close Attacks are automatically Critical Hits.</p>",
"icon": "icons/svg/sleep.svg",
"changes": [],
"includesStatuses": ["Blinded", "Incapacitated", "Prone"],
"flags": {
"cantMove": true,
"cantRush": true,
"speedZero": false,
"crawlOnly": false,
"cantFocus": true,
"cantUseActions": true,
"onlyAttackMoveRush": false,
"cantSee": true,
"isVulnerable": true,
"closeAttacksAutoCrit": true,
"failsMightDexChecks": true,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": false,
"context": ""
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusUnconscious"
}

View File

@ -0,0 +1,58 @@
{
"_id": "vagabondStatusVulnerable",
"name": "Vulnerable",
"type": "status",
"img": "icons/svg/target.svg",
"system": {
"description": "<p>Attacks against it have Favor. It has Hinder on any Dodge Check.</p>",
"icon": "icons/svg/target.svg",
"changes": [],
"includesStatuses": [],
"flags": {
"cantMove": false,
"cantRush": false,
"speedZero": false,
"crawlOnly": false,
"cantFocus": false,
"cantUseActions": false,
"onlyAttackMoveRush": false,
"cantSee": false,
"isVulnerable": true,
"closeAttacksAutoCrit": false,
"failsMightDexChecks": false,
"noMoraleChecks": false,
"immuneToFrightened": false,
"cantAttackCharmer": false,
"reducesItemSlots": false
},
"favorHinder": {
"hinderChecks": false,
"hinderSaves": false,
"hinderAttacks": false,
"favorAgainstChecks": false,
"favorAgainstSaves": false,
"favorAgainstAttacks": true,
"context": "Hinder on Dodge Checks"
},
"modifiers": {
"damageDealt": 0,
"healingReceived": 0
},
"periodic": {
"trigger": null,
"type": null,
"value": "",
"effectDescription": ""
},
"duration": {
"type": null,
"value": null,
"remaining": null
},
"stackable": false,
"maxStacks": 1,
"reference": "Core Rulebook p.36"
},
"effects": [],
"_key": "!items!vagabondStatusVulnerable"
}

BIN
packs/ancestries/000195.ldb Normal file

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000121
MANIFEST-000192

View File

@ -1,5 +1,14 @@
2025/12/17-07:01:48.746097 7fca5d8006c0 Recovering log #120
2025/12/17-07:01:48.746210 7fca5d8006c0 Level-0 table #122: started
2025/12/17-07:01:48.747367 7fca5d8006c0 Level-0 table #122: 3493 bytes OK
2025/12/17-07:01:48.749813 7fca5d8006c0 Delete type=0 #120
2025/12/17-07:01:48.749923 7fca5d8006c0 Delete type=3 #119
2025/12/17-20:32:51.540867 7fa913e006c0 Recovering log #191
2025/12/17-20:32:51.540971 7fa913e006c0 Level-0 table #193: started
2025/12/17-20:32:51.542004 7fa913e006c0 Level-0 table #193: 3491 bytes OK
2025/12/17-20:32:51.544454 7fa913e006c0 Delete type=0 #191
2025/12/17-20:32:51.544525 7fa913e006c0 Delete type=3 #189
2025/12/17-20:32:51.544647 7fa912a006c0 Compacting 4@0 + 1@1 files
2025/12/17-20:32:51.546114 7fa912a006c0 Generated table #195@0: 8 keys, 3720 bytes
2025/12/17-20:32:51.546208 7fa912a006c0 Compacted 4@0 + 1@1 files => 3720 bytes
2025/12/17-20:32:51.547411 7fa912a006c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/12/17-20:32:51.547717 7fa912a006c0 Delete type=2 #182
2025/12/17-20:32:51.547857 7fa912a006c0 Delete type=2 #184
2025/12/17-20:32:51.547988 7fa912a006c0 Delete type=2 #187
2025/12/17-20:32:51.548146 7fa912a006c0 Delete type=2 #190
2025/12/17-20:32:51.548302 7fa912a006c0 Delete type=2 #193

View File

@ -1,3 +1,5 @@
2025/12/17-06:56:48.455056 7f4a862006c0 Recovering log #115
2025/12/17-06:56:48.457304 7f4a862006c0 Delete type=3 #111
2025/12/17-06:56:48.457368 7f4a862006c0 Delete type=0 #115
2025/12/17-20:24:43.671501 7f20ea2006c0 Recovering log #188
2025/12/17-20:24:43.671657 7f20ea2006c0 Level-0 table #190: started
2025/12/17-20:24:43.672684 7f20ea2006c0 Level-0 table #190: 3491 bytes OK
2025/12/17-20:24:43.675122 7f20ea2006c0 Delete type=0 #188
2025/12/17-20:24:43.675212 7f20ea2006c0 Delete type=3 #186

Binary file not shown.

BIN
packs/armor/000077.ldb Normal file

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000006
MANIFEST-000074

View File

@ -1,3 +1,14 @@
2025/12/17-07:01:48.783757 7fca5ce006c0 Recovering log #4
2025/12/17-07:01:48.785948 7fca5ce006c0 Delete type=3 #2
2025/12/17-07:01:48.786033 7fca5ce006c0 Delete type=0 #4
2025/12/17-20:32:51.586467 7fa918e006c0 Recovering log #73
2025/12/17-20:32:51.586558 7fa918e006c0 Level-0 table #75: started
2025/12/17-20:32:51.587354 7fa918e006c0 Level-0 table #75: 1190 bytes OK
2025/12/17-20:32:51.589913 7fa918e006c0 Delete type=0 #73
2025/12/17-20:32:51.590000 7fa918e006c0 Delete type=3 #71
2025/12/17-20:32:51.590083 7fa912a006c0 Compacting 4@0 + 1@1 files
2025/12/17-20:32:51.591405 7fa912a006c0 Generated table #77@0: 6 keys, 1275 bytes
2025/12/17-20:32:51.591532 7fa912a006c0 Compacted 4@0 + 1@1 files => 1275 bytes
2025/12/17-20:32:51.592772 7fa912a006c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/12/17-20:32:51.593114 7fa912a006c0 Delete type=2 #64
2025/12/17-20:32:51.593305 7fa912a006c0 Delete type=2 #66
2025/12/17-20:32:51.593420 7fa912a006c0 Delete type=2 #69
2025/12/17-20:32:51.593526 7fa912a006c0 Delete type=2 #72
2025/12/17-20:32:51.593681 7fa912a006c0 Delete type=2 #75

View File

@ -1,5 +1,5 @@
2025/12/17-00:54:57.262541 7f60caffd6c0 Delete type=3 #1
2025/12/17-00:54:57.264605 7f60ca7fc6c0 Level-0 table #5: started
2025/12/17-00:54:57.265201 7f60ca7fc6c0 Level-0 table #5: 1041 bytes OK
2025/12/17-00:54:57.266154 7f60ca7fc6c0 Delete type=0 #3
2025/12/17-00:54:57.266221 7f60ca7fc6c0 Manual compaction at level-0 from '!items!vagabondArmorHeavy' @ 72057594037927935 : 1 .. '!items!vagabondArmorMedium' @ 0 : 0; will stop at (end)
2025/12/17-20:24:43.706676 7f20e98006c0 Recovering log #70
2025/12/17-20:24:43.706780 7f20e98006c0 Level-0 table #72: started
2025/12/17-20:24:43.707738 7f20e98006c0 Level-0 table #72: 1190 bytes OK
2025/12/17-20:24:43.710189 7f20e98006c0 Delete type=0 #70
2025/12/17-20:24:43.710289 7f20e98006c0 Delete type=3 #68

BIN
packs/armor/MANIFEST-000074 Normal file

Binary file not shown.

BIN
packs/classes/000126.ldb Normal file

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000052
MANIFEST-000123

View File

@ -1,5 +1,14 @@
2025/12/17-07:01:48.752263 7fca5ce006c0 Recovering log #51
2025/12/17-07:01:48.752517 7fca5ce006c0 Level-0 table #53: started
2025/12/17-07:01:48.754100 7fca5ce006c0 Level-0 table #53: 27079 bytes OK
2025/12/17-07:01:48.756444 7fca5ce006c0 Delete type=0 #51
2025/12/17-07:01:48.756596 7fca5ce006c0 Delete type=3 #50
2025/12/17-20:32:51.549270 7fa918e006c0 Recovering log #122
2025/12/17-20:32:51.549589 7fa918e006c0 Level-0 table #124: started
2025/12/17-20:32:51.551450 7fa918e006c0 Level-0 table #124: 27082 bytes OK
2025/12/17-20:32:51.554064 7fa918e006c0 Delete type=0 #122
2025/12/17-20:32:51.554149 7fa918e006c0 Delete type=3 #120
2025/12/17-20:32:51.554269 7fa912a006c0 Compacting 4@0 + 1@1 files
2025/12/17-20:32:51.556945 7fa912a006c0 Generated table #126@0: 19 keys, 28000 bytes
2025/12/17-20:32:51.556983 7fa912a006c0 Compacted 4@0 + 1@1 files => 28000 bytes
2025/12/17-20:32:51.558103 7fa912a006c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/12/17-20:32:51.558336 7fa912a006c0 Delete type=2 #113
2025/12/17-20:32:51.558463 7fa912a006c0 Delete type=2 #115
2025/12/17-20:32:51.558564 7fa912a006c0 Delete type=2 #118
2025/12/17-20:32:51.558667 7fa912a006c0 Delete type=2 #121
2025/12/17-20:32:51.558767 7fa912a006c0 Delete type=2 #124

View File

@ -1,3 +1,5 @@
2025/12/17-06:56:48.460549 7f4a876006c0 Recovering log #46
2025/12/17-06:56:48.462998 7f4a876006c0 Delete type=3 #42
2025/12/17-06:56:48.463089 7f4a876006c0 Delete type=0 #46
2025/12/17-20:24:43.677565 7f20e98006c0 Recovering log #119
2025/12/17-20:24:43.677777 7f20e98006c0 Level-0 table #121: started
2025/12/17-20:24:43.679620 7f20e98006c0 Level-0 table #121: 27082 bytes OK
2025/12/17-20:24:43.681908 7f20e98006c0 Delete type=0 #119
2025/12/17-20:24:43.681991 7f20e98006c0 Delete type=3 #117

Binary file not shown.

BIN
packs/equipment/000082.ldb Normal file

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000010
MANIFEST-000079

View File

@ -1,3 +1,14 @@
2025/12/17-07:01:48.787791 7fca5ec006c0 Recovering log #8
2025/12/17-07:01:48.790138 7fca5ec006c0 Delete type=3 #6
2025/12/17-07:01:48.790246 7fca5ec006c0 Delete type=0 #8
2025/12/17-20:32:51.593884 7fa9198006c0 Recovering log #78
2025/12/17-20:32:51.594727 7fa9198006c0 Level-0 table #80: started
2025/12/17-20:32:51.598554 7fa9198006c0 Level-0 table #80: 74375 bytes OK
2025/12/17-20:32:51.601037 7fa9198006c0 Delete type=0 #78
2025/12/17-20:32:51.601164 7fa9198006c0 Delete type=3 #76
2025/12/17-20:32:51.601336 7fa912a006c0 Compacting 4@0 + 1@1 files
2025/12/17-20:32:51.606358 7fa912a006c0 Generated table #82@0: 371 keys, 57802 bytes
2025/12/17-20:32:51.606395 7fa912a006c0 Compacted 4@0 + 1@1 files => 57802 bytes
2025/12/17-20:32:51.607538 7fa912a006c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/12/17-20:32:51.607782 7fa912a006c0 Delete type=2 #69
2025/12/17-20:32:51.607961 7fa912a006c0 Delete type=2 #71
2025/12/17-20:32:51.608101 7fa912a006c0 Delete type=2 #74
2025/12/17-20:32:51.608873 7fa912a006c0 Delete type=2 #77
2025/12/17-20:32:51.609020 7fa912a006c0 Delete type=2 #80

View File

@ -1,7 +1,5 @@
2025/12/17-00:56:12.031305 7f1a07fff6c0 Recovering log #5
2025/12/17-00:56:12.064751 7f1a07fff6c0 Delete type=0 #5
2025/12/17-00:56:12.064779 7f1a07fff6c0 Delete type=3 #4
2025/12/17-00:56:12.068203 7f1a063ff6c0 Level-0 table #9: started
2025/12/17-00:56:12.069557 7f1a063ff6c0 Level-0 table #9: 56954 bytes OK
2025/12/17-00:56:12.070449 7f1a063ff6c0 Delete type=0 #7
2025/12/17-00:56:12.070542 7f1a063ff6c0 Manual compaction at level-0 from '!items!vagabondEquipAccordion' @ 72057594037927935 : 1 .. '!items.effects!vagabondEquipBackpack.backpackSlotBonus' @ 0 : 0; will stop at (end)
2025/12/17-20:24:43.712652 7f20eac006c0 Recovering log #75
2025/12/17-20:24:43.713463 7f20eac006c0 Level-0 table #77: started
2025/12/17-20:24:43.717482 7f20eac006c0 Level-0 table #77: 74433 bytes OK
2025/12/17-20:24:43.720016 7f20eac006c0 Delete type=0 #75
2025/12/17-20:24:43.720128 7f20eac006c0 Delete type=3 #73

Binary file not shown.

BIN
packs/perks/000178.ldb Normal file

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000105
MANIFEST-000175

View File

@ -1,5 +1,14 @@
2025/12/17-07:01:48.769135 7fca5e2006c0 Recovering log #104
2025/12/17-07:01:48.769497 7fca5e2006c0 Level-0 table #106: started
2025/12/17-07:01:48.773757 7fca5e2006c0 Level-0 table #106: 32401 bytes OK
2025/12/17-07:01:48.777561 7fca5e2006c0 Delete type=0 #104
2025/12/17-07:01:48.777680 7fca5e2006c0 Delete type=3 #103
2025/12/17-20:32:51.568408 7fa9134006c0 Recovering log #174
2025/12/17-20:32:51.568779 7fa9134006c0 Level-0 table #176: started
2025/12/17-20:32:51.570760 7fa9134006c0 Level-0 table #176: 31984 bytes OK
2025/12/17-20:32:51.572940 7fa9134006c0 Delete type=0 #174
2025/12/17-20:32:51.573032 7fa9134006c0 Delete type=3 #172
2025/12/17-20:32:51.573143 7fa912a006c0 Compacting 4@0 + 1@1 files
2025/12/17-20:32:51.576294 7fa912a006c0 Generated table #178@0: 104 keys, 28420 bytes
2025/12/17-20:32:51.576391 7fa912a006c0 Compacted 4@0 + 1@1 files => 28420 bytes
2025/12/17-20:32:51.577753 7fa912a006c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/12/17-20:32:51.578047 7fa912a006c0 Delete type=2 #165
2025/12/17-20:32:51.578189 7fa912a006c0 Delete type=2 #167
2025/12/17-20:32:51.578304 7fa912a006c0 Delete type=2 #170
2025/12/17-20:32:51.578397 7fa912a006c0 Delete type=2 #173
2025/12/17-20:32:51.578494 7fa912a006c0 Delete type=2 #176

View File

@ -1,3 +1,5 @@
2025/12/17-06:56:48.469994 7f4a86c006c0 Recovering log #100
2025/12/17-06:56:48.472443 7f4a86c006c0 Delete type=3 #98
2025/12/17-06:56:48.472537 7f4a86c006c0 Delete type=0 #100
2025/12/17-20:24:43.691811 7f20eb6006c0 Recovering log #171
2025/12/17-20:24:43.692103 7f20eb6006c0 Level-0 table #173: started
2025/12/17-20:24:43.694173 7f20eb6006c0 Level-0 table #173: 31982 bytes OK
2025/12/17-20:24:43.696723 7f20eb6006c0 Delete type=0 #171
2025/12/17-20:24:43.696850 7f20eb6006c0 Delete type=3 #169

BIN
packs/perks/MANIFEST-000175 Normal file

Binary file not shown.

BIN
packs/spells/000141.ldb Normal file

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000067
MANIFEST-000138

View File

@ -1,5 +1,14 @@
2025/12/17-07:01:48.758847 7fca5ec006c0 Recovering log #66
2025/12/17-07:01:48.759124 7fca5ec006c0 Level-0 table #68: started
2025/12/17-07:01:48.763630 7fca5ec006c0 Level-0 table #68: 24215 bytes OK
2025/12/17-07:01:48.765999 7fca5ec006c0 Delete type=0 #66
2025/12/17-07:01:48.766073 7fca5ec006c0 Delete type=3 #65
2025/12/17-20:32:51.558873 7fa9198006c0 Recovering log #137
2025/12/17-20:32:51.559161 7fa9198006c0 Level-0 table #139: started
2025/12/17-20:32:51.561135 7fa9198006c0 Level-0 table #139: 22928 bytes OK
2025/12/17-20:32:51.563502 7fa9198006c0 Delete type=0 #137
2025/12/17-20:32:51.563573 7fa9198006c0 Delete type=3 #135
2025/12/17-20:32:51.563654 7fa912a006c0 Compacting 4@0 + 1@1 files
2025/12/17-20:32:51.566217 7fa912a006c0 Generated table #141@0: 59 keys, 21209 bytes
2025/12/17-20:32:51.566286 7fa912a006c0 Compacted 4@0 + 1@1 files => 21209 bytes
2025/12/17-20:32:51.567497 7fa912a006c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/12/17-20:32:51.567799 7fa912a006c0 Delete type=2 #128
2025/12/17-20:32:51.567974 7fa912a006c0 Delete type=2 #130
2025/12/17-20:32:51.568120 7fa912a006c0 Delete type=2 #133
2025/12/17-20:32:51.568252 7fa912a006c0 Delete type=2 #136
2025/12/17-20:32:51.568398 7fa912a006c0 Delete type=2 #139

View File

@ -1,3 +1,5 @@
2025/12/17-06:56:48.465393 7f4a858006c0 Recovering log #63
2025/12/17-06:56:48.467790 7f4a858006c0 Delete type=3 #61
2025/12/17-06:56:48.467910 7f4a858006c0 Delete type=0 #63
2025/12/17-20:24:43.684399 7f20eac006c0 Recovering log #134
2025/12/17-20:24:43.684679 7f20eac006c0 Level-0 table #136: started
2025/12/17-20:24:43.686489 7f20eac006c0 Level-0 table #136: 22930 bytes OK
2025/12/17-20:24:43.689010 7f20eac006c0 Delete type=0 #134
2025/12/17-20:24:43.689084 7f20eac006c0 Delete type=3 #132

Binary file not shown.

BIN
packs/statuses/000005.ldb Executable file

Binary file not shown.

BIN
packs/statuses/000020.ldb Normal file

Binary file not shown.

BIN
packs/statuses/000022.ldb Normal file

Binary file not shown.

BIN
packs/statuses/000025.ldb Normal file

Binary file not shown.

1
packs/statuses/CURRENT Normal file
View File

@ -0,0 +1 @@
MANIFEST-000024

0
packs/statuses/LOCK Normal file
View File

5
packs/statuses/LOG Normal file
View File

@ -0,0 +1,5 @@
2025/12/17-20:32:51.607024 7fa913e006c0 Recovering log #23
2025/12/17-20:32:51.607711 7fa913e006c0 Level-0 table #25: started
2025/12/17-20:32:51.609113 7fa913e006c0 Level-0 table #25: 7582 bytes OK
2025/12/17-20:32:51.611605 7fa913e006c0 Delete type=0 #23
2025/12/17-20:32:51.611673 7fa913e006c0 Delete type=3 #21

5
packs/statuses/LOG.old Normal file
View File

@ -0,0 +1,5 @@
2025/12/17-20:24:43.724411 7f20ea2006c0 Recovering log #19
2025/12/17-20:24:43.724546 7f20ea2006c0 Level-0 table #22: started
2025/12/17-20:24:43.725885 7f20ea2006c0 Level-0 table #22: 7582 bytes OK
2025/12/17-20:24:43.728462 7f20ea2006c0 Delete type=0 #19
2025/12/17-20:24:43.728558 7f20ea2006c0 Delete type=3 #17

Binary file not shown.

BIN
packs/weapons/000077.ldb Normal file

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000006
MANIFEST-000074

View File

@ -1,3 +1,14 @@
2025/12/17-07:01:48.779786 7fca5d8006c0 Recovering log #4
2025/12/17-07:01:48.781959 7fca5d8006c0 Delete type=3 #2
2025/12/17-07:01:48.782030 7fca5d8006c0 Delete type=0 #4
2025/12/17-20:32:51.577820 7fa913e006c0 Recovering log #73
2025/12/17-20:32:51.578056 7fa913e006c0 Level-0 table #75: started
2025/12/17-20:32:51.579615 7fa913e006c0 Level-0 table #75: 14097 bytes OK
2025/12/17-20:32:51.582010 7fa913e006c0 Delete type=0 #73
2025/12/17-20:32:51.582097 7fa913e006c0 Delete type=3 #71
2025/12/17-20:32:51.582214 7fa912a006c0 Compacting 4@0 + 1@1 files
2025/12/17-20:32:51.584350 7fa912a006c0 Generated table #77@0: 44 keys, 11579 bytes
2025/12/17-20:32:51.584388 7fa912a006c0 Compacted 4@0 + 1@1 files => 11579 bytes
2025/12/17-20:32:51.585706 7fa912a006c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/12/17-20:32:51.585923 7fa912a006c0 Delete type=2 #64
2025/12/17-20:32:51.586060 7fa912a006c0 Delete type=2 #66
2025/12/17-20:32:51.586166 7fa912a006c0 Delete type=2 #69
2025/12/17-20:32:51.586261 7fa912a006c0 Delete type=2 #72
2025/12/17-20:32:51.586347 7fa912a006c0 Delete type=2 #75

View File

@ -1,5 +1,5 @@
2025/12/17-00:54:56.971891 7f5c477fe6c0 Delete type=3 #1
2025/12/17-00:54:56.974013 7f5c453ff6c0 Level-0 table #5: started
2025/12/17-00:54:56.975425 7f5c453ff6c0 Level-0 table #5: 11176 bytes OK
2025/12/17-00:54:56.976648 7f5c453ff6c0 Delete type=0 #3
2025/12/17-00:54:56.976720 7f5c453ff6c0 Manual compaction at level-0 from '!items!vagabondWeaponArbalest' @ 72057594037927935 : 1 .. '!items!vagabondWeaponWhipLeather' @ 0 : 0; will stop at (end)
2025/12/17-20:24:43.699714 7f20ea2006c0 Recovering log #70
2025/12/17-20:24:43.699938 7f20ea2006c0 Level-0 table #72: started
2025/12/17-20:24:43.701440 7f20ea2006c0 Level-0 table #72: 14100 bytes OK
2025/12/17-20:24:43.703861 7f20ea2006c0 Delete type=0 #70
2025/12/17-20:24:43.703956 7f20ea2006c0 Delete type=3 #68

Binary file not shown.

View File

@ -70,6 +70,7 @@
--color-badge-feature: #{mix($color-presence, $color-parchment, 15%)};
--color-badge-ancestry: #{mix($color-luck, $color-parchment, 15%)};
--color-badge-equipment: #{mix($color-dexterity, $color-parchment, 15%)};
--color-badge-status: #{mix($color-danger, $color-parchment, 20%)};
}
// ==========================================
@ -143,4 +144,5 @@ body.theme-dark .vagabond:not(.theme-light),
--color-badge-feature: #383028;
--color-badge-ancestry: #283838;
--color-badge-equipment: #283828;
--color-badge-status: #3d2828;
}

View File

@ -437,6 +437,30 @@
}
}
.damage-breakdown {
@include flex-center;
gap: $spacing-2;
margin-top: $spacing-2;
padding-top: $spacing-2;
border-top: 1px solid rgba(201, 68, 68, 0.2);
.damage-die {
@include flex-center;
gap: $spacing-1;
padding: $spacing-1 $spacing-2;
background-color: rgba(201, 68, 68, 0.15);
border-radius: $radius-md;
font-family: $font-family-mono;
font-weight: $font-weight-bold;
color: var(--color-text-primary);
i {
color: var(--color-danger);
font-size: $font-size-sm;
}
}
}
.damage-formula {
@include flex-center;
gap: $spacing-2;
@ -467,6 +491,54 @@
text-transform: capitalize;
}
}
// Roll Damage button
.card-buttons {
.roll-damage-btn {
@include flex-center;
gap: $spacing-2;
width: 100%;
padding: $spacing-2 $spacing-3;
background-color: rgba(201, 68, 68, 0.2);
border: 1px solid var(--color-danger);
border-radius: $radius-md;
color: var(--color-danger);
font-weight: $font-weight-semibold;
cursor: pointer;
transition: all 0.2s ease;
&:hover:not(:disabled) {
background-color: rgba(201, 68, 68, 0.3);
border-color: var(--color-danger);
}
&:disabled {
opacity: 0.7;
cursor: not-allowed;
}
&.critical {
background-color: rgba(212, 163, 44, 0.2);
border-color: var(--color-warning);
color: var(--color-warning);
&:hover:not(:disabled) {
background-color: rgba(212, 163, 44, 0.3);
}
}
.crit-label {
font-size: $font-size-sm;
color: var(--color-warning);
}
.damage-preview {
font-family: $font-family-mono;
font-size: $font-size-sm;
color: var(--color-text-secondary);
}
}
}
}
// Spell card specific

View File

@ -325,6 +325,81 @@
}
}
}
// ==========================================
// STATUS BAR - Active Status Conditions
// ==========================================
.status-bar {
padding: $spacing-2 $spacing-4;
background-color: var(--color-bg-secondary);
border-bottom: 1px solid var(--color-border);
.status-list {
display: flex;
flex-wrap: wrap;
gap: $spacing-2;
}
.status-chip {
display: inline-flex;
align-items: center;
gap: $spacing-2;
padding: $spacing-1 $spacing-2;
background-color: var(--color-badge-status);
border: 1px solid var(--color-danger);
border-radius: $radius-full;
cursor: pointer;
transition: all $transition-fast;
&:hover {
background-color: rgba(139, 0, 0, 0.25);
border-color: var(--color-danger);
.status-remove {
opacity: 1;
}
}
.status-icon {
width: 18px;
height: 18px;
object-fit: contain;
flex-shrink: 0;
}
.status-name {
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
color: var(--color-text-primary);
white-space: nowrap;
}
.status-remove {
@include flex-center;
width: 18px;
height: 18px;
padding: 0;
margin-left: $spacing-1;
background-color: transparent;
border: none;
border-radius: $radius-full;
color: var(--color-danger);
opacity: 0.6;
cursor: pointer;
transition: all $transition-fast;
&:hover {
background-color: var(--color-danger);
color: white;
opacity: 1;
}
i {
font-size: 10px;
}
}
}
}
}
// ==========================================

View File

@ -104,6 +104,9 @@
&.equipment {
background-color: var(--color-badge-equipment);
}
&.status {
background-color: var(--color-badge-status);
}
}
}
}
@ -876,3 +879,198 @@
}
}
}
// Status item sheet styles
.vagabond.sheet.item {
.status-content {
// Icon section with preview
.status-icon-section {
display: flex;
align-items: flex-end;
gap: $spacing-4;
margin-bottom: $spacing-3;
.stat-group {
flex: 1;
}
.icon-preview {
width: 48px;
height: 48px;
border: 2px solid var(--color-border);
border-radius: $radius-md;
padding: $spacing-1;
background-color: var(--color-bg-input);
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
// Status flags fieldsets
fieldset {
border: 1px solid var(--color-border);
border-radius: $radius-sm;
padding: $spacing-3;
margin: 0;
background-color: var(--color-bg-input);
legend {
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
color: var(--color-accent-primary);
padding: 0 $spacing-2;
}
}
// Flags category groupings
.flags-category {
margin-bottom: $spacing-3;
&:last-child {
margin-bottom: 0;
}
h4 {
margin: 0 0 $spacing-2 0;
font-size: $font-size-xs;
font-weight: $font-weight-semibold;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.03em;
border-bottom: 1px solid var(--color-border-light);
padding-bottom: $spacing-1;
}
}
// Flags grid for checkboxes
.flags-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $spacing-2;
}
// Flag checkbox styling
.flag-checkbox {
display: flex;
align-items: center;
gap: $spacing-2;
font-size: $font-size-sm;
cursor: pointer;
input[type="checkbox"] {
width: 16px;
height: 16px;
margin: 0;
cursor: pointer;
accent-color: var(--color-accent-primary);
}
&:hover {
color: var(--color-accent-primary);
}
}
// Favor/Hinder section
.favor-hinder-section {
.favor-hinder-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: $spacing-4;
margin-bottom: $spacing-3;
}
.fh-category {
h4 {
margin: 0 0 $spacing-2 0;
font-size: $font-size-xs;
font-weight: $font-weight-semibold;
color: var(--color-text-secondary);
text-transform: uppercase;
}
.flags-grid {
grid-template-columns: 1fr;
}
}
}
// Section hints
.section-hint {
font-size: $font-size-xs;
color: var(--color-text-secondary);
font-style: italic;
margin-bottom: $spacing-2;
}
// Included statuses list
.includes-list {
display: flex;
flex-direction: column;
gap: $spacing-2;
.includes-entry {
display: flex;
align-items: center;
gap: $spacing-2;
input {
flex: 1;
padding: $spacing-2;
border: 1px solid var(--color-border);
border-radius: $radius-sm;
background-color: var(--color-bg-primary);
font-size: $font-size-sm;
}
.delete-entry {
@include button-icon;
color: var(--color-danger);
&:hover {
background-color: rgba(201, 68, 68, 0.1);
}
}
}
.add-entry {
@include button-secondary;
align-self: flex-start;
font-size: $font-size-sm;
padding: $spacing-1 $spacing-3;
i {
margin-right: $spacing-1;
}
}
}
// Reference and full-width stat groups
.reference-section,
.stat-group.full-width {
margin-top: $spacing-2;
> label {
display: block;
font-size: $font-size-xs;
font-weight: $font-weight-semibold;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: $spacing-1;
}
input {
width: 100%;
padding: $spacing-2;
border: 1px solid var(--color-border);
border-radius: $radius-sm;
background-color: var(--color-bg-input);
font-size: $font-size-sm;
}
}
}
}

View File

@ -104,13 +104,43 @@
"PLAYER": "OBSERVER",
"ASSISTANT": "OWNER"
}
},
{
"name": "statuses",
"label": "Statuses",
"path": "packs/statuses",
"type": "Item",
"ownership": {
"PLAYER": "OBSERVER",
"ASSISTANT": "OWNER"
}
},
{
"name": "bestiary",
"label": "Bestiary",
"path": "packs/bestiary",
"type": "Actor",
"ownership": {
"PLAYER": "OBSERVER",
"ASSISTANT": "OWNER"
}
}
],
"packFolders": [
{
"name": "Vagabond RPG",
"sorting": "a",
"packs": ["ancestries", "classes", "spells", "perks", "weapons", "armor", "equipment"]
"packs": [
"ancestries",
"classes",
"spells",
"perks",
"weapons",
"armor",
"equipment",
"statuses",
"bestiary"
]
}
],
"documentTypes": {
@ -151,6 +181,9 @@
},
"feature": {
"htmlFields": ["description"]
},
"status": {
"htmlFields": ["description"]
}
}
},

View File

@ -1,4 +1,5 @@
{{!-- Character Sheet Header --}}
<div class="header-wrapper">
<header class="sheet-header">
<div class="header-left">
<img class="profile-img" src="{{actor.img}}" alt="{{actor.name}}"
@ -116,3 +117,7 @@
</div>
</div>
</header>
{{!-- Status Bar (below header) --}}
{{> "systems/vagabond/templates/actor/parts/status-bar.hbs"}}
</div>

View File

@ -1,4 +1,5 @@
{{!-- NPC Sheet Header --}}
<div class="header-wrapper">
<header class="sheet-header npc-header">
<div class="header-portrait">
<img class="profile-img" src="{{actor.img}}" alt="{{actor.name}}"
@ -59,3 +60,7 @@
{{/if}}
</div>
</header>
{{!-- Status Bar (below header) --}}
{{> "systems/vagabond/templates/actor/parts/status-bar.hbs"}}
</div>

View File

@ -0,0 +1,18 @@
{{!-- Status Bar - Displays active status conditions on the actor --}}
{{#if items.statuses.length}}
<div class="status-bar">
<div class="status-list">
{{#each items.statuses as |status|}}
<div class="status-chip" data-item-id="{{status.id}}" data-tooltip="{{status.name}}: {{{status.system.description}}}">
<img class="status-icon" src="{{status.img}}" alt="{{status.name}}" />
<span class="status-name">{{status.name}}</span>
{{#if ../editable}}
<button type="button" class="status-remove" data-action="removeStatus" data-tooltip="{{localize 'VAGABOND.RemoveStatus'}}">
<i class="fa-solid fa-xmark"></i>
</button>
{{/if}}
</div>
{{/each}}
</div>
</div>
{{/if}}

View File

@ -64,7 +64,7 @@
{{/if}}
</div>
{{!-- Damage Section (if hit) --}}
{{!-- Damage Section (if damage was rolled) --}}
{{#if hasDamage}}
<div class="damage-section {{#if isCrit}}critical{{/if}}">
<div class="damage-header">
@ -78,6 +78,13 @@
<span class="damage-total">{{damageTotal}}</span>
<span class="damage-type">{{weapon.damageTypeLabel}}</span>
</div>
<div class="damage-breakdown">
{{#each damageDiceResults}}
<span class="damage-die" data-tooltip="d{{this.faces}}">
<i class="fa-solid fa-dice-d{{this.faces}}"></i> {{this.result}}
</span>
{{/each}}
</div>
<div class="damage-formula">
{{damageFormula}}
{{#if twoHanded}}
@ -87,6 +94,20 @@
</div>
{{/if}}
{{!-- Roll Damage Button (if hit but damage not yet rolled) --}}
{{#if showDamageButton}}
<div class="card-buttons">
<button type="button" class="roll-damage-btn {{#if isCrit}}critical{{/if}}">
<i class="fa-solid fa-burst"></i>
{{localize "VAGABOND.RollDamage"}}
{{#if isCrit}}
<span class="crit-label">({{localize "VAGABOND.Critical"}}!)</span>
{{/if}}
<span class="damage-preview">({{pendingDamageFormula}})</span>
</button>
</div>
{{/if}}
{{!-- Weapon Properties --}}
{{#if weapon.properties.length}}
<div class="weapon-properties">

View File

@ -82,11 +82,11 @@
<div class="favor-hinder-section">
<label>{{localize "VAGABOND.FavorHinder"}}</label>
<div class="favor-hinder-toggles">
<button type="button" class="favor-btn {{#if (eq config.favorHinder 1)}}active{{/if}}" data-favor="1">
<button type="button" class="favor-btn {{#if (eq config.favorHinder 1)}}active{{/if}}" data-action="toggle-favor">
<i class="fa-solid fa-arrow-up"></i>
{{localize "VAGABOND.Favor"}}
</button>
<button type="button" class="hinder-btn {{#if (eq config.favorHinder -1)}}active{{/if}}" data-favor="-1">
<button type="button" class="hinder-btn {{#if (eq config.favorHinder -1)}}active{{/if}}" data-action="toggle-hinder">
<i class="fa-solid fa-arrow-down"></i>
{{localize "VAGABOND.Hinder"}}
</button>
@ -106,10 +106,10 @@
<div class="modifier-section">
<label>{{localize "VAGABOND.SituationalModifier"}}</label>
<div class="modifier-presets">
<button type="button" class="modifier-preset" data-modifier="-5">-5</button>
<button type="button" class="modifier-preset" data-modifier="-1">-1</button>
<button type="button" class="modifier-preset" data-modifier="1">+1</button>
<button type="button" class="modifier-preset" data-modifier="5">+5</button>
<button type="button" class="modifier-preset" data-modifier-preset="-5">-5</button>
<button type="button" class="modifier-preset" data-modifier-preset="-1">-1</button>
<button type="button" class="modifier-preset" data-modifier-preset="1">+1</button>
<button type="button" class="modifier-preset" data-modifier-preset="5">+5</button>
</div>
<div class="modifier-input">
<input type="number" name="modifier" value="{{config.modifier}}" placeholder="0">

View File

@ -0,0 +1,294 @@
{{!-- Status Item Sheet Body --}}
<div class="item-content status-content">
{{!-- Status Icon --}}
<div class="status-icon-section">
<div class="stat-group">
<label>{{localize "VAGABOND.StatusIcon"}}</label>
<input type="text" name="system.icon" value="{{system.icon}}" placeholder="icons/svg/hazard.svg" {{#unless editable}}disabled{{/unless}} />
</div>
<div class="icon-preview">
<img src="{{system.icon}}" alt="Status Icon" />
</div>
</div>
{{!-- Core Status Flags --}}
<fieldset class="status-flags">
<legend>{{localize "VAGABOND.StatusRestrictions"}}</legend>
{{!-- Movement Restrictions --}}
<div class="flags-category">
<h4>{{localize "VAGABOND.MovementRestrictions"}}</h4>
<div class="flags-grid">
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.cantMove" {{#if system.flags.cantMove}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CantMove"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.cantRush" {{#if system.flags.cantRush}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CantRush"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.speedZero" {{#if system.flags.speedZero}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.SpeedZero"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.crawlOnly" {{#if system.flags.crawlOnly}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CrawlOnly"}}
</label>
</div>
</div>
{{!-- Action Restrictions --}}
<div class="flags-category">
<h4>{{localize "VAGABOND.ActionRestrictions"}}</h4>
<div class="flags-grid">
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.cantFocus" {{#if system.flags.cantFocus}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CantFocus"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.cantUseActions" {{#if system.flags.cantUseActions}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CantUseActions"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.onlyAttackMoveRush" {{#if system.flags.onlyAttackMoveRush}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.OnlyAttackMoveRush"}}
</label>
</div>
</div>
{{!-- Sense Restrictions --}}
<div class="flags-category">
<h4>{{localize "VAGABOND.SenseRestrictions"}}</h4>
<div class="flags-grid">
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.cantSee" {{#if system.flags.cantSee}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CantSee"}}
</label>
</div>
</div>
{{!-- Combat Modifiers --}}
<div class="flags-category">
<h4>{{localize "VAGABOND.CombatModifiers"}}</h4>
<div class="flags-grid">
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.isVulnerable" {{#if system.flags.isVulnerable}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.IsVulnerable"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.closeAttacksAutoCrit" {{#if system.flags.closeAttacksAutoCrit}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CloseAttacksAutoCrit"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.failsMightDexChecks" {{#if system.flags.failsMightDexChecks}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.FailsMightDexChecks"}}
</label>
</div>
</div>
{{!-- Morale & Special --}}
<div class="flags-category">
<h4>{{localize "VAGABOND.MoraleSpecial"}}</h4>
<div class="flags-grid">
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.noMoraleChecks" {{#if system.flags.noMoraleChecks}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.NoMoraleChecks"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.immuneToFrightened" {{#if system.flags.immuneToFrightened}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.ImmuneToFrightened"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.cantAttackCharmer" {{#if system.flags.cantAttackCharmer}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.CantAttackCharmer"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.flags.reducesItemSlots" {{#if system.flags.reducesItemSlots}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.ReducesItemSlots"}}
</label>
</div>
</div>
</fieldset>
{{!-- Favor/Hinder Modifiers --}}
<fieldset class="favor-hinder-section">
<legend>{{localize "VAGABOND.FavorHinderModifiers"}}</legend>
<div class="favor-hinder-grid">
{{!-- Hinder on afflicted --}}
<div class="fh-category">
<h4>{{localize "VAGABOND.HinderOnAfflicted"}}</h4>
<div class="flags-grid">
<label class="flag-checkbox">
<input type="checkbox" name="system.favorHinder.hinderChecks" {{#if system.favorHinder.hinderChecks}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.Checks"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.favorHinder.hinderSaves" {{#if system.favorHinder.hinderSaves}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.Saves"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.favorHinder.hinderAttacks" {{#if system.favorHinder.hinderAttacks}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.Attacks"}}
</label>
</div>
</div>
{{!-- Favor against afflicted --}}
<div class="fh-category">
<h4>{{localize "VAGABOND.FavorAgainstAfflicted"}}</h4>
<div class="flags-grid">
<label class="flag-checkbox">
<input type="checkbox" name="system.favorHinder.favorAgainstChecks" {{#if system.favorHinder.favorAgainstChecks}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.Checks"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.favorHinder.favorAgainstSaves" {{#if system.favorHinder.favorAgainstSaves}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.Saves"}}
</label>
<label class="flag-checkbox">
<input type="checkbox" name="system.favorHinder.favorAgainstAttacks" {{#if system.favorHinder.favorAgainstAttacks}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.Attacks"}}
</label>
</div>
</div>
</div>
<div class="stat-group">
<label>{{localize "VAGABOND.FavorHinderContext"}}</label>
<input type="text" name="system.favorHinder.context" value="{{system.favorHinder.context}}" placeholder="e.g. sight-based Detect checks" {{#unless editable}}disabled{{/unless}} />
</div>
</fieldset>
{{!-- Damage/Healing Modifiers --}}
<fieldset class="modifiers-section">
<legend>{{localize "VAGABOND.DamageHealingModifiers"}}</legend>
<div class="item-stats-row">
<div class="stat-group">
<label>{{localize "VAGABOND.DamageDealtModifier"}}</label>
<input type="number" name="system.modifiers.damageDealt" value="{{system.modifiers.damageDealt}}" {{#unless editable}}disabled{{/unless}} />
</div>
<div class="stat-group">
<label>{{localize "VAGABOND.HealingReceivedModifier"}}</label>
<input type="number" name="system.modifiers.healingReceived" value="{{system.modifiers.healingReceived}}" {{#unless editable}}disabled{{/unless}} />
</div>
</div>
</fieldset>
{{!-- Periodic Effects --}}
<fieldset class="periodic-section">
<legend>{{localize "VAGABOND.PeriodicEffects"}}</legend>
<div class="item-stats-row">
<div class="stat-group">
<label>{{localize "VAGABOND.Trigger"}}</label>
<select name="system.periodic.trigger" {{#unless editable}}disabled{{/unless}}>
<option value="" {{#unless system.periodic.trigger}}selected{{/unless}}>{{localize "VAGABOND.None"}}</option>
<option value="startOfTurn" {{#if (eq system.periodic.trigger "startOfTurn")}}selected{{/if}}>{{localize "VAGABOND.StartOfTurn"}}</option>
<option value="endOfTurn" {{#if (eq system.periodic.trigger "endOfTurn")}}selected{{/if}}>{{localize "VAGABOND.EndOfTurn"}}</option>
<option value="eachRound" {{#if (eq system.periodic.trigger "eachRound")}}selected{{/if}}>{{localize "VAGABOND.EachRound"}}</option>
</select>
</div>
<div class="stat-group">
<label>{{localize "VAGABOND.EffectType"}}</label>
<select name="system.periodic.type" {{#unless editable}}disabled{{/unless}}>
<option value="" {{#unless system.periodic.type}}selected{{/unless}}>{{localize "VAGABOND.None"}}</option>
<option value="damage" {{#if (eq system.periodic.type "damage")}}selected{{/if}}>{{localize "VAGABOND.Damage"}}</option>
<option value="fatigue" {{#if (eq system.periodic.type "fatigue")}}selected{{/if}}>{{localize "VAGABOND.Fatigue"}}</option>
<option value="healing" {{#if (eq system.periodic.type "healing")}}selected{{/if}}>{{localize "VAGABOND.Healing"}}</option>
<option value="check" {{#if (eq system.periodic.type "check")}}selected{{/if}}>{{localize "VAGABOND.Check"}}</option>
</select>
</div>
<div class="stat-group">
<label>{{localize "VAGABOND.Value"}}</label>
<input type="text" name="system.periodic.value" value="{{system.periodic.value}}" placeholder="e.g. 1d6" {{#unless editable}}disabled{{/unless}} />
</div>
</div>
<div class="stat-group full-width">
<label>{{localize "VAGABOND.EffectDescription"}}</label>
<input type="text" name="system.periodic.effectDescription" value="{{system.periodic.effectDescription}}" placeholder="e.g. Takes damage at start of turn" {{#unless editable}}disabled{{/unless}} />
</div>
</fieldset>
{{!-- Duration --}}
<fieldset class="duration-section">
<legend>{{localize "VAGABOND.Duration"}}</legend>
<div class="item-stats-row">
<div class="stat-group">
<label>{{localize "VAGABOND.DurationType"}}</label>
<select name="system.duration.type" {{#unless editable}}disabled{{/unless}}>
<option value="" {{#unless system.duration.type}}selected{{/unless}}>{{localize "VAGABOND.UntilRemoved"}}</option>
<option value="rounds" {{#if (eq system.duration.type "rounds")}}selected{{/if}}>{{localize "VAGABOND.Rounds"}}</option>
<option value="minutes" {{#if (eq system.duration.type "minutes")}}selected{{/if}}>{{localize "VAGABOND.Minutes"}}</option>
<option value="hours" {{#if (eq system.duration.type "hours")}}selected{{/if}}>{{localize "VAGABOND.Hours"}}</option>
<option value="untilRest" {{#if (eq system.duration.type "untilRest")}}selected{{/if}}>{{localize "VAGABOND.UntilRest"}}</option>
<option value="special" {{#if (eq system.duration.type "special")}}selected{{/if}}>{{localize "VAGABOND.Special"}}</option>
</select>
</div>
<div class="stat-group">
<label>{{localize "VAGABOND.DurationValue"}}</label>
<input type="number" name="system.duration.value" value="{{system.duration.value}}" min="0" {{#unless editable}}disabled{{/unless}} />
</div>
</div>
</fieldset>
{{!-- Stacking --}}
<fieldset class="stacking-section">
<legend>{{localize "VAGABOND.Stacking"}}</legend>
<div class="item-stats-row">
<div class="stat-group">
<label class="flag-checkbox">
<input type="checkbox" name="system.stackable" {{#if system.stackable}}checked{{/if}} {{#unless editable}}disabled{{/unless}} />
{{localize "VAGABOND.Stackable"}}
</label>
</div>
{{#if system.stackable}}
<div class="stat-group">
<label>{{localize "VAGABOND.MaxStacks"}}</label>
<input type="number" name="system.maxStacks" value="{{system.maxStacks}}" min="1" {{#unless editable}}disabled{{/unless}} />
</div>
{{/if}}
</div>
</fieldset>
{{!-- Included Statuses (for composites) --}}
<fieldset class="includes-section">
<legend>{{localize "VAGABOND.IncludedStatuses"}}</legend>
<p class="section-hint">{{localize "VAGABOND.IncludedStatusesHint"}}</p>
<div class="includes-list">
{{#each system.includesStatuses}}
<div class="includes-entry" data-index="{{@index}}">
<input type="text" name="system.includesStatuses.{{@index}}" value="{{this}}" placeholder="e.g. Blinded" {{#unless ../editable}}disabled{{/unless}} />
{{#if ../editable}}
<button type="button" class="delete-entry" data-action="deleteArrayEntry" data-field="system.includesStatuses" data-index="{{@index}}">
<i class="fas fa-trash"></i>
</button>
{{/if}}
</div>
{{/each}}
{{#if editable}}
<button type="button" class="add-entry" data-action="addArrayEntry" data-field="system.includesStatuses">
<i class="fas fa-plus"></i> {{localize "VAGABOND.AddIncludedStatus"}}
</button>
{{/if}}
</div>
</fieldset>
{{!-- Reference --}}
<div class="reference-section">
<label>{{localize "VAGABOND.Reference"}}</label>
<input type="text" name="system.reference" value="{{system.reference}}" placeholder="Core Rulebook p.36" {{#unless editable}}disabled{{/unless}} />
</div>
{{!-- Description --}}
<div class="item-description">
<label>{{localize "VAGABOND.Description"}}</label>
<div class="editor-wrapper">
{{#if editable}}
<prose-mirror name="system.description" toggled="false">{{{enrichedDescription}}}</prose-mirror>
{{else}}
<div class="editor-content">{{{enrichedDescription}}}</div>
{{/if}}
</div>
</div>
</div>