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/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 new file mode 100644 index 00000000..136c475d --- /dev/null +++ b/src/module/dialog/battleDialog.mjs @@ -0,0 +1,247 @@ +import {ActionManager} from "../sheets/actions/action-manager.mjs"; +import {Talent} from "../data/talent.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 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() + } + + _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..39e620ef --- /dev/null +++ b/src/templates/dialog/battle-dialog.hbs @@ -0,0 +1,349 @@ +
+ +
+ +
+ +
+
+ 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