Merge branch 'main' into feature/416-reaction-roll-query

This commit is contained in:
Dapoolp 2025-07-28 15:44:38 +02:00
commit 702038c41e
17 changed files with 203 additions and 32 deletions

View file

@ -293,6 +293,7 @@
"usedMarks": "Used Marks" "usedMarks": "Used Marks"
}, },
"DeathMove": { "DeathMove": {
"selectMove": "Select Move",
"takeMove": "Take Death Move", "takeMove": "Take Death Move",
"title": "{actor} - Death Move" "title": "{actor} - Death Move"
}, },

View file

@ -13,8 +13,9 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'views', 'death-move'], classes: ['daggerheart', 'dh-style', 'dialog', 'views', 'death-move'],
position: { width: 800, height: 'auto' }, position: { width: 'auto', height: 'auto' },
window: { icon: 'fa-solid fa-skull' },
actions: { actions: {
selectMove: this.selectMove, selectMove: this.selectMove,
takeMove: this.takeMove takeMove: this.takeMove
@ -32,6 +33,7 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
context.selectedMove = this.selectedMove; context.selectedMove = this.selectedMove;
context.options = CONFIG.DH.GENERAL.deathMoves; context.options = CONFIG.DH.GENERAL.deathMoves;
context.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.DeathMove.takeMove');
return context; return context;
} }

View file

@ -66,12 +66,9 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
if (!status) continue; if (!status) continue;
if (status._id) { if (status._id) {
if (status._id !== effect.id) continue; if (status._id !== effect.id) continue;
} else {
if (effect.statuses.size !== 1) continue;
} }
status.isActive = true; status.isActive = true;
if (effect.getFlag('core', 'overlay')) status.isOverlay = true; if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
break;
} }
} }

View file

@ -99,7 +99,17 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
async _preparePartContext(partId, context) { async _preparePartContext(partId, context) {
const partContext = await super._preparePartContext(partId, context); const partContext = await super._preparePartContext(partId, context);
switch (partId) { switch (partId) {
case 'changes': case 'details':
const useGeneric = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.appearance
).showGenericStatusEffects;
if (!useGeneric) {
partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
value: status.id,
label: game.i18n.localize(status.name)
}));
}
break; break;
} }

View file

@ -10,8 +10,28 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.effects.overlay = null; this.effects.overlay = null;
// Categorize effects // Categorize effects
const activeEffects = this.actor ? Array.from(this.actor.effects).filter(x => !x.disabled) : []; const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag('core', 'overlay')); const activeEffects = (this.actor ? this.actor.effects.filter(x => !x.disabled) : []).reduce((acc, effect) => {
acc.push(effect);
const currentStatusActiveEffects = acc.filter(
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first()).name)
);
for (var status of effect.statuses) {
if (!currentStatusActiveEffects.find(x => x.statuses.includes(status))) {
const statusData = statusMap.get(status);
acc.push({
name: game.i18n.localize(statusData.name),
statuses: [status],
img: statusData.icon,
tint: effect.tint
});
}
}
return acc;
}, []);
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
// Draw effects // Draw effects
const promises = []; const promises = [];

View file

@ -322,18 +322,21 @@ export const deathMoves = {
id: 'avoidDeath', id: 'avoidDeath',
name: 'DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name', name: 'DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name',
img: 'icons/magic/time/hourglass-yellow-green.webp', img: 'icons/magic/time/hourglass-yellow-green.webp',
icon: 'fa-person-running',
description: 'DAGGERHEART.CONFIG.DeathMoves.avoidDeath.description' description: 'DAGGERHEART.CONFIG.DeathMoves.avoidDeath.description'
}, },
riskItAll: { riskItAll: {
id: 'riskItAll', id: 'riskItAll',
name: 'DAGGERHEART.CONFIG.DeathMoves.riskItAll.name', name: 'DAGGERHEART.CONFIG.DeathMoves.riskItAll.name',
img: 'icons/sundries/gaming/dice-pair-white-green.webp', img: 'icons/sundries/gaming/dice-pair-white-green.webp',
icon: 'fa-dice',
description: 'DAGGERHEART.CONFIG.DeathMoves.riskItAll.description' description: 'DAGGERHEART.CONFIG.DeathMoves.riskItAll.description'
}, },
blazeOfGlory: { blazeOfGlory: {
id: 'blazeOfGlory', id: 'blazeOfGlory',
name: 'DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name', name: 'DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name',
img: 'icons/magic/life/heart-cross-strong-flame-purple-orange.webp', img: 'icons/magic/life/heart-cross-strong-flame-purple-orange.webp',
icon: 'fa-burst',
description: 'DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description' description: 'DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description'
} }
}; };

View file

@ -41,6 +41,14 @@ export default class DhActiveEffect extends ActiveEffect {
}); });
} }
get localizedStatuses() {
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status.name]));
return this.statuses.map(x => ({
key: x,
name: game.i18n.localize(statusMap.get(x))
}));
}
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
const update = {}; const update = {};
if (!data.img) { if (!data.img) {

View file

@ -388,6 +388,41 @@ export default class DhpActor extends Actor {
return this.system.difficulty ?? 10; return this.system.difficulty ?? 10;
} }
/** @inheritDoc */
async toggleStatusEffect(statusId, { active, overlay = false } = {}) {
const status = CONFIG.statusEffects.find(e => e.id === statusId);
if (!status) throw new Error(`Invalid status ID "${statusId}" provided to Actor#toggleStatusEffect`);
const existing = [];
// Find the effect with the static _id of the status effect
if (status._id) {
const effect = this.effects.get(status._id);
if (effect) existing.push(effect.id);
}
// If no static _id, find all effects that have this status
else {
for (const effect of this.effects) {
if (effect.statuses.has(status.id)) existing.push(effect.id);
}
}
// Remove the existing effects unless the status effect is forced active
if (existing.length) {
if (active) return true;
await this.deleteEmbeddedDocuments('ActiveEffect', existing);
return false;
}
// Create a new effect unless the status effect is forced inactive
if (!active && active !== undefined) return;
const ActiveEffect = getDocumentClass('ActiveEffect');
const effect = await ActiveEffect.fromStatusEffect(statusId);
if (overlay) effect.updateSource({ 'flags.core.overlay': true });
return ActiveEffect.implementation.create(effect, { parent: this, keepId: true });
}
getRollData() { getRollData() {
const rollData = super.getRollData(); const rollData = super.getRollData();
rollData.system = this.system.getRollData(); rollData.system = this.system.getRollData();
@ -508,16 +543,16 @@ export default class DhpActor extends Actor {
Object.entries(healings).forEach(([key, healing]) => { Object.entries(healings).forEach(([key, healing]) => {
healing.parts.forEach(part => { healing.parts.forEach(part => {
const update = updates.find(u => u.key === key); const update = updates.find(u => u.key === key);
if (update) if (update) update.value += part.total;
update.value += part.total;
else updates.push({ value: part.total, key }); else updates.push({ value: part.total, key });
}); });
}); });
updates.forEach( updates.forEach(
u => u =>
(u.value = (u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false)
!(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false) ? u.value * -1 : u.value) ? u.value * -1
: u.value)
); );
await this.modifyResource(updates); await this.modifyResource(updates);

View file

@ -87,6 +87,26 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
this.tooltip.innerHTML = html; this.tooltip.innerHTML = html;
} }
} }
const deathMove = element.dataset.tooltip?.startsWith('#deathMove#');
if (deathMove) {
const name = element.dataset.deathName;
const img = element.dataset.deathImg;
const description = element.dataset.deathDescription;
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/death-move.hbs`,
{
move: { name: name, img: img, description: description }
}
);
this.tooltip.innerHTML = html;
options.direction = this._determineItemTooltipDirection(
element,
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
);
}
} }
super.activate(element, { ...options, html: html }); super.activate(element, { ...options, html: html });

View file

@ -1,4 +1,5 @@
@import '../../utils/fonts.less'; @import '../../utils/fonts.less';
@import '../../utils/colors.less';
.application.daggerheart.dh-style { .application.daggerheart.dh-style {
.actions-list, .actions-list,
@ -36,7 +37,8 @@
.action-item { .action-item {
&:hover { &:hover {
background-color: rgba(255, 255, 255, 0.05); background-color: light-dark(@soft-shadow, @soft-white-shadow);
cursor: pointer;
} }
padding: 5px; padding: 5px;
border-radius: 5px; border-radius: 5px;
@ -47,7 +49,6 @@
align-items: center; align-items: center;
gap: 10px; gap: 10px;
font-family: @font-body; font-family: @font-body;
cursor: pointer;
flex: 1; flex: 1;
i { i {
text-align: center; text-align: center;

View file

@ -0,0 +1,57 @@
@import '../../utils/spacing.less';
@import '../../utils/colors.less';
@import '../../utils/fonts.less';
.daggerheart.dh-style.dialog.death-move {
.death-move-container {
display: flex;
flex-direction: column;
gap: 5px;
.moves-list {
.move-item {
display: flex;
align-items: center;
gap: 5px;
&:hover {
background-color: light-dark(@soft-shadow, @soft-white-shadow);
cursor: pointer;
}
padding: 5px;
border-radius: 5px;
transition: background-color 0.3s ease-in-out;
.label {
display: flex;
align-items: center;
gap: 10px;
font-family: @font-body;
cursor: pointer;
flex: 1;
i {
text-align: center;
width: 30px;
}
}
input[type='radio'] {
margin-left: auto;
}
}
}
}
footer {
margin-top: 8px;
display: flex;
gap: 8px;
button {
flex: 1;
height: 40px;
font-family: @font-body;
font-weight: 600;
}
}
}

View file

@ -1,5 +1,6 @@
@import '../../utils/spacing.less'; @import '../../utils/spacing.less';
@import '../../utils/colors.less'; @import '../../utils/colors.less';
@import '../../utils/fonts.less';
.theme-light .daggerheart.dh-style.views.downtime { .theme-light .daggerheart.dh-style.views.downtime {
.downtime-container .activity-container .activity-selected-marker { .downtime-container .activity-container .activity-selected-marker {
@ -69,7 +70,9 @@
button { button {
flex: 1; flex: 1;
font-family: 'Montserrat', sans-serif; height: 40px;
font-family: @font-body;
font-weight: 600;
} }
} }
} }

View file

@ -12,6 +12,8 @@
@import './downtime/downtime-container.less'; @import './downtime/downtime-container.less';
@import './death-move/death-move-container.less';
@import './beastform/sheet.less'; @import './beastform/sheet.less';
@import './character-creation/creation-action-footer.less'; @import './character-creation/creation-action-footer.less';

View file

@ -38,6 +38,8 @@
@beige-15: #efe6d815; @beige-15: #efe6d815;
@beige-50: #efe6d850; @beige-50: #efe6d850;
@soft-white-shadow: rgba(255, 255, 255, 0.05);
@light-black: rgba(0, 0, 0, 0.3); @light-black: rgba(0, 0, 0, 0.3);
@soft-shadow: rgba(0, 0, 0, 0.05); @soft-shadow: rgba(0, 0, 0, 0.05);

View file

@ -1,19 +1,22 @@
<div> <div>
<div class="downtime-container"> <header class="dialog-header">
{{#each this.options as |option key|}} <h1>{{title}}</h1>
<div class="activity-container"> </header>
<div class="activity-title"> <div class="death-move-container">
<img class="activity-image {{#if (eq ../selectedMove.id key)}}selected{{/if}}" src="{{option.img}}" data-action="selectMove" data-move="{{key}}" /> <ul class="moves-list">
<span class="activity-title-text">{{localize this.name}}</span> {{#each this.options as |option key|}}
</div> <li class="move-item" data-tooltip="{{concat "#" "deathMove" "#"}}" data-action="selectMove" data-move="{{key}}"data-death-img="{{option.img}}" data-death-name="{{this.name}}" data-death-description="{{this.description}}">
<div class="activity-body"> <label class="label" for="{{id}}" data-tooltip="{{localize tooltip}}" data-tooltip-direction="LEFT">
{{localize this.description}} <i class="fa-solid {{icon}} fa-xl"></i>
</div> <span class="label">{{localize this.name}}</span>
</div> </label>
{{/each}} <input type="radio" {{#if (eq ../this.selectedMove.id option.id)}}checked{{/if}}>
</li>
{{/each}}
</ul>
</div> </div>
<footer class="flexrow"> <footer class="flexrow">
<button data-action="takeMove" {{#if (not this.selectedMove)}}disabled{{/if}}>{{localize "DAGGERHEART.APPLICATIONS.DeathMove.takeMove"}}</button> <button data-action="close">{{localize "Cancel"}}</button>
<button data-action="close">{{localize "DAGGERHEART.Application.Cancel"}}</button> <button data-action="takeMove" {{#if (not this.selectedMove)}}disabled{{/if}}>{{localize "DAGGERHEART.APPLICATIONS.DeathMove.selectMove"}}</button>
</footer> </footer>
</div> </div>

View file

@ -156,8 +156,8 @@ Parameters:
{{localize 'DAGGERHEART.EFFECTS.Duration.passive'}} {{localize 'DAGGERHEART.EFFECTS.Duration.passive'}}
{{/if}} {{/if}}
</div> </div>
{{#each item.statuses as |status|}} {{#each item.localizedStatuses as |status|}}
<div class="tag">{{localize (concat 'DAGGERHEART.CONFIG.Condition.' status '.name')}}</div> <div class="tag">{{status.name}}</div>
{{/each}} {{/each}}
</div> </div>
{{else if (not hideLabels)}} {{else if (not hideLabels)}}

View file

@ -0,0 +1,7 @@
<div class="daggerheart dh-style tooltip">
<div class="tooltip-title-container">
<img class="tooltip-image" src="{{move.img}}" />
<h2 class="tooltip-title">{{localize move.name}}</h2>
</div>
<div class="tooltip-description">{{{localize move.description}}}</div>
</div>