Feature/armor stack uses on damage (#300)

* ArmorStack use as User query

* Remove unnecessart args

* Fixes
This commit is contained in:
Dapoulp 2025-07-08 21:06:46 +02:00 committed by GitHub
parent 861dfd977d
commit 059b814fdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 103 additions and 57 deletions

View file

@ -18,6 +18,7 @@ import {
} from './module/systemRegistration/_module.mjs'; } from './module/systemRegistration/_module.mjs';
import { placeables } from './module/canvas/_module.mjs'; import { placeables } from './module/canvas/_module.mjs';
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs'; import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
import { registerDHActorHooks } from './module/documents/actor.mjs';
Hooks.once('init', () => { Hooks.once('init', () => {
CONFIG.DH = SYSTEM; CONFIG.DH = SYSTEM;
@ -154,6 +155,7 @@ Hooks.on('ready', () => {
socketRegistration.registerSocketHooks(); socketRegistration.registerSocketHooks();
registerCountdownApplicationHooks(); registerCountdownApplicationHooks();
registerRollDiceHooks(); registerRollDiceHooks();
registerDHActorHooks();
}); });
Hooks.once('dicesoniceready', () => {}); Hooks.once('dicesoniceready', () => {});

View file

@ -1,6 +1,6 @@
import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs'; import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; const { DialogV2, ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) { export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(resolve, reject, actor, damage) { constructor(resolve, reject, actor, damage) {
@ -122,7 +122,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
getDamageInfo = () => { getDamageInfo = () => {
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected); const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected);
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected); const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
const stressReductions = Object.values(this.availableStressReductions).filter(red => red.selected); const stressReductions = Object.values(this.availableStressReductions ?? {}).filter(red => red.selected);
const currentMarks = const currentMarks =
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length; this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
@ -210,9 +210,17 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
async close(fromSave) { async close(fromSave) {
if (!fromSave) { if (!fromSave) {
this.reject(); this.resolve();
} }
await super.close({}); await super.close({});
} }
static async armorStackQuery({actorId, damage}) {
return new Promise(async (resolve, reject) => {
const actor = await fromUuid(actorId);
if(!actor || !actor?.isOwner) reject();
new DamageReductionDialog(resolve, reject, actor, damage).render({ force: true });
})
}
} }

View file

@ -215,7 +215,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1)); damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
await target.actor.takeDamage(damage, message.system.roll.type); target.actor.takeDamage(damage, message.system.roll.type);
} }
}; };
@ -227,7 +227,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected')); ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
for (var target of targets) { for (var target of targets) {
await target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]); target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]);
} }
}; };

View file

@ -1,10 +1,19 @@
import DamageSelectionDialog from '../applications/dialogs/damageSelectionDialog.mjs'; import DamageSelectionDialog from '../applications/dialogs/damageSelectionDialog.mjs';
import { GMUpdateEvent, socketEvent } from '../systemRegistration/socket.mjs'; import { emitAsGM, emitAsOwner, GMUpdateEvent, socketEvent } from '../systemRegistration/socket.mjs';
import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs'; import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs';
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
export default class DhpActor extends foundry.documents.Actor { export default class DhpActor extends Actor {
/**
* Return the first Actor active owner.
*/
get owner() {
const user = this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, "OWNER") && u.active);;
if(!user) return game.user.isGM ? game.user : null;
return user;
}
/** /**
* Whether this actor is an NPC. * Whether this actor is an NPC.
@ -440,49 +449,40 @@ export default class DhpActor extends foundry.documents.Actor {
} }
async takeDamage(damage, type) { async takeDamage(damage, type) {
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damage, type) === false) return null;
if (this.type === 'companion') { if (this.type === 'companion') {
await this.modifyResource([{ value: 1, type: 'stress' }]); await this.modifyResource([{ value: 1, type: 'stress' }]);
return; return;
} }
const hpDamage = const hpDamage = this.convertDamageToThreshold(damage);
damage >= this.system.damageThresholds.severe
? 3 if (Hooks.call(`${CONFIG.DH.id}.postDamageTreshold`, this, hpDamage, damage, type) === false) return null;
: damage >= this.system.damageThresholds.major
? 2 if(!hpDamage) return;
: damage >= this.system.damageThresholds.minor
? 1 const updates = [{ value: hpDamage, type: 'hitPoints' }];
: 0;
if ( if (
this.type === 'character' && this.type === 'character' &&
this.system.armor && this.system.armor &&
this.system.armor.system.marks.value < this.system.armorScore this.system.armor.system.marks.value < this.system.armorScore
) { ) {
new Promise((resolve, reject) => { const armorStackResult = await this.owner.query('armorStack', {actorId: this.uuid, damage: hpDamage});
new DamageReductionDialog(resolve, reject, this, hpDamage).render(true); if(armorStackResult) {
}) const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
.then(async ({ modifiedDamage, armorSpent, stressSpent }) => { updates.find(u => u.type === 'hitPoints').value = modifiedDamage;
const resources = [ updates.push(
{ value: modifiedDamage, type: 'hitPoints' }, ...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []),
...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []), ...(stressSpent ? [{ value: stressSpent, type: 'stress' }] : [])
...(stressSpent ? [{ value: stressSpent, type: 'stress' }] : []) );
]; }
await this.modifyResource(resources);
})
.catch(() => {
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: game.i18n.format('DAGGERHEART.UI.Notifications.damageIgnore', {
character: this.name
})
});
cls.create(msg.toObject());
});
} else {
await this.modifyResource([{ value: hpDamage, type: 'hitPoints' }]);
} }
await this.modifyResource(updates);
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null;
} }
async takeHealing(resources) { async takeHealing(resources) {
@ -492,6 +492,8 @@ export default class DhpActor extends foundry.documents.Actor {
async modifyResource(resources) { async modifyResource(resources) {
if (!resources.length) return; if (!resources.length) return;
if(resources.find(r => r.type === 'stress')) this.convertStressDamageToHP(resources);
let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } }; let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } };
resources.forEach(r => { resources.forEach(r => {
switch (r.type) { switch (r.type) {
@ -519,7 +521,8 @@ export default class DhpActor extends foundry.documents.Actor {
}); });
Object.values(updates).forEach(async u => { Object.values(updates).forEach(async u => {
if (Object.keys(u.resources).length > 0) { if (Object.keys(u.resources).length > 0) {
if (game.user.isGM) { await emitAsGM(GMUpdateEvent.UpdateDocument, u.target.update.bind(u.target), u.resources, u.target.uuid);
/* if (game.user.isGM) {
await u.target.update(u.resources); await u.target.update(u.resources);
} else { } else {
await game.socket.emit(`system.${CONFIG.DH.id}`, { await game.socket.emit(`system.${CONFIG.DH.id}`, {
@ -530,8 +533,34 @@ export default class DhpActor extends foundry.documents.Actor {
update: u.resources update: u.resources
} }
}); });
} } */
} }
}); });
} }
convertDamageToThreshold(damage) {
return damage >= this.system.damageThresholds.severe
? 3
: damage >= this.system.damageThresholds.major
? 2
: damage >= this.system.damageThresholds.minor
? 1
: 0;
}
convertStressDamageToHP(resources) {
const stressDamage = resources.find(r => r.type === 'stress'),
newValue = this.system.resources.stress.value + stressDamage.value;
if(newValue <= this.system.resources.stress.maxTotal) return;
const hpDamage = resources.find(r => r.type === 'hitPoints');
if(hpDamage) hpDamage.value++;
else resources.push({
type: 'hitPoints',
value: 1
})
}
} }
export const registerDHActorHooks = () => {
CONFIG.queries.armorStack = DamageReductionDialog.armorStackQuery;
}

View file

@ -63,21 +63,28 @@ export const registerSocketHooks = () => {
}); });
}; };
export const emitAsGM = async (eventName, callback, args) => { export const emitAsGM = async (eventName, callback, update, uuid = null) => {
if(!game.user.isGM) { if(!game.user.isGM) {
return new Promise(async (resolve, reject) => { return await game.socket.emit(`system.${CONFIG.DH.id}`, {
try { action: socketEvent.GMUpdate,
const response = await game.socket.emit(`system.${CONFIG.DH.id}`, { data: {
action: socketEvent.GMUpdate, action: eventName,
data: { uuid,
action: eventName, update
update: args
}
});
resolve(response);
} catch (error) {
reject(error);
} }
}) });
} else return callback(args); } else return callback(update);
} }
export const emitAsOwner = (eventName, userId, args) => {
if(userId === game.user.id) return;
if(!eventName || !userId) return false;
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: eventName,
data: {
userId,
...args
}
});
return false;
}

View file

@ -49,7 +49,7 @@
<div class="dice-result"> <div class="dice-result">
{{#if damage.roll}} {{#if damage.roll}}
<div class="dice-actions"> <div class="dice-actions">
<button class="damage-button">{{localize "DAGGERHEART.UI.Chat.DamageRoll.dealDamage"}}</button> <button class="damage-button">{{localize "DAGGERHEART.UI.Chat.damageRoll.dealDamage"}}</button>
</div> </div>
{{else}} {{else}}
<div class="flexrow"> <div class="flexrow">

View file

@ -134,7 +134,7 @@
<div class="dice-actions{{#unless (or hasDamage hasHealing)}} duality-alone{{/unless}}"> <div class="dice-actions{{#unless (or hasDamage hasHealing)}} duality-alone{{/unless}}">
{{#if hasDamage}} {{#if hasDamage}}
{{#if damage.roll}} {{#if damage.roll}}
<button class="duality-action damage-button" data-target-hit="true" data-value="{{roll.total}}"><span>Deal Damage</span></button> <button class="duality-action damage-button" data-target-hit="true" data-value="{{roll.total}}"><span>{{localize "DAGGERHEART.UI.Chat.damageRoll.dealDamage"}}</span></button>
{{else}} {{else}}
<button class="duality-action duality-action-damage" data-value="{{roll.total}}"><span>{{localize "DAGGERHEART.UI.Chat.attackRoll.rollDamage"}}</span></button> <button class="duality-action duality-action-damage" data-value="{{roll.total}}"><span>{{localize "DAGGERHEART.UI.Chat.attackRoll.rollDamage"}}</span></button>
{{/if}} {{/if}}