From 58d9b10251b9c232bfb6558c6f8f8e25a5477e60 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnien Date: Sat, 29 Nov 2025 11:14:16 +0100 Subject: [PATCH] Fix skills and multiple maneuvers per weapons --- css/fvtt-prism-rpg.css | 45 +++++++++++ lang/en.json | 23 +++++- module/applications/sheets/weapon-sheet.mjs | 38 +++++++++- module/config/skill.mjs | 82 +++++---------------- module/models/weapon.mjs | 18 +++-- styles/weapon.less | 59 ++++++++++++++- templates/skill.hbs | 12 ++- templates/weapon.hbs | 53 ++++++++----- 8 files changed, 233 insertions(+), 97 deletions(-) diff --git a/css/fvtt-prism-rpg.css b/css/fvtt-prism-rpg.css index 1b32ab3..af7dec9 100644 --- a/css/fvtt-prism-rpg.css +++ b/css/fvtt-prism-rpg.css @@ -1550,6 +1550,51 @@ i.prismrpg { .prismrpg .weapon-content label { flex: 10%; } +.prismrpg .weapon-content .weapon-maneuvers legend { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} +.prismrpg .weapon-content .weapon-maneuvers legend button { + padding: 2px 8px; + font-size: 12px; +} +.prismrpg .weapon-content .weapon-maneuvers .maneuver-item { + margin-bottom: 16px; + padding: 12px; + border: 1px solid var(--color-border-light-primary); + border-radius: 4px; + background: rgba(0, 0, 0, 0.05); +} +.prismrpg .weapon-content .weapon-maneuvers .maneuver-item:last-child { + margin-bottom: 0; +} +.prismrpg .weapon-content .weapon-maneuvers .maneuver-header { + display: flex; + gap: 8px; + align-items: center; + margin-bottom: 8px; +} +.prismrpg .weapon-content .weapon-maneuvers .maneuver-header input[type="text"] { + flex: 1; +} +.prismrpg .weapon-content .weapon-maneuvers .maneuver-header button { + padding: 4px 8px; + font-size: 12px; +} +.prismrpg .weapon-content .weapon-maneuvers .maneuver-header button[data-action="delete-maneuver"] { + color: var(--color-text-danger, #c00); +} +.prismrpg .weapon-content .weapon-maneuvers .maneuver-header button[data-action="delete-maneuver"]:hover { + background: var(--color-bg-danger, rgba(200, 0, 0, 0.1)); +} +.prismrpg .weapon-content .weapon-maneuvers .hint { + font-style: italic; + color: var(--color-text-light-secondary); + text-align: center; + padding: 12px; +} .prismrpg .armor-content { font-family: var(--font-primary); font-size: calc(var(--font-size-standard) * 1); diff --git a/lang/en.json b/lang/en.json index d67667a..ae74889 100644 --- a/lang/en.json +++ b/lang/en.json @@ -60,6 +60,24 @@ } }, "Character": { + "str": { + "label": "Strength" + }, + "dex": { + "label": "Dexterity" + }, + "con": { + "label": "Constitution" + }, + "int": { + "label": "Intelligence" + }, + "wis": { + "label": "Wisdom" + }, + "cha": { + "label": "Charisma" + }, "FIELDS": { "moneys": { "tinbit": { @@ -500,7 +518,9 @@ "groupPassive": "Weapon Group Passive", "groupPassiveName": "Group Passive Name", "weaponPassive": "Weapon Passive", - "weaponManeuver": "Weapon Maneuver", + "weaponManeuvers": "Weapon Maneuvers", + "addManeuver": "Add Maneuver", + "deleteManeuver": "Delete Maneuver", "maneuverName": "Maneuver Name", "maneuverDescription": "Maneuver Description", "weaponAugment": "Weapon Augment", @@ -666,6 +686,7 @@ "violet": "Violet" }, "Hint": { + "noManeuvers": "No maneuvers defined. Click the + button to add one.", "isCoreSkill": "Check this if this is your character's chosen Core Skill", "attributeBonus": "Choose which attribute receives the +2 bonus", "advancedChecks": "Only Core Skills allow advanced checks", diff --git a/module/applications/sheets/weapon-sheet.mjs b/module/applications/sheets/weapon-sheet.mjs index 7391c74..9da0905 100644 --- a/module/applications/sheets/weapon-sheet.mjs +++ b/module/applications/sheets/weapon-sheet.mjs @@ -24,8 +24,44 @@ export default class PrismRPGWeaponSheet extends PrismRPGItemSheet { const context = await super._prepareContext() context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }) context.enrichedPassiveDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.passiveDescription, { async: true }) - context.enrichedManeuverDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.maneuverDescription, { async: true }) + + // Enrich descriptions for all maneuvers + context.enrichedManeuvers = await Promise.all( + this.document.system.maneuvers.map(async (maneuver) => ({ + ...maneuver, + enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(maneuver.description, { async: true }) + })) + ) + return context } + /** @override */ + _onRender(context, options) { + super._onRender(context, options) + + // Add event listeners for maneuver management + this.element.querySelectorAll('[data-action="add-maneuver"]').forEach(el => { + el.addEventListener("click", this._onAddManeuver.bind(this)) + }) + + this.element.querySelectorAll('[data-action="delete-maneuver"]').forEach(el => { + el.addEventListener("click", this._onDeleteManeuver.bind(this)) + }) + } + + async _onAddManeuver(event) { + event.preventDefault() + const maneuvers = [...this.document.system.maneuvers] + maneuvers.push({ name: "", description: "" }) + await this.document.update({ "system.maneuvers": maneuvers }) + } + + async _onDeleteManeuver(event) { + event.preventDefault() + const index = parseInt(event.currentTarget.closest("[data-maneuver-index]").dataset.maneuverIndex) + const maneuvers = this.document.system.maneuvers.filter((_, i) => i !== index) + await this.document.update({ "system.maneuvers": maneuvers }) + } + } diff --git a/module/config/skill.mjs b/module/config/skill.mjs index ff9bdae..77c1380 100644 --- a/module/config/skill.mjs +++ b/module/config/skill.mjs @@ -4,98 +4,80 @@ * - +5 bonus to basic skill checks * - Access to advanced skill checks * - Access to a Core Skill Class (based on archetype) - * - +2 to one of 3 associated attributes + * - +2 to one chosen attribute */ export const CORE_SKILLS = Object.freeze({ acrobatics: { id: "acrobatics", - label: "PRISMRPG.Skill.CoreSkill.acrobatics", - attributeChoices: ["dex", "wis", "con"] + label: "PRISMRPG.Skill.CoreSkill.acrobatics" }, animalHandling: { id: "animalHandling", - label: "PRISMRPG.Skill.CoreSkill.animalHandling", - attributeChoices: ["str", "con", "dex"] + label: "PRISMRPG.Skill.CoreSkill.animalHandling" }, arcana: { id: "arcana", - label: "PRISMRPG.Skill.CoreSkill.arcana", - attributeChoices: ["str", "int", "wis"] + label: "PRISMRPG.Skill.CoreSkill.arcana" }, athletics: { id: "athletics", - label: "PRISMRPG.Skill.CoreSkill.athletics", - attributeChoices: ["str", "dex", "con"] + label: "PRISMRPG.Skill.CoreSkill.athletics" }, deception: { id: "deception", - label: "PRISMRPG.Skill.CoreSkill.deception", - attributeChoices: ["int", "wis", "cha"] + label: "PRISMRPG.Skill.CoreSkill.deception" }, history: { id: "history", - label: "PRISMRPG.Skill.CoreSkill.history", - attributeChoices: ["str", "wis", "con"] + label: "PRISMRPG.Skill.CoreSkill.history" }, insight: { id: "insight", - label: "PRISMRPG.Skill.CoreSkill.insight", - attributeChoices: ["int", "cha", "wis"] + label: "PRISMRPG.Skill.CoreSkill.insight" }, intimidate: { id: "intimidate", - label: "PRISMRPG.Skill.CoreSkill.intimidate", - attributeChoices: ["str", "cha", "wis"] + label: "PRISMRPG.Skill.CoreSkill.intimidate" }, investigation: { id: "investigation", - label: "PRISMRPG.Skill.CoreSkill.investigation", - attributeChoices: ["int", "wis", "con"] + label: "PRISMRPG.Skill.CoreSkill.investigation" }, medicine: { id: "medicine", - label: "PRISMRPG.Skill.CoreSkill.medicine", - attributeChoices: ["con", "wis", "int"] + label: "PRISMRPG.Skill.CoreSkill.medicine" }, nature: { id: "nature", - label: "PRISMRPG.Skill.CoreSkill.nature", - attributeChoices: ["str", "wis", "int"] + label: "PRISMRPG.Skill.CoreSkill.nature" }, perception: { id: "perception", - label: "PRISMRPG.Skill.CoreSkill.perception", - attributeChoices: ["dex", "wis", "cha"] + label: "PRISMRPG.Skill.CoreSkill.perception" }, performance: { id: "performance", - label: "PRISMRPG.Skill.CoreSkill.performance", - attributeChoices: ["str", "cha", "wis"] + label: "PRISMRPG.Skill.CoreSkill.performance" }, persuasion: { id: "persuasion", - label: "PRISMRPG.Skill.CoreSkill.persuasion", - attributeChoices: ["cha", "dex", "int"] + label: "PRISMRPG.Skill.CoreSkill.persuasion" }, religion: { id: "religion", - label: "PRISMRPG.Skill.CoreSkill.religion", - attributeChoices: ["str", "wis", "cha"] + label: "PRISMRPG.Skill.CoreSkill.religion" }, sleightOfHand: { id: "sleightOfHand", - label: "PRISMRPG.Skill.CoreSkill.sleightOfHand", - attributeChoices: ["dex", "wis", "int"] + label: "PRISMRPG.Skill.CoreSkill.sleightOfHand" }, stealth: { id: "stealth", - label: "PRISMRPG.Skill.CoreSkill.stealth", - attributeChoices: ["int", "dex", "cha"] + label: "PRISMRPG.Skill.CoreSkill.stealth" }, survival: { id: "survival", - label: "PRISMRPG.Skill.CoreSkill.survival", - attributeChoices: ["int", "con", "cha"] + label: "PRISMRPG.Skill.CoreSkill.survival" } }); @@ -130,29 +112,3 @@ export const CORE_SKILL_BONUS = Object.freeze({ basic: 5, // +5 to basic skill checks attributeBonus: 2 // +2 to chosen attribute }); - -/** - * Legacy skill categories (may be deprecated) - */ -export const CATEGORY = Object.freeze({ - layperson: { - id: "layperson", - label: "PRISMRPG.Skill.Category.layperson", - }, - professional: { - id: "professional", - label: "PRISMRPG.Skill.Category.professional", - }, - weapon: { - id: "weapon", - label: "PRISMRPG.Skill.Category.weapon", - }, - armor: { - id: "armor", - label: "PRISMRPG.Skill.Category.armor", - }, - resist: { - id: "resist", - label: "PRISMRPG.Skill.Category.resist", - } -}) diff --git a/module/models/weapon.mjs b/module/models/weapon.mjs index 5f2ea8c..cb2be58 100644 --- a/module/models/weapon.mjs +++ b/module/models/weapon.mjs @@ -66,14 +66,18 @@ export default class PrismRPGWeapon extends foundry.abstract.TypeDataModel { }) // Maneuver(s) available with this weapon - schema.maneuver = new fields.StringField({ + schema.maneuvers = new fields.ArrayField(new fields.SchemaField({ + name: new fields.StringField({ + required: true, + initial: "" + }), + description: new fields.HTMLField({ + required: true, + initial: "" + }) + }), { required: true, - initial: "" - }) - - schema.maneuverDescription = new fields.HTMLField({ - required: true, - initial: "" + initial: [] }) // Augment effects (for equipment progression) diff --git a/styles/weapon.less b/styles/weapon.less index e4db43f..4a19e4b 100644 --- a/styles/weapon.less +++ b/styles/weapon.less @@ -9,8 +9,65 @@ height: 50px; } } - + label { flex: 10%; } + + .weapon-maneuvers { + legend { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + + button { + padding: 2px 8px; + font-size: 12px; + } + } + + .maneuver-item { + margin-bottom: 16px; + padding: 12px; + border: 1px solid var(--color-border-light-primary); + border-radius: 4px; + background: rgba(0, 0, 0, 0.05); + + &:last-child { + margin-bottom: 0; + } + } + + .maneuver-header { + display: flex; + gap: 8px; + align-items: center; + margin-bottom: 8px; + + input[type="text"] { + flex: 1; + } + + button { + padding: 4px 8px; + font-size: 12px; + + &[data-action="delete-maneuver"] { + color: var(--color-text-danger, #c00); + + &:hover { + background: var(--color-bg-danger, rgba(200, 0, 0, 0.1)); + } + } + } + } + + .hint { + font-style: italic; + color: var(--color-text-light-secondary); + text-align: center; + padding: 12px; + } + } } diff --git a/templates/skill.hbs b/templates/skill.hbs index 969b287..e5f25ca 100644 --- a/templates/skill.hbs +++ b/templates/skill.hbs @@ -17,13 +17,11 @@

{{localize "PRISMRPG.Hint.attributeBonus"}}

diff --git a/templates/weapon.hbs b/templates/weapon.hbs index 1914b7c..bb08392 100644 --- a/templates/weapon.hbs +++ b/templates/weapon.hbs @@ -113,23 +113,42 @@ }} - {{! Prism RPG: Weapon Maneuver }} -
- {{localize "PRISMRPG.Label.weaponManeuver"}} - {{formField - systemFields.maneuver - value=system.maneuver - localize=true - label="PRISMRPG.Label.maneuverName" - }} - - {{formInput - systemFields.maneuverDescription - enriched=enrichedManeuverDescription - value=system.maneuverDescription - name="system.maneuverDescription" - toggled=true - }} + {{! Prism RPG: Weapon Maneuvers }} +
+ + {{localize "PRISMRPG.Label.weaponManeuvers"}} + + + + {{#each enrichedManeuvers}} +
+
+ {{formInput + ../systemFields.maneuvers.element.fields.name + value=this.name + name=(concat "system.maneuvers." @index ".name") + placeholder=(localize "PRISMRPG.Label.maneuverName") + }} + +
+ + {{formInput + ../systemFields.maneuvers.element.fields.description + enriched=this.enrichedDescription + value=this.description + name=(concat "system.maneuvers." @index ".description") + toggled=true + }} +
+ {{/each}} + + {{#unless enrichedManeuvers.length}} +

{{localize "PRISMRPG.Hint.noManeuvers"}}

+ {{/unless}}