Merged with development

This commit is contained in:
WBHarry 2025-09-06 23:14:31 +02:00
commit 2aa252b321
498 changed files with 4489 additions and 2601 deletions

View file

@ -14,7 +14,7 @@ export default class DhpActor extends Actor {
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;
if (!user) return game.users.activeGM;
return user;
}
@ -663,13 +663,22 @@ export default class DhpActor extends Actor {
};
resources.forEach(r => {
if (r.keyIsID) {
updates.items[r.key] = {
target: r.target,
resources: {
'system.resource.value': r.target.system.resource.value + r.value
}
};
if (r.itemId) {
const { path, value } = game.system.api.fields.ActionFields.CostField.getItemIdCostUpdate(r);
if (
r.key === 'quantity' &&
r.target.type === 'consumable' &&
value === 0 &&
r.target.system.destroyOnEmpty
) {
r.target.delete();
} else {
updates.items[r.key] = {
target: r.target,
resources: { [path]: value }
};
}
} else {
switch (r.key) {
case 'fear':

View file

@ -1,3 +1,5 @@
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
export default class DhpChatMessage extends foundry.documents.ChatMessage {
targetHook = null;
@ -103,19 +105,29 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
}
if (!game.user.isGM) {
if (!this.isAuthor && !this.speakerActor?.isOwner) {
const applyButtons = html.querySelector('.apply-buttons');
applyButtons?.remove();
if (!this.isAuthor && !this.speakerActor?.isOwner) {
const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button');
buttons.forEach(b => b.remove());
}
const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button');
buttons.forEach(b => b.remove());
}
}
addChatListeners(html) {
html.querySelectorAll('.duality-action-damage').forEach(element =>
element.addEventListener('click', this.onRollDamage.bind(this))
);
html.querySelectorAll('.damage-button').forEach(element =>
element.addEventListener('click', this.onDamage.bind(this))
element.addEventListener('click', this.onApplyDamage.bind(this))
);
html.querySelectorAll('.target-save').forEach(element =>
element.addEventListener('click', this.onRollSave.bind(this))
);
html.querySelectorAll('.roll-all-save-button').forEach(element =>
element.addEventListener('click', this.onRollAllSave.bind(this))
);
html.querySelectorAll('.duality-action-effect').forEach(element =>
@ -139,17 +151,21 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
});
}
getTargetList() {
const targets = this.system.hitTargets ?? [];
return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId));
async onRollDamage(event) {
event.stopPropagation();
const config = foundry.utils.deepClone(this.system);
config.event = event;
this.system.action?.workflow.get('damage')?.execute(config, this._id, true);
}
async onDamage(event) {
async onApplyDamage(event) {
event.stopPropagation();
const targets = this.getTargetList();
const targets = this.filterPermTargets(this.system.hitTargets),
config = foundry.utils.deepClone(this.system);
config.event = event;
if (this.system.onSave) {
const pendingingSaves = this.system.hitTargets.filter(t => t.saved.success === null);
const pendingingSaves = targets.filter(t => t.saved.success === null);
if (pendingingSaves.length) {
const confirm = await foundry.applications.api.DialogV2.confirm({
window: { title: 'Pending Reaction Rolls found' },
@ -160,38 +176,37 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
}
if (targets.length === 0)
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
const targetDamage = [];
const damagePromises = [];
for (let target of targets) {
let damages = foundry.utils.deepClone(this.system.damage);
if (
!this.system.hasHealing &&
this.system.onSave &&
this.system.hitTargets.find(t => t.id === target.id)?.saved?.success === true
) {
const mod = CONFIG.DH.ACTIONS.damageOnSave[this.system.onSave]?.mod ?? 1;
Object.entries(damages).forEach(([k, v]) => {
v.total = 0;
v.parts.forEach(part => {
part.total = Math.ceil(part.total * mod);
v.total += part.total;
});
});
}
this.consumeOnSuccess();
this.system.action?.workflow.get('applyDamage')?.execute(config, targets, true);
}
this.consumeOnSuccess();
if (this.system.hasHealing)
damagePromises.push(
target.actor.takeHealing(damages).then(updates => targetDamage.push({ token: target, updates }))
);
else
damagePromises.push(
target.actor
.takeDamage(damages, this.system.isDirect)
.then(updates => targetDamage.push({ token: target, updates }))
);
async onRollSave(event) {
event.stopPropagation();
const tokenId = event.target.closest('[data-token]')?.dataset.token,
token = game.canvas.tokens.get(tokenId);
if (!token?.actor || !token.isOwner) return true;
if (this.system.source.item && this.system.source.action) {
const action = this.system.action;
if (!action || !action?.hasSave) return;
game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result =>
emitAsGM(
GMUpdateEvent.UpdateSaveMessage,
game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(
action,
result,
this,
token.id
),
{
action: action.uuid,
message: this._id,
token: token.id,
result
}
)
);
}
Promise.all(damagePromises).then(async _ => {
@ -214,56 +229,32 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
});
}
getAction(actor, itemId, actionId) {
const item = actor.items.get(itemId),
action =
actor.system.attack?._id === actionId
? actor.system.attack
: item.system.attack?._id === actionId
? item.system.attack
: item?.system?.actions?.get(actionId);
return action;
async onRollAllSave(event) {
event.stopPropagation();
if (!game.user.isGM) return;
const targets = this.system.hitTargets,
config = foundry.utils.deepClone(this.system);
config.event = event;
this.system.action?.workflow.get('save')?.execute(config, targets, true);
}
async onApplyEffect(event) {
event.stopPropagation();
const actor = await foundry.utils.fromUuid(this.system.source.actor);
if (!actor || !game.user.isGM) return true;
if (this.system.source.item && this.system.source.action) {
const action = this.getAction(actor, this.system.source.item, this.system.source.action);
if (!action || !action?.applyEffects) return;
const targets = this.getTargetList();
if (targets.length === 0)
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
this.consumeOnSuccess();
await action.applyEffects(event, this, targets);
const targets = this.filterPermTargets(this.system.hitTargets),
config = foundry.utils.deepClone(this.system);
config.event = event;
if (targets.length === 0)
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
this.consumeOnSuccess();
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
}
const effects = this.system.action.item.effects.filter(effect =>
this.system.action.effects.some(x => x._id === effect.id)
);
const cls = getDocumentClass('ChatMessage');
const msg = {
user: game.user.id,
speaker: cls.getSpeaker(),
title: game.i18n.localize('DAGGERHEART.UI.Chat.effectSummary.title'),
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/effectSummary.hbs',
{
effects,
targets
}
)
};
cls.create(msg);
}
filterPermTargets(targets) {
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
}
consumeOnSuccess() {
if (!this.system.successConsumed && !this.targetSelection) {
const action = this.system.action;
if (action) action.consume(this.system, true);
}
if (!this.system.successConsumed && !this.targetSelection) this.system.action?.consume(this.system, true);
}
hoverTarget(event) {