From b50e4d3090eb4f1ce2ce14c0e8187c1bc49bdf8b Mon Sep 17 00:00:00 2001 From: macniel Date: Wed, 29 Oct 2025 13:21:48 +0100 Subject: [PATCH 1/9] implements ui for battle dice --- src/main.mjs | 4 +- src/module/dialog/battleDialog.mjs | 171 +++++++++ src/style/molecules/_fieldset.scss | 14 + src/style/organisms/_battle-dialog.scss | 195 ++++++++++ .../organisms/_combat-action-dialog.scss | 8 - src/style/organisms/_xml-import-dialog.scss | 8 - src/style/styles.scss | 4 +- src/templates/dialog/battle-dialog.hbs | 348 ++++++++++++++++++ 8 files changed, 734 insertions(+), 18 deletions(-) create mode 100644 src/module/dialog/battleDialog.mjs create mode 100644 src/style/molecules/_fieldset.scss create mode 100644 src/style/organisms/_battle-dialog.scss create mode 100644 src/templates/dialog/battle-dialog.hbs diff --git a/src/main.mjs b/src/main.mjs index 64bb6f40..2f8fc383 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -31,6 +31,7 @@ import {XmlImportDialog} from "./module/dialog/xmlImportDialog.mjs"; import {MerchantDataModel} from "./module/data/merchant.mjs"; import {MerchantSheet} from "./module/sheets/merchantSheet.mjs"; import {RestingDialog} from "./module/dialog/restingDialog.mjs"; +import {BattleDialog} from "./module/dialog/battleDialog.mjs"; async function preloadHandlebarsTemplates() { return foundry.applications.handlebars.loadTemplates([ @@ -58,7 +59,8 @@ Hooks.once("init", () => { Zonenwunde, Trefferzone, Wunde, - RestingDialog + RestingDialog, + BattleDialog } // Configure custom Document implementations. diff --git a/src/module/dialog/battleDialog.mjs b/src/module/dialog/battleDialog.mjs new file mode 100644 index 00000000..0e68c72a --- /dev/null +++ b/src/module/dialog/battleDialog.mjs @@ -0,0 +1,171 @@ +import {ActionManager} from "../sheets/actions/action-manager.mjs"; + +const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api + + +/** + * @typedef TokenDistance + * @property {Number} x + * @property {Number} y + * @property {Number} d + * @property {Token} token + */ + +export class BattleDialog extends HandlebarsApplicationMixin(ApplicationV2) { + + static DEFAULT_OPTIONS = { + classes: ['dsa41', 'dialog', 'battle'], + tag: "form", + position: { + width: 640, + height: 518 + }, + window: { + resizable: false, + }, + form: { + submitOnChange: true, + closeOnSubmit: false, + handler: BattleDialog.#onSubmitForm + }, + actions: { + selectOffenseActor: BattleDialog.#setOffenseActor, + selectDefenseActor: BattleDialog.#setDefenseActor, + doBattle: BattleDialog.#doBattle, + + } + } + + static PARTS = { + form: { + template: 'systems/DSA_4-1/templates/dialog/battle-dialog.hbs', + } + } + + /** + * @type {Actor} + * @private + */ + _offenseActor = null + _defenseActor = null + + constructor() { + super() + } + + + static async #onSubmitForm(event, form, formData) { + event.preventDefault() + + this._offenseTalent = formData.object['offense.talent'] + this._defenseTalent = formData.object['defense.talent'] + } + + static #setOffenseActor(event, target) { + const {id} = target.dataset + + this._offenseActor = game.actors.get(id) + this.render({parts: ["form"]}) + } + + + static #setDefenseActor(event, target) { + const {id} = target.dataset + + this._defenseActor = game.actors.get(id) + this.render({parts: ["form"]}) + } + + static #doBattle(event, target) { + + // TODO get test values from talents either from the object or from the fields + + if (formData.object['saveOffenseData']) { + + } + if (formData.object['saveDefenseData']) { + + } + + // TODO perform Dice Rolls but in secret mode so its up to the GM if they want to display the result or not + + this.close() + } + + _configureRenderOptions(options) { + super._configureRenderOptions(options) + if (options.window) { + options.window.title = "Vergleichende Proben" + } + return options + } + + + async _prepareContext(options) { + const context = await super._prepareContext(options) + context.actors = game.actors.filter(actor => actor.type === "character" || actor.type === "creature") + + context.offenseTalent = this._offenseTalent ?? '' + context.offenseTalents = {} + + if (this._offenseActor) { + context.offenseActor = this._offenseActor._id + + if (this._offenseActor.system.attribute) { + context.offenseAttributes = {} + Object.entries(this._offenseActor.system.attribute)?.forEach(([key, eigenschaft]) => { + context.offenseAttributes[key] = eigenschaft?.aktuell ?? 0 + }) + } else { + context.offenseAttributes = false + } + + if (this._offenseActor.itemTypes["Skill"]?.length > 0) { + this._offenseActor.itemTypes["Skill"]?.forEach((skill) => { + + if (skill.system.probe.length === 3) { + context.offenseTalents[`${skill.name}: ${skill.system.taw} (${skill.system.probe[0]}/${skill.system.probe[1]}/${skill.system.probe[2]})`] = skill.id + } + }) + } else { + context.offenseTalents = false + } + } + + context.defenseTalent = this._defenseTalent ?? '' + context.defenseTalents = {} + + if (this._defenseActor) { + context.defenseActor = this._defenseActor._id + + if (this._defenseActor.system.attribute) { + + context.defenseAttributes = {} + Object.entries(this._defenseActor.system.attribute)?.forEach(([key, eigenschaft]) => { + context.defenseAttributes[key] = eigenschaft?.aktuell ?? 0 + }) + + } else { + context.defenseAttributes = false + } + if (this._defenseActor.itemTypes["Skill"]?.length > 0) { + this._defenseActor.itemTypes["Skill"]?.forEach((skill) => { + + if (skill.system.probe.length === 3) { + context.defenseTalents[`${skill.name}: ${skill.system.taw} (${skill.system.probe[0]}/${skill.system.probe[1]}/${skill.system.probe[2]})`] = skill.id + } + }) + } else { + context.defenseTalents = false + } + } + + return context + } + + _onRender(context, options) { + + } + + +} \ No newline at end of file diff --git a/src/style/molecules/_fieldset.scss b/src/style/molecules/_fieldset.scss new file mode 100644 index 00000000..682178b9 --- /dev/null +++ b/src/style/molecules/_fieldset.scss @@ -0,0 +1,14 @@ +.dsa41 { + + fieldset { + border-left: 0; + border-bottom: 0; + border-right: 0; + + legend { + padding: 0 16px; + text-align: center; + } + } + +} \ No newline at end of file diff --git a/src/style/organisms/_battle-dialog.scss b/src/style/organisms/_battle-dialog.scss new file mode 100644 index 00000000..4b6398f4 --- /dev/null +++ b/src/style/organisms/_battle-dialog.scss @@ -0,0 +1,195 @@ +.dsa41.dialog.battle { + + section[data-application-part="form"] { + + display: grid; + height: 100%; + + grid-template-columns: 1fr 1fr; + grid-template-rows: 32px 1fr 32px; + gap: 8px; + grid-template-areas: "presets presets" "offense defense" "summary summary"; + + .presets { + grid-area: presets; + + label { + display: flex; + flex-direction: row; + height: 32px; + justify-content: center; + gap: 0 8px; + + span { + height: 32px; + line-height: 32px; + vertical-align: middle; + flex: 0; + } + + select { + flex: 0; + width: 180px; + } + + button { + height: 32px; + flex: 0; + } + + } + } + + .offense-character { + grid-area: offense; + } + + .defense-character { + grid-area: defense; + } + + .summary { + grid-area: summary; + } + + .scroll-y { + overflow: hidden; + overflow-y: auto; + height: calc(3 * (32px + 8px)) + } + + .actor { + height: 32px; + margin-bottom: 8px; + display: grid; + grid-template-columns: 32px 1fr; + + img { + width: 32px; + height: 32px; + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.5); + border: 1px inset; + } + + span { + height: 32px; + line-height: 32px; + vertical-align: middle; + } + + &.selected { + background-color: rgba(255, 140, 0, 0.2); + border: 1px solid orange; + border-radius: 4px; + } + + } + + span.dummylabel { + height: 16px; + line-height: 16px; + display: block; + } + + .attributes { + display: grid; + grid-template-columns: repeat(8, 1fr); + height: 48px; + + .attribut { + label { + span { + display: block; + height: 16px; + line-height: 16px; + vertical-align: middle; + width: 100%; + text-align: center; + } + + input { + height: 32px; + width: 100%; + } + + output { + height: 32px; + width: 100%; + line-height: 32px; + vertical-align: middle; + display: block; + padding: 0 8px; + border: 1px inset; + border-radius: 4px; + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); + } + } + } + + } + + .talent { + display: grid; + grid-template-columns: 1fr 32px 32px 32px 32px; + height: 48px; + + label { + + span { + display: block; + height: 16px; + line-height: 16px; + vertical-align: middle; + text-align: center; + width: 100%; + } + + input { + height: 32px; + width: 100%; + + &.attrib { + padding: 0; + text-align: center; + } + } + + } + } + + .summary { + + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-areas: "offense buttons defense"; + + button { + grid-area: buttons; + } + + .offenseActorSave { + grid-area: offense; + + input { + position: relative; + top: 2px; + } + } + + .defenseActorSave { + grid-area: defense; + justify-self: end; + + input { + position: relative; + top: 2px; + } + } + + } + + } + + +} \ No newline at end of file diff --git a/src/style/organisms/_combat-action-dialog.scss b/src/style/organisms/_combat-action-dialog.scss index 0773beaa..274d1032 100644 --- a/src/style/organisms/_combat-action-dialog.scss +++ b/src/style/organisms/_combat-action-dialog.scss @@ -23,17 +23,9 @@ flex: 1; - border-bottom: 0; - border-left: 0; - border-right: 0; padding: 0; margin: 0; - legend { - text-align: center; - padding: 0 16px; - } - &.modding { flex: 0; } diff --git a/src/style/organisms/_xml-import-dialog.scss b/src/style/organisms/_xml-import-dialog.scss index c8f59e36..0254be47 100644 --- a/src/style/organisms/_xml-import-dialog.scss +++ b/src/style/organisms/_xml-import-dialog.scss @@ -30,14 +30,6 @@ fieldset { grid-area: options; - border-left: 0; - border-bottom: 0; - border-right: 0; - - legend { - padding: 0 16px; - text-align: center; - } div { diff --git a/src/style/styles.scss b/src/style/styles.scss index b811eb52..024a438f 100644 --- a/src/style/styles.scss +++ b/src/style/styles.scss @@ -10,6 +10,7 @@ @use "molecules/sheet-header"; @use "molecules/coins"; @use "molecules/weights"; +@use "molecules/fieldset"; @use "molecules/tabs"; @use "molecules/paperdoll"; @@ -30,4 +31,5 @@ @use "organisms/xml-import-dialog"; @use "organisms/combat-action-dialog"; @use "organisms/merchant-sheet"; -@use "organisms/resting-dialog"; \ No newline at end of file +@use "organisms/resting-dialog"; +@use "organisms/battle-dialog"; \ No newline at end of file diff --git a/src/templates/dialog/battle-dialog.hbs b/src/templates/dialog/battle-dialog.hbs new file mode 100644 index 00000000..1ddd70ad --- /dev/null +++ b/src/templates/dialog/battle-dialog.hbs @@ -0,0 +1,348 @@ +
+ +
+ +
+ +
+
+ Charakterauswahl +
+ {{#each actors}} +
+ {{this.name}} + {{this.name}} +
+ {{/each}} +
+
+ + {{#if (not offenseAttributes)}} +
+ Eigenschaften + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ {{else}} +
+ Eigenschaften + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ {{/if}} + + {{#if offenseTalents}} +
+ Talent +   + +
+ {{else}} +
+ Talent +
+ + + + + +
+
+ {{/if}} + +
+ Erschwernisse + +
+
+ +
+
+ Charakterauswahl +
+ {{#each actors}} +
+ {{this.name}} + {{this.name}} +
+ {{/each}} +
+
+ + {{#if (not defenseAttributes)}} +
+ Eigenschaften + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ {{else}} +
+ Eigenschaften + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ {{/if}} + + {{#if defenseTalents}} +
+ Talent +   + +
+ {{else}} +
+ Talent +
+ + + + + +
+
+ {{/if}} + +
+ Erschwernisse + +
+
+ +
+ {{#if (or (not offenseTalents) (not offenseAttributes))}} +
+
+ {{/if}} + + {{#if (or (not defenseTalents) (not defenseAttributes))}} +
+
+ {{/if}} + +
+ +
\ No newline at end of file From 196b99b52ea4c3440bf243b5e8df70ddb0761703 Mon Sep 17 00:00:00 2001 From: macniel Date: Wed, 29 Oct 2025 15:14:22 +0100 Subject: [PATCH 2/9] implements roll mechanic for battle dice --- src/module/data/talent.mjs | 101 +++++++++++++++++++++++++ src/module/dialog/battleDialog.mjs | 96 ++++++++++++++++++++--- src/templates/dialog/battle-dialog.hbs | 3 +- 3 files changed, 189 insertions(+), 11 deletions(-) create mode 100644 src/module/data/talent.mjs diff --git a/src/module/data/talent.mjs b/src/module/data/talent.mjs new file mode 100644 index 00000000..04762cce --- /dev/null +++ b/src/module/data/talent.mjs @@ -0,0 +1,101 @@ +export class Talent { + + + /** + * @typedef TalentEigenschaften + * @property Number mu + * @property Number kl + * @property Number in + * @property Number ch + * @property Number ff + * @property Number ge + * @property Number ko + * @property Number kk + */ + + /** + * @typedef TalentData + * @property {String} name + * @property {Number} taw + * @property {TalentEigenschaften} eigenschaften + * @property {"mu","kl","in","ch","ff","ge","ko","kk"} eigenschaft1 + * @property {"mu","kl","in","ch","ff","ge","ko","kk"} eigenschaft2 + * @property {"mu","kl","in","ch","ff","ge","ko","kk"} eigenschaft3 + */ + + /** + * + * @param {TalentData} data + **/ + constructor(data) { + this.data = data + } + + /** + * @param {"publicroll", "gmroll", "privateroll"} rollMode + * @returns {Promise<{tap: any, meisterlich: boolean, patzer: boolean, evaluatedRoll: Roll & {_evaluated: true, _total: number, readonly total: number}}>} + */ + evaluate(rollMode) { + return this.#talentRoll(this.data, rollMode) + } + + + /** + * + * @param {TalentData} data + * @param {"publicroll", "gmroll", "privateroll"} rollMode + * @returns {Promise<{tap: any, meisterlich: boolean, patzer: boolean, evaluatedRoll: Roll & {_evaluated: true, _total: number, readonly total: number}}>} + */ + async #talentRoll(data, rollMode) { + + + let roll1 = new Roll("3d20"); + + let evaluated1 = (await roll1.evaluate()) + + const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, { + taw: data.taw, + werte: [data.eigenschaften[data.eigenschaft1], data.eigenschaften[data.eigenschaft2], data.eigenschaften[data.eigenschaft3]], + }) + + return { + ...dsaDieRollEvaluated, + evaluatedRoll: evaluated1, + } + } + + _evaluateRoll(rolledDice, { + taw, + lowerThreshold = 1, + upperThreshold = 20, + countToMeisterlich = 3, + countToPatzer = 3, + werte = [] + }) { + let tap = taw; + let meisterlichCounter = 0; + let patzerCounter = 0; + let failCounter = 0; + + rolledDice.forEach((rolledDie, index) => { + if (tap < 0 && rolledDie.result > werte[index]) { + tap -= rolledDie.result - werte[index]; + if (tap < 0) { // konnte nicht vollständig ausgeglichen werden + failCounter++; + } + } else if (rolledDie.result > werte[index]) { // taw ist bereits aufgebraucht und wert kann nicht ausgeglichen werden + tap -= rolledDie.result - werte[index]; + failCounter++; + } + if (rolledDie.result <= lowerThreshold) meisterlichCounter++; + if (rolledDie.result > upperThreshold) patzerCounter++; + }) + + return { + tap, + meisterlich: meisterlichCounter === countToMeisterlich, + patzer: patzerCounter === countToPatzer, + } + } + +} \ No newline at end of file diff --git a/src/module/dialog/battleDialog.mjs b/src/module/dialog/battleDialog.mjs index 0e68c72a..136c475d 100644 --- a/src/module/dialog/battleDialog.mjs +++ b/src/module/dialog/battleDialog.mjs @@ -1,4 +1,5 @@ import {ActionManager} from "../sheets/actions/action-manager.mjs"; +import {Talent} from "../data/talent.mjs"; const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api @@ -76,19 +77,94 @@ export class BattleDialog extends HandlebarsApplicationMixin(ApplicationV2) { this.render({parts: ["form"]}) } - static #doBattle(event, target) { - - // TODO get test values from talents either from the object or from the fields - - if (formData.object['saveOffenseData']) { - - } - if (formData.object['saveDefenseData']) { - - } + static async #doBattle(event, target) { // TODO perform Dice Rolls but in secret mode so its up to the GM if they want to display the result or not + let offenseTalent = {} + + if (this._offenseActor && this._offenseActor.items.get(this._offenseTalent)) { + const skill = this._offenseActor.items.get(this._offenseTalent) + offenseTalent.name = skill.name + offenseTalent.taw = skill.system.taw + offenseTalent.probe = skill.system.probe + } else { + offenseTalent.name = this.element.querySelector('input[name="offense.talent.name"]').value + offenseTalent.taw = this.element.querySelector('input[name="offense.talent.taw"]').value + offenseTalent.probe = [ + this.element.querySelector('input[name="offense.talent.probe.0.name"]').value, + this.element.querySelector('input[name="offense.talent.probe.1.name"]').value, + this.element.querySelector('input[name="offense.talent.probe.2.name"]').value + ] + } + + offenseTalent.eigenschaften = {} + + if (this._offenseActor && this._offenseActor.system.attribute) { + offenseTalent.eigenschaften = this._offenseActor.system.attribute + } else { + offenseTalent.eigenschaften = { + mu: this.element.querySelector('input[name="offenseAttributes.mu"]').value, + kl: this.element.querySelector('input[name="offenseAttributes.in"]').value, + in: this.element.querySelector('input[name="offenseAttributes.kl"]').value, + ch: this.element.querySelector('input[name="offenseAttributes.ch"]').value, + ff: this.element.querySelector('input[name="offenseAttributes.ff"]').value, + ge: this.element.querySelector('input[name="offenseAttributes.ge"]').value, + ko: this.element.querySelector('input[name="offenseAttributes.ko"]').value, + kk: this.element.querySelector('input[name="offenseAttributes.kk"]').value, + + } + } + + let defenseTalent = {} + + if (this._defenseActor && this._defenseActor.items.get(this._defenseTalent)) { + const skill = this._defenseActor.items.get(this._defenseTalent) + defenseTalent.name = skill.name + defenseTalent.taw = skill.system.taw + defenseTalent.probe = skill.system.probe + } else { + defenseTalent.name = this.element.querySelector('input[name="defense.talent.name"]').value + defenseTalent.taw = this.element.querySelector('input[name="defense.talent.taw"]').value + defenseTalent.probe = [ + this.element.querySelector('input[name="defense.talent.probe.0.name"]').value, + this.element.querySelector('input[name="defense.talent.probe.1.name"]').value, + this.element.querySelector('input[name="defense.talent.probe.2.name"]').value + ] + } + + + defenseTalent.eigenschaften = {} + + if (this._defenseActor && this._defenseActor.system.attribute) { + defenseTalent.eigenschaften = this._defenseActor.system.attribute + } else { + defenseTalent.eigenschaften = { + mu: this.element.querySelector('input[name="defenseAttributes.mu"]').value, + kl: this.element.querySelector('input[name="defenseAttributes.in"]').value, + in: this.element.querySelector('input[name="defenseAttributes.kl"]').value, + ch: this.element.querySelector('input[name="defenseAttributes.ch"]').value, + ff: this.element.querySelector('input[name="defenseAttributes.ff"]').value, + ge: this.element.querySelector('input[name="defenseAttributes.ge"]').value, + ko: this.element.querySelector('input[name="defenseAttributes.ko"]').value, + kk: this.element.querySelector('input[name="defenseAttributes.kk"]').value, + + } + } + + + const offense = await (new Talent(offenseTalent)).evaluate("gmroll") + const defense = await (new Talent(defenseTalent)).evaluate("gmroll") + + offense.evaluatedRoll.toMessage({ + speaker: ChatMessage.getSpeaker({actor: this._offenseActor}), + flavor: `Talent: ${offenseTalent.name}
TaP: ${offense.tap}
${offense.meisterlich ? "Meisterlich" : ""}${offense.patzer ? "Petzer" : ""}`, + }) + defense.evaluatedRoll.toMessage({ + speaker: ChatMessage.getSpeaker({actor: this._defenseActor}), + flavor: `Talent: ${defenseTalent.name}
TaP: ${defense.tap}
${defense.meisterlich ? "Meisterlich" : ""}${defense.patzer ? "Petzer" : ""}`, + }) + this.close() } diff --git a/src/templates/dialog/battle-dialog.hbs b/src/templates/dialog/battle-dialog.hbs index 1ddd70ad..39e620ef 100644 --- a/src/templates/dialog/battle-dialog.hbs +++ b/src/templates/dialog/battle-dialog.hbs @@ -2,6 +2,7 @@
+ +
+ +
+
+ + {{/if}} + +
+ {{#each system.herkunft}} +
+ {{this.name}} + Grad {{grad}} + {{#if ../editable}} + + {{/if}} +
+ {{/each}} +
+ + + + diff --git a/src/templates/item/liturgy/tab-meta.hbs b/src/templates/item/liturgy/tab-meta.hbs new file mode 100644 index 00000000..d2ed49df --- /dev/null +++ b/src/templates/item/liturgy/tab-meta.hbs @@ -0,0 +1,75 @@ +
+ +
+ + + + +
+ +
+ Grade der Liturgie +
+ {{#if system.auswirkung.I}} +
+ I +
{{system.auswirkung.I}}
+
+ {{/if}} + {{#if system.auswirkung.II}} +
+ II +
{{system.auswirkung.II}}
+
+ {{/if}} + {{#if system.auswirkung.III}} +
+ III +
{{system.auswirkung.III}}
+
+ {{/if}} + {{#if system.auswirkung.IV}} +
+ IV +
{{system.auswirkung.IV}}
+
+ {{/if}} + {{#if system.auswirkung.V}} +
+ V +
{{system.auswirkung.V}}
+
+ {{/if}} + {{#if system.auswirkung.VI}} +
+ VI +
{{system.auswirkung.VI}}
+
+ {{/if}} + {{#if system.auswirkung.VII}} +
+ VII +
{{system.auswirkung.VII}}
+
+ {{/if}} + {{#if system.auswirkung.VIII}} +
+ VIII +
{{system.auswirkung.VIII}}
+
+ {{/if}} +
+
+ +
+ From 9ce299a202578e0706dedb6eed99032da6cd8826 Mon Sep 17 00:00:00 2001 From: macniel Date: Thu, 30 Oct 2025 10:20:02 +0100 Subject: [PATCH 5/9] enables rolling of character flaws --- src/module/sheets/character/advsf.mjs | 34 +++++++++++++------ src/module/sheets/characterSheet.mjs | 26 ++++++++++++-- .../organisms/character-tabs/_advsf.scss | 27 +++++++++++++-- src/templates/actor/character/tab-advsf.hbs | 10 +++++- src/templates/ui/partial-advantage-button.hbs | 11 ++++++ 5 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/module/sheets/character/advsf.mjs b/src/module/sheets/character/advsf.mjs index 878225e9..f0074d84 100644 --- a/src/module/sheets/character/advsf.mjs +++ b/src/module/sheets/character/advsf.mjs @@ -9,18 +9,32 @@ export default { context.name = context.derived.name ?? actorData.name context.effects = actorData.effects ?? [] context.advantages = [] + context.flaws = [] actorData.itemTypes.Advantage.forEach((item) => { - context.advantages.push({ - id: item._id, - name: item.name, - value: item.system.value, - options: item.system.auswahl, - description: item.system.description, - isAdvantage: !item.system.nachteil, - isDisadvantage: item.system.nachteil, - isBadAttribute: item.system.schlechteEigenschaft - }) + if (!item.system.schlechteEigenschaft) { + context.advantages.push({ + id: item._id, + name: item.name, + value: item.system.value, + options: item.system.auswahl, + description: item.system.description, + isAdvantage: !item.system.nachteil, + isDisadvantage: item.system.nachteil, + isBadAttribute: item.system.schlechteEigenschaft + }) + } else { + context.flaws.push({ + id: item._id, + name: item.name, + value: item.system.value, + options: item.system.auswahl, + description: item.system.description, + isAdvantage: !item.system.nachteil, + isDisadvantage: item.system.nachteil, + isBadAttribute: item.system.schlechteEigenschaft + }) + } } ) diff --git a/src/module/sheets/characterSheet.mjs b/src/module/sheets/characterSheet.mjs index da8df1df..e8159b67 100644 --- a/src/module/sheets/characterSheet.mjs +++ b/src/module/sheets/characterSheet.mjs @@ -35,6 +35,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { actions: { rollCombatSkill: CharacterSheet.#rollCombatSkill, rollSkill: CharacterSheet.#rollSkill, + rollFlaw: CharacterSheet.#rollFlaw, roll: CharacterSheet.#dieRoll, editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage, openEmbeddedDocument: CharacterSheet.#openEmbeddedDocument, @@ -45,6 +46,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { cancelCooldown: CharacterSheet.#cancelCooldown, activateCooldown: CharacterSheet.#activateCooldown, rest: CharacterSheet.#startResting, + } } @@ -115,10 +117,10 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { } } - static #dieRoll(event) { + static #dieRoll(event, target) { event.preventDefault() - const dataset = event.currentTarget.dataset - if (dataset.roll) { + const {roll} = target.dataset + if (roll) { let label = dataset.label ? `[Attribut] ${dataset.label}` : '' let roll = new Roll(dataset.roll, this.actor.getRollData()) roll.toMessage({ @@ -130,6 +132,24 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { } } + static async #rollFlaw(event, target) { + event.preventDefault() + const {itemId} = target.dataset + if (itemId) { + const flaw = this.document.items.get(itemId) + if (flaw) { + const target = flaw.system.value + let roll = await new Roll(`1d20`).evaluate() + let diff = target - roll.terms[0].results[0].result + roll.toMessage({ + speaker: ChatMessage.getSpeaker({actor: this.actor}), + flavor: `Schlechte Eigenschaft: ${flaw.name}
Ergebnis: ${Math.abs(diff)}${diff > 0 ? " übrig" : " daneben"}`, + rollMode: game.settings.get('core', 'rollMode'), + }); + } + } + } + static async #progressCooldown(event, target) { const {cooldownId} = target.dataset const cooldowns = this.document.system.cooldowns diff --git a/src/style/organisms/character-tabs/_advsf.scss b/src/style/organisms/character-tabs/_advsf.scss index f03a8898..ecf0b2e8 100644 --- a/src/style/organisms/character-tabs/_advsf.scss +++ b/src/style/organisms/character-tabs/_advsf.scss @@ -6,7 +6,7 @@ gap: 8px; } - .advantages, .special-abilities { + .advantages, .special-abilities, .flaws { margin-bottom: 16px; ul { @@ -19,7 +19,7 @@ display: inline-block; } - .advantage, .special-ability { + .advantage, .special-ability, .flaw { position: relative; border: 1px solid gold; box-shadow: 2px 2px 4px #000; @@ -63,6 +63,29 @@ margin-left: 8px; } + &.flaw { + position: relative; + margin-left: 24px; + + .die { + + path { + fill: #6a24d8ff; + } + + position: absolute; + left: -24px; + + height: 24px; + width: 24px; + } + + &::after { + background: rgba(106, 36, 216, 0.5); + } + + } + &.disadvantage { font-style: italic; diff --git a/src/templates/actor/character/tab-advsf.hbs b/src/templates/actor/character/tab-advsf.hbs index 346a6ba4..63858a2d 100644 --- a/src/templates/actor/character/tab-advsf.hbs +++ b/src/templates/actor/character/tab-advsf.hbs @@ -2,7 +2,15 @@ data-tab="{{tabs.advsf.id}}" data-group="{{tabs.advsf.group}}">
-
+
+

Schlechte Eigenschaften

+
    + {{#each this.flaws}} +
  • {{> "systems/DSA_4-1/templates/ui/partial-advantage-button.hbs" this}}
  • + {{/each}} +
+
+

Vor- und Nachteile

- - {{this.wert}} - - - {{this.name}} - + {{this.wert}} + {{this.name}}
From eb6f13f78e16f040a13320c97a0ee0b13053ad4d Mon Sep 17 00:00:00 2001 From: macniel Date: Thu, 30 Oct 2025 11:03:58 +0100 Subject: [PATCH 8/9] restores wounds logic --- src/module/documents/character.mjs | 18 ++++--- src/module/sheets/character/effects.mjs | 52 +++++++++---------- src/module/sheets/characterSheet.mjs | 19 ++++++- src/packs/_source/wunden/wunde.json | 2 +- src/templates/actor/character/tab-combat.hbs | 4 ++ src/templates/actor/character/tab-effects.hbs | 2 +- 6 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/module/documents/character.mjs b/src/module/documents/character.mjs index 2a9a5cd8..d6a39418 100644 --- a/src/module/documents/character.mjs +++ b/src/module/documents/character.mjs @@ -125,14 +125,16 @@ export class Character extends Actor { // half KO is the maximum a character can sustain wounds before collapsing systemData.wunden.max = ko / 2; if (game.settings.get("DSA_4-1", "optional_trefferzonen")) { - systemData.wunden.kopf = 0; - systemData.wunden.brust = 0; - systemData.wunden.bauch = 0; - systemData.wunden.ruecken = 0; - systemData.wunden.armlinks = 0; - systemData.wunden.armrechts = 0; - systemData.wunden.beinlinks = 0; - systemData.wunden.beinrechts = 0; + systemData.wunden.kopf = 0 + systemData.wunden.brust = 0 + systemData.wunden.bauch = 0 + systemData.wunden.ruecken = 0 + systemData.wunden.armlinks = 0 + systemData.wunden.armrechts = 0 + systemData.wunden.beinlinks = 0 + systemData.wunden.beinrechts = 0 + } else { + systemData.wunden.gesamt = 0 } systemData.ws = ko / 2; diff --git a/src/module/sheets/character/effects.mjs b/src/module/sheets/character/effects.mjs index e59e080d..f24fce2a 100644 --- a/src/module/sheets/character/effects.mjs +++ b/src/module/sheets/character/effects.mjs @@ -12,36 +12,34 @@ export default { context.isGM = game.user.isGM context.effects = [] - Object.values(actorData.items).forEach((item, index) => { - if (item.type === "ActiveEffect") { - const effect = item.effects[0]; - const conditions = [] - if (effect) { - effect.changes.forEach(change => { - if (change.key.indexOf("wunden") === -1) { - const key = change.key - .replace(/system\./g, "") - .replace(/\.mod/g, "") - .replace(/attribute./g, "") - .replace(/.links/g, "(Links)") - .replace(/.rechts/g, "(Rechts)") - const value = Number(change.value) > 0 ? "+" + change.value : change.value - conditions.push( - `${key}${value}` - ) - } - }) + for (let i = 0; i < actorData.appliedEffects.length; i++) { + const item = actorData.appliedEffects[i] + const conditions = [] + + item.changes.forEach(change => { + if (change.key.indexOf("wunden") === -1) { + const key = change.key + .replace(/system\./g, "") + .replace(/\.mod/g, "") + .replace(/attribute./g, "") + .replace(/.links/g, "(Links)") + .replace(/.rechts/g, "(Rechts)") + const value = Number(change.value) > 0 ? "+" + change.value : change.value + conditions.push( + `${key}${value}` + ) } + }) - context.effects.push({ - name: item.name, - conditions: conditions.join(" "), - id: item._id, - actor: actorData._id - }); - } - }) + context.effects.push({ + name: item.parent.name, + conditions: conditions.join(" "), + id: item.parent._id, + actor: actorData._id + }); + + } return context }, diff --git a/src/module/sheets/characterSheet.mjs b/src/module/sheets/characterSheet.mjs index e8159b67..18511d06 100644 --- a/src/module/sheets/characterSheet.mjs +++ b/src/module/sheets/characterSheet.mjs @@ -46,6 +46,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { cancelCooldown: CharacterSheet.#cancelCooldown, activateCooldown: CharacterSheet.#activateCooldown, rest: CharacterSheet.#startResting, + removeEffect: CharacterSheet.#removeEffect, } } @@ -235,6 +236,22 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { dialog.render(true) } + static async #removeEffect(event, target) { + const {actorId, effectId} = target.dataset + + if (actorId === this.document._id) { + + const item = this.document.items.get(effectId) + + if (item.type === "ActiveEffect") { + this.document.deleteEmbeddedDocuments("Item", [effectId]) + } + + } + + } + + /** * Handle form submission * @this {AdvantageSheet} @@ -317,7 +334,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { context.effects = actorData.effects ?? [] context.maxWounds = actorData.system.wunden.max ?? 3 - context.wounds = actorData.system.wunden.aktuell ?? 0 + context.wounds = actorData.system.wunden.gesamt ?? 0 context.woundsFilled = [] for (let i = 1; i <= context.maxWounds; i++) { context.woundsFilled[i] = i <= context.wounds diff --git a/src/packs/_source/wunden/wunde.json b/src/packs/_source/wunden/wunde.json index 0f6ff0cb..a2463c62 100644 --- a/src/packs/_source/wunden/wunde.json +++ b/src/packs/_source/wunden/wunde.json @@ -5,7 +5,7 @@ "image": "icons/skills/wounds/bone-broken-knee-beam.webp", "effects": [ { - "key": "system.wunden", + "key": "system.wunden.gesamt", "mode": 2, "value": "1", "priority": 10 diff --git a/src/templates/actor/character/tab-combat.hbs b/src/templates/actor/character/tab-combat.hbs index 652a4e89..67511b0f 100644 --- a/src/templates/actor/character/tab-combat.hbs +++ b/src/templates/actor/character/tab-combat.hbs @@ -34,6 +34,10 @@ {{derived.be}}
+
+ + {{derived.gs.aktuell}} +
diff --git a/src/templates/actor/character/tab-effects.hbs b/src/templates/actor/character/tab-effects.hbs index b131f219..eb492c9c 100644 --- a/src/templates/actor/character/tab-effects.hbs +++ b/src/templates/actor/character/tab-effects.hbs @@ -18,7 +18,7 @@ {{this.conditions}} {{#if ../isGM}} - {{/if}} {{/each}} From 236263146529db979628c0c5691ab28e579d44e9 Mon Sep 17 00:00:00 2001 From: macniel Date: Thu, 30 Oct 2025 11:08:07 +0100 Subject: [PATCH 9/9] fixes catastrophic failure in xmlImport --- src/module/xml-import/xml-import.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module/xml-import/xml-import.mjs b/src/module/xml-import/xml-import.mjs index c0875d70..ee3f3757 100644 --- a/src/module/xml-import/xml-import.mjs +++ b/src/module/xml-import/xml-import.mjs @@ -655,7 +655,7 @@ export class XmlImport { name: kultur.name, type: "Culture", system: { - variant: kultur.variante.name ?? "", + variant: kultur.variante?.name ?? kultur.string ?? "", feminineDemonym: kultur.string, masculineDemonym: kultur.string, description: "Importiert",