mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-07-22 02:19:54 +02:00
[Rework] ChatMessage Damage (#2079)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
* Initial rework * Removed unneeded method * Removed outcommented code * Added migration * Fixed DamageActions * Semi-corrected reroll of one of multiple results on a die * Code improvement * Added aseDie.rerollResult method to specifically reroll a specific result die in a grouping * TagTeamDialog somewhat working * Added migration for TagTeamData * Fix for mean tagTeamDialog.finish * . * Improved migration * Internalised ChatDamageData.prepareRolls to its own constructor * Moved DamageTypes to roll.options * TagTeamDialog fixes to accomodate new flat damage.types structure * Changed so all default rolls become BaseRolls to access convenience functions and getters * . * Fixed so that party.tagTeam now uses a declared damageRollData(ChatDamageData) field. rollData.options.damage is retired. * Critical damage fixes * Corrected TagTeamDialog rerender logic when selecting a roll * Fixed so that DamageTypes are retained and joined together throughout TagTeamDialog * Removed some remaining types.<>.roll references * [Rework] Damage and Damage Resource split (#2094) * Changed the label of ActionSheet damage.resources to Mark Resources * Fix assigning includeBase * Localize add resource * Maybe simplify API * Fix onRollSimple * Handle erroring action update differently --------- Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
This commit is contained in:
parent
4b651836ff
commit
effab8db0c
51 changed files with 1007 additions and 788 deletions
|
|
@ -13,18 +13,19 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
if (this.damage.includeBase) {
|
||||
const baseDamage = this.getParentHitPointDamage();
|
||||
if (baseDamage) {
|
||||
if (!this.damage.parts.hitPoints) {
|
||||
this.damage.parts.hitPoints = baseDamage;
|
||||
if (!this.damage.main) {
|
||||
this.damage.main = baseDamage;
|
||||
} else {
|
||||
for (const type of baseDamage.type) this.damage.parts.hitPoints.type.add(type);
|
||||
for (const type of baseDamage.type) this.damage.main.type.add(type);
|
||||
|
||||
this.damage.parts.hitPoints.value.custom = {
|
||||
this.damage.main.value.custom = {
|
||||
enabled: true,
|
||||
formula: `${baseDamage.value.getFormula()} + ${this.damage.parts.hitPoints.value.getFormula()}`
|
||||
formula: `${baseDamage.value.getFormula()} + ${this.damage.main.value.getFormula()}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.roll.useDefault) {
|
||||
this.roll.trait = this.item.system.attack.roll.trait;
|
||||
this.roll.type = 'attack';
|
||||
|
|
@ -33,18 +34,18 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
}
|
||||
|
||||
getParentHitPointDamage() {
|
||||
return this.item?.system?.attack.damage.parts.hitPoints;
|
||||
return this.item?.system?.attack.damage.main;
|
||||
}
|
||||
|
||||
get damageFormula() {
|
||||
const hitPointsPart = this.damage.parts.hitPoints;
|
||||
const hitPointsPart = this.damage.main;
|
||||
if (!hitPointsPart) return '0';
|
||||
|
||||
return hitPointsPart.value.getFormula();
|
||||
}
|
||||
|
||||
get altDamageFormula() {
|
||||
const hitPointsPart = this.damage.parts.hitPoints;
|
||||
const hitPointsPart = this.damage.main;
|
||||
if (!hitPointsPart) return '0';
|
||||
|
||||
return hitPointsPart.valueAlt.getFormula();
|
||||
|
|
@ -73,7 +74,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
||||
|
||||
const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
|
||||
for (const { value, valueAlt, type } of damage.parts) {
|
||||
for (const { value, valueAlt, type } of [damage.main, ...damage.resources].filter(d => !!d)) {
|
||||
const usedValue = useAltDamage ? valueAlt : value;
|
||||
const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
||||
const str = damageString
|
||||
|
|
@ -82,7 +83,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
|
||||
});
|
||||
|
||||
const icons = Array.from(type)
|
||||
const icons = Array.from(type ?? [])
|
||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||
.filter(Boolean);
|
||||
|
||||
|
|
|
|||
|
|
@ -289,7 +289,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
hasEffect: this.hasEffect,
|
||||
hasSave: this.hasSave,
|
||||
onSave: this.save?.damageMod,
|
||||
isDirect: !!this.damage?.direct,
|
||||
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
||||
data: this.getRollData(),
|
||||
evaluate: this.hasRoll,
|
||||
|
|
@ -307,20 +306,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
};
|
||||
|
||||
if (this.damage) {
|
||||
config.isDirect = this.damage.direct;
|
||||
config.isDirect = !!this.damage.main?.direct;
|
||||
|
||||
const groupAttackTokens = this.damage.groupAttack
|
||||
const groupAttackTokens = this.damage.main?.groupAttack
|
||||
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
|
||||
this.actor.id,
|
||||
this.damage.groupAttack
|
||||
this.damage.main.groupAttack
|
||||
)
|
||||
: null;
|
||||
|
||||
config.damageOptions = {
|
||||
groupAttack: this.damage.groupAttack
|
||||
groupAttack: this.damage.main?.groupAttack
|
||||
? {
|
||||
numAttackers: Math.max(groupAttackTokens.length, 1),
|
||||
range: this.damage.groupAttack
|
||||
range: this.damage.main.groupAttack
|
||||
}
|
||||
: null
|
||||
};
|
||||
|
|
@ -430,11 +429,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
}
|
||||
|
||||
get hasDamage() {
|
||||
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing';
|
||||
return this.type !== 'healing' && (Boolean(this.damage.main) || !foundry.utils.isEmpty(this.damage.resources));
|
||||
}
|
||||
|
||||
get hasHealing() {
|
||||
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing';
|
||||
return this.type === 'healing' && !foundry.utils.isEmpty(this.damage.resources);
|
||||
}
|
||||
|
||||
get hasSave() {
|
||||
|
|
@ -470,6 +469,25 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (source.damage?.parts && !source.damage.resources && !source.damage.main) {
|
||||
source.damage.main = null;
|
||||
source.damage.resources = {};
|
||||
for (const [partKey, part] of Object.entries(source.damage.parts)) {
|
||||
if (partKey === 'hitPoints' && source.type !== 'healing') {
|
||||
source.damage.main = {
|
||||
...part,
|
||||
includeBase: source.damage.includeBase,
|
||||
direct: source.damage.direct,
|
||||
groupAttack: source.damage.groupAttack
|
||||
};
|
||||
} else {
|
||||
source.damage.resources[partKey] = part;
|
||||
}
|
||||
}
|
||||
|
||||
delete source.damage.parts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,8 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
* @returns Formula string
|
||||
*/
|
||||
getDamageFormula() {
|
||||
const strings = [];
|
||||
for (const { value } of this.damage.parts) {
|
||||
strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {}));
|
||||
}
|
||||
if (!this.damage.main) return '';
|
||||
|
||||
return strings.join(' + ');
|
||||
return Roll.replaceFormulaData(this.damage.main.value.getFormula(), this.actor?.getRollData() ?? {});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,13 +84,11 @@ export default class DhpAdversary extends DhCreature {
|
|||
type: 'attack'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hitPoints: {
|
||||
type: ['physical'],
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
multiplier: 'flat'
|
||||
}
|
||||
main: {
|
||||
type: ['physical'],
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
multiplier: 'flat'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,15 +104,13 @@ export default class DhCharacter extends DhCreature {
|
|||
trait: 'strength'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hitPoints: {
|
||||
type: ['physical'],
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '@profd4'
|
||||
}
|
||||
main: {
|
||||
type: ['physical'],
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '@profd4'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -838,7 +836,7 @@ export default class DhCharacter extends DhCreature {
|
|||
isReversed: true
|
||||
};
|
||||
|
||||
this.attack.damage.parts.hitPoints.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
|
||||
this.attack.damage.main.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
|
||||
|
||||
// Clamp resources (must be done last to ensure all updates occur)
|
||||
this.resources.clamp();
|
||||
|
|
|
|||
|
|
@ -40,17 +40,17 @@ export function getTierAdjustedAdversary(source, tier) {
|
|||
|
||||
// Store initial attack damage for abilities that have you deal a "standard attack"
|
||||
const initialAttack = {
|
||||
type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(),
|
||||
value: getFormula(source.system.attack.damage?.parts.hitPoints?.value)
|
||||
type: source.system.attack.damage?.main?.type?.toSorted(),
|
||||
value: getFormula(source.system.attack.damage?.main?.value)
|
||||
};
|
||||
|
||||
// Update damage of base attack.
|
||||
try {
|
||||
const damage = source.system.attack.damage;
|
||||
if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary');
|
||||
if (!damage?.main) throw new Error('Unexpected missing damage in adversary');
|
||||
|
||||
for (const property of ['value', 'valueAlt']) {
|
||||
const data = damage.parts.hitPoints[property];
|
||||
const data = damage.main[property];
|
||||
const previousFormula = getFormula(data);
|
||||
const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
|
||||
applyAdjustedDamage(data, value);
|
||||
|
|
@ -82,12 +82,12 @@ export function getTierAdjustedAdversary(source, tier) {
|
|||
|
||||
// Update damage in item actions and convert all formula matches in the descriptions to the new damage
|
||||
for (const action of Object.values(item.system.actions)) {
|
||||
if (!action.damage?.parts.hitPoints) continue;
|
||||
if (!action.damage?.main) continue;
|
||||
try {
|
||||
// Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead
|
||||
const result = [];
|
||||
for (const property of ['value', 'valueAlt']) {
|
||||
const { [property]: data, type: damageType } = action.damage.parts.hitPoints;
|
||||
const { [property]: data, type: damageType } = action.damage.main;
|
||||
const previousFormula = getFormula(data);
|
||||
const isActuallyAttack =
|
||||
previousFormula === initialAttack.value &&
|
||||
|
|
@ -199,7 +199,7 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR
|
|||
}
|
||||
|
||||
/**
|
||||
* Get formula from either damage parts *or* a simple formula object.
|
||||
* Get formula from either damage data *or* a simple formula object.
|
||||
* @returns {string} the new formula data
|
||||
*/
|
||||
function getFormula(data) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||
import { ChatDamageData } from './chatDamageData.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
|
|
@ -49,7 +50,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
originItem: originItemField(),
|
||||
action: new fields.StringField()
|
||||
}),
|
||||
damage: new fields.ObjectField(),
|
||||
damage: new fields.EmbeddedDataField(ChatDamageData),
|
||||
damageOptions: new fields.ObjectField(),
|
||||
costs: new fields.ArrayField(new fields.ObjectField()),
|
||||
successConsumed: new fields.BooleanField({ initial: false })
|
||||
|
|
@ -132,28 +133,21 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
});
|
||||
}
|
||||
|
||||
/* TODO: Change how damage data is stored somehow to enable better rerolling */
|
||||
async getRerolledDamage() {
|
||||
if (!this.damage) return;
|
||||
if (!this.damage.active) return;
|
||||
|
||||
const rerolls = [];
|
||||
const update = { system: { damage: {} } };
|
||||
for (const partKey in this.damage) {
|
||||
const part = this.damage[partKey];
|
||||
const testRoll = Roll.fromData(part.parts[0].roll);
|
||||
const rerolled = await testRoll.reroll();
|
||||
rerolls.push(rerolled);
|
||||
const update = { system: { damage: { main: null, resources: _replace({}) } } };
|
||||
if (this.damage.main) {
|
||||
const reroll = await this.damage.main.reroll();
|
||||
rerolls.push(reroll);
|
||||
update.system.damage.main = reroll.toJSON();
|
||||
}
|
||||
|
||||
if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] };
|
||||
const partData = update.system.damage[partKey].parts[0];
|
||||
update.system.damage[partKey].total = rerolled.total;
|
||||
partData.modifierTotal = rerolled.terms.reduce((acc, x) => {
|
||||
if (x.isDeterministic && !x.operator) acc += x.total;
|
||||
return acc;
|
||||
}, 0);
|
||||
partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination }));
|
||||
partData.total = rerolled.total;
|
||||
partData.roll = rerolled.toJSON();
|
||||
for (const key of Object.keys(this.damage.resources)) {
|
||||
const reroll = await this.damage.resources[key].reroll();
|
||||
rerolls.push(reroll);
|
||||
update.system.damage.resources[key] = reroll.toJSON();
|
||||
}
|
||||
|
||||
await triggerChatRollFx(rerolls);
|
||||
|
|
@ -198,6 +192,43 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
this.isGM = game.user.isGM; //temp
|
||||
}
|
||||
|
||||
static migrateData(source) {
|
||||
const { main, resources, ...flatDamageKeys } = source.damage ?? {};
|
||||
if (source.damage && !main && !resources) {
|
||||
source.damage.main = null;
|
||||
source.damage.resources = {};
|
||||
|
||||
const getRoll = key => {
|
||||
const damageData = source.damage[key];
|
||||
const oldRoll = damageData.parts[0]?.roll;
|
||||
return oldRoll ? JSON.stringify({
|
||||
...oldRoll,
|
||||
class: 'BaseRoll',
|
||||
options: {
|
||||
...oldRoll.options,
|
||||
damageTypes: damageData.parts[0].damageTypes ?? []
|
||||
}
|
||||
}) : null;
|
||||
};
|
||||
|
||||
for (const key of Object.keys(flatDamageKeys)) {
|
||||
if (key === 'hitPoints' && source.hasDamage && !source.hasHealing) {
|
||||
source.damage.main = getRoll('hitPoints');
|
||||
}
|
||||
else {
|
||||
source.damage.resources[key] = getRoll(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(flatDamageKeys)) {
|
||||
delete source.damage[key];
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
getTargetList() {
|
||||
const targets =
|
||||
this.targetMode && this.parent.isAuthor
|
||||
|
|
|
|||
58
module/data/chat-message/chatDamageData.mjs
Normal file
58
module/data/chat-message/chatDamageData.mjs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||
|
||||
export class ChatDamageData extends foundry.abstract.DataModel {
|
||||
constructor(data = {}, options = {}) {
|
||||
super(data, options);
|
||||
|
||||
this._prepareRolls();
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
main: new fields.JSONField({ nullable: true, validate: ChatDamageData.#validateRoll}),
|
||||
resources: new fields.TypedObjectField(new fields.JSONField({validate: ChatDamageData.#validateRoll}))
|
||||
};
|
||||
}
|
||||
|
||||
get active() {
|
||||
return !!this.main || Boolean(Object.keys(this.resources).length);
|
||||
}
|
||||
|
||||
static #validateRoll(rollJSON) {
|
||||
if (rollJSON) {
|
||||
const roll = JSON.parse(rollJSON);
|
||||
if (!roll.evaluated) throw new Error('Roll objects added to ChatMessage documents must be evaluated');
|
||||
}
|
||||
}
|
||||
|
||||
_prepareRolls() {
|
||||
this.main &&= Roll.fromData(this.main);
|
||||
for (const key of Object.keys(this.resources)) {
|
||||
this.resources[key] = Roll.fromData(this.resources[key]);
|
||||
}
|
||||
}
|
||||
|
||||
async rerollDamageDie(isResource, damageType, dice, resultIndex) {
|
||||
const reroll = isResource ? this.resources[damageType] : this.main;
|
||||
const rerollDice = reroll.dice[dice];
|
||||
await rerollDice.rerollResult(resultIndex);
|
||||
await reroll._evaluate();
|
||||
|
||||
const rerolledResult = rerollDice.results[rerollDice.results.length - 1];
|
||||
if (rerolledResult) {
|
||||
const fakeRoll = {
|
||||
_evaluated: true,
|
||||
dice: [new foundry.dice.terms.Die({
|
||||
...rerollDice,
|
||||
results: [rerolledResult],
|
||||
total: rerolledResult.value,
|
||||
faces: rerollDice.faces
|
||||
})],
|
||||
options: { appearance: {} }
|
||||
};
|
||||
await triggerChatRollFx([fakeRoll]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,20 +12,10 @@ export default class DamageField extends fields.SchemaField {
|
|||
|
||||
/** @inheritDoc */
|
||||
constructor(options, context = {}) {
|
||||
const damageFields = {
|
||||
parts: new IterableTypedObjectField(DHDamageData),
|
||||
includeBase: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||
}),
|
||||
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
|
||||
groupAttack: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.groupAttackRange,
|
||||
blank: true,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
|
||||
})
|
||||
};
|
||||
super(damageFields, options, context);
|
||||
super({
|
||||
main: new fields.EmbeddedDataField(DHDamageData, { nullable: true }),
|
||||
resources: new IterableTypedObjectField(DHResourceData)
|
||||
}, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,25 +31,23 @@ export default class DamageField extends fields.SchemaField {
|
|||
this.hasRoll &&
|
||||
DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id &&
|
||||
!force
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let formulas = this.damage.parts.map(p => ({
|
||||
formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
|
||||
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
||||
applyTo: p.applyTo
|
||||
}));
|
||||
const damageFormula = this.damage.main ?
|
||||
DamageField.formatFormulas.call(this, [this.damage.main], config)[0] : null;
|
||||
const resourceFormulas = DamageField.formatFormulas.call(this, this.damage.resources, config);
|
||||
|
||||
if (!formulas.length) return false;
|
||||
|
||||
formulas = DamageField.formatFormulas.call(this, formulas, config);
|
||||
if (!damageFormula && !resourceFormulas.length) return false;
|
||||
|
||||
messageId = config.message?._id ?? messageId;
|
||||
const message = game.messages.get(messageId);
|
||||
const damageConfig = {
|
||||
dialog: {},
|
||||
...config,
|
||||
roll: formulas,
|
||||
damageFormula,
|
||||
resourceFormulas,
|
||||
data: this.getRollData(),
|
||||
isCritical: Boolean(message?.system.roll?.isCritical)
|
||||
};
|
||||
|
|
@ -92,7 +80,7 @@ export default class DamageField extends fields.SchemaField {
|
|||
|
||||
const targetDamage = [];
|
||||
const damagePromises = [];
|
||||
for (let target of targets) {
|
||||
for (const target of targets) {
|
||||
const actor = foundry.utils.fromUuidSync(target.actorId);
|
||||
if (!actor) continue;
|
||||
if (!config.hasHealing && config.onSave && target.saved?.success === true) {
|
||||
|
|
@ -114,13 +102,12 @@ export default class DamageField extends fields.SchemaField {
|
|||
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
||||
);
|
||||
else {
|
||||
const configDamage = foundry.utils.deepClone(config.damage);
|
||||
const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
||||
const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
||||
if (configDamage.hitPoints) {
|
||||
for (const part of configDamage.hitPoints.parts) {
|
||||
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
||||
}
|
||||
const configDamage = config.damage.clone();
|
||||
configDamage.main &&= configDamage.main.toJSON();
|
||||
if (configDamage.main) {
|
||||
const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
||||
const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
||||
configDamage.main.total = Math.ceil(configDamage.main.total * multiplier * takenMultiplier);
|
||||
}
|
||||
|
||||
damagePromises.push(
|
||||
|
|
@ -183,13 +170,19 @@ export default class DamageField extends fields.SchemaField {
|
|||
/**
|
||||
* Prepare formulas for Damage Roll
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object[]} formulas Array of formatted formulas object
|
||||
* @param {DHResourceData[]} damageData Array of DHResourceData
|
||||
* @param {object} data Action getRollData
|
||||
* @returns
|
||||
*/
|
||||
static formatFormulas(formulas, data) {
|
||||
static formatFormulas(damageData, data) {
|
||||
const formulas = damageData.map(x => ({
|
||||
formula: DamageField.getFormulaValue.call(this, x, data).getFormula(this.actor),
|
||||
damageTypes: x.type ?? new Set(),
|
||||
applyTo: x.applyTo
|
||||
}));
|
||||
|
||||
const formattedFormulas = [];
|
||||
formulas.forEach(formula => {
|
||||
for (const formula of formulas) {
|
||||
if (isNaN(formula.formula))
|
||||
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
|
||||
const same = formattedFormulas.find(
|
||||
|
|
@ -197,7 +190,8 @@ export default class DamageField extends fields.SchemaField {
|
|||
);
|
||||
if (same) same.formula += ` + ${formula.formula}`;
|
||||
else formattedFormulas.push(formula);
|
||||
});
|
||||
}
|
||||
|
||||
return formattedFormulas;
|
||||
}
|
||||
|
||||
|
|
@ -294,6 +288,7 @@ export class DHResourceData extends foundry.abstract.DataModel {
|
|||
/** @override */
|
||||
static defineSchema() {
|
||||
return {
|
||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||
applyTo: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.healingTypes,
|
||||
required: true,
|
||||
|
|
@ -316,7 +311,16 @@ export class DHDamageData extends DHResourceData {
|
|||
static defineSchema() {
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||
includeBase: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||
}),
|
||||
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
|
||||
groupAttack: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.groupAttackRange,
|
||||
blank: true,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
|
||||
}),
|
||||
type: new fields.SetField(
|
||||
new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ export function ActionMixin(Base) {
|
|||
result = this.parent;
|
||||
} else {
|
||||
result = await this.item.update({ [path]: updates }, options);
|
||||
if (!result) return result;
|
||||
}
|
||||
|
||||
return this.inCollection
|
||||
|
|
|
|||
|
|
@ -67,13 +67,11 @@ export default class DHWeapon extends AttachableItem {
|
|||
type: 'attack'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hitPoints: {
|
||||
type: ['physical'],
|
||||
value: {
|
||||
multiplier: 'prof',
|
||||
dice: 'd8'
|
||||
}
|
||||
main: {
|
||||
type: ['physical'],
|
||||
value: {
|
||||
multiplier: 'prof',
|
||||
dice: 'd8'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -230,11 +228,12 @@ export default class DHWeapon extends AttachableItem {
|
|||
game.i18n.localize(`DAGGERHEART.CONFIG.Burden.${burden}`)
|
||||
];
|
||||
|
||||
for (const { value, type } of attack.damage.parts) {
|
||||
if (attack.damage.main) {
|
||||
const { value, type } = attack.damage.main;
|
||||
const parts = value.custom.enabled ? [game.i18n.localize('DAGGERHEART.GENERAL.custom')] : [value.dice];
|
||||
if (!value.custom.enabled && value.bonus) parts.push(value.bonus.signedString());
|
||||
|
||||
if (type.size > 0) {
|
||||
if (type?.size) {
|
||||
const typeTags = Array.from(type)
|
||||
.map(t => game.i18n.localize(`DAGGERHEART.CONFIG.DamageType.${t}.abbreviation`))
|
||||
.join(' | ');
|
||||
|
|
@ -243,7 +242,7 @@ export default class DHWeapon extends AttachableItem {
|
|||
|
||||
tags.push(parts.join(''));
|
||||
}
|
||||
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
|
|
@ -258,10 +257,10 @@ export default class DHWeapon extends AttachableItem {
|
|||
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`));
|
||||
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
||||
|
||||
for (const { value, type } of damage.parts) {
|
||||
for (const { value, type } of [damage.main, ...damage.resources].filter(d => !!d)) {
|
||||
const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
|
||||
|
||||
const icons = Array.from(type)
|
||||
const icons = Array.from(type ?? [])
|
||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||
.filter(Boolean);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { ChatDamageData } from './chat-message/chatDamageData.mjs';
|
||||
|
||||
export default class TagTeamData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -37,6 +39,7 @@ export class MemberData extends foundry.abstract.DataModel {
|
|||
}),
|
||||
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
||||
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||
damageRollData: new fields.EmbeddedDataField(ChatDamageData),
|
||||
selected: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue