adds the ability to sumon the rest and relax dialog

pull/62/head
macniel 2025-10-28 14:10:55 +01:00
parent f5be5250ab
commit 9e0e85efe6
8 changed files with 506 additions and 6 deletions

View File

@ -30,6 +30,8 @@ import {ProfessionSheet} from "./module/sheets/ProfessionSheet.mjs";
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";
async function preloadHandlebarsTemplates() {
return foundry.applications.handlebars.loadTemplates([
// ui partials.
@ -55,7 +57,8 @@ Hooks.once("init", () => {
Zonenruestung,
Zonenwunde,
Trefferzone,
Wunde
Wunde,
RestingDialog
}
// Configure custom Document implementations.
@ -306,6 +309,26 @@ Hooks.once("init", () => {
})
Handlebars.registerHelper("fieldTooltip", (...args) => {
const [fieldName, actorId] = args
const actor = game.actors.find(p => p._id === actorId)
let tooltip = ""
if (actor) {
Object.entries(actor.getModificationsOn(fieldName)).forEach(([key, value]) => {
tooltip += `${key}: ${value}<br/>`
})
} else {
tooltip = `${fieldName} not found`
}
return new Handlebars.SafeString(tooltip)
})
Handlebars.registerHelper("currency", (data) => {

View File

@ -0,0 +1,306 @@
import {XmlImport} from "../xml-import/xml-import.mjs";
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
classes: ['dsa41', 'dialog', 'resting'],
tag: "form",
position: {
width: 480,
height: 800
},
window: {
resizable: false,
title: "Rasten und Regenerieren"
},
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: RestingDialog.#onSubmitForm
},
actions: {}
}
static PARTS = {
form: {
template: 'systems/DSA_4-1/templates/dialog/resting-dialog.hbs',
}
}
/**
* @type {Actor}
* @private
*/
_actor = null
/**
* @typedef RestingCircumstance
* @property {String} name Displaytext of this circumstance modifier
* @property {Number|[Number]} aspMod modifier to AsP regeneration
* @property {Number|[Number]} lepMod modifier to LeP regeneration
* @property {Boolean|Number} active indicates if this modifier is active and or which index of aspMod/lepMod to be used
*/
/**
* @typedef RestingOptions
* @property {Number} length the time of this rest, will be used for reducing exhaustion and overexertion
* @property {Number} wounds the amount of wounds the actor has suffered
* @property {Number|Boolean} treated if the wounds have been treated with the Skill "Heilkunde: Wunden" the Value is the Modificator for the wound regeneration
* @property {RestingCircumstance} circumstances the circumstances of this Rest
*/
#type = {
DRAUßEN: "draußen",
SCHLAFSAAL: "schlafsaal",
EINZELZIMMER: "einzelzimmer",
SUITE: "suite",
}
/**
* @type {[RestingCircumstance]}
* @private
*/
#circumstances = [
{
name: "Ruhestörung",
display: "boolean",
lepMod: -1,
aspMod: -1,
group: "interrupted",
active: false,
value: "on"
},
{
name: "Wache gehalten",
display: "boolean",
lepMod: -1,
aspMod: -1,
group: "watch",
active: false,
value: "on"
},
{
name: "Unter freiem Himmel",
display: "radio",
aspMod: 0,
lepMod: 0,
active: false,
value: this.#type.DRAUßEN,
group: "type"
},
{
name: "Schlechtes Wetter",
aspMod: [-1, -2, -3, -4, -5],
lepMod: [-1, -2, -3, -4, -5],
display: "range",
noLabel: "Gutes Wetter",
labels: [
"Schlechtes Wetter I",
"Schlechtes Wetter II",
"Schlechtes Wetter III",
"Schlechtes Wetter IV",
"Schlechtes Wetter V",
],
group: "bad_weather",
active: -1,
},
{
name: "Schlechter Lagerplatz",
aspMod: -1,
lepMod: -1,
display: "boolean",
group: "bad_camp",
active: false,
value: "on"
},
{
name: "Magere Lagerstätte (Schlafsaal, Heuboden)",
aspMod: 0,
lepMod: 0,
display: "radio",
group: "type",
value: this.#type.SCHLAFSAAL,
active: false,
},
{
name: "Komfortable Schlafstätte",
aspMod: 1,
lepMod: 1,
display: "radio",
group: "type",
value: this.#type.EINZELZIMMER,
active: false,
},
{
name: "Luxuriöse Schlafstätte (Suite)",
aspMod: 2,
lepMod: 2,
display: "radio",
group: "type",
value: this.#type.SUITE,
active: false,
}
]
constructor(actor) {
super();
this._actor = actor;
this.restDuration = 6
this.restingType = this.#circumstances.find(p => p.value === this.#type.DRAUßEN)
this.badWeather = -1
this.woundTreated = false
this.woundRegenerationModifier = 0
this.wounds = 1
this.badCamp = false
this.watch = false
this.interrupted = false
}
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
console.log(formData)
this.restDuration = formData.object.length
this.restingType = formData.object.type
this.badWeather = formData.object.bad_weather
this.woundTreated = formData.object.wound_treated
this.woundRegenerationModifier = formData.object.wound_treat_modifier
this.wounds = formData.object.wounds
this.badCamp = formData.object.bad_camp === "on"
this.watch = formData.object.watch === "on"
this.interrupted = formData.object.interrupted === "on"
const elementLepMod = this.element.querySelector('output[name="lepMod"]')
const elementKoMod = this.element.querySelector('output[name="koMod"]')
const elementAspMod = this.element.querySelector('output[name="aspMod"]')
const elementInMod = this.element.querySelector('output[name="inMod"]')
const elementWoundMod = this.element.querySelector('output[name="woundMod"]')
const context = this.#updateData()
elementLepMod.value = context.lepMod
elementKoMod.value = context.koRoll
elementAspMod.value = context.aspMod
elementInMod.value = context.inRoll
elementWoundMod.value = context.woundMod
}
#updateData(context = {}) {
context.circumstances = this.#circumstances
context.actorId = this._actor._id
context.hasWounds = true
context.hasAsP = true
context.wounds = this.wounds
// TODO count wounds
let lepRestModifier = 0
let aspRestModifier = 0
if (this.restingType) {
const circ = this.#circumstances.find(p => p.value === this.restingType)
if (circ) {
lepRestModifier += Number(circ.lepMod)
aspRestModifier += Number(circ.aspMod)
}
}
if (this.restingType === this.#type.DRAUßEN && this.badWeather !== -1) {
const circ = this.#circumstances.find(p => p.group === "bad_weather")
if (circ) {
if (circ["lepMod"][this.badWeather] !== 0) {
lepRestModifier += circ["lepMod"][this.badWeather]
}
if (circ["aspMod"][this.badWeather] !== 0) {
aspRestModifier += circ["aspMod"][this.badWeather]
}
}
}
if (this.woundTreated) {
context.woundMod = `1w20-${this.woundRegenerationModifier ?? 0}`
} else {
if (context.hasWounds) {
context.woundMod = `1w20+${context.wounds * 3}`
}
}
if (this.restingType === this.#type.DRAUßEN && this.badCamp) {
const circ = this.#circumstances.find(p => p.group === "bad_camp")
if (circ) {
lepRestModifier += Number(circ.lepMod)
aspRestModifier += Number(circ.aspMod)
}
}
if (this.watch) {
const circ = this.#circumstances.find(p => p.group === "watch")
if (circ) {
lepRestModifier += Number(circ.lepMod)
aspRestModifier += Number(circ.aspMod)
}
}
if (this.interrupted) {
const circ = this.#circumstances.find(p => p.group === "interrupted")
if (circ) {
lepRestModifier += Number(circ.lepMod)
aspRestModifier += Number(circ.aspMod)
}
}
const [lepDieAmount, lepModifier] = this._actor.system.regeneration.lep.split("d6")
const [aspDieAmount, aspModifier] = this._actor.system.regeneration.asp.split("d6")
const lepMod = (Number(lepModifier) + (lepRestModifier ?? ""))
const aspMod = (Number(aspModifier) + (aspRestModifier ?? ""))
if (lepMod == 0) {
context.lepMod = lepDieAmount + "d6"
} else if (lepMod > 0) {
context.lepMod = lepDieAmount + "d6+" + lepMod
} else {
context.lepMod = lepDieAmount + "d6" + lepMod
}
if (aspMod == 0) {
context.aspMod = aspDieAmount + "d6"
} else if (lepMod > 0) {
context.aspMod = aspDieAmount + "d6+" + aspMod
} else {
context.aspMod = aspDieAmount + "d6" + aspMod
}
if (this._actor.system.regeneration.ko < 0) {
context.koRoll = `1w20${this._actor.system.regeneration.ko}`
} else {
context.koRoll = `1w20+${this._actor.system.regeneration.ko}`
}
if (this._actor.system.regeneration.in < 0) {
context.inRoll = `1w20${this._actor.system.regeneration.in}`
} else {
context.inRoll = `1w20+${this._actor.system.regeneration.in}`
}
console.log(this, context)
return context
}
async _prepareContext(options) {
const context = await super._prepareContext(options)
return this.#updateData(context)
}
_onRender(context, options) {
}
}

View File

@ -10,6 +10,7 @@ import Spells from "./character/spells.mjs"
import {CombatActionDialog} from "../dialog/combatAction.mjs";
import {ActionManager} from "./actions/action-manager.mjs";
import {DefenseActionDialog} from "../dialog/defenseAction.mjs";
import {RestingDialog} from "../dialog/restingDialog.mjs";
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
@ -43,6 +44,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
progressCooldown: CharacterSheet.#progressCooldown,
cancelCooldown: CharacterSheet.#cancelCooldown,
activateCooldown: CharacterSheet.#activateCooldown,
rest: CharacterSheet.#startResting,
}
}
@ -207,6 +209,12 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}
}
static #startResting(event, target) {
const dialog = new RestingDialog(this.document)
dialog.render(true)
}
/**
* Handle form submission
* @this {AdvantageSheet}
@ -242,6 +250,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
const actorData = context.document
context.system = actorData.system
context.actorId = actorData._id
context.isOwner = actorData.isOwner
context.flags = actorData.flags
context.derived = context.document.system
@ -309,7 +318,6 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
context.keper = Math.min((actorData.system.kap.aktuell / actorData.system.kap.max) * 100, 100)
context.aspper = Math.min((actorData.system.asp.aktuell / actorData.system.asp.max) * 100, 100)
context.lepcurrent = actorData.system.lep.aktuell ?? 0
context.aupcurrent = actorData.system.aup.aktuell ?? 0
const fernkampf = actorData.findEquipmentOnSlot("fernkampf", actorData.system.setEquipped, actorData)
@ -391,6 +399,9 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
cooldown.tooltip = `${cooldown.data.maneuver.name}<br/>Waffe:${weapon.name}<br/>Ziel: ${target.name}<br/>Wurfziel: ${cooldown.data.targetNumber}<br/>Aktionen verbleibend: ${cooldown.current}`
})
context.hasSpells = actorData.itemTypes["Spell"].length > 0
context.hasLiturgies = actorData.itemTypes["Liturgy"].length > 0
context.attributes = [
{
eigenschaft: "mu",

View File

@ -0,0 +1,80 @@
.application.dsa41.dialog.resting {
section {
display: flex;
flex-direction: column;
height: 100%;
gap: 16px;
.options {
flex: 1;
div.setting {
margin-left: 32px;
padding-bottom: 16px;
&.range {
padding-bottom: 0;
margin-left: 34px;
}
&.combined {
input[type="checkbox"], input[type="radio"] {
margin-left: -32px;
padding-right: 8px;
}
span {
height: 32px;
line-height: 32px;
vertical-align: 2px;
}
}
&.radio, &.boolean {
padding-bottom: 0;
input[type="checkbox"], input[type="radio"] {
margin-left: -32px;
padding-right: 8px;
}
span {
height: 32px;
line-height: 32px;
vertical-align: 2px;
}
}
}
}
fieldset {
flex: 0;
.results {
display: grid;
grid-template-columns: 120px 1fr;
span {
}
}
}
.actions {
flex: 0;
width: 100%;
}
}
}

View File

@ -30,3 +30,4 @@
@use "organisms/xml-import-dialog";
@use "organisms/combat-action-dialog";
@use "organisms/merchant-sheet";
@use "organisms/resting-dialog";

View File

@ -40,7 +40,7 @@
{{#if this.hasLiturgies}}
<div class="sidebar-element resource-bar">
<label>KE: {{this.ke}}</label><span class="resource-fill kap" style="width: {{this.keper}}%"></span>
<label>KaP: {{this.ke}}</label><span class="resource-fill kap" style="width: {{this.keper}}%"></span>
</div>
{{/if}}
@ -50,6 +50,10 @@
</div>
{{/if}}
<div class="sidebar-element button">
<button data-action="rest"><i class="fa-solid fa-bed"></i> Rasten</button>
</div>
{{#each attacks}}
<div class="attack">
<h3>{{this.using}} ({{this.name}})</h3>

View File

@ -4,20 +4,20 @@
<div class="combatline">
<div class="initiaitve">
<div class="initiaitve" data-tooltip="{{fieldTooltip "ini" actorId}}">
<label>Initiative:</label>
<input type="number" name="system.attribute.ini.wuerfel" value="{{this.inidice}}"/>
<span class="inline">w6</span>
<input type="number" name="system.attribute.ini.aktuell" value="{{this.inivalue}}"/>
</div>
<div class="lebensenergie">
<div class="lebensenergie" data-tooltip="{{fieldTooltip "lep" actorId}}">
<label>Lebensenergie:</label>
<input type="number" name="system.lep.aktuell" value="{{this.system.lep.aktuell}}"/>
<span class="inline">von</span>
<input type="number" disabled value="{{this.system.lep.max}}"/>
</div>
{{#if ausdauer}}
<div class="ausdauer">
<div class="ausdauer" data-tooltip="{{fieldTooltip "aus" actorId}}">
<label>Ausdauerpunkte:</label>
<input type="number" name="system.aup.aktuell" value="{{this.system.aup.aktuell}}"/>
<span class="inline">von</span>

View File

@ -0,0 +1,75 @@
<section>
<div class="options">
<div class="setting">
<label>
<span>Dauer der Rast</span>
<input type="number" name="length" placeholder="6" value="{{length}}"/>
</label>
</div>
<div class="setting">
<label>
<span>Wunden</span>
<input type="number" name="wounds" placeholder="0" value="{{wounds}}"/>
</label>
</div>
<div class="setting combined">
<label>
<input type="checkbox" name="wound_treated"/><span>Wunde behandelt</span>
<input type="number" name="wound_treat_modifier" placeholder="0" value="{{wound_treat_modifier}}"/>
</label>
</div>
{{#each circumstances}}
<div class="setting {{this.display}}">
<label>
{{#if (eq this.display "range")}}
<span>{{this.name}}</span>
<input type="range" min="-1" max="4" step="1" name="{{this.group}}" value="-1" list="markers"/>
<datalist id="markers">
<option value="-1" label="{{this.noLabel}}"></option>
{{#each this.labels}}
<option value="{{@key}}" label="{{this}}"></option>
{{/each}}
</datalist>
{{/if}}
{{#if (eq this.display "boolean")}}
<input type="checkbox" name="{{this.group}}" value="{{this.value}}" {{checked this.active}}/>
<span>{{this.name}}</span>
{{/if}}
{{#if (eq this.display "radio")}}
<input type="radio" name="{{this.group}}" value="{{this.value}}" {{checked this.active}}/>
<span>{{this.name}}</span>
{{/if}}
</label>
</div>
{{/each}}
</div>
<fieldset>
<legend>Modifikatoren</legend>
<div class="results">
<span data-tooltip="{{fieldTooltip 'regeneration.lep' actorId}}">LeP-Regeneration</span>
<output name="lepMod">{{lepMod}}</output>
<span data-tooltip="{{fieldTooltip 'regeneration.ko' actorId}}">KO Wurf</span>
<output name="koMod">{{koRoll}}</output>
{{#if hasAsP}}
<span data-tooltip="{{fieldTooltip 'regeneration.asp' actorId}}">AsP-Regeneration</span>
<output name="aspMod">{{aspMod}}</output>
<span data-tooltip="{{fieldTooltip 'regeneration.in' actorId}}">IN Wurf</span>
<output name="inMod">{{inRoll}}</output>
{{/if}}
{{#if hasWounds}}
<span>Wunden-Heilung</span>
<output name="woundMod">{{woundMod}}</output>
{{/if}}
</div>
</fieldset>
<button class="actions"><i class="fa-solid fa-bed"></i> Regenerieren</button>
</section>