mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-17 15:39:02 +01:00
Merged with main
This commit is contained in:
commit
f679e87a4b
23 changed files with 317 additions and 118 deletions
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from './module/systemRegistration/_module.mjs';
|
||||
import { placeables } from './module/canvas/_module.mjs';
|
||||
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
|
||||
import { registerDHActorHooks } from './module/documents/actor.mjs';
|
||||
|
||||
Hooks.once('init', () => {
|
||||
CONFIG.DH = SYSTEM;
|
||||
|
|
@ -154,6 +155,7 @@ Hooks.on('ready', () => {
|
|||
socketRegistration.registerSocketHooks();
|
||||
registerCountdownApplicationHooks();
|
||||
registerRollDiceHooks();
|
||||
registerDHActorHooks();
|
||||
});
|
||||
|
||||
Hooks.once('dicesoniceready', () => {});
|
||||
|
|
|
|||
|
|
@ -214,6 +214,10 @@
|
|||
"encounter": "Encounter"
|
||||
}
|
||||
},
|
||||
"DeleteConfirmation": {
|
||||
"title": "Delete {type} - {name}",
|
||||
"text": "Are you sure you want to delete {name}?"
|
||||
},
|
||||
"DamageReduction": {
|
||||
"armorMarks": "Armor Marks",
|
||||
"armorWithStress": "Spend 1 stress to use an extra mark",
|
||||
|
|
@ -1126,6 +1130,7 @@
|
|||
"examples": { "label": "Examples" },
|
||||
"advantageOn": { "label": "Gain Advantage On" },
|
||||
"tokenImg": { "label": "Token Image" },
|
||||
"tokenRingImg": { "label": "Subject Texture" },
|
||||
"tokenSize": {
|
||||
"placeholder": "Using character dimensions",
|
||||
"height": { "label": "Height" },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
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) {
|
||||
constructor(resolve, reject, actor, damage, damageType) {
|
||||
|
|
@ -223,9 +223,17 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
async close(fromSave) {
|
||||
if (!fromSave) {
|
||||
this.reject();
|
||||
this.resolve();
|
||||
}
|
||||
|
||||
await super.close({});
|
||||
}
|
||||
|
||||
static async armorStackQuery({ actorId, damage, type }) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const actor = await fromUuid(actorId);
|
||||
if (!actor || !actor?.isOwner) reject();
|
||||
new DamageReductionDialog(resolve, reject, actor, damage, type).render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,18 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #removeExperience(_, target) {
|
||||
const experience = this.actor.system.experiences[target.dataset.experience];
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.GENERAL.Experience.single`),
|
||||
name: experience.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: experience.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,10 +82,24 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
static async #deleteAdversary(event, target) {
|
||||
const adversaryKey = target.dataset.adversary;
|
||||
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
|
||||
console.log(target.dataset.potentialAdversar);
|
||||
const newAdversaries = foundry.utils
|
||||
.getProperty(this.actor, path)
|
||||
.filter(x => x && (x?.uuid ?? x) !== adversaryKey);
|
||||
const property = foundry.utils.getProperty(this.actor, path);
|
||||
const adversary = property.find(x => (x?.uuid ?? x) === adversaryKey);
|
||||
|
||||
if (adversary) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize('TYPES.Actor.adversary'),
|
||||
name: adversary.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: adversary.name })
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
const newAdversaries = property.filter(x => x && (x?.uuid ?? x) !== adversaryKey);
|
||||
await this.actor.update({ [path]: newAdversaries });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,9 +92,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
|
||||
description: `${experience.name} ${
|
||||
experience.modifier < 0 ? experience.modifier : `+${experience.modifier}`
|
||||
}`
|
||||
description: `${experience.name} ${experience.total.signedString()}`
|
||||
};
|
||||
const msg = new cls({
|
||||
type: 'abilityUse',
|
||||
|
|
|
|||
|
|
@ -188,7 +188,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* @param {HTMLElement} el
|
||||
* @returns {foundry.documents.Item?}
|
||||
*/
|
||||
const getItem = el => this.actor.items.get(el.closest('[data-item-id]')?.dataset.itemId);
|
||||
const getItem = element => {
|
||||
const listElement = (element.target ?? element).closest('[data-item-id]');
|
||||
const itemId = listElement.dataset.itemId;
|
||||
|
||||
switch (listElement.dataset.type) {
|
||||
case 'effect':
|
||||
return this.document.effects.get(itemId);
|
||||
default:
|
||||
return this.document.items.get(itemId);
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
|
|
@ -249,7 +259,23 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
{
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
callback: el => getItem(el).delete()
|
||||
callback: async el => {
|
||||
const item = getItem(el);
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`TYPES.${item.documentName}.${item.type}`),
|
||||
name: item.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
|
||||
name: item.name
|
||||
})
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
item.delete();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
@ -495,7 +521,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const config = {
|
||||
event: event,
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilitychecktitle', {
|
||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: abilityLabel
|
||||
}),
|
||||
roll: {
|
||||
|
|
|
|||
|
|
@ -42,9 +42,12 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee
|
|||
/**@inheritdoc */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
||||
context.isNPC = this.actor.isNPC;
|
||||
|
||||
if (context.systemFields.attack) {
|
||||
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,7 +218,6 @@ export default function DHApplicationMixin(Base) {
|
|||
*/
|
||||
static async #createDoc(event, button) {
|
||||
const { documentClass, type } = button.dataset;
|
||||
console.log(documentClass, type);
|
||||
const parent = this.document;
|
||||
|
||||
const cls = getDocumentClass(documentClass);
|
||||
|
|
@ -250,7 +249,23 @@ export default function DHApplicationMixin(Base) {
|
|||
*/
|
||||
static async #deleteDoc(_event, button) {
|
||||
const { type, docId } = button.dataset;
|
||||
await this.document.getEmbeddedDocument(type, docId, { strict: true }).delete();
|
||||
const document = this.document.getEmbeddedDocument(type, docId, { strict: true });
|
||||
const typeName = game.i18n.localize(
|
||||
document.type === 'base' ? `DOCUMENT.${type}` : `TYPES.${type}.${document.type}`
|
||||
);
|
||||
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: typeName,
|
||||
name: document.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: document.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
await document.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,19 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
static async #removeAction(event, button) {
|
||||
event.stopPropagation();
|
||||
const actionIndex = button.closest('[data-index]').dataset.index;
|
||||
const action = this.document.system.actions[actionIndex];
|
||||
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||
name: action.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
await this.document.update({
|
||||
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
|
||||
});
|
||||
|
|
@ -180,6 +193,20 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
static async #removeFeature(event, button) {
|
||||
event.stopPropagation();
|
||||
const target = button.closest('.feature-item');
|
||||
const feature = this.document.system.features.find(x => x && x.id === target.id);
|
||||
|
||||
if (feature) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`TYPES.Item.feature`),
|
||||
name: feature.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
await this.document.update({
|
||||
'system.features': this.document.system.features
|
||||
|
|
|
|||
|
|
@ -30,8 +30,17 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
|||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
||||
context.document = context.document.toObject();
|
||||
context.document.effects = this.document.effects.map(effect => {
|
||||
const data = effect.toObject();
|
||||
data.id = effect.id;
|
||||
if (effect.type === 'beastform') data.mandatory = true;
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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'));
|
||||
|
||||
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 }]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
|||
base64: false,
|
||||
nullable: true
|
||||
}),
|
||||
tokenRingImg: new fields.FilePathField({
|
||||
initial: 'icons/svg/mystery-man.svg',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
tokenSize: new fields.SchemaField({
|
||||
height: new fields.NumberField({ integer: true, nullable: true }),
|
||||
width: new fields.NumberField({ integer: true, nullable: true })
|
||||
|
|
@ -28,6 +33,11 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
|||
width: this.characterTokenData.tokenSize.width,
|
||||
texture: {
|
||||
src: this.characterTokenData.tokenImg
|
||||
},
|
||||
ring: {
|
||||
subject: {
|
||||
texture: this.characterTokenData.tokenRingImg
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.adversary',
|
||||
type: 'adversary',
|
||||
settingSheet: DHAdversarySettings,
|
||||
settingSheet: DHAdversarySettings
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField(),
|
||||
modifier: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
total: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
})
|
||||
),
|
||||
bonuses: new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayFie
|
|||
import BaseDataItem from './base.mjs';
|
||||
|
||||
export default class DHBeastform extends BaseDataItem {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Beastform'];
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ITEMS.Beastform'];
|
||||
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
|
|
@ -29,12 +29,17 @@ export default class DHBeastform extends BaseDataItem {
|
|||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
tokenRingImg: new fields.FilePathField({
|
||||
initial: 'icons/svg/mystery-man.svg',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
tokenSize: new fields.SchemaField({
|
||||
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||
}),
|
||||
examples: new fields.StringField(),
|
||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
||||
advantageOn: new fields.StringField(),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
|
||||
};
|
||||
}
|
||||
|
|
@ -56,40 +61,54 @@ export default class DHBeastform extends BaseDataItem {
|
|||
'Item',
|
||||
this.features.map(x => x.toObject())
|
||||
);
|
||||
const effects = await this.parent.parent.createEmbeddedDocuments(
|
||||
|
||||
const extraEffects = await this.parent.parent.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
this.parent.effects.map(x => x.toObject())
|
||||
this.parent.effects.filter(x => x.type !== 'beastform').map(x => x.toObject())
|
||||
);
|
||||
|
||||
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
type: 'beastform',
|
||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp',
|
||||
system: {
|
||||
isBeastform: true,
|
||||
characterTokenData: {
|
||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||
tokenSize: {
|
||||
height: this.parent.parent.prototypeToken.height,
|
||||
width: this.parent.parent.prototypeToken.width
|
||||
}
|
||||
},
|
||||
advantageOn: this.advantageOn,
|
||||
featureIds: features.map(x => x.id),
|
||||
effectIds: effects.map(x => x.id)
|
||||
}
|
||||
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
||||
await beastformEffect.updateSource({
|
||||
system: {
|
||||
characterTokenData: {
|
||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
|
||||
tokenSize: {
|
||||
height: this.parent.parent.prototypeToken.height,
|
||||
width: this.parent.parent.prototypeToken.width
|
||||
}
|
||||
},
|
||||
advantageOn: this.advantageOn,
|
||||
featureIds: features.map(x => x.id),
|
||||
effectIds: extraEffects.map(x => x.id)
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
|
||||
|
||||
await updateActorTokens(this.parent.parent, {
|
||||
height: this.tokenSize.height,
|
||||
width: this.tokenSize.width,
|
||||
texture: {
|
||||
src: this.tokenImg
|
||||
},
|
||||
ring: {
|
||||
subject: {
|
||||
texture: this.tokenRingImg
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onCreate() {
|
||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
type: 'beastform',
|
||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp'
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
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 { LevelOptionType } from '../data/levelTier.mjs';
|
||||
import DHFeature from '../data/item/feature.mjs';
|
||||
import { damageKeyToNumber, getDamageKey } from '../helpers/utils.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.
|
||||
|
|
@ -361,16 +370,16 @@ export default class DhpActor extends foundry.documents.Actor {
|
|||
const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null;
|
||||
return modifier !== null
|
||||
? [
|
||||
{
|
||||
value: modifier,
|
||||
label: roll.label
|
||||
? modifier >= 0
|
||||
? `${roll.label} +${modifier}`
|
||||
: `${roll.label} ${modifier}`
|
||||
: null,
|
||||
title: roll.label
|
||||
}
|
||||
]
|
||||
{
|
||||
value: modifier,
|
||||
label: roll.label
|
||||
? modifier >= 0
|
||||
? `${roll.label} +${modifier}`
|
||||
: `${roll.label} ${modifier}`
|
||||
: null,
|
||||
title: roll.label
|
||||
}
|
||||
]
|
||||
: [];
|
||||
}
|
||||
|
||||
|
|
@ -457,6 +466,8 @@ export default class DhpActor extends foundry.documents.Actor {
|
|||
}
|
||||
|
||||
async takeDamage(baseDamage, type) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, baseDamage, type) === false) return null;
|
||||
|
||||
if (this.type === 'companion') {
|
||||
await this.modifyResource([{ value: 1, type: 'stress' }]);
|
||||
return;
|
||||
|
|
@ -464,41 +475,33 @@ export default class DhpActor extends foundry.documents.Actor {
|
|||
|
||||
const flatReduction = this.system.bonuses.damageReduction[type];
|
||||
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
|
||||
const hpDamage = this.convertDamageToThreshold(damage);
|
||||
|
||||
const hpDamage =
|
||||
damage >= this.system.damageThresholds.severe
|
||||
? 3
|
||||
: damage >= this.system.damageThresholds.major
|
||||
? 2
|
||||
: damage >= this.system.damageThresholds.minor
|
||||
? 1
|
||||
: 0;
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postDamageTreshold`, this, hpDamage, damage, type) === false) return null;
|
||||
|
||||
if (hpDamage && this.type === 'character' && this.#canReduceDamage(hpDamage, type)) {
|
||||
new Promise((resolve, reject) => {
|
||||
new DamageReductionDialog(resolve, reject, this, hpDamage, type).render(true);
|
||||
})
|
||||
.then(async ({ modifiedDamage, armorSpent, stressSpent }) => {
|
||||
const resources = [
|
||||
{ value: modifiedDamage, type: 'hitPoints' },
|
||||
...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []),
|
||||
...(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' }]);
|
||||
if (!hpDamage) return;
|
||||
|
||||
const updates = [{ value: hpDamage, type: 'hitPoints' }];
|
||||
|
||||
if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage, type)) {
|
||||
const armorStackResult = await this.owner.query('armorStack', {
|
||||
actorId: this.uuid,
|
||||
damage: hpDamage,
|
||||
type: type
|
||||
});
|
||||
if (armorStackResult) {
|
||||
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
|
||||
updates.find(u => u.type === 'hitPoints').value = modifiedDamage;
|
||||
updates.push(
|
||||
...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []),
|
||||
...(stressSpent ? [{ value: stressSpent, type: 'stress' }] : [])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await this.modifyResource(updates);
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null;
|
||||
}
|
||||
|
||||
async takeHealing(resources) {
|
||||
|
|
@ -508,6 +511,8 @@ export default class DhpActor extends foundry.documents.Actor {
|
|||
|
||||
async modifyResource(resources) {
|
||||
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: {} } };
|
||||
resources.forEach(r => {
|
||||
switch (r.type) {
|
||||
|
|
@ -535,7 +540,13 @@ export default class DhpActor extends foundry.documents.Actor {
|
|||
});
|
||||
Object.values(updates).forEach(async u => {
|
||||
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);
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
|
|
@ -546,8 +557,35 @@ export default class DhpActor extends foundry.documents.Actor {
|
|||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,7 +45,13 @@ export const registerSocketHooks = () => {
|
|||
await game.settings.set(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Resources.Fear,
|
||||
Math.max(0, Math.min(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear, data.update))
|
||||
Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||
data.update
|
||||
)
|
||||
)
|
||||
);
|
||||
/* Hooks.callAll(socketEvent.DhpFearUpdate);
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.DhpFearUpdate }); */
|
||||
|
|
@ -63,21 +69,28 @@ export const registerSocketHooks = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const emitAsGM = async (eventName, callback, args) => {
|
||||
if(!game.user.isGM) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const response = await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: eventName,
|
||||
update: args
|
||||
}
|
||||
});
|
||||
resolve(response);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
export const emitAsGM = async (eventName, callback, update, uuid = null) => {
|
||||
if (!game.user.isGM) {
|
||||
return await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: eventName,
|
||||
uuid,
|
||||
update
|
||||
}
|
||||
})
|
||||
} 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
{{#each document.system.experiences as |experience key|}}
|
||||
<li class="experience-item">
|
||||
<input class="name" type="text" name="system.experiences.{{key}}.name" value="{{experience.name}}" />
|
||||
<input class="modifier" type="text" name="system.experiences.{{key}}.modifier" value="{{experience.modifier}}" data-dtype="Number" />
|
||||
<input class="modifier" type="text" name="system.experiences.{{key}}.total" value="{{experience.total}}" data-dtype="Number" />
|
||||
<a data-action="removeExperience" data-experience="{{key}}" data-tooltip="{{localize 'CONTROLS.CommonDelete'}}"><i class="fa-solid fa-trash"></i></a>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
{{#each source.system.experiences as |experience id|}}
|
||||
<div class="experience-row">
|
||||
<div class="experience-value">
|
||||
+{{experience.modifier}}
|
||||
+{{experience.total}}
|
||||
</div>
|
||||
<span class="experience-name">{{experience.name}}</span>
|
||||
<div class="controls">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<span>{{effect.name}}</span>
|
||||
<div class="controls">
|
||||
<a data-action="editDoc" data-type="ActiveEffect" data-doc-id="{{effect.id}}"><i class="fa-solid fa-pen-to-square"></i></a>
|
||||
<a data-action="deleteDoc" data-type="ActiveEffect" data-doc-id="{{effect.id}}"><i class="fa-solid fa-trash"></i></a>
|
||||
<a data-action="deleteDoc" data-type="ActiveEffect" data-doc-id="{{effect.id}}" {{disabled effect.mandatory}}><i class="fa-solid fa-trash icon-button {{disabled effect.mandatory}}"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@
|
|||
{{formGroup systemFields.examples value=source.system.examples localize=true}}
|
||||
</div>
|
||||
|
||||
<fieldset class="two-columns">
|
||||
<legend>{{localize "DAGGERHEART.ITEMS.Beastform.FIELDS.advantageOn.label"}}</legend>
|
||||
|
||||
{{!-- {{formGroup systemFields.examples value=source.system.examples localize=true}} --}}
|
||||
</fieldset>
|
||||
{{formGroup systemFields.advantageOn value=source.system.advantageOn localize=true}}
|
||||
|
||||
<fieldset class="two-columns even">
|
||||
<legend>{{localize "DAGGERHEART.ITEMS.Beastform.tokenTitle"}}</legend>
|
||||
|
|
@ -21,6 +17,10 @@
|
|||
{{formGroup systemFields.tokenImg value=source.system.tokenImg localize=true}}
|
||||
</div>
|
||||
|
||||
<div class="full-width">
|
||||
{{formGroup systemFields.tokenRingImg value=source.system.tokenRingImg localize=true}}
|
||||
</div>
|
||||
|
||||
{{formGroup systemFields.tokenSize.fields.height value=source.system.tokenSize.height localize=true placeholder=(localize "DAGGERHEART.ITEMS.Beastform.FIELDS.tokenSize.placeholder") }}
|
||||
{{formGroup systemFields.tokenSize.fields.width value=source.system.tokenSize.width localize=true placeholder=(localize "DAGGERHEART.ITEMS.Beastform.FIELDS.tokenSize.placeholder")}}
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
<div class="dice-result">
|
||||
{{#if damage.roll}}
|
||||
<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>
|
||||
{{else}}
|
||||
<div class="flexrow">
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
<div class="dice-actions{{#unless (or hasDamage hasHealing)}} duality-alone{{/unless}}">
|
||||
{{#if hasDamage}}
|
||||
{{#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}}
|
||||
<button class="duality-action duality-action-damage" data-value="{{roll.total}}"><span>{{localize "DAGGERHEART.UI.Chat.attackRoll.rollDamage"}}</span></button>
|
||||
{{/if}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue