mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-07-21 18:09: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
|
|
@ -6,6 +6,7 @@ import * as documents from './module/documents/_module.mjs';
|
||||||
import { macros } from './module/_module.mjs';
|
import { macros } from './module/_module.mjs';
|
||||||
import * as collections from './module/documents/collections/_module.mjs';
|
import * as collections from './module/documents/collections/_module.mjs';
|
||||||
import * as dice from './module/dice/_module.mjs';
|
import * as dice from './module/dice/_module.mjs';
|
||||||
|
import * as die from './module/dice/die/_module.mjs';
|
||||||
import * as fields from './module/data/fields/_module.mjs';
|
import * as fields from './module/data/fields/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
|
|
@ -23,7 +24,7 @@ import TokenManager from './module/documents/tokenManager.mjs';
|
||||||
CONFIG.DH = SYSTEM;
|
CONFIG.DH = SYSTEM;
|
||||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||||
|
|
||||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
|
CONFIG.Dice.rolls = [Roll = BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
|
||||||
CONFIG.Dice.daggerheart = {
|
CONFIG.Dice.daggerheart = {
|
||||||
DHRoll: DHRoll,
|
DHRoll: DHRoll,
|
||||||
DualityRoll: DualityRoll,
|
DualityRoll: DualityRoll,
|
||||||
|
|
@ -38,6 +39,8 @@ CONFIG.RegionBehavior.dataModels = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
||||||
|
CONFIG.Dice.terms.d = die.BaseDie;
|
||||||
|
CONFIG.Dice.types = [die.BaseDie, CONFIG.Dice.terms.f];
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
CONFIG.Actor.dataModels = models.actors.config;
|
CONFIG.Actor.dataModels = models.actors.config;
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,10 @@
|
||||||
"startCountdown": "Start Countdown"
|
"startCountdown": "Start Countdown"
|
||||||
},
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
|
"addResource": "Add Resource",
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"flatMultiplier": "Flat Multiplier"
|
"flatMultiplier": "Flat Multiplier",
|
||||||
|
"markResources": "Mark Resources"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"customFormula": "Custom Formula",
|
"customFormula": "Custom Formula",
|
||||||
|
|
@ -2483,7 +2485,6 @@
|
||||||
"reroll": "Reroll",
|
"reroll": "Reroll",
|
||||||
"rerolled": "Rerolled",
|
"rerolled": "Rerolled",
|
||||||
"rerollThing": "Reroll {thing}",
|
"rerollThing": "Reroll {thing}",
|
||||||
"resource": "Resource",
|
|
||||||
"result": {
|
"result": {
|
||||||
"single": "Result",
|
"single": "Result",
|
||||||
"plural": "Results"
|
"plural": "Results"
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.config = CONFIG.DH;
|
context.config = CONFIG.DH;
|
||||||
context.title = this.config.title ?? this.title;
|
context.title = this.config.title ?? this.title;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
|
||||||
|
const { damageFormula, resourceFormulas } = this.roll.constructFormulas(this.config);
|
||||||
|
context.damageFormula = damageFormula;
|
||||||
|
context.resourceFormulas = resourceFormulas;
|
||||||
|
|
||||||
context.hasHealing = this.config.hasHealing;
|
context.hasHealing = this.config.hasHealing;
|
||||||
context.directDamage = this.config.directDamage;
|
context.directDamage = this.config.directDamage;
|
||||||
context.selectedMessageMode = this.config.selectedMessageMode;
|
context.selectedMessageMode = this.config.selectedMessageMode;
|
||||||
|
|
@ -73,7 +77,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
static updateRollConfiguration(_event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const data = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
foundry.utils.mergeObject(this.config.roll, data.roll);
|
|
||||||
|
if (this.config.damageFormula)
|
||||||
|
foundry.utils.mergeObject(this.config.damageFormula, data.damageFormula);
|
||||||
|
|
||||||
|
foundry.utils.mergeObject(this.config.resourceFormulas, data.resourceFormulas);
|
||||||
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||||
this.config.selectedMessageMode = data.selectedMessageMode;
|
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||||
|
import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs';
|
||||||
import { MemberData } from '../../data/tagTeamData.mjs';
|
import { MemberData } from '../../data/tagTeamData.mjs';
|
||||||
import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
|
import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
|
||||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
@ -140,7 +141,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const hasRolled = Boolean(data.rollData);
|
const hasRolled = Boolean(data.rollData);
|
||||||
if (!hasRolled) return false;
|
if (!hasRolled) return false;
|
||||||
|
|
||||||
return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage);
|
return !data.rollData.options.hasDamage || data.damageRollData.active;
|
||||||
});
|
});
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -181,7 +182,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||||
const critSelected = !selectedRoll
|
const critSelected = !selectedRoll
|
||||||
? undefined
|
? undefined
|
||||||
: (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
: (selectedRoll?.roll?.isCritical ?? false);
|
||||||
|
|
||||||
partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members);
|
partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members);
|
||||||
partContext.joinedRoll = await this.getJoinedRoll({
|
partContext.joinedRoll = await this.getJoinedRoll({
|
||||||
|
|
@ -235,8 +236,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||||
const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
const critSelected = !selectedRoll ? undefined : (selectedRoll?.roll?.isCritical ?? false);
|
||||||
const damage = data.rollData?.options?.damage;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
|
|
@ -247,9 +247,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
hasRolled: Boolean(data.rollData),
|
hasRolled: Boolean(data.rollData),
|
||||||
rollOptions,
|
rollOptions,
|
||||||
damageRollOptions,
|
damageRollOptions,
|
||||||
damage: damage,
|
damage: data.damageRollData,
|
||||||
critDamage: await this.getCriticalDamage(damage),
|
critDamage: await this.getCriticalDamage(data.damageRollData),
|
||||||
useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
useCritDamage: critSelected || (critSelected === undefined && data.roll?.isCritical)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,7 +379,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
let rollIsSelected = false;
|
let rollIsSelected = false;
|
||||||
for (const member of Object.values(members)) {
|
for (const member of Object.values(members)) {
|
||||||
const rollFinished = Boolean(member.rollData);
|
const rollFinished = Boolean(member.rollData);
|
||||||
const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true;
|
const damageFinished = member.rollData?.options?.hasDamage ? member.damageRollData.active : true;
|
||||||
|
|
||||||
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
||||||
rollIsSelected = rollIsSelected || member.selected;
|
rollIsSelected = rollIsSelected || member.selected;
|
||||||
|
|
@ -541,16 +541,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
await action.workflow.get('damage').execute(config, null, true);
|
await action.workflow.get('damage').execute(config, null, true);
|
||||||
if (!config.damage) return;
|
if (!config.damage) return;
|
||||||
|
|
||||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
|
||||||
await this.updatePartyData(
|
await this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
[`system.tagTeam.members.${memberKey}.damageRollData`]: config.damage
|
||||||
...current,
|
|
||||||
options: {
|
|
||||||
...current.options,
|
|
||||||
damage: config.damage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
);
|
);
|
||||||
|
|
@ -558,15 +551,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
static async #removeDamageRoll(_, button) {
|
static async #removeDamageRoll(_, button) {
|
||||||
const { memberKey } = button.dataset;
|
const { memberKey } = button.dataset;
|
||||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
[`system.tagTeam.members.${memberKey}.damageRollData`]: {
|
||||||
...current,
|
main: null,
|
||||||
options: {
|
resources: _replace({})
|
||||||
...current.options,
|
|
||||||
damage: null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
|
|
@ -574,81 +563,35 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #rerollDamageDice(_, button) {
|
static async #rerollDamageDice(_, button) {
|
||||||
const { memberKey, damageKey, part, dice } = button.dataset;
|
const { isResource, memberKey, damageKey, diceIndex, resultIndex } = button.dataset;
|
||||||
const memberData = this.party.system.tagTeam.members[memberKey];
|
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||||
const partData = memberData.rollData.options.damage[damageKey].parts[part];
|
await memberData.damageRollData.rerollDamageDie(isResource, damageKey, diceIndex, resultIndex);
|
||||||
const activeDiceResultKey = Object.keys(partData.dice[dice].results).find(
|
|
||||||
index => partData.dice[dice].results[index].active
|
|
||||||
);
|
|
||||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(
|
|
||||||
partData,
|
|
||||||
dice,
|
|
||||||
activeDiceResultKey
|
|
||||||
);
|
|
||||||
|
|
||||||
const rollData = this.party.system.tagTeam.members[memberKey].rollData;
|
|
||||||
rollData.options.damage[damageKey].parts = rollData.options.damage[damageKey].parts.map((damagePart, index) => {
|
|
||||||
if (index !== Number.parseInt(part)) return damagePart;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...damagePart,
|
|
||||||
total: parsedRoll.total,
|
|
||||||
dice: rerolledDice
|
|
||||||
};
|
|
||||||
});
|
|
||||||
rollData.options.damage[damageKey].total = rollData.options.damage[damageKey].parts.reduce((acc, part) => {
|
|
||||||
acc += part.total;
|
|
||||||
return acc;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
|
const basePath = `system.tagTeam.members.${memberKey}.damageRollData`;
|
||||||
|
const updatePath = isResource ? `${basePath}.resources.${damageKey}` : `${basePath}.main`;
|
||||||
|
const updateValue = isResource ?
|
||||||
|
memberData.damageRollData.resources[damageKey] : memberData.damageRollData.main;
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members.${memberKey}.rollData`]: rollData
|
[updatePath]: updateValue.toJSON()
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCriticalDamage(damage) {
|
async getCriticalDamage(origDamage) {
|
||||||
const newDamage = foundry.utils.deepClone(damage);
|
const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
|
||||||
for (let key in newDamage) {
|
if (newDamage?.main) {
|
||||||
var damage = newDamage[key];
|
const criticalDamage = await getCritDamageBonus(newDamage.main.formula);
|
||||||
damage.formula = '';
|
if (criticalDamage) {
|
||||||
damage.total = 0;
|
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
|
||||||
|
criticalTerm.evaluate();
|
||||||
for (let part of damage.parts) {
|
newDamage.main = await Roll.fromTerms([
|
||||||
const criticalDamage = await getCritDamageBonus(part.formula);
|
...origDamage.main.terms,
|
||||||
if (criticalDamage) {
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
part.modifierTotal += criticalDamage;
|
criticalTerm
|
||||||
part.total += criticalDamage;
|
]);
|
||||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
newDamage.main.options = foundry.utils.deepClone(origDamage.main.options);
|
||||||
part.roll = new Roll(part.formula);
|
|
||||||
}
|
|
||||||
|
|
||||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
|
||||||
damage.total += part.total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNonCriticalDamage(config) {
|
|
||||||
const newDamage = foundry.utils.deepClone(config.damage);
|
|
||||||
for (let key in newDamage) {
|
|
||||||
var damage = newDamage[key];
|
|
||||||
damage.formula = '';
|
|
||||||
damage.total = 0;
|
|
||||||
|
|
||||||
for (let part of damage.parts) {
|
|
||||||
const critDamageBonus = await getCritDamageBonus(part.formula);
|
|
||||||
part.modifierTotal -= critDamageBonus;
|
|
||||||
part.total -= critDamageBonus;
|
|
||||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
|
||||||
part.roll = new Roll(part.formula);
|
|
||||||
|
|
||||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
|
||||||
damage.total += part.total;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -657,6 +600,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
static async #selectRoll(_, button) {
|
static async #selectRoll(_, button) {
|
||||||
const { memberKey } = button.dataset;
|
const { memberKey } = button.dataset;
|
||||||
|
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce(
|
[`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce(
|
||||||
|
|
@ -667,7 +611,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
/* Selecting a roll must update all member sections hbs to display the correct damage information incase of a critical */
|
||||||
|
[
|
||||||
|
...Object.keys(this.party.system.tagTeam.members),
|
||||||
|
this.constructor.PARTS.rollSelection.id,
|
||||||
|
this.constructor.PARTS.result.id
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -685,29 +634,65 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||||
|
|
||||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
mainRoll.damageRollData = baseMainRoll.damageRollData ?
|
||||||
const systemData = mainRoll.rollData.options;
|
ChatDamageData.fromJSON(JSON.stringify(baseMainRoll.damageRollData)) : null;
|
||||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
const secondaryRoll = new MemberData(baseSecondaryRoll.toObject());
|
||||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ?
|
||||||
|
ChatDamageData.fromJSON(JSON.stringify(baseSecondaryRoll.damageRollData)) : null;
|
||||||
|
|
||||||
if (secondaryRollData?.options.hasDamage) {
|
const isCritical = overrideIsCritical ?? mainRoll.roll.isCritical;
|
||||||
|
if (isCritical) mainRoll.damageRollData = await this.getCriticalDamage(mainRoll.damageRollData);
|
||||||
|
|
||||||
|
if (secondaryRoll.damageRollData) {
|
||||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
? await this.getCriticalDamage(secondaryRoll.damageRollData)
|
||||||
: secondaryRollData.options.damage;
|
: secondaryRoll.damageRollData;
|
||||||
if (systemData.damage) {
|
if (mainRoll.damageRollData) {
|
||||||
for (const [key, damage] of Object.entries(secondaryDamage ?? {})) {
|
if (secondaryDamage.main) {
|
||||||
if (key in systemData.damage) {
|
if (mainRoll.damageRollData.main) {
|
||||||
systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula]
|
mainRoll.damageRollData.main = Roll.fromTerms([
|
||||||
.filter(x => x)
|
...baseMainRoll.damageRollData.main.terms,
|
||||||
.join(' + ');
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
systemData.damage[key].total += damage.total;
|
...baseSecondaryRoll.damageRollData.main.terms
|
||||||
systemData.damage[key].parts.push(...damage.parts);
|
]);
|
||||||
|
|
||||||
|
/* Joining the roll.options of both rolls */
|
||||||
|
const joinedDamageTypes = new Set([
|
||||||
|
...baseMainRoll.damageRollData.main.options.damageTypes,
|
||||||
|
...baseSecondaryRoll.damageRollData.main.options.damageTypes
|
||||||
|
]);
|
||||||
|
mainRoll.damageRollData.main.options = {
|
||||||
|
...baseMainRoll.damageRollData.main.options,
|
||||||
|
damageTypes: [...joinedDamageTypes]
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
systemData.damage[key] = damage;
|
mainRoll.damageRollData.main = secondaryDamage.main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) {
|
||||||
|
if (key in mainRoll.damageRollData.resources) {
|
||||||
|
mainRoll.damageRollData.resources[key] = Roll.fromTerms([
|
||||||
|
...baseMainRoll.damageRollData.resources[key].terms,
|
||||||
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
|
...baseSecondaryRoll.damageRollData.resources[key].terms
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* Joining the roll.options of both rolls */
|
||||||
|
const joinedDamageTypes = new Set([
|
||||||
|
...baseMainRoll.damageRollData.resources[key].options.damageTypes,
|
||||||
|
...baseSecondaryRoll.damageRollData.resources[key].options.damageTypes
|
||||||
|
]);
|
||||||
|
mainRoll.damageRollData.resources[key].options = {
|
||||||
|
...baseMainRoll.damageRollData.resources[key].options,
|
||||||
|
damageTypes: [...joinedDamageTypes]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
mainRoll.damageRollData.resources[key] = damage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
systemData.damage = secondaryDamage;
|
mainRoll.damageRollData = secondaryDamage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -762,13 +747,27 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
|
const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
|
||||||
mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||||
|
|
||||||
|
/* This could assumably be done better. For some reason rolls don't get correctly done through rollData.toJSON */
|
||||||
|
const systemData = {
|
||||||
|
...mainRoll.options,
|
||||||
|
damage: joinedRoll.damageRollData?.toJSON()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (joinedRoll.damageRollData.main) {
|
||||||
|
systemData.damage.main = joinedRoll.damageRollData.toJSON();
|
||||||
|
}
|
||||||
|
for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) {
|
||||||
|
systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage'),
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
msgData = {
|
msgData = {
|
||||||
type: 'dualityRoll',
|
type: 'dualityRoll',
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||||
speaker: cls.getSpeaker({ actor: mainActor }),
|
speaker: cls.getSpeaker({ actor: mainActor }),
|
||||||
system: mainRoll.options,
|
system: systemData,
|
||||||
rolls: [JSON.stringify(joinedRoll.roll)],
|
rolls: [JSON.stringify(joinedRoll.roll)],
|
||||||
sound: null,
|
sound: null,
|
||||||
flags: { core: { RollTable: true } }
|
flags: { core: { RollTable: true } }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
|
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
|
||||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||||
|
|
||||||
const { ApplicationV2 } = foundry.applications.api;
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -31,8 +31,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
removeElement: this.removeElement,
|
removeElement: this.removeElement,
|
||||||
removeTransformActor: this.removeTransformActor,
|
removeTransformActor: this.removeTransformActor,
|
||||||
editEffect: this.editEffect,
|
editEffect: this.editEffect,
|
||||||
addDamage: this.addDamage,
|
addDamage: this.#onAddDamage,
|
||||||
removeDamage: this.removeDamage,
|
removeDamage: this.#onRemoveDamage,
|
||||||
|
addDamageResource: this.#onAddDamageResource,
|
||||||
|
removeDamageResource: this.#onRemoveDamageResource,
|
||||||
editDoc: this.editDoc,
|
editDoc: this.editDoc,
|
||||||
addTrigger: this.addTrigger,
|
addTrigger: this.addTrigger,
|
||||||
removeTrigger: this.removeTrigger,
|
removeTrigger: this.removeTrigger,
|
||||||
|
|
@ -157,9 +159,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
context.tabs = this._getTabs(this.constructor.TABS);
|
context.tabs = this._getTabs(this.constructor.TABS);
|
||||||
context.config = CONFIG.DH;
|
context.config = CONFIG.DH;
|
||||||
if (this.action.damage) {
|
if (this.action.damage) {
|
||||||
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
|
||||||
|
context.allDamageTypesUsed = allKeys.every(k => k in this.action._source.damage.resources);
|
||||||
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
if (this.action.damage?.main?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +255,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
const submitData = this._prepareSubmitData(event, formData);
|
const submitData = this._prepareSubmitData(event, formData);
|
||||||
|
|
||||||
const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
||||||
this.action = await this.action.update(data);
|
this.action = (await this.action.update(data)) ?? this.action;
|
||||||
|
|
||||||
this.sheetUpdate?.(this.action);
|
this.sheetUpdate?.(this.action);
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -299,53 +301,70 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
static addDamage(_event) {
|
/** @this DHActionBaseConfig */
|
||||||
if (!this.action.damage.parts) return;
|
static #onAddDamage() {
|
||||||
|
if (!this.action.damage || this.action.damage?.main) return;
|
||||||
|
|
||||||
const choices = getUnusedDamageTypes(this.action._source.damage.parts);
|
const data = this.action.toObject();
|
||||||
|
data.damage.main = {
|
||||||
|
...DHDamageData.schema.getInitialValue(),
|
||||||
|
applyTo: 'hitPoints',
|
||||||
|
type: 'physical'
|
||||||
|
};
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @this DHActionBaseConfig */
|
||||||
|
static #onRemoveDamage() {
|
||||||
|
if (!this.action.damage?.main) return;
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.damage.main = null;
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @this DHActionBaseConfig */
|
||||||
|
static #onAddDamageResource(_event) {
|
||||||
|
if (!this.action.damage) return;
|
||||||
|
|
||||||
|
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
|
||||||
|
const unused = allKeys.filter(k => !(k in this.action._source.damage.resources));
|
||||||
|
const choices = unused.map(k => ({ value: k, label: _loc(CONFIG.DH.GENERAL.healingTypes[k].label) }));
|
||||||
const content = new foundry.data.fields.StringField({
|
const content = new foundry.data.fields.StringField({
|
||||||
label: game.i18n.localize('Damage Type'),
|
label: _loc('DAGGERHEART.GENERAL.Resource.single'),
|
||||||
choices,
|
choices,
|
||||||
required: true
|
required: true
|
||||||
}).toFormGroup(
|
}).toFormGroup({}, {
|
||||||
{},
|
name: 'type',
|
||||||
{
|
localize: true,
|
||||||
name: 'type',
|
nameAttr: 'value',
|
||||||
localize: true,
|
labelAttr: 'label'
|
||||||
nameAttr: 'value',
|
}).outerHTML;
|
||||||
labelAttr: 'label'
|
|
||||||
}
|
|
||||||
).outerHTML;
|
|
||||||
|
|
||||||
const callback = (_, button) => {
|
const callback = (_, button) => {
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
const type = choices[button.form.elements.type.value].value;
|
const type = choices[button.form.elements.type.value].value;
|
||||||
const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue();
|
data.damage.resources[type] = {
|
||||||
part.applyTo = type;
|
...this.action.schema.fields.damage.fields.resources.element.getInitialValue(),
|
||||||
if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
applyTo: type
|
||||||
part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
|
};
|
||||||
|
|
||||||
data.damage.parts[type] = part;
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeDialog = new foundry.applications.api.DialogV2({
|
const typeDialog = new foundry.applications.api.DialogV2({
|
||||||
buttons: [
|
buttons: [
|
||||||
foundry.utils.mergeObject(
|
{
|
||||||
{
|
action: 'ok',
|
||||||
action: 'ok',
|
label: 'Confirm',
|
||||||
label: 'Confirm',
|
icon: 'fas fa-check',
|
||||||
icon: 'fas fa-check',
|
default: true,
|
||||||
default: true
|
callback
|
||||||
},
|
}
|
||||||
{ callback: callback }
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
content: content,
|
content: content,
|
||||||
rejectClose: false,
|
rejectClose: false,
|
||||||
modal: false,
|
modal: false,
|
||||||
window: {
|
window: {
|
||||||
title: game.i18n.localize('Add Damage')
|
title: _loc('DAGGERHEART.ACTIONS.Config.damage.addResource')
|
||||||
},
|
},
|
||||||
position: { width: 300 }
|
position: { width: 300 }
|
||||||
});
|
});
|
||||||
|
|
@ -353,12 +372,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
typeDialog.render(true);
|
typeDialog.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeDamage(_event, button) {
|
/** @this DHActionBaseConfig */
|
||||||
if (!this.action.damage.parts) return;
|
static #onRemoveDamageResource(_event, button) {
|
||||||
|
if (!this.action.damage?.resources) return;
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
const key = button.dataset.key;
|
const key = button.dataset.key;
|
||||||
delete data.damage.parts[key];
|
data.damage.resources[key] = _del;
|
||||||
data.damage.parts[`${key}`] = _del;
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
|
||||||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
@ -8,8 +9,10 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
classes: ['adversary-settings'],
|
classes: ['adversary-settings'],
|
||||||
position: { width: 455, height: 'auto' },
|
position: { width: 455, height: 'auto' },
|
||||||
actions: {
|
actions: {
|
||||||
addExperience: DHAdversarySettings.#addExperience,
|
addExperience: this.#onAddExperience,
|
||||||
removeExperience: DHAdversarySettings.#removeExperience
|
removeExperience: this.#onRemoveExperience,
|
||||||
|
addDamage: this.#onAddDamage,
|
||||||
|
removeDamage: this.#onRemoveDamage
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -71,7 +74,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
* Adds a new experience entry to the actor.
|
* Adds a new experience entry to the actor.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #addExperience() {
|
static async #onAddExperience() {
|
||||||
const newExperience = {
|
const newExperience = {
|
||||||
name: 'Experience',
|
name: 'Experience',
|
||||||
modifier: 0
|
modifier: 0
|
||||||
|
|
@ -83,7 +86,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
* Removes an experience entry from the actor.
|
* Removes an experience entry from the actor.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #removeExperience(_, target) {
|
static async #onRemoveExperience(_, target) {
|
||||||
const experience = this.actor.system.experiences[target.dataset.experience];
|
const experience = this.actor.system.experiences[target.dataset.experience];
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
|
|
@ -98,4 +101,28 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this DHAdversarySettings
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static #onAddDamage() {
|
||||||
|
this.actor.update({
|
||||||
|
'system.attack.damage.main': {
|
||||||
|
...DHDamageData.schema.getInitialValue(),
|
||||||
|
applyTo: 'hitPoints',
|
||||||
|
type: 'physical'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this DHAdversarySettings
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static #onRemoveDamage() {
|
||||||
|
this.actor.update({
|
||||||
|
'system.attack.damage.main': null
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
||||||
},
|
},
|
||||||
callback: async li => {
|
onClick: async (_event, li) => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
||||||
message.update({ rolls: [reroll] });
|
message.update({ rolls: [reroll] });
|
||||||
|
|
@ -126,7 +126,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
: false;
|
: false;
|
||||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||||
},
|
},
|
||||||
callback: async li => {
|
onClick: async (_event, li) => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
const update = await message.system.getRerolledDamage();
|
const update = await message.system.getRerolledDamage();
|
||||||
message.update(update);
|
message.update(update);
|
||||||
|
|
@ -179,28 +179,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRollSimple(event, message) {
|
async onRollSimple(event, message) {
|
||||||
const buttonType = event.target.dataset.type ?? 'damage',
|
const buttonType = event.target.dataset.type ?? 'damage';
|
||||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
const total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0);
|
||||||
damages = {
|
const targets = Array.from(game.user.targets);
|
||||||
hitPoints: {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
applyTo: 'hitPoints',
|
|
||||||
damageTypes: [],
|
|
||||||
total
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
targets = Array.from(game.user.targets);
|
|
||||||
|
|
||||||
if (targets.length === 0)
|
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.noTargetsSelected'));
|
||||||
|
for (const target of targets) {
|
||||||
targets.forEach(target => {
|
if (buttonType === 'healing') target.actor.takeHealing({ hitPoints: total });
|
||||||
if (buttonType === 'healing') target.actor.takeHealing(damages);
|
else target.actor.takeDamage({ total });
|
||||||
else target.actor.takeDamage(damages);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async abilityUseButton(event, message) {
|
async abilityUseButton(event, message) {
|
||||||
|
|
@ -256,26 +243,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = game.messages.get(messageData._id);
|
const message = game.messages.get(messageData._id);
|
||||||
const target = event.target.closest('[data-die-index]');
|
const target = event.target.closest('[data-result]');
|
||||||
|
|
||||||
if (target.dataset.type === 'damage') {
|
if (target.dataset.type === 'damage') {
|
||||||
const { damageType, part, dice, result } = target.dataset;
|
const { isResource, damageType, dice, result } = target.dataset;
|
||||||
const damagePart = message.system.damage[damageType].parts[part];
|
await message.system.damage.rerollDamageDie(isResource, damageType, dice, result);
|
||||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
|
|
||||||
const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
|
const updatePath = isResource ? `system.damage.resources.${damageType}` : 'system.damage.main';
|
||||||
if (index !== Number(part)) return damagePart;
|
const updateValue = isResource ?
|
||||||
return {
|
message.system.damage.resources[damageType] : message.system.damage.main;
|
||||||
...damagePart,
|
await message.update({
|
||||||
total: parsedRoll.total,
|
[updatePath]: updateValue.toJSON()
|
||||||
dice: rerolledDice
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const updateMessage = game.messages.get(message._id);
|
|
||||||
await updateMessage.update({
|
|
||||||
[`system.damage.${damageType}`]: {
|
|
||||||
total: parsedRoll.total,
|
|
||||||
parts: damageParts
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
||||||
|
|
|
||||||
|
|
@ -825,7 +825,7 @@ export const refreshTypes = {
|
||||||
export const itemAbilityCosts = {
|
export const itemAbilityCosts = {
|
||||||
resource: {
|
resource: {
|
||||||
id: 'resource',
|
id: 'resource',
|
||||||
label: 'DAGGERHEART.GENERAL.resource',
|
label: 'DAGGERHEART.GENERAL.Resource.single',
|
||||||
group: 'Global'
|
group: 'Global'
|
||||||
},
|
},
|
||||||
quantity: {
|
quantity: {
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,19 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
if (this.damage.includeBase) {
|
if (this.damage.includeBase) {
|
||||||
const baseDamage = this.getParentHitPointDamage();
|
const baseDamage = this.getParentHitPointDamage();
|
||||||
if (baseDamage) {
|
if (baseDamage) {
|
||||||
if (!this.damage.parts.hitPoints) {
|
if (!this.damage.main) {
|
||||||
this.damage.parts.hitPoints = baseDamage;
|
this.damage.main = baseDamage;
|
||||||
} else {
|
} 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,
|
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) {
|
if (this.roll.useDefault) {
|
||||||
this.roll.trait = this.item.system.attack.roll.trait;
|
this.roll.trait = this.item.system.attack.roll.trait;
|
||||||
this.roll.type = 'attack';
|
this.roll.type = 'attack';
|
||||||
|
|
@ -33,18 +34,18 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
getParentHitPointDamage() {
|
getParentHitPointDamage() {
|
||||||
return this.item?.system?.attack.damage.parts.hitPoints;
|
return this.item?.system?.attack.damage.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
get damageFormula() {
|
get damageFormula() {
|
||||||
const hitPointsPart = this.damage.parts.hitPoints;
|
const hitPointsPart = this.damage.main;
|
||||||
if (!hitPointsPart) return '0';
|
if (!hitPointsPart) return '0';
|
||||||
|
|
||||||
return hitPointsPart.value.getFormula();
|
return hitPointsPart.value.getFormula();
|
||||||
}
|
}
|
||||||
|
|
||||||
get altDamageFormula() {
|
get altDamageFormula() {
|
||||||
const hitPointsPart = this.damage.parts.hitPoints;
|
const hitPointsPart = this.damage.main;
|
||||||
if (!hitPointsPart) return '0';
|
if (!hitPointsPart) return '0';
|
||||||
|
|
||||||
return hitPointsPart.valueAlt.getFormula();
|
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`));
|
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
||||||
|
|
||||||
const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
|
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 usedValue = useAltDamage ? valueAlt : value;
|
||||||
const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
||||||
const str = damageString
|
const str = damageString
|
||||||
|
|
@ -82,7 +83,7 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
|
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)
|
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
hasEffect: this.hasEffect,
|
hasEffect: this.hasEffect,
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
onSave: this.save?.damageMod,
|
onSave: this.save?.damageMod,
|
||||||
isDirect: !!this.damage?.direct,
|
|
||||||
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: this.hasRoll,
|
evaluate: this.hasRoll,
|
||||||
|
|
@ -307,20 +306,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.damage) {
|
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(
|
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
|
||||||
this.actor.id,
|
this.actor.id,
|
||||||
this.damage.groupAttack
|
this.damage.main.groupAttack
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
config.damageOptions = {
|
config.damageOptions = {
|
||||||
groupAttack: this.damage.groupAttack
|
groupAttack: this.damage.main?.groupAttack
|
||||||
? {
|
? {
|
||||||
numAttackers: Math.max(groupAttackTokens.length, 1),
|
numAttackers: Math.max(groupAttackTokens.length, 1),
|
||||||
range: this.damage.groupAttack
|
range: this.damage.main.groupAttack
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
};
|
};
|
||||||
|
|
@ -430,11 +429,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasDamage() {
|
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() {
|
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() {
|
get hasSave() {
|
||||||
|
|
@ -470,6 +469,25 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return acc;
|
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
|
* @returns Formula string
|
||||||
*/
|
*/
|
||||||
getDamageFormula() {
|
getDamageFormula() {
|
||||||
const strings = [];
|
if (!this.damage.main) return '';
|
||||||
for (const { value } of this.damage.parts) {
|
|
||||||
strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {}));
|
|
||||||
}
|
|
||||||
|
|
||||||
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'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: {
|
main: {
|
||||||
hitPoints: {
|
type: ['physical'],
|
||||||
type: ['physical'],
|
applyTo: 'hitPoints',
|
||||||
applyTo: 'hitPoints',
|
value: {
|
||||||
value: {
|
multiplier: 'flat'
|
||||||
multiplier: 'flat'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,15 +104,13 @@ export default class DhCharacter extends DhCreature {
|
||||||
trait: 'strength'
|
trait: 'strength'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: {
|
main: {
|
||||||
hitPoints: {
|
type: ['physical'],
|
||||||
type: ['physical'],
|
applyTo: 'hitPoints',
|
||||||
applyTo: 'hitPoints',
|
value: {
|
||||||
value: {
|
custom: {
|
||||||
custom: {
|
enabled: true,
|
||||||
enabled: true,
|
formula: '@profd4'
|
||||||
formula: '@profd4'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -838,7 +836,7 @@ export default class DhCharacter extends DhCreature {
|
||||||
isReversed: true
|
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)
|
// Clamp resources (must be done last to ensure all updates occur)
|
||||||
this.resources.clamp();
|
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"
|
// Store initial attack damage for abilities that have you deal a "standard attack"
|
||||||
const initialAttack = {
|
const initialAttack = {
|
||||||
type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(),
|
type: source.system.attack.damage?.main?.type?.toSorted(),
|
||||||
value: getFormula(source.system.attack.damage?.parts.hitPoints?.value)
|
value: getFormula(source.system.attack.damage?.main?.value)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update damage of base attack.
|
// Update damage of base attack.
|
||||||
try {
|
try {
|
||||||
const damage = source.system.attack.damage;
|
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']) {
|
for (const property of ['value', 'valueAlt']) {
|
||||||
const data = damage.parts.hitPoints[property];
|
const data = damage.main[property];
|
||||||
const previousFormula = getFormula(data);
|
const previousFormula = getFormula(data);
|
||||||
const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
|
const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
|
||||||
applyAdjustedDamage(data, value);
|
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
|
// 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)) {
|
for (const action of Object.values(item.system.actions)) {
|
||||||
if (!action.damage?.parts.hitPoints) continue;
|
if (!action.damage?.main) continue;
|
||||||
try {
|
try {
|
||||||
// Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead
|
// Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead
|
||||||
const result = [];
|
const result = [];
|
||||||
for (const property of ['value', 'valueAlt']) {
|
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 previousFormula = getFormula(data);
|
||||||
const isActuallyAttack =
|
const isActuallyAttack =
|
||||||
previousFormula === initialAttack.value &&
|
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
|
* @returns {string} the new formula data
|
||||||
*/
|
*/
|
||||||
function getFormula(data) {
|
function getFormula(data) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||||
|
import { ChatDamageData } from './chatDamageData.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
originItem: originItemField(),
|
originItem: originItemField(),
|
||||||
action: new fields.StringField()
|
action: new fields.StringField()
|
||||||
}),
|
}),
|
||||||
damage: new fields.ObjectField(),
|
damage: new fields.EmbeddedDataField(ChatDamageData),
|
||||||
damageOptions: new fields.ObjectField(),
|
damageOptions: new fields.ObjectField(),
|
||||||
costs: new fields.ArrayField(new fields.ObjectField()),
|
costs: new fields.ArrayField(new fields.ObjectField()),
|
||||||
successConsumed: new fields.BooleanField({ initial: false })
|
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() {
|
async getRerolledDamage() {
|
||||||
if (!this.damage) return;
|
if (!this.damage.active) return;
|
||||||
|
|
||||||
const rerolls = [];
|
const rerolls = [];
|
||||||
const update = { system: { damage: {} } };
|
const update = { system: { damage: { main: null, resources: _replace({}) } } };
|
||||||
for (const partKey in this.damage) {
|
if (this.damage.main) {
|
||||||
const part = this.damage[partKey];
|
const reroll = await this.damage.main.reroll();
|
||||||
const testRoll = Roll.fromData(part.parts[0].roll);
|
rerolls.push(reroll);
|
||||||
const rerolled = await testRoll.reroll();
|
update.system.damage.main = reroll.toJSON();
|
||||||
rerolls.push(rerolled);
|
}
|
||||||
|
|
||||||
if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] };
|
for (const key of Object.keys(this.damage.resources)) {
|
||||||
const partData = update.system.damage[partKey].parts[0];
|
const reroll = await this.damage.resources[key].reroll();
|
||||||
update.system.damage[partKey].total = rerolled.total;
|
rerolls.push(reroll);
|
||||||
partData.modifierTotal = rerolled.terms.reduce((acc, x) => {
|
update.system.damage.resources[key] = reroll.toJSON();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await triggerChatRollFx(rerolls);
|
await triggerChatRollFx(rerolls);
|
||||||
|
|
@ -198,6 +192,43 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
this.isGM = game.user.isGM; //temp
|
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() {
|
getTargetList() {
|
||||||
const targets =
|
const targets =
|
||||||
this.targetMode && this.parent.isAuthor
|
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 */
|
/** @inheritDoc */
|
||||||
constructor(options, context = {}) {
|
constructor(options, context = {}) {
|
||||||
const damageFields = {
|
super({
|
||||||
parts: new IterableTypedObjectField(DHDamageData),
|
main: new fields.EmbeddedDataField(DHDamageData, { nullable: true }),
|
||||||
includeBase: new fields.BooleanField({
|
resources: new IterableTypedObjectField(DHResourceData)
|
||||||
initial: false,
|
}, options, context);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,25 +31,23 @@ export default class DamageField extends fields.SchemaField {
|
||||||
this.hasRoll &&
|
this.hasRoll &&
|
||||||
DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id &&
|
DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id &&
|
||||||
!force
|
!force
|
||||||
)
|
) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let formulas = this.damage.parts.map(p => ({
|
const damageFormula = this.damage.main ?
|
||||||
formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
|
DamageField.formatFormulas.call(this, [this.damage.main], config)[0] : null;
|
||||||
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
const resourceFormulas = DamageField.formatFormulas.call(this, this.damage.resources, config);
|
||||||
applyTo: p.applyTo
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!formulas.length) return false;
|
if (!damageFormula && !resourceFormulas.length) return false;
|
||||||
|
|
||||||
formulas = DamageField.formatFormulas.call(this, formulas, config);
|
|
||||||
|
|
||||||
messageId = config.message?._id ?? messageId;
|
messageId = config.message?._id ?? messageId;
|
||||||
const message = game.messages.get(messageId);
|
const message = game.messages.get(messageId);
|
||||||
const damageConfig = {
|
const damageConfig = {
|
||||||
dialog: {},
|
dialog: {},
|
||||||
...config,
|
...config,
|
||||||
roll: formulas,
|
damageFormula,
|
||||||
|
resourceFormulas,
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
isCritical: Boolean(message?.system.roll?.isCritical)
|
isCritical: Boolean(message?.system.roll?.isCritical)
|
||||||
};
|
};
|
||||||
|
|
@ -92,7 +80,7 @@ export default class DamageField extends fields.SchemaField {
|
||||||
|
|
||||||
const targetDamage = [];
|
const targetDamage = [];
|
||||||
const damagePromises = [];
|
const damagePromises = [];
|
||||||
for (let target of targets) {
|
for (const target of targets) {
|
||||||
const actor = foundry.utils.fromUuidSync(target.actorId);
|
const actor = foundry.utils.fromUuidSync(target.actorId);
|
||||||
if (!actor) continue;
|
if (!actor) continue;
|
||||||
if (!config.hasHealing && config.onSave && target.saved?.success === true) {
|
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 }))
|
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
||||||
);
|
);
|
||||||
else {
|
else {
|
||||||
const configDamage = foundry.utils.deepClone(config.damage);
|
const configDamage = config.damage.clone();
|
||||||
const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
configDamage.main &&= configDamage.main.toJSON();
|
||||||
const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
if (configDamage.main) {
|
||||||
if (configDamage.hitPoints) {
|
const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
||||||
for (const part of configDamage.hitPoints.parts) {
|
const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
||||||
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
configDamage.main.total = Math.ceil(configDamage.main.total * multiplier * takenMultiplier);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
damagePromises.push(
|
damagePromises.push(
|
||||||
|
|
@ -183,13 +170,19 @@ export default class DamageField extends fields.SchemaField {
|
||||||
/**
|
/**
|
||||||
* Prepare formulas for Damage Roll
|
* Prepare formulas for Damage Roll
|
||||||
* Must be called within Action context or similar.
|
* 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
|
* @param {object} data Action getRollData
|
||||||
* @returns
|
* @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 = [];
|
const formattedFormulas = [];
|
||||||
formulas.forEach(formula => {
|
for (const formula of formulas) {
|
||||||
if (isNaN(formula.formula))
|
if (isNaN(formula.formula))
|
||||||
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
|
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
|
||||||
const same = formattedFormulas.find(
|
const same = formattedFormulas.find(
|
||||||
|
|
@ -197,7 +190,8 @@ export default class DamageField extends fields.SchemaField {
|
||||||
);
|
);
|
||||||
if (same) same.formula += ` + ${formula.formula}`;
|
if (same) same.formula += ` + ${formula.formula}`;
|
||||||
else formattedFormulas.push(formula);
|
else formattedFormulas.push(formula);
|
||||||
});
|
}
|
||||||
|
|
||||||
return formattedFormulas;
|
return formattedFormulas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,6 +288,7 @@ export class DHResourceData extends foundry.abstract.DataModel {
|
||||||
/** @override */
|
/** @override */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
|
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||||
applyTo: new fields.StringField({
|
applyTo: new fields.StringField({
|
||||||
choices: CONFIG.DH.GENERAL.healingTypes,
|
choices: CONFIG.DH.GENERAL.healingTypes,
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -316,7 +311,16 @@ export class DHDamageData extends DHResourceData {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...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(
|
type: new fields.SetField(
|
||||||
new fields.StringField({
|
new fields.StringField({
|
||||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,7 @@ export function ActionMixin(Base) {
|
||||||
result = this.parent;
|
result = this.parent;
|
||||||
} else {
|
} else {
|
||||||
result = await this.item.update({ [path]: updates }, options);
|
result = await this.item.update({ [path]: updates }, options);
|
||||||
|
if (!result) return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.inCollection
|
return this.inCollection
|
||||||
|
|
|
||||||
|
|
@ -67,13 +67,11 @@ export default class DHWeapon extends AttachableItem {
|
||||||
type: 'attack'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: {
|
main: {
|
||||||
hitPoints: {
|
type: ['physical'],
|
||||||
type: ['physical'],
|
value: {
|
||||||
value: {
|
multiplier: 'prof',
|
||||||
multiplier: 'prof',
|
dice: 'd8'
|
||||||
dice: 'd8'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,11 +228,12 @@ export default class DHWeapon extends AttachableItem {
|
||||||
game.i18n.localize(`DAGGERHEART.CONFIG.Burden.${burden}`)
|
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];
|
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 (!value.custom.enabled && value.bonus) parts.push(value.bonus.signedString());
|
||||||
|
|
||||||
if (type.size > 0) {
|
if (type?.size) {
|
||||||
const typeTags = Array.from(type)
|
const typeTags = Array.from(type)
|
||||||
.map(t => game.i18n.localize(`DAGGERHEART.CONFIG.DamageType.${t}.abbreviation`))
|
.map(t => game.i18n.localize(`DAGGERHEART.CONFIG.DamageType.${t}.abbreviation`))
|
||||||
.join(' | ');
|
.join(' | ');
|
||||||
|
|
@ -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 (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`));
|
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 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)
|
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { ChatDamageData } from './chat-message/chatDamageData.mjs';
|
||||||
|
|
||||||
export default class TagTeamData extends foundry.abstract.DataModel {
|
export default class TagTeamData extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -37,6 +39,7 @@ export class MemberData extends foundry.abstract.DataModel {
|
||||||
}),
|
}),
|
||||||
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
||||||
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||||
|
damageRollData: new fields.EmbeddedDataField(ChatDamageData),
|
||||||
selected: new fields.BooleanField({ initial: false })
|
selected: new fields.BooleanField({ initial: false })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,8 @@ export default class BaseRoll extends Roll {
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TOOLTIP_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRollTooltip.hbs';
|
static TOOLTIP_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRollTooltip.hbs';
|
||||||
|
|
||||||
|
get modifierTotal() {
|
||||||
|
return this.total - this.dice.reduce((acc, dice) => acc + dice.total, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,36 +13,34 @@ export default class DamageRoll extends DHRoll {
|
||||||
|
|
||||||
static DefaultDialog = DamageDialog;
|
static DefaultDialog = DamageDialog;
|
||||||
|
|
||||||
|
static createRollInstance(config) {
|
||||||
|
return new this(undefined, config.data, config);
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}) {
|
||||||
if (config.dialog.configure === false) roll.constructFormula(config);
|
if (config.dialog.configure === false) roll.constructFormulas(config);
|
||||||
for (const roll of config.roll) await roll.roll.evaluate();
|
|
||||||
|
|
||||||
roll._evaluated = true;
|
const evaluateRoll = async roll => {
|
||||||
|
await roll.roll.evaluate();
|
||||||
const parts = [];
|
roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] };
|
||||||
for (const rollData of config.roll) {
|
return roll.roll;
|
||||||
const roll = rollData.roll;
|
|
||||||
parts.push({
|
|
||||||
...rollData,
|
|
||||||
...roll.options.roll,
|
|
||||||
total: roll.total,
|
|
||||||
formula: roll.formula,
|
|
||||||
dice: roll.dice.map(d => ({
|
|
||||||
dice: d.denomination,
|
|
||||||
total: d.total,
|
|
||||||
formula: d.formula,
|
|
||||||
results: d.results
|
|
||||||
})),
|
|
||||||
damageTypes: [...(rollData.damageTypes ?? [])],
|
|
||||||
roll,
|
|
||||||
type: config.type,
|
|
||||||
modifierTotal: this.calculateTotalModifiers(roll)
|
|
||||||
});
|
|
||||||
rollData.roll = JSON.stringify(roll.toJSON());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.damage = this.unifyDamageRoll(parts);
|
if (!config.damage) config.damage = { main: null, resources: {} };
|
||||||
|
|
||||||
|
if (config.damageFormula) {
|
||||||
|
config.damage.main = await evaluateRoll(config.damageFormula);
|
||||||
|
config.damage.main.options = { damageTypes:
|
||||||
|
config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const roll of config.resourceFormulas) {
|
||||||
|
config.damage.resources[roll.applyTo] = await evaluateRoll(roll);
|
||||||
|
}
|
||||||
|
|
||||||
|
roll._evaluated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
|
|
@ -53,9 +51,10 @@ export default class DamageRoll extends DHRoll {
|
||||||
const diceRolls = [];
|
const diceRolls = [];
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
config.mute = true;
|
config.mute = true;
|
||||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
const pool = foundry.dice.terms.PoolTerm.fromRolls([
|
||||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
...(config.damage.main ? [config.damage.main] : []),
|
||||||
);
|
...Object.values(config.damage.resources)
|
||||||
|
]);
|
||||||
diceRolls.push(Roll.fromTerms([pool]));
|
diceRolls.push(Roll.fromTerms([pool]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,22 +65,14 @@ export default class DamageRoll extends DHRoll {
|
||||||
await super.buildPost(roll, config, message);
|
await super.buildPost(roll, config, message);
|
||||||
|
|
||||||
if (config.source?.message) {
|
if (config.source?.message) {
|
||||||
chatMessage.update({ 'system.damage': config.damage });
|
chatMessage.update({ 'system.damage': {
|
||||||
|
...config.damage.toObject(),
|
||||||
|
main: config.damage.main,
|
||||||
|
resources: config.damage.resources
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static unifyDamageRoll(rolls) {
|
|
||||||
const unified = {};
|
|
||||||
rolls.forEach(r => {
|
|
||||||
const resource = unified[r.applyTo] ?? { formula: '', total: 0, parts: [] };
|
|
||||||
resource.formula += `${resource.formula !== '' ? ' + ' : ''}${r.formula}`;
|
|
||||||
resource.total += r.total;
|
|
||||||
resource.parts.push(r);
|
|
||||||
unified[r.applyTo] = resource;
|
|
||||||
});
|
|
||||||
return unified;
|
|
||||||
}
|
|
||||||
|
|
||||||
static formatGlobal(rolls) {
|
static formatGlobal(rolls) {
|
||||||
let formula, total;
|
let formula, total;
|
||||||
const applyTo = new Set(rolls.flatMap(r => r.applyTo));
|
const applyTo = new Set(rolls.flatMap(r => r.applyTo));
|
||||||
|
|
@ -130,10 +121,8 @@ export default class DamageRoll extends DHRoll {
|
||||||
const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
|
const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
|
||||||
const changeKeys = [];
|
const changeKeys = [];
|
||||||
|
|
||||||
for (const roll of this.options.roll) {
|
for (const damageType of this.options.damageFormula?.damageTypes?.values?.() ?? []) {
|
||||||
for (const damageType of roll.damageTypes?.values?.() ?? []) {
|
changeKeys.push(`system.bonuses.${type}.${damageType}`);
|
||||||
changeKeys.push(`system.bonuses.${type}.${damageType}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = this.data.parent?.items?.get(this.options.source.item);
|
const item = this.data.parent?.items?.get(this.options.source.item);
|
||||||
|
|
@ -151,62 +140,69 @@ export default class DamageRoll extends DHRoll {
|
||||||
return changeKeys;
|
return changeKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFormula(config) {
|
constructFormulas(config) {
|
||||||
|
return {
|
||||||
|
damageFormula: this.constructFormula(this.options.damageFormula, config, true),
|
||||||
|
resourceFormulas: this.options.resourceFormulas.map(x => this.constructFormula(x, config))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructFormula(formulaData, config, isDamage) {
|
||||||
|
if (!formulaData) return null;
|
||||||
this.options.isCritical = config.isCritical;
|
this.options.isCritical = config.isCritical;
|
||||||
for (const [index, part] of this.options.roll.entries()) {
|
|
||||||
const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id;
|
formulaData.roll = new Roll(Roll.replaceFormulaData(formulaData.formula, config.data));
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data);
|
||||||
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
if (formulaData.extraFormula) {
|
||||||
part.modifiers = this.applyBaseBonus(part);
|
formulaData.roll.terms.push(
|
||||||
this.addModifiers(part);
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
part.modifiers?.forEach(m => {
|
...this.constructor.parse(formulaData.extraFormula, this.options.data)
|
||||||
part.roll.terms.push(...this.formatModifier(m.value));
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (isDamage && formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
formulaData.modifiers = this.applyBaseBonus(formulaData);
|
||||||
|
this.addModifiers(formulaData);
|
||||||
|
formulaData.modifiers?.forEach(m => {
|
||||||
|
formulaData.roll.terms.push(...this.formatModifier(m.value));
|
||||||
|
});
|
||||||
|
|
||||||
|
/* To Remove When Reaction System */
|
||||||
|
for (const mod in config.modifiers) {
|
||||||
|
const modifier = config.modifiers[mod];
|
||||||
|
if (
|
||||||
|
modifier.beforeCrit === true &&
|
||||||
|
(modifier.enabled || modifier.value)
|
||||||
|
) modifier.callback(formulaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
/* To Remove When Reaction System */
|
||||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
for (const mod in config.modifiers) {
|
||||||
for (const mod in config.modifiers) {
|
const modifier = config.modifiers[mod];
|
||||||
const modifier = config.modifiers[mod];
|
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(formulaData);
|
||||||
if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.extraFormula) {
|
if (config.damageOptions.groupAttack?.numAttackers > 1) {
|
||||||
part.roll.terms.push(
|
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
|
||||||
...this.constructor.parse(part.extraFormula, this.options.data)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) {
|
|
||||||
const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm];
|
const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm];
|
||||||
for (const term of part.roll.terms) {
|
for (const term of formulaData.roll.terms) {
|
||||||
if (damageTypes.some(type => term instanceof type)) {
|
if (damageTypes.some(type => term instanceof type)) {
|
||||||
term.number *= config.damageOptions.groupAttack.numAttackers;
|
term.number *= config.damageOptions.groupAttack.numAttackers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isCritical && isHitpointPart) {
|
if (config.isCritical) {
|
||||||
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
const total = formulaData.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
part.roll.terms.push(...this.formatModifier(total));
|
formulaData.roll.terms.push(...this.formatModifier(total));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
|
||||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
|
||||||
for (const mod in config.modifiers) {
|
|
||||||
const modifier = config.modifiers[mod];
|
|
||||||
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
part.roll._formula = this.constructor.getFormula(part.roll.terms);
|
|
||||||
}
|
}
|
||||||
return this.options.roll;
|
|
||||||
|
formulaData.roll._formula = this.constructor.getFormula(formulaData.roll.terms);
|
||||||
|
|
||||||
|
return formulaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
/* To Remove When Reaction System */
|
||||||
|
|
@ -299,76 +295,4 @@ export default class DamageRoll extends DHRoll {
|
||||||
config.modifiers = mods;
|
config.modifiers = mods;
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async reroll(rollPart, dice, result) {
|
|
||||||
let diceIndex = 0;
|
|
||||||
let parsedRoll = game.system.api.dice.DamageRoll.fromData({
|
|
||||||
...rollPart.roll,
|
|
||||||
terms: rollPart.roll.terms.map(term => {
|
|
||||||
const isDie = term.class === 'Die';
|
|
||||||
const fixedTerm = {
|
|
||||||
...term,
|
|
||||||
...(isDie ? { results: rollPart.dice[diceIndex].results } : {})
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isDie) diceIndex++;
|
|
||||||
return fixedTerm;
|
|
||||||
}),
|
|
||||||
class: 'DamageRoll',
|
|
||||||
evaluated: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsedDiceTerms = Object.keys(parsedRoll.terms).reduce((acc, key) => {
|
|
||||||
const term = parsedRoll.terms[key];
|
|
||||||
if (term instanceof CONFIG.Dice.termTypes.DiceTerm) acc[Object.keys(acc).length] = term;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
const term = parsedDiceTerms[dice];
|
|
||||||
const termResult = parsedDiceTerms[dice].results[result];
|
|
||||||
|
|
||||||
const newIndex = parsedDiceTerms[dice].results.length;
|
|
||||||
await term.reroll(`/r1=${termResult.result}`);
|
|
||||||
|
|
||||||
const diceRolls = [];
|
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
|
||||||
const newResult = parsedDiceTerms[dice].results[newIndex];
|
|
||||||
diceRolls.push({
|
|
||||||
_evaluated: true,
|
|
||||||
dice: [
|
|
||||||
new foundry.dice.terms.Die({
|
|
||||||
...term,
|
|
||||||
total: newResult.result,
|
|
||||||
faces: term._faces,
|
|
||||||
results: [newResult]
|
|
||||||
})
|
|
||||||
],
|
|
||||||
options: { appearance: {} }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await triggerChatRollFx(diceRolls);
|
|
||||||
await parsedRoll.evaluate();
|
|
||||||
|
|
||||||
const results = parsedRoll.dice[dice].results.map(result => ({
|
|
||||||
...result,
|
|
||||||
discarded: !result.active
|
|
||||||
}));
|
|
||||||
const newResult = results.splice(results.length - 1, 1);
|
|
||||||
results.splice(Number(result) + 1, 0, newResult[0]);
|
|
||||||
|
|
||||||
const rerolledDice = parsedRoll.dice.map((x, index) => {
|
|
||||||
const isRerollDice = index === Number(dice);
|
|
||||||
if (!isRerollDice) return { ...x, dice: x.denomination };
|
|
||||||
return {
|
|
||||||
dice: parsedRoll.dice[dice].denomination,
|
|
||||||
total: parsedRoll.dice[dice].total,
|
|
||||||
results: results.map(result => ({
|
|
||||||
...result,
|
|
||||||
hasRerolls: result.hasRerolls || isRerollDice
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { parsedRoll, rerolledDice };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||||
import { triggerChatRollFx } from '../helpers/utils.mjs';
|
import { triggerChatRollFx } from '../helpers/utils.mjs';
|
||||||
|
import BaseRoll from './baseRoll.mjs';
|
||||||
|
|
||||||
export default class DHRoll extends Roll {
|
export default class DHRoll extends BaseRoll {
|
||||||
baseTerms = [];
|
baseTerms = [];
|
||||||
constructor(formula, data = {}, options = {}) {
|
constructor(formula, data = {}, options = {}) {
|
||||||
super(formula, data, foundry.utils.mergeObject(options, { roll: [] }, { overwrite: false }));
|
super(formula, data, foundry.utils.mergeObject(options, { roll: [] }, { overwrite: false }));
|
||||||
|
|
@ -40,6 +41,10 @@ export default class DHRoll extends Roll {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createRollInstance(config) {
|
||||||
|
return new this(config.roll.formula, config.data, config);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Partial<RollConfig>} config
|
* @param {Partial<RollConfig>} config
|
||||||
* @returns {Promise<RollConfig>}
|
* @returns {Promise<RollConfig>}
|
||||||
|
|
@ -57,7 +62,7 @@ export default class DHRoll extends Roll {
|
||||||
|
|
||||||
this.temporaryModifierBuilder(config);
|
this.temporaryModifierBuilder(config);
|
||||||
|
|
||||||
let roll = new this(config.roll.formula, config.data, config);
|
let roll = this.createRollInstance(config);
|
||||||
if (config.dialog.configure !== false) {
|
if (config.dialog.configure !== false) {
|
||||||
// Open Roll Dialog
|
// Open Roll Dialog
|
||||||
const DialogClass = config.dialog?.class ?? this.DefaultDialog;
|
const DialogClass = config.dialog?.class ?? this.DefaultDialog;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import HopeDie from './hopeDie.mjs';
|
||||||
import FearDie from './fearDie.mjs';
|
import FearDie from './fearDie.mjs';
|
||||||
import AdvantageDie from './advantageDie.mjs';
|
import AdvantageDie from './advantageDie.mjs';
|
||||||
import DisadvantageDie from './disadvantageDie.mjs';
|
import DisadvantageDie from './disadvantageDie.mjs';
|
||||||
|
export { default as BaseDie } from './baseDie.mjs';
|
||||||
|
|
||||||
export const diceTypes = {
|
export const diceTypes = {
|
||||||
DualityDie,
|
DualityDie,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export default class AdvantageDie extends foundry.dice.terms.Die {
|
import BaseDie from './baseDie.mjs';
|
||||||
|
|
||||||
|
export default class AdvantageDie extends BaseDie {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
|
||||||
12
module/dice/die/baseDie.mjs
Normal file
12
module/dice/die/baseDie.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default class BaseDie extends foundry.dice.terms.Die {
|
||||||
|
async rerollResult(resultIndex) {
|
||||||
|
const result = this.results[resultIndex];
|
||||||
|
result.rerolled = true;
|
||||||
|
result.active = false;
|
||||||
|
await this.roll({ reroll: true });
|
||||||
|
|
||||||
|
const rerolledResult = this.results[this.results.length - 1];
|
||||||
|
this.results.splice(this.results.length - 1, 1);
|
||||||
|
this.results.splice(resultIndex, 0, rerolledResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export default class DisadvantageDie extends foundry.dice.terms.Die {
|
import BaseDie from './baseDie.mjs';
|
||||||
|
|
||||||
|
export default class DisadvantageDie extends BaseDie {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import BaseDie from './baseDie.mjs';
|
||||||
import { updateResourcesForDualityReroll } from '../helpers.mjs';
|
import { updateResourcesForDualityReroll } from '../helpers.mjs';
|
||||||
|
|
||||||
export default class DualityDie extends foundry.dice.terms.Die {
|
export default class DualityDie extends BaseDie {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -409,8 +409,4 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
return rerolled;
|
return rerolled;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromJSON(json) {
|
|
||||||
return super.fromJSON(json);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -622,6 +622,11 @@ export default class DhpActor extends Actor {
|
||||||
return rollData;
|
return rollData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if damage can be reduced in one way or another.
|
||||||
|
* @param {number} hpDamage the amount of marked hp that will be marked
|
||||||
|
* @param {Set<string>} types a list of damage types
|
||||||
|
*/
|
||||||
#canReduceDamage(hpDamage, types) {
|
#canReduceDamage(hpDamage, types) {
|
||||||
const { stressDamageReduction, disabledArmor, reduceSeverity, thresholdImmunities } =
|
const { stressDamageReduction, disabledArmor, reduceSeverity, thresholdImmunities } =
|
||||||
this.system.rules.damageReduction;
|
this.system.rules.damageReduction;
|
||||||
|
|
@ -648,35 +653,32 @@ export default class DhpActor extends Actor {
|
||||||
return canUseArmor || canUseStress || hasReduceSeverity || hasThresholdImmunity;
|
return canUseArmor || canUseStress || hasReduceSeverity || hasThresholdImmunity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeDamage(damages, isDirect = false) {
|
async takeDamage(args, isDirect = false) {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damages) === false) return null;
|
args = this.#parseDamageArgs(args);
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, args) === false) return null;
|
||||||
|
|
||||||
if (this.type === 'companion') {
|
if (this.type === 'companion') {
|
||||||
await this.modifyResource([{ value: 1, key: 'stress' }]);
|
await this.modifyResource([{ value: 1, key: 'stress' }]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updates = [];
|
const updates = args.resourceUpdates;
|
||||||
|
if (args.main) {
|
||||||
|
// todo: avoid side effects, but hook currently requires it
|
||||||
|
args.main.value = this.calculateDamage(args.main.value, args.main.damageTypes);
|
||||||
|
}
|
||||||
|
|
||||||
Object.entries(damages).forEach(([key, damage]) => {
|
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, args) === false) return null;
|
||||||
damage.parts.forEach(part => {
|
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
|
||||||
part.total = this.calculateDamage(part.total, part.damageTypes);
|
|
||||||
const update = updates.find(u => u.key === key);
|
|
||||||
if (update) {
|
|
||||||
update.value += part.total;
|
|
||||||
update.damageTypes.add(...new Set(part.damageTypes));
|
|
||||||
} else updates.push({ value: part.total, key, damageTypes: new Set(part.damageTypes) });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
|
// Convert deducted resources to a record of updates. Return if nothing to do.
|
||||||
|
if (!updates.some(u => u.value) && !args.main) return;
|
||||||
|
|
||||||
if (!updates.length) return;
|
if (args.main) {
|
||||||
|
const hpDamage = {
|
||||||
const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
value: this.convertDamageToThreshold(args.main.value),
|
||||||
if (hpDamage?.value) {
|
damageTypes: new Set(args.main.damageTypes),
|
||||||
hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
|
key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id
|
||||||
|
};
|
||||||
if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
|
if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
|
||||||
const armorSlotResult = await this.owner.query(
|
const armorSlotResult = await this.owner.query(
|
||||||
'armorSlot',
|
'armorSlot',
|
||||||
|
|
@ -691,7 +693,7 @@ export default class DhpActor extends Actor {
|
||||||
);
|
);
|
||||||
if (armorSlotResult) {
|
if (armorSlotResult) {
|
||||||
const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult;
|
const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult;
|
||||||
updates.find(u => u.key === 'hitPoints').value = modifiedDamage;
|
hpDamage.value = modifiedDamage;
|
||||||
for (const armorChange of armorChanges) {
|
for (const armorChange of armorChanges) {
|
||||||
updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid });
|
updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid });
|
||||||
}
|
}
|
||||||
|
|
@ -701,20 +703,24 @@ export default class DhpActor extends Actor {
|
||||||
else updates.push({ value: stressSpent, key: 'stress' });
|
else updates.push({ value: stressSpent, key: 'stress' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (this.type === 'adversary') {
|
||||||
if (this.type === 'adversary') {
|
|
||||||
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
|
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
|
||||||
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
|
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
|
||||||
}, 0);
|
}, 0);
|
||||||
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
|
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
|
||||||
|
if (this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]) {
|
||||||
if (
|
hpDamage.value = Math.max(0, hpDamage.value - 1);
|
||||||
hpDamage.value &&
|
|
||||||
this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
|
|
||||||
) {
|
|
||||||
hpDamage.value -= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge existing hitPoint deduction with finalised damage deduction
|
||||||
|
const existing = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
||||||
|
if (existing) {
|
||||||
|
existing.value += hpDamage.value;
|
||||||
|
existing.damageTypes = hpDamage.damageTypes;
|
||||||
|
} else {
|
||||||
|
updates.push(hpDamage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await game.system.registeredTriggers.runTrigger(
|
const results = await game.system.registeredTriggers.runTrigger(
|
||||||
|
|
@ -730,11 +736,9 @@ export default class DhpActor extends Actor {
|
||||||
resourceMap.updateResources();
|
resourceMap.updateResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
updates.forEach(
|
for (const u of updates) {
|
||||||
u =>
|
u.value = u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value;
|
||||||
(u.value =
|
}
|
||||||
u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.modifyResource(updates);
|
await this.modifyResource(updates);
|
||||||
|
|
||||||
|
|
@ -743,6 +747,36 @@ export default class DhpActor extends Actor {
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async takeHealing(args) {
|
||||||
|
args = this.#parseDamageArgs({ resources: 'resources' in args ? args.resources : args });
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, args) === false) return null;
|
||||||
|
|
||||||
|
const updates = args.resourceUpdates;
|
||||||
|
for (const u of updates) {
|
||||||
|
const shouldFlip = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false);
|
||||||
|
u.value = shouldFlip ? u.value * -1 : u.value;
|
||||||
|
}
|
||||||
|
await this.modifyResource(updates);
|
||||||
|
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null;
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse damage args that may be coming from takeHealing or takeDamage. Used to simplify macro usage and roll vs non-roll usage */
|
||||||
|
#parseDamageArgs(args = {}) {
|
||||||
|
const damageRoll = 'total' in args ? args : (args.main ?? args.damage);
|
||||||
|
const damageValue = typeof damageRoll === 'number' ? damageRoll : damageRoll?.total;
|
||||||
|
const damageTypes = Array.from(damageRoll?.options?.damageTypes ?? damageRoll?.damageTypes ?? []);
|
||||||
|
const main = typeof damageValue === 'number' ? { key: 'damage', value: damageValue, damageTypes } : null;
|
||||||
|
const resourceUpdates = Object.entries(args.resources ?? {}).map(([key, damage]) => ({
|
||||||
|
key,
|
||||||
|
value: typeof damage === 'number' ? damage : damage?.total ?? 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { main, resourceUpdates };
|
||||||
|
}
|
||||||
|
|
||||||
calculateDamage(baseDamage, type) {
|
calculateDamage(baseDamage, type) {
|
||||||
if (this.canResist(type, 'immunity')) return 0;
|
if (this.canResist(type, 'immunity')) return 0;
|
||||||
if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
|
if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
|
||||||
|
|
@ -767,32 +801,6 @@ export default class DhpActor extends Actor {
|
||||||
return reduction === Infinity ? 0 : reduction;
|
return reduction === Infinity ? 0 : reduction;
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeHealing(healings) {
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, healings) === false) return null;
|
|
||||||
|
|
||||||
const updates = [];
|
|
||||||
Object.entries(healings).forEach(([key, healing]) => {
|
|
||||||
healing.parts.forEach(part => {
|
|
||||||
const update = updates.find(u => u.key === key);
|
|
||||||
if (update) update.value += part.total;
|
|
||||||
else updates.push({ value: part.total, key });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updates.forEach(
|
|
||||||
u =>
|
|
||||||
(u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false)
|
|
||||||
? u.value * -1
|
|
||||||
: u.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.modifyResource(updates);
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null;
|
|
||||||
|
|
||||||
return updates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resources are modified asynchronously, so be careful not to update the same resource in
|
* Resources are modified asynchronously, so be careful not to update the same resource in
|
||||||
* quick succession.
|
* quick succession.
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,8 @@ export default class RegisterHandlebarsHelpers {
|
||||||
return formula;
|
return formula;
|
||||||
}
|
}
|
||||||
|
|
||||||
static damageSymbols(damageParts) {
|
static damageSymbols(damageData) {
|
||||||
const allTypes = [...new Set([...damageParts].flatMap(x => Array.from(x.type)))];
|
const symbols = damageData.type.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon);
|
||||||
const symbols = allTypes.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon);
|
|
||||||
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
|
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -700,19 +700,6 @@ export async function RefreshFeatures(
|
||||||
return refreshedActors;
|
return refreshedActors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUnusedDamageTypes(parts) {
|
|
||||||
const usedKeys = Object.keys(parts);
|
|
||||||
return Object.keys(CONFIG.DH.GENERAL.healingTypes).reduce((acc, key) => {
|
|
||||||
if (!usedKeys.includes(key))
|
|
||||||
acc.push({
|
|
||||||
value: key,
|
|
||||||
label: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label)
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns resolved armor sources ordered by application order */
|
/** Returns resolved armor sources ordered by application order */
|
||||||
export function getArmorSources(actor) {
|
export function getArmorSources(actor) {
|
||||||
const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData);
|
const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ export class Migration_2_5_2 extends MigrationHandlerBase {
|
||||||
async updateActiveEffectSource(effectSource, item) {
|
async updateActiveEffectSource(effectSource, item) {
|
||||||
let shouldUpdate = false;
|
let shouldUpdate = false;
|
||||||
const newChanges = [];
|
const newChanges = [];
|
||||||
const srdItem = item?._stats.compendiumSource ?
|
|
||||||
|
const srdItem = item?._stats?.compendiumSource ?
|
||||||
await foundry.utils.fromUuid(item?._stats.compendiumSource) :
|
await foundry.utils.fromUuid(item?._stats.compendiumSource) :
|
||||||
null;
|
null;
|
||||||
for (let i = 0; i < effectSource.system.changes.length; i++) {
|
for (let i = 0; i < effectSource.system.changes.length; i++) {
|
||||||
|
|
|
||||||
20
module/systemRegistration/migration-handlers/2_6_0.mjs
Normal file
20
module/systemRegistration/migration-handlers/2_6_0.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { MigrationHandlerBase } from './base.mjs';
|
||||||
|
|
||||||
|
export class Migration_2_6_0 extends MigrationHandlerBase {
|
||||||
|
version = '2.6.0';
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async updateActorSource(actor) {
|
||||||
|
if (actor.type === 'party' && Object.keys(actor.system.tagTeam.members).length) {
|
||||||
|
return {
|
||||||
|
_id: actor._id,
|
||||||
|
system: {
|
||||||
|
tagTeam: {
|
||||||
|
initiator: null,
|
||||||
|
members: _replace({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* @import DHItem from "../../documents/item.mjs";
|
* @import DHItem from "../../documents/item.mjs";
|
||||||
|
* @import DhActor from "../../documents/actor.mjs";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
* The base class of an async migration.
|
* The base class of an async migration.
|
||||||
* These are generally run between versions for things that require compendiums or must be done in post.
|
* These are generally run between versions for things that require compendiums or must be done in post.
|
||||||
* The migrate() functions calls the various updateXSource() functions.
|
* The migrate() functions calls the various updateXSource() functions.
|
||||||
|
|
@ -22,6 +23,16 @@ export class MigrationHandlerBase {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a world actor
|
||||||
|
* @param {DhActor} actor
|
||||||
|
* @returns {Promise<object>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async updateActorSource(actor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
async migrate() {
|
async migrate() {
|
||||||
// todo: handle more than just migrating effects. Right now this can only migrate effects
|
// todo: handle more than just migrating effects. Right now this can only migrate effects
|
||||||
// NOTE: the preload is hardcoded, we should not hardcode it
|
// NOTE: the preload is hardcoded, we should not hardcode it
|
||||||
|
|
@ -60,10 +71,39 @@ export class MigrationHandlerBase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const actor of game.actors) {
|
const updateActor = async actor => {
|
||||||
|
const actorUpdate = await this.updateActorSource(actor);
|
||||||
|
if (actorUpdate) {
|
||||||
|
batch.push({
|
||||||
|
action: 'update',
|
||||||
|
documentName: 'Actor',
|
||||||
|
updates: [actorUpdate]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const aeUpdates = [];
|
||||||
for (const item of actor.items) {
|
for (const item of actor.items) {
|
||||||
await updateItem(item);
|
await updateItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const effect of actor.effects) {
|
||||||
|
const changes = await this.updateActiveEffectSource(effect.toObject(), { parent: actor });
|
||||||
|
if (changes) aeUpdates.push(changes);
|
||||||
|
}
|
||||||
|
if (aeUpdates.length) {
|
||||||
|
batch.push({
|
||||||
|
action: 'update',
|
||||||
|
documentName: 'ActiveEffect',
|
||||||
|
updates: aeUpdates,
|
||||||
|
parent: actor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (const actor of game.actors) {
|
||||||
|
await updateActor(actor);
|
||||||
|
|
||||||
progress.advance();
|
progress.advance();
|
||||||
}
|
}
|
||||||
for (const item of game.items) {
|
for (const item of game.items) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { defaultRestOptions } from '../config/generalConfig.mjs';
|
import { defaultRestOptions } from '../config/generalConfig.mjs';
|
||||||
import { Migration_2_5_2 } from './migration-handlers/2_5_2.mjs';
|
import { Migration_2_5_2 } from './migration-handlers/2_5_2.mjs';
|
||||||
|
import { Migration_2_6_0 } from './migration-handlers/2_6_0.mjs';
|
||||||
|
|
||||||
export async function runMigrations() {
|
export async function runMigrations() {
|
||||||
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
||||||
|
|
@ -329,7 +330,8 @@ export async function runMigrations() {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
const migrations = [
|
const migrations = [
|
||||||
new Migration_2_5_2()
|
new Migration_2_5_2(),
|
||||||
|
new Migration_2_6_0()
|
||||||
].filter(m => m.version && foundry.utils.isNewerVersion(m.version, lastMigrationVersion));
|
].filter(m => m.version && foundry.utils.isNewerVersion(m.version, lastMigrationVersion));
|
||||||
|
|
||||||
for (const handler of migrations) {
|
for (const handler of migrations) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
font-size: var(--font-size-20);
|
||||||
|
color: light-dark(@dark, @beige);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
.bonuses {
|
.bonuses {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
.critical-chip {
|
.critical-chip {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="nest-inputs">
|
<div class="nest-inputs">
|
||||||
{{formField ../fields.scalable label="DAGGERHEART.GENERAL.scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox" localize=true}}
|
{{formField ../fields.scalable label="DAGGERHEART.GENERAL.scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox" localize=true}}
|
||||||
{{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="DAGGERHEART.GENERAL.resource" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
|
{{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="DAGGERHEART.GENERAL.Resource.single" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
|
||||||
{{formField ../fields.value label="DAGGERHEART.GENERAL.amount" value=cost.value name=(concat "cost." index ".value") localize=true}}
|
{{formField ../fields.value label="DAGGERHEART.GENERAL.amount" value=cost.value name=(concat "cost." index ".value") localize=true}}
|
||||||
{{formField ../fields.step label="DAGGERHEART.GENERAL.step" value=cost.step name=(concat "cost." index ".step") disabled=(not cost.scalable) localize=true}}
|
{{formField ../fields.step label="DAGGERHEART.GENERAL.step" value=cost.step name=(concat "cost." index ".step") disabled=(not cost.scalable) localize=true}}
|
||||||
<a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a>
|
<a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a>
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,114 @@
|
||||||
|
{{#unless (eq @root.source.type 'healing')}}
|
||||||
<fieldset class="one-column">
|
<fieldset class="one-column">
|
||||||
<legend class="with-icon">
|
<legend class="with-icon">
|
||||||
{{#if (eq @root.source.type 'healing')}}
|
|
||||||
{{localize "DAGGERHEART.GENERAL.healing"}}
|
|
||||||
{{else}}
|
|
||||||
{{localize "DAGGERHEART.GENERAL.damage"}}
|
{{localize "DAGGERHEART.GENERAL.damage"}}
|
||||||
|
{{#if source.main}}
|
||||||
|
<a data-action="removeDamage"><i class="fa-solid fa-trash icon-button"></i></a>
|
||||||
|
{{else}}
|
||||||
|
<a data-action="addDamage"><i class="fa-solid fa-plus icon-button"></i></a>
|
||||||
|
{{/if}}
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
{{#if source.main}}
|
||||||
|
<div class="nest-inputs">
|
||||||
|
{{#if @root.hasBaseDamage}}
|
||||||
|
{{formField @root.fields.damage.fields.main.fields.includeBase value=source.main.includeBase name="damage.main.includeBase" classes="checkbox" localize=true }}
|
||||||
|
{{/if}}
|
||||||
|
{{#unless (eq @root.source.type 'healing')}}
|
||||||
|
{{formField baseFields.main.fields.direct value=source.main.direct name=(concat path "damage.main.direct") localize=true classes="checkbox"}}
|
||||||
|
{{/unless}}
|
||||||
|
{{#if (and @root.isNPC (not (eq path 'system.attack.')))}}
|
||||||
|
{{formField baseFields.main.fields.groupAttack value=source.main.groupAttack name=(concat path "damage.main.groupAttack") localize=true classes="select"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{> damageData damage=source.main fields=fields.main.fields basePath=(concat path "damage.main")}}
|
||||||
|
{{#if horde}}
|
||||||
|
{{> hordeDamage source=source.main fields=fields.main.fields basePath=(concat path "damage.main")}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if (ne @root.source.type 'healing')}}
|
||||||
|
{{formField fields.main.fields.type value=source.main.type name=(concat path "damage.main.type") localize=true}}
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless (eq path 'system.attack.')}}<a data-action="addDamage" {{#if @root.allDamageTypesUsed}}disabled{{/if}}><i class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
|
</fieldset>
|
||||||
</legend>
|
{{/unless}}
|
||||||
<div class="nest-inputs">
|
|
||||||
{{#if @root.hasBaseDamage}}
|
|
||||||
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }}
|
|
||||||
{{/if}}
|
|
||||||
{{#unless (eq @root.source.type 'healing')}}
|
|
||||||
{{formField baseFields.direct value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}}
|
|
||||||
{{/unless}}
|
|
||||||
{{#if (and @root.isNPC (not (eq path 'system.attack.')))}}
|
|
||||||
{{formField baseFields.groupAttack value=source.groupAttack name=(concat path "damage.groupAttack") localize=true classes="select"}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}}
|
{{#unless (eq path 'system.attack.')}}
|
||||||
{{#each source.parts as |dmg key|}}
|
{{! In the future, consider allowing this even on NPCs}}
|
||||||
<div class="nest-inputs">
|
<fieldset class="one-column">
|
||||||
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column{{#if ../path}} no-style{{/if}}">
|
<legend class="with-icon">
|
||||||
<legend class="with-icon">
|
{{#if (eq @root.source.type 'healing')}}
|
||||||
{{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}}
|
{{localize "DAGGERHEART.GENERAL.healing"}}
|
||||||
{{#unless (or dmg.base ../path)}}
|
{{else}}
|
||||||
<a data-action="removeDamage" data-key="{{dmg.applyTo}}"><i class="fas fa-trash"></i></a>
|
{{localize "DAGGERHEART.ACTIONS.Config.damage.markResources"}}
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
</legend>
|
{{#unless @root.allDamageTypesUsed}}<a data-action="addDamageResource"><i class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
|
||||||
|
</legend>
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}}
|
{{#each source.resources as |dmg key|}}
|
||||||
{{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}}
|
<div class="nest-inputs">
|
||||||
{{/if}}
|
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column">
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
|
<legend class="with-icon">
|
||||||
<div class="nest-inputs">
|
{{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}}
|
||||||
<fieldset class="one-column">
|
{{#unless (or dmg.base ../path)}}
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}</legend>
|
<a data-action="removeDamageResource" data-key="{{key}}"><i class="fas fa-trash"></i></a>
|
||||||
{{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}}
|
{{/unless}}
|
||||||
</fieldset>
|
</legend>
|
||||||
<fieldset class="one-column">
|
{{> damageData damage=dmg fields=../fields.resources.element.fields basePath=(concat ../path "damage.resources." dmg.applyTo)}}
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}</legend>
|
</fieldset>
|
||||||
{{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path}}
|
</div>
|
||||||
</fieldset>
|
{{/each}}
|
||||||
</div>
|
</fieldset>
|
||||||
{{else}}
|
{{/unless}}
|
||||||
{{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}}
|
|
||||||
{{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." dmg.applyTo ".type") localize=true}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if ../horde}}
|
|
||||||
<fieldset class="one-column">
|
|
||||||
<legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
|
|
||||||
<div class="nest-inputs">
|
|
||||||
<input type="hidden" name="{{../path}}damage.parts.{{dmg.applyTo}}.valueAlt.multiplier" value="flat">
|
|
||||||
{{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }}
|
|
||||||
{{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.dice") classes="inline-child" localize=true}}
|
|
||||||
{{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.bonus") localize=true classes="inline-child"}}
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
{{/if}}
|
|
||||||
<input type="hidden" name="{{concat ../path "damage.parts." dmg.applyTo ".base"}}" value="{{dmg.base}}">
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
{{#*inline "formula"}}
|
{{#*inline "formula"}}
|
||||||
{{#unless dmg.base}}
|
{{#unless isBase}}
|
||||||
{{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." key "." target ".custom.enabled") classes="checkbox" localize=true}}
|
{{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat basePath ".custom.enabled") classes="checkbox" localize=true}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{#if source.custom.enabled}}
|
{{#if source.custom.enabled}}
|
||||||
{{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." key "." target ".custom.formula") localize=true}}
|
{{formField fields.custom.fields.formula value=source.custom.formula name=(concat basePath ".custom.formula") localize=true}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="nest-inputs">
|
<div class="nest-inputs">
|
||||||
{{#unless @root.isNPC}}
|
{{#unless @root.isNPC}}
|
||||||
{{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." key "." target ".multiplier") localize=true}}
|
{{formField fields.multiplier value=source.multiplier name=(concat basePath ".multiplier") localize=true}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." key "." target ".flatMultiplier") localize=true }}{{/if}}
|
{{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat basePath ".flatMultiplier") localize=true }}{{/if}}
|
||||||
{{formField fields.dice value=source.dice name=(concat path "damage.parts." key "." target ".dice") localize=true}}
|
{{formField fields.dice value=source.dice name=(concat basePath ".dice") localize=true}}
|
||||||
{{formField fields.bonus value=source.bonus name=(concat path "damage.parts." key "." target ".bonus") localize=true}}
|
{{formField fields.bonus value=source.bonus name=(concat basePath ".bonus") localize=true}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if @root.isNPC}}
|
{{#if @root.isNPC}}
|
||||||
<input type="hidden" name="{{path}}damage.parts.{{key}}.{{target}}.multiplier" value="flat">
|
<input type="hidden" name="{{basePath}}.{{target}}.multiplier" value="flat">
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/inline}}
|
||||||
|
|
||||||
|
{{#*inline "damageData"}}
|
||||||
|
{{#if (and (not @root.isNPC) @root.hasRoll (not damage.base))}}
|
||||||
|
{{formField fields.resultBased value=damage.resultBased name=(concat basePath ".resultBased") localize=true classes="checkbox"}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if (and (not @root.isNPC) @root.hasRoll (not damage.base) damage.resultBased)}}
|
||||||
|
<div class="nest-inputs">
|
||||||
|
<fieldset class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}</legend>
|
||||||
|
{{> formula key=damage.applyTo fields=fields.value.fields type=fields.type isBase=damage.base source=damage.value basePath=(concat basePath ".value")}}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}</legend>
|
||||||
|
{{> formula key=damage.applyTo fields=fields.valueAlt.fields type=fields.type isBase=damage.base source=damage.valueAlt basePath=(concat basePath ".valueAlt")}}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{> formula key=damage.applyTo fields=fields.value.fields type=fields.type isBase=damage.base source=damage.value basePath=(concat basePath ".value")}}
|
||||||
|
{{/if}}
|
||||||
|
<input type="hidden" name="{{concat basePath ".base"}}" value="{{damage.base}}">
|
||||||
|
{{/inline}}
|
||||||
|
|
||||||
|
{{#*inline "hordeDamage"}}
|
||||||
|
<fieldset class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
|
||||||
|
<div class="nest-inputs">
|
||||||
|
<input type="hidden" name="{{basePath}}.valueAlt.multiplier" value="flat">
|
||||||
|
{{formField fields.valueAlt.fields.flatMultiplier value=source.valueAlt.flatMultiplier name=(concat basePath ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }}
|
||||||
|
{{formField fields.valueAlt.fields.dice value=source.valueAlt.dice name=(concat basePath ".valueAlt.dice") classes="inline-child" localize=true}}
|
||||||
|
{{formField fields.valueAlt.fields.bonus value=source.valueAlt.bonus name=(concat basePath ".valueAlt.bonus") localize=true classes="inline-child"}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
{{/inline}}
|
{{/inline}}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
<fieldset class="action-category">
|
<fieldset class="action-category">
|
||||||
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
|
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
|
||||||
<div>{{localize "DAGGERHEART.GENERAL.resource"}}</div>
|
<div>{{localize "DAGGERHEART.GENERAL.Resource.single"}}</div>
|
||||||
</legend>
|
</legend>
|
||||||
<div class="action-category-data open">
|
<div class="action-category-data open">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|
|
||||||
|
|
@ -16,33 +16,6 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#each @root.formula}}
|
|
||||||
<div class="damage-formula">
|
|
||||||
<span class="damage-resource"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{roll.formula}}</span>
|
|
||||||
<span class="damage-details">
|
|
||||||
{{#with (lookup @root.config.GENERAL.healingTypes applyTo)}}
|
|
||||||
{{localize label}}
|
|
||||||
{{/with}}
|
|
||||||
{{#unless @root.hasHealing}}
|
|
||||||
{{#if damageTypes}}
|
|
||||||
{{#each damageTypes as | type | }}
|
|
||||||
{{#with (lookup @root.config.GENERAL.damageTypes type)}}
|
|
||||||
<i class="fa-solid {{icon}}"></i>
|
|
||||||
{{/with}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
{{/unless}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="bonuses form-group flexrow">
|
|
||||||
<input type="text" value="{{extraFormula}}" name="roll.{{ @index }}.extraFormula" placeholder="{{localize "DAGGERHEART.GENERAL.situationalBonus"}}">
|
|
||||||
<button class="critical-chip" data-action="toggleCritical">
|
|
||||||
<span><i class="{{#if @root.isCritical}}fa-solid{{else}}fa-regular{{/if}} fa-circle"></i></span>
|
|
||||||
<span class="label">{{localize "DAGGERHEART.GENERAL.criticalShort"}}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
{{#if damageOptions.groupAttack}}
|
{{#if damageOptions.groupAttack}}
|
||||||
<fieldset class="group-attack-container">
|
<fieldset class="group-attack-container">
|
||||||
<legend>{{localize "DAGGERHEART.ACTIONS.Settings.groupAttack.label"}}</legend>
|
<legend>{{localize "DAGGERHEART.ACTIONS.Settings.groupAttack.label"}}</legend>
|
||||||
|
|
@ -60,6 +33,45 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if @root.damageFormula}}
|
||||||
|
{{#with @root.damageFormula}}
|
||||||
|
<div class="damage-formula">
|
||||||
|
<span class="damage-resource"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{roll.formula}}</span>
|
||||||
|
<span class="damage-details">
|
||||||
|
{{localize "DAGGERHEART.GENERAL.damage"}}
|
||||||
|
{{#if damageTypes}}
|
||||||
|
{{#each damageTypes as | type | }}
|
||||||
|
{{#with (lookup @root.config.GENERAL.damageTypes type)}}
|
||||||
|
<i class="fa-solid {{icon}}"></i>
|
||||||
|
{{/with}}
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="bonuses form-group flexrow">
|
||||||
|
<input type="text" value="{{extraFormula}}" name="damageFormula.extraFormula" placeholder="{{localize "DAGGERHEART.GENERAL.situationalBonus"}}">
|
||||||
|
<button class="critical-chip" data-action="toggleCritical">
|
||||||
|
<span><i class="{{#if @root.isCritical}}fa-solid{{else}}fa-regular{{/if}} fa-circle"></i></span>
|
||||||
|
<span class="label">{{localize "DAGGERHEART.GENERAL.criticalShort"}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/with}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#each @root.resourceFormulas}}
|
||||||
|
<div class="damage-formula">
|
||||||
|
<span class="damage-resource"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{roll.formula}}</span>
|
||||||
|
<span class="damage-details">
|
||||||
|
{{#with (lookup @root.config.GENERAL.healingTypes applyTo)}}
|
||||||
|
{{localize label}}
|
||||||
|
{{/with}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="bonuses form-group flexrow">
|
||||||
|
<input type="text" value="{{extraFormula}}" name={{concat "resourceFormulas." @key ".extraFormula"}} placeholder="{{localize "DAGGERHEART.GENERAL.situationalBonus"}}">
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
{{#unless (empty @root.modifiers)}}
|
{{#unless (empty @root.modifiers)}}
|
||||||
<fieldset class="modifier-container two-columns">
|
<fieldset class="modifier-container two-columns">
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</legend>
|
<legend>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</legend>
|
||||||
|
|
@ -76,6 +88,7 @@
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
<div class="damage-section-controls">
|
<div class="damage-section-controls">
|
||||||
{{#if directDamage}}
|
{{#if directDamage}}
|
||||||
<select class="roll-mode-select" name="selectedMessageMode">
|
<select class="roll-mode-select" name="selectedMessageMode">
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
{{#each damage as |damage key|}}
|
{{#if damage.main}}
|
||||||
|
{{> damage roll=damage.main label=(localize "DAGGERHEART.GENERAL.damage") memberKey=key}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#each damage.resources as |roll key|}}
|
||||||
|
{{> damage roll=roll label=(localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")) memberKey=../key isResource="true" }}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#*inline "damage"}}
|
||||||
<div class="roll-data {{#if isCritical}}critical{{/if}}">
|
<div class="roll-data {{#if isCritical}}critical{{/if}}">
|
||||||
<div class="duality-label">
|
<div class="duality-label">
|
||||||
<span>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}:</span>
|
<span>{{label}}:</span>
|
||||||
<span>{{damage.total}}</span>
|
<span>{{roll.total}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{#each damage.parts as |part|}}
|
|
||||||
<div class="roll-dice-container">
|
<div class="roll-dice-container">
|
||||||
{{#each part.dice as |dice index|}}
|
{{#each roll.dice}}
|
||||||
<a class="roll-dice" data-action="rerollDamageDice" data-member-key="{{../../../key}}" data-damage-key="{{@../../key}}" data-part="{{@../index}}" data-dice="{{index}}">
|
{{#each results as |result index|}}
|
||||||
<span class="dice-label">{{dice.total}}</span>
|
{{#if result.active}}
|
||||||
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" dice.dice ".svg"}}" />
|
<a class="roll-dice" data-action="rerollDamageDice" data-member-key="{{../../memberKey}}" data-damage-key="{{@../../key}}" {{#if ../../isResource}}data-is-resource="true"{{/if}} data-dice-index="{{@../key}}" data-result-index="{{index}}">
|
||||||
</a>
|
<span class="dice-label">{{result.result}}</span>
|
||||||
{{#unless @last}}
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" ../denomination ".svg"}}" />
|
||||||
<span class="roll-operator">+</span>
|
</a>
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if part.modifierTotal}}
|
{{#unless @last}}
|
||||||
{{#if part.dice.length}}<span class="roll-operator">{{#if (gte part.modifierTotal 0)}}+{{else}}-{{/if}}</span>{{/if}}
|
<span class="roll-operator">+</span>
|
||||||
<span class="roll-value">{{positive part.modifierTotal}}</span>
|
{{/unless}}
|
||||||
{{/if}}
|
{{/each}}
|
||||||
</div>
|
{{#if roll.modifierTotal}}
|
||||||
{{/each}}
|
{{#if roll.dice.length}}<span class="roll-operator">{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}</span>{{/if}}
|
||||||
|
<span class="roll-value">{{positive roll.modifierTotal}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/inline}}
|
||||||
|
|
@ -6,19 +6,19 @@
|
||||||
{{#if hintText}}
|
{{#if hintText}}
|
||||||
<div class="hint">{{localize hintText}}</div>
|
<div class="hint">{{localize hintText}}</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if joinedRoll.roll}}
|
{{#if joinedRoll.rollData}}
|
||||||
<div class="result-container">
|
<div class="result-container">
|
||||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
|
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
|
||||||
<div class="result-info">
|
<div class="result-info">
|
||||||
<div class="damage-info">{{joinedRoll.roll.total}}</div>
|
<div class="damage-info">{{joinedRoll.rollData.total}}</div>
|
||||||
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
|
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if joinedRoll.rollData.options.hasDamage}}
|
{{#if joinedRoll.damageRollData}}
|
||||||
<div class="result-container">
|
<div class="result-container">
|
||||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
|
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
|
||||||
{{#each joinedRoll.rollData.options.damage as |damage key|}}
|
{{#each joinedRoll.damageRollData.types as |damage key|}}
|
||||||
<div class="result-info">
|
<div class="result-info">
|
||||||
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
|
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
|
||||||
<div class="damage-info">{{damage.total}}</div>
|
<div class="damage-info">{{damage.total}}</div>
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,10 @@
|
||||||
|
|
||||||
{{#if roll}}
|
{{#if roll}}
|
||||||
<div class="roll-data {{#if roll.withHope}}hope{{else if roll.withFear}}fear{{else}}critical{{/if}}">
|
<div class="roll-data {{#if roll.withHope}}hope{{else if roll.withFear}}fear{{else}}critical{{/if}}">
|
||||||
<div class="duality-label">{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}</div>
|
<div class="duality-label">
|
||||||
|
{{roll.total}}
|
||||||
|
{{#if roll.isCritical}}{{roll.totalLabel}}{{else}}{{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}{{/if}}
|
||||||
|
</div>
|
||||||
<div class="roll-dice-container">
|
<div class="roll-dice-container">
|
||||||
<a class="roll-dice" data-action="rerollDice" data-member="{{@root.partId}}" data-dice-type="hope">
|
<a class="roll-dice" data-action="rerollDice" data-member="{{@root.partId}}" data-dice-type="hope">
|
||||||
<span class="dice-label">{{roll.dHope.total}}</span>
|
<span class="dice-label">{{roll.dHope.total}}</span>
|
||||||
|
|
@ -100,15 +103,12 @@
|
||||||
<a class="roll-button" data-action="makeDamageRoll" data-member-key="{{@root.partId}}" {{#unless readyToRoll}}disabled{{/unless}}>
|
<a class="roll-button" data-action="makeDamageRoll" data-member-key="{{@root.partId}}" {{#unless readyToRoll}}disabled{{/unless}}>
|
||||||
<img src="systems/daggerheart/assets/icons/dice/hope/d20.svg" />
|
<img src="systems/daggerheart/assets/icons/dice/hope/d20.svg" />
|
||||||
</a>
|
</a>
|
||||||
|
<a class="delete-button" data-action="removeDamageRoll" data-member-key="{{@root.partId}}" {{#unless damage.active}}disabled{{/unless}}>
|
||||||
{{#if damage}}
|
<i class="fa-solid fa-trash"></i>
|
||||||
<a class="delete-button" data-action="removeDamageRoll" data-member-key="{{@root.partId}}" {{#unless rollData.options.damage}}disabled{{/unless}}>
|
</a>
|
||||||
<i class="fa-solid fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{{#if damage}}
|
{{#if damage.active}}
|
||||||
{{#if useCritDamage}}
|
{{#if useCritDamage}}
|
||||||
{{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=critDamage isCritical=true }}
|
{{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=critDamage isCritical=true }}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
>
|
>
|
||||||
{{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}}
|
{{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}}
|
||||||
{{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}}
|
{{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}}
|
||||||
{{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage baseFields=fields.damage.fields }}{{/if}}
|
{{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields source=source.damage baseFields=fields.damage.fields }}{{/if}}
|
||||||
{{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}}
|
{{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}}
|
||||||
{{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}}
|
{{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}}
|
||||||
{{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}}
|
{{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}}
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,5 @@
|
||||||
</div>
|
</div>
|
||||||
{{formGroup systemFields.criticalThreshold value=document._source.system.criticalThreshold label="DAGGERHEART.ACTIONS.Settings.criticalThreshold" name="system.criticalThreshold" localize=true}}
|
{{formGroup systemFields.criticalThreshold value=document._source.system.criticalThreshold label="DAGGERHEART.ACTIONS.Settings.criticalThreshold" name="system.criticalThreshold" localize=true}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields horde=(eq document._source.system.type 'horde')}}
|
{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields horde=(eq document._source.system.type 'horde')}}
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -18,26 +18,26 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="two-columns">
|
<fieldset class="two-columns">
|
||||||
{{#with systemFields.attack.fields.damage.fields.parts.element.fields as | fields | }}
|
{{#with systemFields.attack.fields.damage.fields.main.fields as | fields | }}
|
||||||
{{#with ../document.system.attack.damage.parts.hitPoints as | source | }}
|
{{#with ../document.system.attack.damage.main as | source | }}
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.damage"}}</legend>
|
<legend>{{localize "DAGGERHEART.GENERAL.damage"}}</legend>
|
||||||
<span>{{localize "DAGGERHEART.ACTIONS.Config.general.customFormula"}}</span>
|
<span>{{localize "DAGGERHEART.ACTIONS.Config.general.customFormula"}}</span>
|
||||||
{{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.parts.hitPoints.value.custom.enabled"}}
|
{{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.main.value.custom.enabled"}}
|
||||||
{{#if source.value.custom.enabled}}
|
{{#if source.value.custom.enabled}}
|
||||||
<span>{{localize "DAGGERHEART.ACTIONS.Config.general.formula"}}</span>
|
<span>{{localize "DAGGERHEART.ACTIONS.Config.general.formula"}}</span>
|
||||||
{{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.parts.hitPoints.value.custom.formula"}}
|
{{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.main.value.custom.formula"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<span>{{localize "DAGGERHEART.GENERAL.Dice.single"}}</span>
|
<span>{{localize "DAGGERHEART.GENERAL.Dice.single"}}</span>
|
||||||
{{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.parts.hitPoints.value.dice"}}
|
{{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.main.value.dice"}}
|
||||||
<span>{{localize "DAGGERHEART.GENERAL.bonus"}}</span>
|
<span>{{localize "DAGGERHEART.GENERAL.bonus"}}</span>
|
||||||
{{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.parts.hitPoints.value.bonus" localize=true}}
|
{{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.main.value.bonus" localize=true}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<span>{{localize "DAGGERHEART.GENERAL.type"}}</span>
|
<span>{{localize "DAGGERHEART.GENERAL.type"}}</span>
|
||||||
{{formInput fields.type value=source.type name="system.attack.damage.parts.hitPoints.type" localize=true}}
|
{{formInput fields.type value=source.type name="system.attack.damage.main.type" localize=true}}
|
||||||
<span>{{localize "DAGGERHEART.CONFIG.DamageType.direct.name"}}</span>
|
<span>{{localize "DAGGERHEART.CONFIG.DamageType.direct.name"}}</span>
|
||||||
{{formInput @root.systemFields.attack.fields.damage.fields.direct value=@root.document.system.attack.damage.direct name="system.attack.damage.direct" localize=true}}
|
{{formInput @root.systemFields.attack.fields.damage.fields.main.fields.direct value=@root.document.system.attack.damage.main.direct name="system.attack.damage.main.direct" localize=true}}
|
||||||
<input type="hidden" name="system.attack.damage.parts.hitPoints.value.multiplier" value="{{source.value.multiplier}}">
|
<input type="hidden" name="system.attack.damage.main.value.multiplier" value="{{source.value.multiplier}}">
|
||||||
{{/with}}
|
{{/with}}
|
||||||
{{/with}}
|
{{/with}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="two-columns">
|
<fieldset class="two-columns">
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
<div class="roll-buttons">
|
<div class="roll-buttons">
|
||||||
{{#if areas.length}}<button class="action-areas end-button"><i class="fa-solid fa-crosshairs"></i></button>{{/if}}
|
{{#if areas.length}}<button class="action-areas end-button"><i class="fa-solid fa-crosshairs"></i></button>{{/if}}
|
||||||
{{#if hasDamage}}
|
{{#if hasDamage}}
|
||||||
{{#unless (empty damage)}}
|
{{#if damage.active}}
|
||||||
<button class="duality-action damage-button">{{localize "DAGGERHEART.UI.Chat.damageRoll.dealDamage"}}</button>
|
<button class="duality-action damage-button">{{localize "DAGGERHEART.UI.Chat.damageRoll.dealDamage"}}</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button class="duality-action duality-action-damage">{{localize "DAGGERHEART.UI.Chat.attackRoll.rollDamage"}}</button>
|
<button class="duality-action duality-action-damage">{{localize "DAGGERHEART.UI.Chat.attackRoll.rollDamage"}}</button>
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if hasHealing}}
|
{{#if hasHealing}}
|
||||||
{{#unless (empty damage)}}
|
{{#if damage.active}}
|
||||||
<button class="duality-action damage-button">{{localize "DAGGERHEART.UI.Chat.healingRoll.applyHealing"}}</button>
|
<button class="duality-action damage-button">{{localize "DAGGERHEART.UI.Chat.healingRoll.applyHealing"}}</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button class="duality-action duality-action-damage">{{localize "DAGGERHEART.UI.Chat.attackRoll.rollHealing"}}</button>
|
<button class="duality-action duality-action-damage">{{localize "DAGGERHEART.UI.Chat.attackRoll.rollHealing"}}</button>
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (and hasEffect)}}<button class="duality-action-effect">{{localize "DAGGERHEART.UI.Chat.attackRoll.applyEffect"}}</button>{{/if}}
|
{{#if (and hasEffect)}}<button class="duality-action-effect">{{localize "DAGGERHEART.UI.Chat.attackRoll.applyEffect"}}</button>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="roll-part damage-section dice-roll" data-action="expandRoll"{{#if (empty damage)}} hidden{{/if}}>
|
<div class="roll-part damage-section dice-roll" data-action="expandRoll"{{#unless damage.active}} hidden{{/unless}}>
|
||||||
<div class="roll-part-header">
|
<div class="roll-part-header">
|
||||||
<div>
|
<div>
|
||||||
{{#if hasHealing}}
|
{{#if hasHealing}}
|
||||||
|
|
@ -10,60 +10,79 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="roll-part-extra on-reduced">
|
<div class="roll-part-extra on-reduced">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{{#each damage as | roll index | }}
|
{{#if damage.main}}
|
||||||
<div class="roll-formula">{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}: {{total}}</div>
|
{{> formula roll=damage.main label=(localize "DAGGERHEART.GENERAL.damage") }}
|
||||||
|
{{/if}}
|
||||||
|
{{#each damage.resources as | roll index | }}
|
||||||
|
{{> formula roll=roll label=(ifThen ../hasHealing (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')) (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll'))) }}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="roll-part-content dice-result">
|
<div class="roll-part-content dice-result">
|
||||||
<div class="dice-tooltip">
|
<div class="dice-tooltip">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{{#each damage as | roll index | }}
|
{{#if damage.main}}
|
||||||
<fieldset>
|
{{> damage label=(localize "DAGGERHEART.GENERAL.damage") roll=damage.main isDirect=isDirect }}
|
||||||
<legend>
|
{{/if}}
|
||||||
{{#if ../hasHealing}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}{{else}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}{{/if}} <div class="roll-formula">{{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}</div>{{#if (and (eq index "hitPoints") ../isDirect)}} <div class="roll-formula">{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}</div>{{/if}}
|
|
||||||
</legend>
|
{{#each damage.resources as | roll index | }}
|
||||||
{{#each roll.parts}}
|
{{> damage
|
||||||
{{#if (and (not @root.hasHealing) damageTypes.length)}}
|
label=(ifThen ../hasHealing (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')) (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')))
|
||||||
<label class="roll-part-header"><span>
|
roll=roll
|
||||||
{{#each damageTypes}}
|
isResource=true
|
||||||
{{localize (concat 'DAGGERHEART.CONFIG.ArmorFeature.' this '.name')}}
|
}}
|
||||||
{{#unless @last}}/{{/unless}}
|
|
||||||
{{/each}}
|
|
||||||
<div class="roll-formula">{{total}}</div></span></label>
|
|
||||||
{{/if}}
|
|
||||||
<div class="roll-dice">
|
|
||||||
{{#if dice.length}}
|
|
||||||
{{#each dice}}
|
|
||||||
{{#each results}}
|
|
||||||
{{#unless discarded}}
|
|
||||||
<div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
|
|
||||||
<div
|
|
||||||
class="dice reroll-button {{../dice}}"
|
|
||||||
data-die-index="0" data-type="damage" data-damage-type="{{@../../../key}}" data-part="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}"
|
|
||||||
>
|
|
||||||
{{#if hasRerolls}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerolled"}}"></i>{{/if}}
|
|
||||||
{{result}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
{{/each}}
|
|
||||||
{{/each}}
|
|
||||||
{{#if modifierTotal}}
|
|
||||||
<div class="roll-die{{#if (gt modifierTotal 0)}} has-plus{{/if}}">
|
|
||||||
<div class="font-20">{{modifierTotal}}</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<div class="roll-die">
|
|
||||||
<div class="font-20">{{total}}</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</fieldset>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#*inline "formula"}}
|
||||||
|
<div class="roll-formula">{{label}}: {{roll.total}}</div>
|
||||||
|
{{/inline}}
|
||||||
|
|
||||||
|
{{#*inline "damage"}}
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
{{label}}
|
||||||
|
<div class="roll-formula">{{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}</div>{{#if isDirect}} <div class="roll-formula">{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}</div>{{/if}}
|
||||||
|
</legend>
|
||||||
|
{{#if (and (not @root.hasHealing) roll.options.damageTypes.length)}}
|
||||||
|
<label class="roll-part-header"><span>
|
||||||
|
{{#each roll.options.damageTypes}}
|
||||||
|
{{localize (concat 'DAGGERHEART.CONFIG.ArmorFeature.' this '.name')}}
|
||||||
|
{{#unless @last}}/{{/unless}}
|
||||||
|
{{/each}}
|
||||||
|
<div class="roll-formula">{{roll.total}}</div></span></label>
|
||||||
|
{{/if}}
|
||||||
|
<div class="roll-dice">
|
||||||
|
{{#if roll.dice.length}}
|
||||||
|
{{#each roll.dice}}
|
||||||
|
{{#each results}}
|
||||||
|
{{#if active}}
|
||||||
|
<div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
|
||||||
|
<div
|
||||||
|
class="dice reroll-button {{../denomination}}"
|
||||||
|
data-type="damage" data-damage-type="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}" {{#if ../../isResource}}data-is-resource="true"{{/if}}
|
||||||
|
>
|
||||||
|
{{#if hasRerolls}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerolled"}}"></i>{{/if}}
|
||||||
|
{{result}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#if roll.modifierTotal}}
|
||||||
|
<div class="roll-die{{#if (gt roll.modifierTotal 0)}} has-plus{{/if}}">
|
||||||
|
<div class="font-20">{{roll.modifierTotal}}</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<div class="roll-die">
|
||||||
|
<div class="font-20">{{roll.total}}</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{/inline}}
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="tag">
|
<div class="tag">
|
||||||
<span>{{{damageFormula attack}}} {{{damageSymbols attack.damage.parts}}}</span>
|
<span>{{{damageFormula attack}}} {{{damageSymbols attack.damage.main}}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{#if description}}
|
{{#if description}}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
{{/with}}
|
{{/with}}
|
||||||
</div>
|
</div>
|
||||||
<div class="tag">
|
<div class="tag">
|
||||||
<span>{{{damageFormula item.system.attack}}} {{{damageSymbols item.system.attack.damage.parts}}}</span>
|
<span>{{{damageFormula item.system.attack}}} {{{damageSymbols item.system.attack.damage.main}}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{#if description}}
|
{{#if description}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue