diff --git a/.gitignore b/.gitignore index 4a0f346..6fcca06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,30 @@ +# Documentation source (game rules, design docs) +_docs/ + +# Dependencies node_modules/ + +# Build output css/ packs/ +dist/ + +# OS +.DS_Store +Thumbs.db + +# Editor +.vscode/ +.idea/ +*.swp +*.swo + +# Logs +*.log +npm-debug.log* + +# Environment +.env +.env.local + +.github/ diff --git a/lang/en.json b/lang/en.json index 37ffea8..6bf4459 100644 --- a/lang/en.json +++ b/lang/en.json @@ -30,15 +30,19 @@ "AWEMMY.Condition.Vulnerable": "Vulnerable", "AWEMMY.Condition.Panel": "Conditions", "AWEMMY.Roll.ConditionBonus": "Condition", + "AWEMMY.Item.Description": "Description", + "AWEMMY.Error.TraitPasteFailed": "Failed to update traits — please try again.", "AWEMMY.Kit.Use": "Use Kit", - "AWEMMY.Kit.Used": "{name} used (charges: {value}/{max})", - "AWEMMY.Kit.Depleted": "{name} has no charges remaining!", + "AWEMMY.Kit.Used": "{name} used", "AWEMMY.Ability.Cost.Free": "Free", + "AWEMMY.Ability.Cost.Variable": "Variable (Δ)", "AWEMMY.Ability.TypeLabel": "Type", "AWEMMY.Ability.Type.Field": "Field", "AWEMMY.Ability.Type.Archetype": "Archetype", "AWEMMY.Ability.Type.General": "General", "AWEMMY.Ability.Type.Beginner": "Beginner", + "AWEMMY.Ability.Type.Advanced": "Advanced", + "AWEMMY.Ability.Type.Pinnacle": "Pinnacle", "AWEMMY.Archetype.PrerequisiteLevel": "Prerequisite Level", "AWEMMY.Item.Ability": "Ability", "AWEMMY.Item.Field": "Field", @@ -105,8 +109,12 @@ "AWEMMY.Ability.Frequency": "Frequency", "AWEMMY.Ability.Requirements": "Requirements", "AWEMMY.Ability.Trigger": "Trigger", + "AWEMMY.Ability.Range": "Range", + "AWEMMY.Ability.Targets": "Targets", + "AWEMMY.Ability.Duration": "Duration", "AWEMMY.Ability.Traits": "Traits", "AWEMMY.Ability.AddTrait": "Add trait...", + "AWEMMY.Ability.IsDaily": "Daily (1/day)", "AWEMMY.Ability.FlowPointCost": "Flow Point Cost", "AWEMMY.Ability.Use": "Use Ability", "AWEMMY.Ability.AlreadyUsed": "{name} has already been used today!", @@ -128,9 +136,8 @@ "AWEMMY.Field.Specializations": "Specializations", "AWEMMY.Field.AddSpecialization": "Add specialization...", "AWEMMY.Field.KnowledgeBonus": "Knowledge Bonus", - "AWEMMY.Background.Bonus": "Background Bonus", + "AWEMMY.Background.AttributeBoosts": "Attribute Boosts", "AWEMMY.Kit.Field": "Field", - "AWEMMY.Kit.Charges": "Charges", "AWEMMY.Equipment.Quantity": "Quantity", "AWEMMY.Equipment.Weight": "Weight", "AWEMMY.Sheet.EditItem": "Edit", @@ -143,7 +150,6 @@ "AWEMMY.Rest.LongRestTitle": "{name} rests for 8 hours", "AWEMMY.Rest.HPRestored": "Recovered {amount} HP (now {max}/{max})", "AWEMMY.Rest.AbilitiesReset": "{count} daily ability(ies) reset", - "AWEMMY.Rest.KitsReplenished": "{count} kit(s) replenished", "AWEMMY.Rest.AlreadyRested": "Already at full health — no recovery needed.", "AWEMMY.TAH.Stats": "Stats", diff --git a/module/applications/hud/action-handler.js b/module/applications/hud/action-handler.js index 49f8572..e4df7bd 100644 --- a/module/applications/hud/action-handler.js +++ b/module/applications/hud/action-handler.js @@ -167,12 +167,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { const kits = this.actor.itemTypes?.kit ?? [] const actions = [] for (const kit of kits) { - const charges = kit.system.charges - const chargesText = `${charges.value}/${charges.max}` actions.push({ name: kit.name, id: kit.id, - info1: { text: chargesText }, encodedValue: ['kit', kit.id].join(this.delimiter) }) } diff --git a/module/applications/sheets/ability-sheet.mjs b/module/applications/sheets/ability-sheet.mjs index bfba181..56ffa64 100644 --- a/module/applications/sheets/ability-sheet.mjs +++ b/module/applications/sheets/ability-sheet.mjs @@ -4,7 +4,7 @@ export default class AwEAbilitySheet extends AwEItemSheet { /** @override */ static DEFAULT_OPTIONS = { classes: ["ability"], - position: { width: 620 }, + position: { width: 620, height: 560 }, window: { contentClasses: ["ability-content"] } } diff --git a/module/applications/sheets/base-item-sheet.mjs b/module/applications/sheets/base-item-sheet.mjs index e6e44ec..6db37bd 100644 --- a/module/applications/sheets/base-item-sheet.mjs +++ b/module/applications/sheets/base-item-sheet.mjs @@ -21,7 +21,7 @@ export default class AwEItemSheet extends HandlebarsApplicationMixin(foundry.app classes: ["awemmy", "item"], position: { width: 600, - height: "auto" + height: 480 }, form: { submitOnChange: true @@ -91,6 +91,22 @@ export default class AwEItemSheet extends HandlebarsApplicationMixin(foundry.app const handler = this.options.actions?.[actionName] if (handler) handler.call(this, event, input) }) + // Auto-split comma-separated values on paste + input.addEventListener("paste", async event => { + const pasted = (event.clipboardData ?? window.clipboardData).getData("text") + const parts = pasted.split(",").map(s => s.trim().toLowerCase()).filter(Boolean) + if (parts.length < 2) return // single value: let default paste handle it + event.preventDefault() + const fieldName = input.dataset.field ?? "system.traits" + const current = foundry.utils.getProperty(this.document, fieldName) ?? [] + const merged = [...new Set([...current, ...parts])] + try { + await this.document.update({ [fieldName]: merged }) + } catch (err) { + ui.notifications.error(game.i18n.localize("AWEMMY.Error.TraitPasteFailed")) + console.error("AwE | trait paste update failed:", err) + } + }) }) } diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs index 79ed0a6..be2a5b8 100644 --- a/module/applications/sheets/character-sheet.mjs +++ b/module/applications/sheets/character-sheet.mjs @@ -293,12 +293,8 @@ export default class AwECharacterSheet extends AwEActorSheet { } static async #onDailyReset(event, target) { - const actor = this.document - const dailyAbilities = actor.itemTypes.ability.filter(i => i.system.usedToday) - if (!dailyAbilities.length) return - const updates = dailyAbilities.map(i => ({ _id: i.id, "system.usedToday": false })) - await actor.updateEmbeddedDocuments("Item", updates) - ui.notifications.info(game.i18n.localize("AWEMMY.Ability.DailyResetDone")) + const count = await this.document.resetDailyAbilities() + if (count > 0) ui.notifications.info(game.i18n.localize("AWEMMY.Ability.DailyResetDone")) } static async #onLongRest(event, target) { diff --git a/module/config/system.mjs b/module/config/system.mjs index 4db246c..0dc47ab 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -3,8 +3,8 @@ export const DEV_MODE = false export const ATTRIBUTES = { agility: { id: "agility", abbrev: "AGI", label: "AWEMMY.Attribute.Agility" }, - fitness: { id: "fitness", abbrev: "FIT", label: "AWEMMY.Attribute.Fitness" }, awareness: { id: "awareness", abbrev: "AWA", label: "AWEMMY.Attribute.Awareness" }, + fitness: { id: "fitness", abbrev: "FIT", label: "AWEMMY.Attribute.Fitness" }, influence: { id: "influence", abbrev: "INF", label: "AWEMMY.Attribute.Influence" } } @@ -26,6 +26,7 @@ export const ABILITY_COST = { "three": { id: "three", label: "ΔΔΔ" }, "reaction": { id: "reaction", label: "↩" }, "free": { id: "free", label: "AWEMMY.Ability.Cost.Free" }, + "variable": { id: "variable", label: "AWEMMY.Ability.Cost.Variable" }, "none": { id: "none", label: "—" } } @@ -33,7 +34,9 @@ export const ABILITY_TYPE = { "field": { id: "field", label: "AWEMMY.Ability.Type.Field" }, "archetype": { id: "archetype", label: "AWEMMY.Ability.Type.Archetype" }, "general": { id: "general", label: "AWEMMY.Ability.Type.General" }, - "beginner": { id: "beginner", label: "AWEMMY.Ability.Type.Beginner" } + "beginner": { id: "beginner", label: "AWEMMY.Ability.Type.Beginner" }, + "advanced": { id: "advanced", label: "AWEMMY.Ability.Type.Advanced" }, + "pinnacle": { id: "pinnacle", label: "AWEMMY.Ability.Type.Pinnacle" } } export const OUTCOME_LABELS = { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index a643b15..8b1064a 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -146,21 +146,15 @@ export default class AwEActor extends Actor { } /** - * Use a kit item: decrement charges and post a chat message. + * Use a kit item: post a chat message. * @param {string} kitId - The kit item ID. */ async useKit(kitId) { const item = this.items.get(kitId) if (!item) return - const charges = item.system.charges - if (charges.value <= 0) { - ui.notifications.warn(game.i18n.format("AWEMMY.Kit.Depleted", { name: item.name })) - return - } - await item.update({ "system.charges.value": charges.value - 1 }) await ChatMessage.create({ speaker: ChatMessage.getSpeaker({ actor: this }), - content: `
${game.i18n.format("AWEMMY.Kit.Used", { name: item.name, value: charges.value - 1, max: charges.max })}
` + content: `${game.i18n.format("AWEMMY.Kit.Used", { name: item.name })}
` }) } @@ -173,7 +167,8 @@ export default class AwEActor extends Actor { if (!item) return const sys = item.system - if (sys.usedToday) { + const isDaily = sys.isDaily + if (isDaily && sys.usedToday) { ui.notifications.warn(game.i18n.format("AWEMMY.Ability.AlreadyUsed", { name: item.name })) return } @@ -187,7 +182,6 @@ export default class AwEActor extends Actor { await this.update({ "system.flowPoints.value": fp - sys.flowPointCost }) } - const isDaily = sys.frequency?.toLowerCase().includes("day") if (isDaily) await item.update({ "system.usedToday": true }) const abilityTypeLabel = game.i18n.localize(SYSTEM.ABILITY_TYPE[sys.abilityType]?.label ?? sys.abilityType) @@ -212,7 +206,18 @@ export default class AwEActor extends Actor { } /** - * Perform a long rest: restore HP, reset daily abilities, refill kits, post chat card. + * Reset all once-per-day abilities (usedToday → false). + * @returns {Promise