daggerheart/module/applications/dialogs/deathMove.mjs
Chris Ryan 9d75157e17
[Feature] Death moves and Fate rolls (#1463)
* Update the death move descriptions

* Renamed to DhDeathMove

* Partial Fate Roll creation and Fate Roll Enricher (/fr)

* Hide stuff not required for fate roll

* Hide formula display; code removal; start to add Fear die as a choice for Fate roll

* Fix chat message display; start moving towards supporting Hope and Fear for Fate roll

* /fr now supports type=X, where X is Hope or Fear, if not supplied, defaults to Hope

* Fixed DSN rolling; removed console messages; chat message clean up

* Add localisation entry

* Trying to sort out the button for the fate roll

* Style the fate message based on Hope/Fear colors.

* Partial improvement on the fate template buttons - chat display is correct, but the roll dialog is wrong

* Fixed enricher button; localization fixes; debug cleanup

* Error checking for the fate type parsing in all potential problem locations

* Added localization for the fate type parsing error

* Start on Avoid Death death move

* debug stuff

* More death moves setup/testing

* Avoid fate scars update in place, with scars migrating to an integer value.

* Remove some debug code; add Blaze Of Glory shell

* Start on Guaranteed Critical for Blaze of Glory

* Partial implementation of Blaze of Glory

* Dice/critical checks/tests

* Moved detection of guaranteed critical to before the roll dialog is created, so it can be skipped; removed debug code

* Remove debug

* Update Blaze of Glory effect description

* Risk It All - critical roll - clear all stress and HP

* Auto remove all marked stress and HP for Risk It All, if Hope value rolled covers it.

* Display the Death Move description in chat expanded if the appropriate config setting is on

* Made the Blaze of Glory ActiveEffect image use configured version

* Update the current Hope value if the scar value change affects it

* Scars management in the Character details editor

* Separate less file for the Death Moves instead of reusing Downtime

* Added result messages to the Death Move chat output and removed debug statements

* Some localization, style and smaller changes

* Fixed RiskItAll resource handling method

* Risk It All success chat message start

* [Add] Hope/Scar Interplay (#1531)

* Migrated character.maxHope to homebrew settings

* Added a visual for scars

* .

* .

* Pass the hope value in the button data; skeleton risk it all dialog to fill out.

* Start on risk it dialog

* More dialog stuff

* Remove non-existent field

* Dialog templating and logic

* .

* Ensure effect is Applied to Actor (#1547)

Co-authored-by: Chris Ryan <chrisr@blackhole>

* [Fix] 1548 - Standalone Item Add Actions (#1549)

* Fixed so that items not on an actor don't error out on creating actions

* Fixed deletion of items error

* Raised version

* Fix the sliders to do the correct maximums

* Pass the actor id through the button; fix /dr and /fr flavor text

* Remove debug message

---------

Co-authored-by: Chris Ryan <chrisr@blackhole>
Co-authored-by: WBHarry <williambjrklund@gmail.com>
Co-authored-by: WBHarry <89362246+WBHarry@users.noreply.github.com>
2026-01-17 15:11:50 +01:00

208 lines
7.5 KiB
JavaScript

import { enrichedFateRoll } from '../../enrichers/FateRollEnricher.mjs';
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(actor) {
super({});
this.actor = actor;
this.selectedMove = null;
this.showRiskItAllButton = false;
this.riskItAllButtonLabel = '';
this.riskItAllHope = 0;
}
get title() {
return game.i18n.format('DAGGERHEART.APPLICATIONS.DeathMove.title', { actor: this.actor.name });
}
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'dh-style', 'dialog', 'views', 'death-move'],
position: { width: 'auto', height: 'auto' },
window: { icon: 'fa-solid fa-skull' },
actions: {
selectMove: this.selectMove,
takeMove: this.takeMove
}
};
static PARTS = {
application: {
id: 'death-move',
template: 'systems/daggerheart/templates/dialogs/deathMove.hbs'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.selectedMove = this.selectedMove;
context.options = CONFIG.DH.GENERAL.deathMoves;
context.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.DeathMove.takeMove');
return context;
}
async handleAvoidDeath() {
const target = this.actor.uuid;
const config = await enrichedFateRoll({
target,
title: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name'),
label: `${game.i18n.localize('DAGGERHEART.GENERAL.hope')} ${game.i18n.localize('DAGGERHEART.GENERAL.fateRoll')}`,
fateType: 'Hope'
});
if (!config.roll.fate) return;
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
// apply scarring - for now directly apply - later add a button.
const newScarAmount = this.actor.system.scars + 1;
await this.actor.update({
system: {
scars: newScarAmount
}
});
if (newScarAmount >= this.actor.system.resources.hope.max) {
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
}
return game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.gainScar');
}
return game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
}
async handleRiskItAll() {
const config = await enrichedDualityRoll({
reaction: true,
traitValue: null,
target: this.actor,
difficulty: null,
title: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.riskItAll.name'),
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityDice'),
actionType: null,
advantage: null,
customConfig: { skips: { resources: true, reaction: true } }
});
if (!config.roll.result) return;
const clearAllStressAndHitpointsUpdates = [
{ key: 'hitPoints', clear: true },
{ key: 'stress', clear: true }
];
let chatMessage = '';
if (config.roll.isCritical) {
config.resourceUpdates.addResources(clearAllStressAndHitpointsUpdates);
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllCritical');
}
if (config.roll.result.duality == 1) {
if (
config.roll.hope.value >=
this.actor.system.resources.hitPoints.value + this.actor.system.resources.stress.value
) {
config.resourceUpdates.addResources(clearAllStressAndHitpointsUpdates);
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllSuccessWithEnoughHope');
} else {
chatMessage = game.i18n.format('DAGGERHEART.UI.Chat.deathMove.riskItAllSuccess', {
hope: config.roll.hope.value
});
this.showRiskItAllButton = true;
this.riskItAllHope = config.roll.hope.value;
this.riskItAllButtonLabel = game.i18n.format('DAGGERHEART.UI.Chat.deathMove.riskItAllDialogButton');
}
}
if (config.roll.result.duality == -1) {
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllFailure');
}
await config.resourceUpdates.updateResources();
return chatMessage;
}
async handleBlazeOfGlory() {
this.actor.createEmbeddedDocuments('ActiveEffect', [
{
name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'),
description: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description'),
img: CONFIG.DH.GENERAL.deathMoves.blazeOfGlory.img,
changes: [
{
key: 'system.rules.roll.guaranteedCritical',
mode: 2,
value: 'true'
}
]
}
]);
return game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.blazeOfGlory');
}
static selectMove(_, button) {
const move = button.dataset.move;
this.selectedMove = CONFIG.DH.GENERAL.deathMoves[move];
this.render();
}
static async takeMove() {
this.close();
let result = '';
if (CONFIG.DH.GENERAL.deathMoves.blazeOfGlory === this.selectedMove) {
result = await this.handleBlazeOfGlory();
}
if (CONFIG.DH.GENERAL.deathMoves.avoidDeath === this.selectedMove) {
result = await this.handleAvoidDeath();
}
if (CONFIG.DH.GENERAL.deathMoves.riskItAll === this.selectedMove) {
result = await this.handleRiskItAll();
}
if (!result) return;
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
.expandRollMessage?.desc;
const cls = getDocumentClass('ChatMessage');
const msg = {
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/deathMove.hbs',
{
player: this.actor.name,
actor: this.actor,
actorId: this.actor._id,
author: game.users.get(game.user.id),
title: game.i18n.localize(this.selectedMove.name),
img: this.selectedMove.img,
description: game.i18n.localize(this.selectedMove.description),
result: result,
open: autoExpandDescription ? 'open' : '',
chevron: autoExpandDescription ? 'fa-chevron-up' : 'fa-chevron-down',
showRiskItAllButton: this.showRiskItAllButton,
riskItAllButtonLabel: this.riskItAllButtonLabel,
riskItAllHope: this.riskItAllHope
}
),
title: game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.title'),
speaker: cls.getSpeaker(),
flags: {
daggerheart: {
cssClass: 'dh-chat-message dh-style'
}
}
};
cls.create(msg);
}
}