diff --git a/PROJECT_ROADMAP.json b/PROJECT_ROADMAP.json index 0f2f5ff..5726e7d 100644 --- a/PROJECT_ROADMAP.json +++ b/PROJECT_ROADMAP.json @@ -545,7 +545,7 @@ "id": "4.1", "name": "Create base VagabondItemSheet class", "description": "Extended ItemSheet with common methods, type-specific tab handling", - "completed": false, + "completed": true, "tested": false, "priority": "critical", "dependencies": ["2.3"] @@ -554,7 +554,7 @@ "id": "4.2", "name": "Implement Ancestry item sheet", "description": "Being type dropdown, size dropdown, traits editor (add/remove trait entries)", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.7"] @@ -563,7 +563,7 @@ "id": "4.3", "name": "Implement Class item sheet", "description": "Key stat, training grants, progression table editor, feature definitions", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.8"] @@ -572,7 +572,7 @@ "id": "4.4", "name": "Implement Spell item sheet", "description": "Damage type, effect text, crit effect, delivery options checkboxes, cost formula display", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.9"] @@ -581,7 +581,7 @@ "id": "4.5", "name": "Implement Perk item sheet", "description": "Prerequisites editor (stat/training/spell requirements), description, effects", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.10"] @@ -590,7 +590,7 @@ "id": "4.6", "name": "Implement Weapon item sheet", "description": "Damage dice selector, grip type, properties checkboxes, attack skill dropdown, value/slots", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.11"] @@ -599,7 +599,7 @@ "id": "4.7", "name": "Implement Armor item sheet", "description": "Armor value, type dropdown, dodge penalty checkbox, value/slots", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.12"] @@ -608,7 +608,7 @@ "id": "4.8", "name": "Implement Equipment item sheet", "description": "Generic item fields: description, quantity, slots, value, consumable flag", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.13"] @@ -617,7 +617,7 @@ "id": "4.9", "name": "Implement Feature item sheet", "description": "Source class, level requirement, description, passive/active toggle, effects editor", - "completed": false, + "completed": true, "tested": false, "priority": "high", "dependencies": ["4.1", "1.14"] diff --git a/lang/en.json b/lang/en.json index e013d73..81e9ca1 100644 --- a/lang/en.json +++ b/lang/en.json @@ -287,6 +287,18 @@ "VAGABOND.TabAbilities": "Abilities", "VAGABOND.TabMagic": "Magic", "VAGABOND.TabBiography": "Biography", + "VAGABOND.TabDetails": "Details", + "VAGABOND.TabEffects": "Effects", + + "VAGABOND.ActiveEffects": "Active Effects", + "VAGABOND.AddEffect": "Add Effect", + "VAGABOND.NewEffect": "New Effect", + "VAGABOND.EditEffect": "Edit Effect", + "VAGABOND.DeleteEffect": "Delete Effect", + "VAGABOND.DeleteEffectConfirm": "Are you sure you want to delete the effect \"{name}\"?", + "VAGABOND.ToggleEffect": "Toggle Effect", + "VAGABOND.NoEffects": "No active effects on this item.", + "VAGABOND.EffectsHint": "Effects on this item will be applied when it is equipped or added to a character.", "VAGABOND.CharacterName": "Character Name", "VAGABOND.NPCName": "NPC Name", @@ -379,5 +391,144 @@ "VAGABOND.ItemNew": "New {type}", "VAGABOND.ItemDeleteTitle": "Delete {name}", - "VAGABOND.ItemDeleteConfirm": "Are you sure you want to delete {name}?" + "VAGABOND.ItemDeleteConfirm": "Are you sure you want to delete {name}?", + "VAGABOND.ItemName": "Item Name", + + "VAGABOND.DamageTypeBlunt": "Blunt", + "VAGABOND.DamageTypePiercing": "Piercing", + "VAGABOND.DamageTypeSlashing": "Slashing", + "VAGABOND.DamageTypeFire": "Fire", + "VAGABOND.DamageTypeCold": "Cold", + "VAGABOND.DamageTypeShock": "Shock", + "VAGABOND.DamageTypeAcid": "Acid", + "VAGABOND.DamageTypePoison": "Poison", + "VAGABOND.DamageTypeNone": "None", + "VAGABOND.DamageBase": "Damage Base", + + "VAGABOND.Grip": "Grip", + "VAGABOND.AttackSkill": "Attack Skill", + "VAGABOND.SkillMelee": "Melee", + "VAGABOND.SkillRanged": "Ranged", + "VAGABOND.VersatileDamage": "Versatile Damage", + "VAGABOND.BonusDamage": "Bonus Damage", + "VAGABOND.AttackBonus": "Attack Bonus", + "VAGABOND.Slots": "Slots", + "VAGABOND.Value": "Value", + "VAGABOND.Quantity": "Quantity", + + "VAGABOND.WeaponProperties": "Weapon Properties", + "VAGABOND.Material": "Material", + "VAGABOND.MaterialMundane": "Mundane", + "VAGABOND.MaterialSilvered": "Silvered", + "VAGABOND.MaterialAdamantine": "Adamantine", + "VAGABOND.MaterialMagical": "Magical", + "VAGABOND.Equipped": "Equipped", + "VAGABOND.EquippedHand": "Equipped Hand", + "VAGABOND.HandMain": "Main", + "VAGABOND.HandOff": "Off", + "VAGABOND.HandBoth": "Both", + + "VAGABOND.Relic": "Relic", + "VAGABOND.RelicTier": "Relic Tier", + "VAGABOND.RequiresAttunement": "Requires Attunement", + "VAGABOND.Attuned": "Attuned", + "VAGABOND.RelicAbilityName": "Ability Name", + "VAGABOND.RelicAbilityDescription": "Ability Description", + "VAGABOND.ActivationCost": "Activation Cost", + "VAGABOND.UsesPerDay": "Uses/Day", + "VAGABOND.UsesRemaining": "Uses Remaining", + "VAGABOND.RelicLore": "Lore", + + "VAGABOND.ArmorValue": "Armor Value", + "VAGABOND.ArmorType": "Armor Type", + "VAGABOND.ArmorTypeNone": "None", + "VAGABOND.ArmorTypeLight": "Light", + "VAGABOND.ArmorTypeMedium": "Medium", + "VAGABOND.ArmorTypeHeavy": "Heavy", + "VAGABOND.ArmorTypeShield": "Shield", + "VAGABOND.MagicBonus": "Magic Bonus", + "VAGABOND.Penalties": "Penalties", + "VAGABOND.DodgePenalty": "Dodge Penalty", + "VAGABOND.HindersDodge": "Hinders Dodge", + "VAGABOND.PreventsRage": "Prevents Rage", + + "VAGABOND.SlotsPerItem": "Slots Per Item", + "VAGABOND.Category": "Category", + "VAGABOND.CategoryGear": "Gear", + "VAGABOND.CategoryTool": "Tool", + "VAGABOND.CategoryConsumable": "Consumable", + "VAGABOND.CategoryContainer": "Container", + "VAGABOND.CategoryTreasure": "Treasure", + "VAGABOND.CategoryMisc": "Misc", + "VAGABOND.Consumable": "Consumable", + "VAGABOND.Uses": "Uses", + "VAGABOND.UsesCurrent": "Current", + "VAGABOND.UsesMax": "Max", + "VAGABOND.AutoDestroy": "Auto Destroy", + "VAGABOND.ContainerCapacity": "Container Capacity", + "VAGABOND.MagicProperties": "Magic Properties", + "VAGABOND.IsTrinket": "Is Trinket", + "VAGABOND.CanCastThrough": "Can Cast Through", + + "VAGABOND.BeingTypeHumanlike": "Humanlike", + "VAGABOND.BeingTypeFae": "Fae", + "VAGABOND.BeingTypeCryptid": "Cryptid", + "VAGABOND.BeingTypeBeast": "Beast", + "VAGABOND.BeingTypeUndead": "Undead", + "VAGABOND.BeingTypeArtificial": "Artificial", + "VAGABOND.BeingTypePrimordial": "Primordial", + "VAGABOND.BeingTypeHellspawn": "Hellspawn", + "VAGABOND.SizeTiny": "Tiny", + "VAGABOND.SizeGargantuan": "Gargantuan", + "VAGABOND.TraitName": "Trait Name", + "VAGABOND.TraitDescription": "Description", + "VAGABOND.NoTraits": "No traits defined", + + "VAGABOND.MaxDice": "Max Dice", + "VAGABOND.UseClassDefault": "Use Class Default", + "VAGABOND.DeliveryTypes": "Delivery Types", + "VAGABOND.DurationTypes": "Duration Types", + + "VAGABOND.StatRequirements": "Stat Requirements", + "VAGABOND.TrainingRequirements": "Training Requirements", + "VAGABOND.SpellRequirements": "Spell Requirements", + "VAGABOND.PerkRequirements": "Perk Requirements", + "VAGABOND.CustomRequirements": "Custom Requirements", + "VAGABOND.CommaSeparated": "Comma separated", + "VAGABOND.IsRitual": "Is Ritual", + "VAGABOND.RitualDuration": "Ritual Duration", + "VAGABOND.RecoveryPer": "Recovery", + "VAGABOND.ShortRest": "Short Rest", + "VAGABOND.LongRest": "Long Rest", + "VAGABOND.PerDay": "Per Day", + "VAGABOND.PerEncounter": "Per Encounter", + "VAGABOND.LuckCost": "Luck Cost", + "VAGABOND.GrantsLuck": "Grants Luck", + "VAGABOND.RitualComponents": "Components", + + "VAGABOND.SourceClass": "Source Class", + "VAGABOND.Activation": "Activation", + "VAGABOND.ActivationType": "Type", + "VAGABOND.Action": "Action", + "VAGABOND.BonusAction": "Bonus Action", + "VAGABOND.Reaction": "Reaction", + "VAGABOND.FreeAction": "Free Action", + "VAGABOND.Special": "Special", + "VAGABOND.Unlimited": "Unlimited", + "VAGABOND.Requirements": "Requirements", + + "VAGABOND.KeyStat": "Key Stat", + "VAGABOND.IsCaster": "Is Caster", + "VAGABOND.ActionStyle": "Casting Skill", + "VAGABOND.TrainedSkills": "Trained Skills", + "VAGABOND.StartingPack": "Starting Pack", + "VAGABOND.CustomResource": "Custom Resource", + "VAGABOND.ResourceName": "Resource Name", + "VAGABOND.ResourceMax": "Max Formula", + "VAGABOND.Progression": "Progression", + "VAGABOND.SpellsKnown": "Spells Known", + "VAGABOND.ClassFeatures": "Class Features", + "VAGABOND.NoProgression": "No progression entries", + "VAGABOND.FeatureName": "Feature Name", + "VAGABOND.FeatureDescription": "Description" } diff --git a/module/sheets/_module.mjs b/module/sheets/_module.mjs index 8de2719..e0e2e80 100644 --- a/module/sheets/_module.mjs +++ b/module/sheets/_module.mjs @@ -3,6 +3,10 @@ * Exports all actor and item sheet classes for the Vagabond RPG system. */ +// Actor sheets export { default as VagabondActorSheet } from "./base-actor-sheet.mjs"; export { default as VagabondCharacterSheet } from "./character-sheet.mjs"; export { default as VagabondNPCSheet } from "./npc-sheet.mjs"; + +// Item sheets +export { default as VagabondItemSheet } from "./base-item-sheet.mjs"; diff --git a/module/sheets/base-item-sheet.mjs b/module/sheets/base-item-sheet.mjs new file mode 100644 index 0000000..32d61c3 --- /dev/null +++ b/module/sheets/base-item-sheet.mjs @@ -0,0 +1,447 @@ +/** + * Base Item Sheet for Vagabond RPG + * + * Provides common functionality for all item sheets: + * - Tab navigation (for complex items) + * - Form handling with automatic updates + * - Rich text editor integration + * - Type-specific rendering via parts system + * + * Uses Foundry VTT v13 ItemSheetV2 API with HandlebarsApplicationMixin. + * + * @extends ItemSheetV2 + * @mixes HandlebarsApplicationMixin + */ + +const { HandlebarsApplicationMixin } = foundry.applications.api; +const { ItemSheetV2 } = foundry.applications.sheets; + +export default class VagabondItemSheet extends HandlebarsApplicationMixin(ItemSheetV2) { + /** + * @param {Object} options - Application options + */ + constructor(options = {}) { + super(options); + + // Active tab tracking (for items with tabs) + this._activeTab = "details"; + } + + /* -------------------------------------------- */ + /* Static Properties */ + /* -------------------------------------------- */ + + /** @override */ + static DEFAULT_OPTIONS = { + id: "vagabond-item-sheet-{id}", + classes: ["vagabond", "sheet", "item"], + tag: "form", + window: { + title: "VAGABOND.ItemSheet", + icon: "fa-solid fa-suitcase", + resizable: true, + }, + position: { + width: 520, + height: 480, + }, + form: { + handler: VagabondItemSheet.#onFormSubmit, + submitOnChange: true, + closeOnSubmit: false, + }, + actions: { + changeTab: VagabondItemSheet.#onChangeTab, + addArrayEntry: VagabondItemSheet.#onAddArrayEntry, + deleteArrayEntry: VagabondItemSheet.#onDeleteArrayEntry, + createEffect: VagabondItemSheet.#onCreateEffect, + editEffect: VagabondItemSheet.#onEditEffect, + deleteEffect: VagabondItemSheet.#onDeleteEffect, + toggleEffect: VagabondItemSheet.#onToggleEffect, + }, + }; + + /** @override */ + static PARTS = { + header: { + template: "systems/vagabond/templates/item/parts/item-header.hbs", + }, + tabs: { + template: "systems/vagabond/templates/item/parts/item-tabs.hbs", + }, + body: { + template: "systems/vagabond/templates/item/parts/item-body.hbs", + }, + effects: { + template: "systems/vagabond/templates/item/parts/item-effects.hbs", + }, + }; + + /* -------------------------------------------- */ + /* Getters */ + /* -------------------------------------------- */ + + /** + * Convenient alias for the item document. + * @returns {VagabondItem} + */ + get item() { + return this.document; + } + + /** @override */ + get title() { + return this.document.name; + } + + /** + * Get the available tabs for this item type. + * All items have Details and Effects tabs. + * @returns {Object[]} Array of tab definitions + */ + get tabs() { + return [ + { + id: "details", + label: "VAGABOND.TabDetails", + icon: "fas fa-list", + }, + { + id: "effects", + label: "VAGABOND.TabEffects", + icon: "fas fa-bolt", + }, + ]; + } + + /** + * Whether this item type uses tabs. + * @returns {boolean} + */ + get hasTabs() { + return this.tabs !== null && this.tabs.length > 0; + } + + /* -------------------------------------------- */ + /* Data Preparation */ + /* -------------------------------------------- */ + + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + + // Basic item data + context.item = this.item; + context.system = this.item.system; + context.source = this.item.toObject().system; + + // Sheet state + context.hasTabs = this.hasTabs; + context.activeTab = this._activeTab; + if (this.hasTabs) { + context.tabs = this.tabs.map((tab) => ({ + ...tab, + active: tab.id === this._activeTab, + cssClass: tab.id === this._activeTab ? "active" : "", + })); + } + + // Roll data for formulas in templates + context.rollData = this.item.getRollData(); + + // Editable state + context.editable = this.isEditable; + context.owner = this.item.isOwner; + context.limited = this.item.limited; + + // System configuration + context.config = CONFIG.VAGABOND; + + // Item type for conditional rendering + context.itemType = this.item.type; + + // Active Effects on this item + context.effects = this._prepareEffects(); + + // Type-specific context + await this._prepareTypeContext(context, options); + + return context; + } + + /** + * Prepare Active Effects for display. + * @returns {Object[]} Array of effect data for rendering + * @protected + */ + _prepareEffects() { + return this.item.effects.map((effect) => ({ + id: effect.id, + name: effect.name, + icon: effect.icon, + disabled: effect.disabled, + duration: effect.duration, + source: effect.sourceName, + })); + } + + /** + * Prepare type-specific context data. + * Subclasses should override this. + * + * @param {Object} context - The context object to augment + * @param {Object} options - Render options + * @protected + */ + async _prepareTypeContext(_context, _options) { + // Override in subclasses + } + + /* -------------------------------------------- */ + /* Rendering */ + /* -------------------------------------------- */ + + /** @override */ + _onRender(context, options) { + super._onRender(context, options); + + // Clean up inactive tabs if using tabs + if (this.hasTabs) { + this._cleanupInactiveTabs(); + } + + // Initialize rich text editors + this._initializeEditors(); + } + + /** + * Remove tab content sections that don't match the active tab. + * ApplicationV2's parts rendering appends new parts without removing old ones. + * @protected + */ + _cleanupInactiveTabs() { + if (!this.element) return; + + const activeTabClass = `${this._activeTab}-tab`; + const tabContents = this.element.querySelectorAll(".tab-content"); + + for (const tabContent of tabContents) { + if (!tabContent.classList.contains(activeTabClass)) { + tabContent.remove(); + } + } + } + + /** + * Initialize rich text editors for description fields. + * @protected + */ + _initializeEditors() { + // Foundry automatically handles ProseMirror/TinyMCE for HTMLField inputs + // Additional initialization can be added here if needed + } + + /* -------------------------------------------- */ + /* Action Handlers */ + /* -------------------------------------------- */ + + /** + * Handle form submission. + * @param {Event} event + * @param {HTMLFormElement} form + * @param {FormDataExtended} formData + */ + static async #onFormSubmit(event, form, formData) { + const sheet = this; + const updateData = foundry.utils.expandObject(formData.object); + + // Clean up array data that may have gaps from deletions + sheet._cleanArrayData(updateData); + + await sheet.item.update(updateData); + } + + /** + * Clean array data to remove gaps from deleted entries. + * Converts { "0": {...}, "2": {...} } to [ {...}, {...} ] + * @param {Object} data - The update data object + * @protected + */ + _cleanArrayData(data) { + // Handle system-level arrays + if (data.system) { + this._cleanArraysRecursive(data.system); + } + } + + /** + * Recursively clean arrays in an object. + * @param {Object} obj - Object to clean + * @protected + */ + _cleanArraysRecursive(obj) { + for (const [key, value] of Object.entries(obj)) { + if (value && typeof value === "object" && !Array.isArray(value)) { + // Check if this looks like an indexed object (array-like) + const keys = Object.keys(value); + const isIndexed = keys.every((k) => /^\d+$/.test(k)); + + if (isIndexed && keys.length > 0) { + // Convert to array, filtering out nulls/undefined + obj[key] = Object.values(value).filter((v) => v != null); + } else { + // Recurse into nested objects + this._cleanArraysRecursive(value); + } + } + } + } + + /** + * Handle tab change action. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onChangeTab(event, target) { + event.preventDefault(); + const tab = target.dataset.tab; + if (!tab) return; + + this._activeTab = tab; + this.render(); + } + + /** + * Handle adding a new entry to an array field. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onAddArrayEntry(event, target) { + event.preventDefault(); + const field = target.dataset.field; + if (!field) return; + + const currentArray = foundry.utils.getProperty(this.item, field) || []; + const template = this._getArrayEntryTemplate(field); + + await this.item.update({ + [field]: [...currentArray, template], + }); + } + + /** + * Handle deleting an entry from an array field. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onDeleteArrayEntry(event, target) { + event.preventDefault(); + const field = target.dataset.field; + const index = parseInt(target.dataset.index, 10); + if (!field || isNaN(index)) return; + + const currentArray = foundry.utils.getProperty(this.item, field) || []; + const newArray = [...currentArray]; + newArray.splice(index, 1); + + await this.item.update({ + [field]: newArray, + }); + } + + /** + * Get the default template for a new array entry. + * Subclasses should override for their specific array fields. + * @param {string} field - The field path + * @returns {Object} Default entry object + * @protected + */ + _getArrayEntryTemplate(_field) { + // Default template - subclasses override for specific fields + return { name: "", description: "" }; + } + + /* -------------------------------------------- */ + /* Active Effect Actions */ + /* -------------------------------------------- */ + + /** + * Handle creating a new Active Effect on this item. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onCreateEffect(event, _target) { + event.preventDefault(); + + // Create a new effect with default values + const effectData = { + name: game.i18n.localize("VAGABOND.NewEffect"), + icon: "icons/svg/aura.svg", + origin: this.item.uuid, + disabled: false, + }; + + const [created] = await this.item.createEmbeddedDocuments("ActiveEffect", [effectData]); + + // Open the effect config sheet + if (created) { + created.sheet.render(true); + } + } + + /** + * Handle editing an existing Active Effect. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onEditEffect(event, target) { + event.preventDefault(); + const effectId = target.closest("[data-effect-id]")?.dataset.effectId; + if (!effectId) return; + + const effect = this.item.effects.get(effectId); + if (effect) { + effect.sheet.render(true); + } + } + + /** + * Handle deleting an Active Effect. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onDeleteEffect(event, target) { + event.preventDefault(); + const effectId = target.closest("[data-effect-id]")?.dataset.effectId; + if (!effectId) return; + + const effect = this.item.effects.get(effectId); + if (!effect) return; + + // Confirm deletion + const confirmed = await Dialog.confirm({ + title: game.i18n.localize("VAGABOND.DeleteEffect"), + content: `

${game.i18n.format("VAGABOND.DeleteEffectConfirm", { name: effect.name })}

`, + }); + + if (confirmed) { + await effect.delete(); + } + } + + /** + * Handle toggling an Active Effect's disabled state. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onToggleEffect(event, target) { + event.preventDefault(); + const effectId = target.closest("[data-effect-id]")?.dataset.effectId; + if (!effectId) return; + + const effect = this.item.effects.get(effectId); + if (effect) { + await effect.update({ disabled: !effect.disabled }); + } + } +} diff --git a/module/vagabond.mjs b/module/vagabond.mjs index 4884993..729fe47 100644 --- a/module/vagabond.mjs +++ b/module/vagabond.mjs @@ -3,7 +3,7 @@ * @module vagabond */ -/* global Actors */ +/* global Actors, Items */ // Import configuration import { VAGABOND } from "./helpers/config.mjs"; @@ -35,7 +35,12 @@ import { } from "./applications/_module.mjs"; // Import sheet classes -import { VagabondActorSheet, VagabondCharacterSheet, VagabondNPCSheet } from "./sheets/_module.mjs"; +import { + VagabondActorSheet, + VagabondCharacterSheet, + VagabondNPCSheet, + VagabondItemSheet, +} from "./sheets/_module.mjs"; // Import test registration (for Quench) import { registerQuenchTests } from "./tests/quench-init.mjs"; @@ -64,6 +69,20 @@ async function preloadHandlebarsTemplates() { "systems/vagabond/templates/actor/npc-actions.hbs", "systems/vagabond/templates/actor/npc-abilities.hbs", "systems/vagabond/templates/actor/npc-notes.hbs", + // Item sheet parts + "systems/vagabond/templates/item/parts/item-header.hbs", + "systems/vagabond/templates/item/parts/item-tabs.hbs", + "systems/vagabond/templates/item/parts/item-body.hbs", + "systems/vagabond/templates/item/parts/item-effects.hbs", + // Item type templates + "systems/vagabond/templates/item/types/weapon.hbs", + "systems/vagabond/templates/item/types/armor.hbs", + "systems/vagabond/templates/item/types/equipment.hbs", + "systems/vagabond/templates/item/types/ancestry.hbs", + "systems/vagabond/templates/item/types/class.hbs", + "systems/vagabond/templates/item/types/spell.hbs", + "systems/vagabond/templates/item/types/perk.hbs", + "systems/vagabond/templates/item/types/feature.hbs", ]; return loadTemplates(templatePaths); @@ -93,6 +112,7 @@ Hooks.once("init", async () => { VagabondActorSheet, VagabondCharacterSheet, VagabondNPCSheet, + VagabondItemSheet, }, }; @@ -131,12 +151,12 @@ Hooks.once("init", async () => { label: "VAGABOND.SheetNPC", }); - // Register Item sheet classes (TODO: Phase 4) - // Items.unregisterSheet("core", ItemSheet); - // Items.registerSheet("vagabond", VagabondItemSheet, { - // makeDefault: true, - // label: "VAGABOND.SheetItem" - // }); + // Register Item sheet classes + Items.unregisterSheet("core", ItemSheet); + Items.registerSheet("vagabond", VagabondItemSheet, { + makeDefault: true, + label: "VAGABOND.SheetItem", + }); // Preload Handlebars templates await preloadHandlebarsTemplates(); diff --git a/styles/scss/_mixins.scss b/styles/scss/_mixins.scss index d259c50..b45b848 100644 --- a/styles/scss/_mixins.scss +++ b/styles/scss/_mixins.scss @@ -92,6 +92,38 @@ } } +@mixin button-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + font-size: $font-size-sm; + color: $color-text-secondary; + background-color: transparent; + border: 1px solid transparent; + border-radius: $radius-sm; + cursor: pointer; + transition: all $transition-fast; + + &:hover:not(:disabled) { + color: $color-text-primary; + background-color: $color-parchment-dark; + border-color: $color-border; + } + + &:focus { + outline: 2px solid $color-accent-primary; + outline-offset: 2px; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + // Form inputs @mixin input-base { padding: $spacing-2 $spacing-3; diff --git a/styles/scss/sheets/_item-sheet.scss b/styles/scss/sheets/_item-sheet.scss index 9a486e6..650347e 100644 --- a/styles/scss/sheets/_item-sheet.scss +++ b/styles/scss/sheets/_item-sheet.scss @@ -1,101 +1,656 @@ // Vagabond RPG - Item Sheet Styles // ================================= -// Placeholder - will be expanded in Phase 4 .vagabond.sheet.item { - min-width: 400px; - min-height: 300px; + min-width: 500px; + min-height: 400px; - .sheet-header { + // Header styling + .sheet-header.item-header { @include flex-between; padding: $spacing-4; background-color: $color-parchment-dark; border-bottom: 2px solid $color-border; + gap: $spacing-4; .item-img { - width: 60px; - height: 60px; + width: 64px; + height: 64px; object-fit: cover; border: 2px solid $color-border; border-radius: $radius-md; + cursor: pointer; + + &:hover { + border-color: $color-accent-primary; + } } .header-fields { flex: 1; - margin-left: $spacing-4; + display: flex; + flex-direction: column; + gap: $spacing-2; + + .item-name { + margin: 0; + + input { + width: 100%; + font-size: $font-size-lg; + font-weight: bold; + border: none; + background: transparent; + border-bottom: 1px solid transparent; + + &:focus { + border-bottom-color: $color-accent-primary; + } + } + } + + .item-type { + .type-badge { + display: inline-block; + padding: $spacing-1 $spacing-3; + font-size: $font-size-sm; + font-weight: 500; + text-transform: capitalize; + background-color: $color-parchment; + border: 1px solid $color-border; + border-radius: $radius-full; + + &.weapon { + background-color: mix($color-danger, $color-parchment, 15%); + } + &.armor { + background-color: mix($color-info, $color-parchment, 15%); + } + &.spell { + background-color: mix($color-reason, $color-parchment, 15%); + } + &.perk { + background-color: mix($color-success, $color-parchment, 15%); + } + &.class { + background-color: mix($color-warning, $color-parchment, 15%); + } + } + } } } + // Sheet body .sheet-body { padding: $spacing-4; + overflow-y: auto; + max-height: calc(100% - 80px); } - // Item description - .item-description { - .editor { - min-height: 150px; - } - } -} - -// Spell item specific -.vagabond.sheet.item.spell { - .spell-details { - @include grid(2); - margin-bottom: $spacing-4; + // Item content container + .item-content { + display: flex; + flex-direction: column; + gap: $spacing-4; } - .delivery-options { - margin-bottom: $spacing-4; - - .option-grid { - @include grid(3, $spacing-2); - } - } -} - -// Perk item specific -.vagabond.sheet.item.perk { - .prerequisites { - @include panel; - padding: $spacing-3; - margin-bottom: $spacing-4; - - .prereq-list { - @include flex-column; - gap: $spacing-2; - } - } -} - -// Weapon item specific -.vagabond.sheet.item.weapon { - .weapon-properties { + // Stats row + .item-stats-row { display: flex; flex-wrap: wrap; - gap: $spacing-2; - margin-bottom: $spacing-4; + gap: $spacing-4; + align-items: flex-end; - .property-tag { - padding: $spacing-1 $spacing-2; + .stat-group { + display: flex; + flex-direction: column; + gap: $spacing-1; + min-width: 80px; + + &.full-width { + flex: 1 1 100%; + } + + &.wide { + min-width: 140px; + + select { + min-width: 130px; + } + } + + label { + font-size: $font-size-sm; + font-weight: 500; + color: $color-text-secondary; + } + + input[type="text"], + input[type="number"], + select, + textarea { + padding: $spacing-2; + border: 1px solid $color-border; + border-radius: $radius-sm; + background-color: $color-parchment-light; + + &:focus { + border-color: $color-accent-primary; + outline: none; + } + + &:disabled { + background-color: $color-parchment; + cursor: not-allowed; + } + } + + input[type="checkbox"] { + width: 18px; + height: 18px; + margin: 0; + cursor: pointer; + } + + input[type="number"] { + width: 70px; + } + + .range-input { + display: flex; + align-items: center; + gap: $spacing-2; + + input { + width: 60px; + } + } + + .units { + font-size: $font-size-sm; + color: $color-text-secondary; + } + + textarea { + min-height: 60px; + resize: vertical; + } + } + } + + // Fieldset styling + fieldset { + border: 1px solid $color-border; + border-radius: $radius-md; + padding: $spacing-3; + margin: 0; + background-color: rgba($color-parchment-dark, 0.3); + + legend { + padding: 0 $spacing-2; + font-weight: 600; font-size: $font-size-sm; - background-color: $color-parchment-dark; + display: flex; + align-items: center; + gap: $spacing-2; + + button { + @include button-icon; + padding: $spacing-1; + font-size: $font-size-xs; + } + } + + &.collapsed { + padding: $spacing-2; + background-color: transparent; + } + } + + // Properties grid (checkboxes) + .properties-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: $spacing-2; + + .property-checkbox { + display: flex; + align-items: center; + gap: $spacing-2; + cursor: pointer; + font-size: $font-size-sm; + + input[type="checkbox"] { + width: 16px; + height: 16px; + } + } + } + + // Relic section + .relic-section { + .relic-toggle { + display: flex; + align-items: center; + gap: $spacing-2; + cursor: pointer; + } + + .relic-fields { + display: flex; + flex-direction: column; + gap: $spacing-3; + margin-top: $spacing-3; + } + } + + // Description editor + .item-description { + display: flex; + flex-direction: column; + gap: $spacing-2; + + > label { + font-weight: 600; + } + + .editor-container { border: 1px solid $color-border; - border-radius: $radius-full; + border-radius: $radius-sm; + overflow: hidden; + + .editor { + min-height: 120px; + padding: $spacing-2; + background-color: $color-parchment-light; + } + } + } + + // Traits list (ancestry) + .traits-list { + display: flex; + flex-direction: column; + gap: $spacing-3; + + .trait-entry { + padding: $spacing-3; + border: 1px solid $color-border; + border-radius: $radius-sm; + background-color: $color-parchment-light; + + .trait-header { + display: flex; + gap: $spacing-2; + margin-bottom: $spacing-2; + + input { + flex: 1; + font-weight: 500; + } + + button { + @include button-icon; + color: $color-danger; + } + } + + .trait-description textarea { + width: 100%; + min-height: 60px; + } + } + + .no-traits { + color: $color-text-secondary; + font-style: italic; + text-align: center; + padding: $spacing-4; + } + } + + // Prerequisite sections (perk) + .prerequisites-section { + .prereq-stats { + margin-bottom: $spacing-3; + } + + .stat-prereq-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: $spacing-2; + margin-top: $spacing-2; + + .stat-prereq { + display: flex; + flex-direction: column; + align-items: center; + gap: $spacing-1; + + label { + font-size: $font-size-xs; + font-weight: 600; + color: $color-text-secondary; + } + + input { + width: 40px; + text-align: center; + } + } + } + + .prereq-label { + font-weight: 500; + font-size: $font-size-sm; + } + + .prereq-training, + .prereq-spells, + .prereq-perks, + .prereq-custom { + margin-top: $spacing-3; + + label { + display: block; + margin-bottom: $spacing-1; + font-size: $font-size-sm; + font-weight: 500; + } + + input { + width: 100%; + } + + .hint { + font-size: $font-size-xs; + color: $color-text-secondary; + font-style: italic; + } + } + } + + // Progression table (class) + .progression-section { + .progression-table { + margin-top: $spacing-3; + + .progression-header { + display: grid; + grid-template-columns: 50px 60px 80px 80px 1fr 40px; + gap: $spacing-2; + padding: $spacing-2; + background-color: $color-parchment-dark; + font-size: $font-size-xs; + font-weight: 600; + border-radius: $radius-sm $radius-sm 0 0; + } + + .progression-row { + display: grid; + grid-template-columns: 50px 60px 80px 80px 1fr 40px; + gap: $spacing-2; + padding: $spacing-2; + border-bottom: 1px solid $color-border; + background-color: $color-parchment-light; + + &:last-child { + border-radius: 0 0 $radius-sm $radius-sm; + } + + input { + width: 100%; + padding: $spacing-1; + font-size: $font-size-sm; + } + + button { + @include button-icon; + color: $color-danger; + } + } + + .no-progression { + padding: $spacing-4; + text-align: center; + color: $color-text-secondary; + font-style: italic; + } + } + } + + // Features list (class) + .features-section { + .features-list { + display: flex; + flex-direction: column; + gap: $spacing-3; + margin-top: $spacing-3; + + .feature-entry { + padding: $spacing-3; + border: 1px solid $color-border; + border-radius: $radius-sm; + background-color: $color-parchment-light; + + .feature-header { + display: flex; + gap: $spacing-2; + align-items: center; + margin-bottom: $spacing-2; + + input[type="text"] { + flex: 1; + font-weight: 500; + } + + input[type="number"] { + width: 50px; + } + + .passive-toggle { + display: flex; + align-items: center; + gap: $spacing-1; + font-size: $font-size-sm; + cursor: pointer; + } + + button { + @include button-icon; + color: $color-danger; + } + } + + .feature-description textarea { + width: 100%; + min-height: 60px; + } + } + + .no-features { + padding: $spacing-4; + text-align: center; + color: $color-text-secondary; + font-style: italic; + } + } + } + + // Spell effect and crit effect + .spell-effect, + .spell-crit-effect { + display: flex; + flex-direction: column; + gap: $spacing-2; + + > label { + font-weight: 600; + } + + .editor-container { + border: 1px solid $color-border; + border-radius: $radius-sm; + + .editor { + min-height: 80px; + padding: $spacing-2; + background-color: $color-parchment-light; + } + } + } + + // Uses section + .uses-section, + .consumable-uses { + .item-stats-row { + margin-top: $spacing-2; + } + } + + // Activation section + .activation-section { + .item-stats-row { + margin-top: $spacing-2; + } + } + + // Effects tab + .effects-tab { + padding: $spacing-4; + + .effects-list { + display: flex; + flex-direction: column; + gap: $spacing-3; + } + + .effects-header { + display: flex; + justify-content: space-between; + align-items: center; + + h3 { + margin: 0; + font-size: $font-size-lg; + font-weight: 600; + } + + .add-effect { + @include button-primary; + padding: $spacing-1 $spacing-3; + font-size: $font-size-sm; + + i { + margin-right: $spacing-1; + } + } + } + + .effect-items { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: $spacing-2; + } + + .effect-item { + display: flex; + align-items: center; + gap: $spacing-2; + padding: $spacing-2; + background-color: $color-parchment-light; + border: 1px solid $color-border; + border-radius: $radius-sm; + + &.disabled { + opacity: 0.6; + + .effect-name { + text-decoration: line-through; + } + + // Ensure controls remain clickable when effect is disabled + .effect-controls { + opacity: 1; + pointer-events: auto; + + button { + pointer-events: auto; + } + } + } + + .effect-icon { + width: 24px; + height: 24px; + border-radius: $radius-sm; + border: 1px solid $color-border; + } + + .effect-name { + flex: 1; + font-weight: 500; + } + + .effect-controls { + display: flex; + gap: $spacing-1; + + .effect-control { + @include button-icon; + + &.delete { + color: $color-danger; + + &:hover { + background-color: rgba($color-danger, 0.1); + } + } + } + } + } + + .no-effects { + color: $color-text-secondary; + font-style: italic; + text-align: center; + padding: $spacing-4; + } + + .effects-hint { + font-size: $font-size-sm; + color: $color-text-secondary; + margin-top: $spacing-2; + + i { + margin-right: $spacing-1; + } + } + } +} + +// Type-specific additional styles +.vagabond.sheet.item.weapon { + .weapon-content { + .item-properties { + margin-top: $spacing-2; } } } -// Class item specific .vagabond.sheet.item.class { - .progression-table { - width: 100%; - margin-bottom: $spacing-4; + min-width: 600px; + min-height: 600px; - th { - position: sticky; - top: 0; - background-color: $color-parchment-dark; + .class-content { + .progression-header { + // Adjust columns without delete button column for non-editable + &.readonly { + grid-template-columns: 50px 60px 80px 80px 1fr; + } } } } diff --git a/templates/item/parts/item-body.hbs b/templates/item/parts/item-body.hbs new file mode 100644 index 0000000..aac6db9 --- /dev/null +++ b/templates/item/parts/item-body.hbs @@ -0,0 +1,5 @@ +{{!-- Item Sheet Body - Routes to type-specific template --}} +
+ {{!-- Type-specific content is injected by subclass sheets --}} + {{> (concat "systems/vagabond/templates/item/types/" itemType ".hbs") }} +
diff --git a/templates/item/parts/item-effects.hbs b/templates/item/parts/item-effects.hbs new file mode 100644 index 0000000..18a224f --- /dev/null +++ b/templates/item/parts/item-effects.hbs @@ -0,0 +1,44 @@ +{{!-- Item Effects Tab --}} +
+
+
+

{{localize "VAGABOND.ActiveEffects"}}

+ {{#if editable}} + + {{/if}} +
+ + {{#if effects.length}} + + {{else}} +

{{localize "VAGABOND.NoEffects"}}

+ {{/if}} + +

+ + {{localize "VAGABOND.EffectsHint"}} +

+
+
diff --git a/templates/item/parts/item-header.hbs b/templates/item/parts/item-header.hbs new file mode 100644 index 0000000..66c6a4c --- /dev/null +++ b/templates/item/parts/item-header.hbs @@ -0,0 +1,14 @@ +{{!-- Item Sheet Header --}} +
+ {{item.name}} + +
+

+ +

+ +
+ {{localize (concat "VAGABOND.ItemType" (capitalize itemType))}} +
+
+
diff --git a/templates/item/parts/item-tabs.hbs b/templates/item/parts/item-tabs.hbs new file mode 100644 index 0000000..8cd6899 --- /dev/null +++ b/templates/item/parts/item-tabs.hbs @@ -0,0 +1,9 @@ +{{!-- Item Sheet Tabs --}} + diff --git a/templates/item/types/ancestry.hbs b/templates/item/types/ancestry.hbs new file mode 100644 index 0000000..436f704 --- /dev/null +++ b/templates/item/types/ancestry.hbs @@ -0,0 +1,71 @@ +{{!-- Ancestry Item Sheet Body --}} +
+ {{!-- Core Stats Row --}} +
+
+ + +
+ +
+ + +
+
+ + {{!-- Traits Section --}} +
+ + {{localize "VAGABOND.Traits"}} + {{#if editable}} + + {{/if}} + + +
+ {{#each system.traits as |trait index|}} +
+
+ + {{#if ../editable}} + + {{/if}} +
+
+ +
+
+ {{else}} +

{{localize "VAGABOND.NoTraits"}}

+ {{/each}} +
+
+ + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+
diff --git a/templates/item/types/armor.hbs b/templates/item/types/armor.hbs new file mode 100644 index 0000000..dd6f46f --- /dev/null +++ b/templates/item/types/armor.hbs @@ -0,0 +1,135 @@ +{{!-- Armor Item Sheet Body --}} +
+ {{!-- Core Stats Row --}} +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {{!-- Secondary Stats Row --}} +
+
+ + +
+ +
+ + + c +
+ +
+ + +
+
+ + {{!-- Penalties --}} +
+ {{localize "VAGABOND.Penalties"}} +
+ + + +
+
+ + {{!-- Relic Section (collapsible) --}} +
+ + + + + {{#if system.relic.isRelic}} +
+
+
+ + +
+
+ + +
+ {{#if system.relic.requiresAttunement}} +
+ + +
+ {{/if}} +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+ {{#if (gt system.relic.usesPerDay 0)}} +
+ + +
+ {{/if}} +
+ +
+ + +
+
+ {{/if}} +
+ + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+
diff --git a/templates/item/types/class.hbs b/templates/item/types/class.hbs new file mode 100644 index 0000000..92fe713 --- /dev/null +++ b/templates/item/types/class.hbs @@ -0,0 +1,163 @@ +{{!-- Class Item Sheet Body --}} +
+ {{!-- Core Stats Row --}} +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {{!-- Casting Skill (if caster) --}} + {{#if system.isCaster}} +
+
+ + +
+
+ {{/if}} + + {{!-- Trained Skills --}} +
+ + + {{localize "VAGABOND.CommaSeparated"}} +
+ + {{!-- Starting Pack --}} +
+ +
+ {{editor system.startingPack target="system.startingPack" button=true editable=editable engine="prosemirror"}} +
+
+ + {{!-- Custom Resource --}} +
+ {{localize "VAGABOND.CustomResource"}} +
+
+ + +
+
+ + +
+
+
+ + {{!-- Progression Table --}} +
+ + {{localize "VAGABOND.Progression"}} + {{#if editable}} + + {{/if}} + + +
+
+ {{localize "VAGABOND.Level"}} + {{localize "VAGABOND.Mana"}} + {{localize "VAGABOND.CastingMax"}} + {{localize "VAGABOND.SpellsKnown"}} + {{localize "VAGABOND.Features"}} + {{#if editable}}{{/if}} +
+ + {{#each system.progression as |prog index|}} +
+ + + + + + {{#if ../editable}} + + {{/if}} +
+ {{else}} +

{{localize "VAGABOND.NoProgression"}}

+ {{/each}} +
+
+ + {{!-- Features --}} +
+ + {{localize "VAGABOND.ClassFeatures"}} + {{#if editable}} + + {{/if}} + + +
+ {{#each system.features as |feature index|}} +
+
+ + + + {{#if ../editable}} + + {{/if}} +
+
+ +
+
+ {{else}} +

{{localize "VAGABOND.NoFeatures"}}

+ {{/each}} +
+
+ + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+
diff --git a/templates/item/types/equipment.hbs b/templates/item/types/equipment.hbs new file mode 100644 index 0000000..30f4214 --- /dev/null +++ b/templates/item/types/equipment.hbs @@ -0,0 +1,169 @@ +{{!-- Equipment Item Sheet Body --}} +
+ {{!-- Core Stats Row --}} +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + c +
+
+ + {{!-- Category and Flags --}} +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {{!-- Consumable Uses (if consumable) --}} + {{#if system.consumable}} +
+ {{localize "VAGABOND.Uses"}} +
+
+ + +
+
+ + +
+
+ + +
+
+
+ {{/if}} + + {{!-- Container Capacity (if container) --}} + {{#if (eq system.category "container")}} +
+
+ + + {{localize "VAGABOND.Slots"}} +
+
+ {{/if}} + + {{!-- Magic Properties --}} +
+ {{localize "VAGABOND.MagicProperties"}} +
+ + +
+
+ + {{!-- Relic Section (collapsible) --}} +
+ + + + + {{#if system.relic.isRelic}} +
+
+
+ + +
+
+ + +
+ {{#if system.relic.requiresAttunement}} +
+ + +
+ {{/if}} +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+ {{#if (gt system.relic.usesPerDay 0)}} +
+ + +
+ {{/if}} +
+ +
+ + +
+
+ {{/if}} +
+ + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+
diff --git a/templates/item/types/feature.hbs b/templates/item/types/feature.hbs new file mode 100644 index 0000000..13f5ed1 --- /dev/null +++ b/templates/item/types/feature.hbs @@ -0,0 +1,83 @@ +{{!-- Feature Item Sheet Body --}} +
+ {{!-- Core Stats Row --}} +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {{!-- Activation (for active features) --}} + {{#unless system.passive}} +
+ {{localize "VAGABOND.Activation"}} +
+
+ + +
+
+ + +
+
+
+ {{/unless}} + + {{!-- Uses (if limited) --}} +
+ {{localize "VAGABOND.Uses"}} +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {{!-- Requirements --}} +
+ + +
+ + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+
diff --git a/templates/item/types/perk.hbs b/templates/item/types/perk.hbs new file mode 100644 index 0000000..fdb67ce --- /dev/null +++ b/templates/item/types/perk.hbs @@ -0,0 +1,140 @@ +{{!-- Perk Item Sheet Body --}} +
+ {{!-- Prerequisites Section --}} +
+ {{localize "VAGABOND.Prerequisites"}} + + {{!-- Stat Requirements --}} +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {{!-- Training Requirements --}} +
+ + + {{localize "VAGABOND.CommaSeparated"}} +
+ + {{!-- Spell Requirements --}} +
+ + + {{localize "VAGABOND.CommaSeparated"}} +
+ + {{!-- Perk Requirements --}} +
+ + + {{localize "VAGABOND.CommaSeparated"}} +
+ + {{!-- Custom Requirements --}} +
+ + +
+
+ + {{!-- Usage & Activation --}} +
+
+ + +
+ +
+ + +
+ + {{#if system.isRitual}} +
+ + + min +
+ {{/if}} +
+ + {{!-- Uses (if not passive) --}} + {{#unless system.passive}} +
+ {{localize "VAGABOND.Uses"}} +
+
+ + +
+
+ + +
+
+ + +
+
+
+ {{/unless}} + + {{!-- Luck Integration --}} +
+
+ + +
+
+ + +
+
+ + {{!-- Ritual Components --}} + {{#if system.isRitual}} +
+ + +
+ {{/if}} + + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+
diff --git a/templates/item/types/spell.hbs b/templates/item/types/spell.hbs new file mode 100644 index 0000000..d92c5af --- /dev/null +++ b/templates/item/types/spell.hbs @@ -0,0 +1,134 @@ +{{!-- Spell Item Sheet Body --}} +
+ {{!-- Damage Row --}} +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {{!-- Delivery Types --}} +
+ {{localize "VAGABOND.DeliveryTypes"}} +
+ + + + + + + + + +
+
+ + {{!-- Duration Types --}} +
+ {{localize "VAGABOND.DurationTypes"}} +
+ + + +
+
+ + {{!-- Focus Tracking --}} +
+
+ + +
+
+ + {{!-- Effect --}} +
+ +
+ {{editor system.effect target="system.effect" button=true editable=editable engine="prosemirror"}} +
+
+ + {{!-- Crit Effect --}} +
+ +
+ {{editor system.critEffect target="system.critEffect" button=true editable=editable engine="prosemirror"}} +
+
+ + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+
diff --git a/templates/item/types/weapon.hbs b/templates/item/types/weapon.hbs new file mode 100644 index 0000000..620ec29 --- /dev/null +++ b/templates/item/types/weapon.hbs @@ -0,0 +1,235 @@ +{{!-- Weapon Item Sheet Body --}} +
+ {{!-- Core Stats Row --}} +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {{!-- Secondary Stats Row --}} +
+ {{#if (eq system.grip "versatile")}} +
+ + +
+ {{/if}} + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {{!-- Range (for ranged/thrown) --}} +
+
+ +
+ + {{system.range.units}} +
+
+ +
+ + +
+ +
+ + + c +
+ +
+ + +
+
+ + {{!-- Properties --}} +
+ {{localize "VAGABOND.WeaponProperties"}} +
+ + + + + + + + +
+
+ + {{!-- Material --}} +
+
+ + +
+ +
+ + +
+ + {{#if system.equipped}} +
+ + +
+ {{/if}} +
+ + {{!-- Relic Section (collapsible) --}} +
+ + + + + {{#if system.relic.isRelic}} +
+
+
+ + +
+
+ + +
+ {{#if system.relic.requiresAttunement}} +
+ + +
+ {{/if}} +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+ {{#if (gt system.relic.usesPerDay 0)}} +
+ + +
+ {{/if}} +
+ +
+ + +
+
+ {{/if}} +
+ + {{!-- Description --}} +
+ +
+ {{editor system.description target="system.description" button=true editable=editable engine="prosemirror"}} +
+
+