fixes some user experience issues

pull/65/head
macniel 2025-11-09 19:14:35 +01:00
parent 28f19772f2
commit 7e34251397
15 changed files with 239 additions and 79 deletions

View File

@ -1,22 +1,49 @@
{
"TYPES": {
"Actor": {
"Character": "Held",
"Creature": "Kreatur",
"Group": "Heldengruppe",
"Merchant": "Händler"
},
"Item": {
"ActiveEffect": "Aktiver Effekt",
"Equipment": "Ausrüstungsgegenstand",
"Skill": "Talent",
"Advantage": "Vor-/Nachteil",
"SpecialAbility": "Sonderfertigkeit",
"Spell": "Zauber",
"Liturgy": "Liturgie",
"Species": "Spezies",
"Culture": "Kultur",
"Profession": "Profession"
}
"TYPES": {
"Actor": {
"Character": "Held",
"Creature": "Kreatur",
"Group": "Heldengruppe",
"Merchant": "Händler"
},
"Item": {
"ActiveEffect": "Aktiver Effekt",
"Equipment": "Ausrüstungsgegenstand",
"Skill": "Talent",
"Advantage": "Vor-/Nachteil",
"SpecialAbility": "Sonderfertigkeit",
"Spell": "Zauber",
"Liturgy": "Liturgie",
"Species": "Spezies",
"Culture": "Kultur",
"Profession": "Profession"
}
},
"COOLDOWN": {
"progress": "{t} weiter durchführen",
"cancel": "{t} abbrechen",
"activate": "{t} auslösen"
},
"WEAPON": {
"attack": "Mit {weapon} angreifen",
"parry": "Mit {weapon} parrieren",
"damage": "Mit {weapon} schaden machen",
"initiative": "Initiative würfeln"
},
"COMBAT_DIALOG": {
"notReadyReason": {
"title": "Angriff kann aus folgenden Gründen nicht ausgeführt werden:",
"noTarget": "Kein Ziel ausgewählt",
"noWeapon": "Keine Waffe ausgewählt",
"noSkill": "Kein Waffentalent ausgewählt",
"noManeuver": "Kein Manöver ausgewählt",
"impossible": "Erschwernis zu hoch für Talentwert"
}
},
"COMBAT_DIALOG_TP": {
"windowTitle": "Schaden Würfeln",
"regularFormula": "Schadensformel:",
"bonusDamage": "Zusätzlicher Schaden:",
"buttonText": "Würfeln"
}
}

View File

@ -50,12 +50,12 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
*/
_actor = null
constructor(actor) {
constructor(actor, data) {
super();
this._actor = actor
this._targetId = null
this._skillId = null
this._weaponId = null
this._skillId = data.skill ? data.skill : null
this._weaponId = data.weapon ? data.weapon : null
this._defenseManeuverId = null
this._actionManager = new ActionManager(this._actor)
CombatActionDialog._instance = this
@ -288,9 +288,9 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
const context = await super._prepareContext(options)
context.actor = this._actor
context.distanceUnit = game.scenes.current.grid.units
context.distanceUnit = game.scenes.current?.grid.units
if (this._actor.getActiveTokens()[0]?.id) {
if (context.distanceUnit && this._actor.getActiveTokens()[0]?.id) {
context.tokenDistances = this.#evaluateDistances()
context.weapons = this.#evaluateWeapons()
@ -307,7 +307,28 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
// TODO get W/M of weapon NOW
context.ready = this._targetId && this._weaponId && this._skillId && this._defenseManeuverId
if (this._targetNumber >= 0 && this._targetId && this._weaponId && this._skillId && maneuver) {
context.ready = true
} else {
context.notReadyReason = `<em>${game.i18n.format("COMBAT_DIALOG.notReadyReason.title")}</em><ul>`
if (!this._targetId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noTarget")}</li>`
}
if (!this._weaponId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noWeapon")}</li>`
}
if (!this._skillId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noSkill")}</li>`
}
if (!maneuver) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noManeuver")}</li>`
}
if (!this._targetNumber < 0) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.impossible")}</li>`
}
context.notReadyReason += "</ul>"
context.ready = false
}
return context
} else {
ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
@ -335,12 +356,10 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
target.textContent = `(${result})`
targetDescription.textContent = this._modDescription
if (result <= 0) {
context.ready = false
if (result <= 0 || !context.ready) {
this.element.querySelector(".actions button").classList.remove("ready")
this.element.querySelector(".actions button").setAttribute("disabled", true)
} else {
context.ready = true
this.element.querySelector(".actions button").classList.add("ready")
this.element.querySelector(".actions button").removeAttribute("disabled")
}

View File

@ -49,16 +49,16 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
*/
_actor = null
constructor(actor, attackData) {
constructor(actor, data, attackData) {
super();
this._attackData = attackData ?? {
/*this._attackData = attackData ?? {
modToDefense: 0,
attacker: null,
weapon: null, // is important to note as weapons like Chain Weapons or Flails can ignore Shields
}
}*/
this._actor = actor
this._skillId = null
this._weaponId = null
this._skillId = data.skill ? data.skill : null
this._weaponId = data.weapon ? data.weapon : null
this._defenseManeuverId = null
this._actionManager = new ActionManager(this._actor)
//if (this._actor) {
@ -254,7 +254,26 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
// TODO get W/M of weapon NOW
context.ready = this._targetId && this._weaponId && this._skillId && this._defenseManeuverId
if (this._weaponId && this._skillId && this._defenseManeuverId) {
context.ready = true
} else {
context.notReadyReason = `<em>${game.i18n.format("COMBAT_DIALOG.notReadyReason.title")}</em><ul>`
if (!this._weaponId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noWeapon")}</li>`
}
if (!this._skillId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noSkill")}</li>`
}
if (!maneuver) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noManeuver")}</li>`
}
if (!this._targetNumber < 0) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.impossible")}</li>`
}
context.notReadyReason += "</ul>"
context.ready = false
}
return context
} else {
ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
@ -282,12 +301,10 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
target.textContent = `(${result})`
targetDescription.textContent = this._modDescription
if (result <= 0) {
context.ready = false
if (result <= 0 || !context.ready) {
this.element.querySelector(".actions button").classList.remove("ready")
this.element.querySelector(".actions button").setAttribute("disabled", true)
} else {
context.ready = true
this.element.querySelector(".actions button").classList.add("ready")
this.element.querySelector(".actions button").removeAttribute("disabled")
}

View File

@ -4,6 +4,7 @@ function loadPartials(hbs) {
hbs.loadTemplates([
// ui partials.
'systems/DSA_4-1/templates/ui/partial-rollable-button.hbs',
'systems/DSA_4-1/templates/ui/partial-cooldown.hbs',
'systems/DSA_4-1/templates/ui/partial-rollable-weaponskill-button.hbs',
'systems/DSA_4-1/templates/ui/partial-rollable-language-button.hbs',
'systems/DSA_4-1/templates/ui/partial-attribute-button.hbs',

View File

@ -50,6 +50,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
activateCooldown: CharacterSheet.#activateCooldown,
rest: CharacterSheet.#startResting,
removeEffect: CharacterSheet.#removeEffect,
rollDamage: CharacterSheet.#rollDamage,
}
}
@ -202,12 +203,14 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}
static #openCombatAction(event, target) {
let {weapon, skill} = target.dataset
switch (target.dataset.mode) {
case "attack":
new CombatActionDialog(this.document).render(true)
new CombatActionDialog(this.document, {weapon, skill}).render(true)
break
case "defense":
new DefenseActionDialog(this.document).render(true)
new DefenseActionDialog(this.document, {weapon, skill}).render(true)
break
}
}
@ -261,6 +264,39 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
await this.document.update(formData.object)
}
static async #rollDamage(event, target) {
let {weapon, isRanged} = target.dataset
isRanged = isRanged == "true"
weapon = this.document.items.get(weapon)
if (weapon) {
const damageFormula = isRanged ? weapon.system.rangedAttackDamage : weapon.system.meleeAttackDamage
const calculation = await foundry.applications.api.DialogV2.prompt({
window: {title: game.i18n.format("COMBAT_DIALOG_TP.windowTitle")},
content: `<div><label><span>${game.i18n.format("COMBAT_DIALOG_TP.regularFormula")}</span><input type="text" name="formula" value="${damageFormula}"></label></div><div><label><span>${game.i18n.format("COMBAT_DIALOG_TP.bonusDamage")}</span><input type="text" name="bonusDamage" value="0"></label></div>`,
ok: {
label: game.i18n.format("COMBAT_DIALOG_TP.buttonText"),
callback: (event, button, dialog) => {
return {
formula: button.form.elements.formula.value,
bonusDamage: button.form.elements.bonusDamage.value
}
}
}
});
const sanitisedFormula = calculation.formula.replace(/wW/g, "d")
const suffix = calculation.bonusDamage >= 0 ? "+" + calculation.bonusDamage : calculation.bonusDamage
let r = new Roll(sanitisedFormula + suffix, this.document.getRollData());
const label = `Schadenswurf`
await r.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.document}),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
})
}
}
_getTabsConfig(group) {
const tabs = foundry.utils.deepClone(super._getTabsConfig(group))
@ -363,7 +399,10 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
fkitems.forEach(skill => {
context.attacks.push({
name: skill.name,
id: fernkampf._id,
skillId: skill._id,
using: fernkampf.name,
isRanged: true,
at: `${this.document.system.fk.aktuell + skill.system.at}`,
tp: `${fernkampf.system.rangedAttackDamage}`,
ini: `${context.inidice}w6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`,
@ -382,7 +421,10 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
const obj = skill
context.attacks.push({
name: obj.name,
id: links._id,
skillId: skill._id,
using: links.name,
isRanged: false,
at: `${this.document.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`,
pa: `${this.document.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`,
tp: `${links.system.meleeAttackDamage}`,
@ -402,7 +444,10 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
const obj = skill
context.attacks.push({
name: obj.name,
id: rechts._id,
skillId: skill._id,
using: rechts.name,
isRanged: false,
at: `${this.document.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`,
pa: `${this.document.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`,
tp: `${rechts.system.meleeAttackDamage}`,

View File

@ -24,7 +24,6 @@
}
input,
.rkp .pill,
.cooldown > span,
.attribute.rollable > .name,
.attribute.rollable > .wert {

View File

@ -9,14 +9,12 @@
padding: 2px 0 0 2px;
margin: 4px 0 0 4px;
gap: 8px;
@if darkmode {
background: rgba(0, 0, 0, 0.3);
} @else {
background: assets.$tab-pane-background;
}
background: assets.$tab-pane-background;
.icon {
width: 32px;
object-fit: cover;
max-width: 32px;
max-height: 32px;
padding: 0;
}
@ -55,4 +53,12 @@
padding: 0 4px 4px 0;
z-index: 2;
}
&.worn {
.name {
font-weight: bold;
}
}
}

View File

@ -133,10 +133,28 @@
.ini {
grid-area: ini;
cursor: pointer;
label {
cursor: pointer;
}
&:hover {
text-shadow: 0 0 2px rgba(255, 0, 0, 1);
}
}
.tp {
grid-area: tp;
cursor: pointer;
label {
cursor: pointer;
}
&:hover {
text-shadow: 0 0 2px rgba(255, 0, 0, 1);
}
}
}

View File

@ -36,6 +36,14 @@
font-size: large;
}
.rkp {
.pill {
cursor: pointer;
}
}
}
div.head-data {
@ -50,6 +58,7 @@
.profile-img {
width: $sidebar-width - 16px;
}
}
nav.sheet-tabs.tabs {

View File

@ -45,6 +45,8 @@
li {
cursor: pointer;
height: 32px;
display: grid;
line-height: 32px;
@ -63,6 +65,7 @@
}
&.name-only {
padding-left: 40px;
display: block;
}
}

View File

@ -2,7 +2,7 @@
"id": "DSA_4-1",
"title": "Das Schwarze Auge 4.1",
"description": "Noch ein Spielsystem für Das Schwarze Auge 4.1",
"version": "0.5.1-rc1",
"version": "0.0.1",
"compatibility": {
"minimum": 12,
"verified": 13
@ -349,5 +349,5 @@
"primaryTokenAttribute": "lep.aktuell",
"url": "https://git.macniel.online/macniel/foundry-dsa41-game",
"manifest": "https://git.macniel.online/macniel/foundry-dsa41-game/raw/branch/main/src/system.json",
"download": "https://git.macniel.online/macniel/foundry-dsa41-game/releases/download/0.5.1-rc1/release.zip"
"download": "https://git.macniel.online/macniel/foundry-dsa41-game/releases/download/0.0.1/release.zip"
}

View File

@ -62,25 +62,34 @@
<h3>{{this.using}} ({{this.name}})</h3>
{{#if this.at}}
<div class="at sidebar-element rollable" data-mode="attack" data-action="openCombatAction">
<div data-tooltip="{{localize "WEAPON.attack" weapon=this.name}}"
class="at sidebar-element rollable" data-is-ranged="{{this.isRanged}}"
data-weapon="{{this.id}}" data-skill="{{this.skillId}}" data-mode="attack"
data-action="openCombatAction">
<label>AT</label>
<div class="formula">{{this.at}}</div>
</div>
{{/if}}
{{#if this.pa}}
<div class="pa sidebar-element rollable" data-mode="defense" data-action="openCombatAction">
<div data-tooltip="{{localize "WEAPON.parry" weapon=this.name}}" class="pa sidebar-element rollable"
data-is-ranged="{{this.isRanged}}" data-weapon="{{this.id}}" data-skill="{{this.skillId}}"
data-mode="defense" data-action="openCombatAction">
<label>PA</label>
<div class="formula">{{this.pa}}</div>
</div>
{{/if}}
{{#if this.at}}
<div class="tp sidebar-element rollable" data-roll="{{this.tproll}}" data-label="Schaden">
{{#if this.tp}}
<div data-tooltip="{{localize "WEAPON.damage" weapon=this.name}}"
class="tp sidebar-element rollable" data-is-ranged="{{this.isRanged}}"
data-skill="{{this.skillId}}" data-weapon="{{this.id}}" data-action="rollDamage"
data-label="Schaden">
<label>TP</label>
<div class="formula">{{this.tp}}</div>
</div>
{{/if}}
{{#if this.ini}}
<div class="ini sidebar-element rollable" data-action="openInitiative"><label>INI</label>
<div data-tooltip="{{localize "WEAPON.initiative" weapon=this.name}}"
class="ini sidebar-element rollable" data-action="openInitiative"><label>INI</label>
<div class="formula">{{this.ini}}</div>
</div>
{{/if}}
@ -91,25 +100,7 @@
<section class="cooldowns">
<h3>Abklingzeiten</h3>
{{#each this.cooldowns}}
<div class="cooldown{{#if this.data.cssClass}} {{this.data.cssClass}}{{/if}}">
<div class="progress" style="width: {{this.progress}}"></div>
{{#if (gt this.current 0)}}
<button class="btn-left" data-action="progressCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-timer"></i>
</button>
<span data-tooltip="{{this.tooltip}}">{{this.title}}</span>
<button class="btn-right" data-action="cancelCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-xmark"></i>
</button>
{{else}}
<button class="btn-left" data-action="activateCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-person-running"></i>
</button>
<span data-tooltip="{{this.tooltip}}">{{this.title}}</span>
<button class="btn-right" data-action="cancelCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-xmark"></i>
</button>
{{/if}}</div>
{{> "systems/DSA_4-1/templates/ui/partial-cooldown.hbs" this}}
{{/each}}
</section>

View File

@ -5,7 +5,7 @@
<ul>
{{#each tokenDistances}}
<li class="token {{#if isSelected}}selected{{/if}}" data-action="selectTarget"
<li class="target token {{#if isSelected}}selected{{/if}}" data-action="selectTarget"
data-target-id="{{this.id}}"><img src="{{this.token.texture.src}}"
style="width: 32px; height: 32px"/><span>{{this.actor.name}}</span><span>({{this.d}} {{../distanceUnit}}
)</span></li>
@ -18,7 +18,7 @@
<legend>Waffe auswählen</legend>
<ul>
{{#each weapons}}
<li class="{{#if isSelected}}selected{{/if}}" data-action="selectWeaponAndSkill"
<li class="weapon {{#if isSelected}}selected{{/if}}" data-action="selectWeaponAndSkill"
data-weapon-id="{{this.weaponId}}" data-skill-id="{{this.skillId}}"><img src="{{this.img}}"
style="width: 32px; height: 32px"/><span>{{this.name}}
({{this.skillName}})</span><span>{{#if this.combatStatistics}}
@ -31,7 +31,7 @@
<legend>Manöver auswählen</legend>
<ul>
{{#each maneuver}}
<li class="{{#if isSelected}}selected{{/if}} name-only" data-action="selectManeuver"
<li class="maneuver {{#if isSelected}}selected{{/if}} name-only" data-action="selectManeuver"
data-maneuver-id="{{this.id}}" class="{{this.source}}">{{this.name}}</li>
{{/each}}
</ul>
@ -55,7 +55,8 @@
<div class="actions">
<button {{#if ready}}class="ready"{{/if}} type="submit"><i class="fa-solid fa-swords"></i>Angreifen <span
<button {{#if ready}}class="ready" {{else}}data-tooltip="{{notReadyReason}}"{{/if}} type="submit"><i
class="fa-solid fa-swords"></i>Angreifen <span
class="value"></span></button>
</div>

View File

@ -3,7 +3,7 @@
<legend>Waffe auswählen</legend>
<ul>
{{#each weapons}}
<li class="{{#if isSelected}}selected{{/if}}" data-action="selectWeaponAndSkill"
<li class="weapon {{#if isSelected}}selected{{/if}}" data-action="selectWeaponAndSkill"
data-weapon-id="{{this.weaponId}}" data-skill-id="{{this.skillId}}">
{{#if this.img}}<img src="{{this.img}}" style="width: 32px; height: 32px"/>{{else}}
<span style="width: 32px"></span> {{/if}}
@ -19,7 +19,7 @@
<legend>Manöver auswählen</legend>
<ul>
{{#each maneuver}}
<li class="{{#if isSelected}}selected{{/if}} name-only" data-action="selectManeuver"
<li class="maneuver {{#if isSelected}}selected{{/if}} name-only" data-action="selectManeuver"
data-maneuver-id="{{this.id}}" class="{{this.source}}">{{this.name}}</li>
{{/each}}
</ul>
@ -43,7 +43,8 @@
<div class="actions">
<button {{#if ready}}class="ready"{{/if}} type="submit"><i class="fa-solid fa-shield"></i>Verteidigen <span
<button {{#if ready}}class="ready" {{else}}data-tooltip="{{notReadyReason}}"{{/if}} type="submit"><i
class="fa-solid fa-shield"></i>Verteidigen <span
class="value"></span></button>
</div>

View File

@ -0,0 +1,23 @@
<div class="cooldown{{#if this.data.cssClass}} {{this.data.cssClass}}{{/if}}">
<div class="progress" style="width: {{this.progress}}"></div>
{{#if (gt this.current 0)}}
<button data-tooltip="{{localize "COOLDOWN.progress" t=this.title}}" class="btn-left"
data-action="progressCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-person-running-fast"></i>
</button>
<span data-tooltip="{{this.tooltip}}">{{this.title}}</span>
<button data-tooltip="{{localize "COOLDOWN.cancel" t=this.title}}" class="btn-right"
data-action="cancelCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-xmark"></i>
</button>
{{else}}
<button data-tooltip="{{localize "COOLDOWN.activate" t=this.title}}" class="btn-left"
data-action="activateCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-person-running"></i>
</button>
<span data-tooltip="{{this.tooltip}}">{{this.title}}</span>
<button data-tooltip="{{localize "COOLDOWN.cancel" t=this.title}}" class="btn-right"
data-action="cancelCooldown" data-cooldown-id="{{@key}}">
<i class="fa fa-xmark"></i>
</button>
{{/if}}</div>