mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Feature/336 damage targeted resources (#376)
* Unify healing & damage * create DHResourceData * Damages parts roll * h * ChatMessage & takeDamage updates * Adapt healing * No, there was not a console.log !
This commit is contained in:
parent
26376b49db
commit
7cbbb3168e
25 changed files with 415 additions and 232 deletions
|
|
@ -35,6 +35,9 @@
|
||||||
"Settings": {
|
"Settings": {
|
||||||
"resultBased": {
|
"resultBased": {
|
||||||
"label": "Formula based on Hope/Fear result."
|
"label": "Formula based on Hope/Fear result."
|
||||||
|
},
|
||||||
|
"applyTo": {
|
||||||
|
"label": "Targeted Resource"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
|
|
@ -662,6 +665,10 @@
|
||||||
"armorStack": {
|
"armorStack": {
|
||||||
"name": "Armor Stack",
|
"name": "Armor Stack",
|
||||||
"abbreviation": "AS"
|
"abbreviation": "AS"
|
||||||
|
},
|
||||||
|
"fear": {
|
||||||
|
"name": "Fear",
|
||||||
|
"abbreviation": "FR"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ItemResourceType": {
|
"ItemResourceType": {
|
||||||
|
|
@ -1252,6 +1259,7 @@
|
||||||
},
|
},
|
||||||
"fear": "Fear",
|
"fear": "Fear",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
|
"healing": "Healing",
|
||||||
"hitPoints": {
|
"hitPoints": {
|
||||||
"single": "Hit Point",
|
"single": "Hit Point",
|
||||||
"plural": "Hit Points",
|
"plural": "Hit Points",
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
|
context.config = CONFIG.DH;
|
||||||
context.title = this.config.title
|
context.title = this.config.title
|
||||||
? this.config.title
|
? this.config.title
|
||||||
: game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
|
: game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
|
||||||
context.extraFormula = this.config.extraFormula;
|
// context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
context.directDamage = this.config.directDamage;
|
context.directDamage = this.config.directDamage;
|
||||||
context.selectedRollMode = this.config.selectedRollMode;
|
context.selectedRollMode = this.config.selectedRollMode;
|
||||||
|
|
@ -55,13 +56,12 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
label,
|
label,
|
||||||
icon
|
icon
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(_event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
this.config.extraFormula = rest.extraFormula;
|
foundry.utils.mergeObject(this.config.roll, rest.roll)
|
||||||
this.config.selectedRollMode = rest.selectedRollMode;
|
this.config.selectedRollMode = rest.selectedRollMode;
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.damage = damage;
|
this.damage = damage;
|
||||||
|
|
||||||
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
||||||
const maxArmorMarks = canApplyArmor
|
const maxArmorMarks = canApplyArmor
|
||||||
? Math.min(
|
? Math.min(
|
||||||
actor.system.armorScore - actor.system.armor.system.marks.value,
|
actor.system.armorScore - actor.system.armor.system.marks.value,
|
||||||
actor.system.rules.damageReduction.maxArmorMarked.total
|
actor.system.rules.damageReduction.maxArmorMarked.value
|
||||||
)
|
)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
context.armorScore = this.actor.system.armorScore;
|
context.armorScore = this.actor.system.armorScore;
|
||||||
context.armorMarks = currentMarks;
|
context.armorMarks = currentMarks;
|
||||||
context.basicMarksUsed =
|
context.basicMarksUsed =
|
||||||
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.total;
|
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
||||||
|
|
||||||
const stressReductionStress = this.availableStressReductions
|
const stressReductionStress = this.availableStressReductions
|
||||||
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game.canvas.pan(token);
|
game.canvas.pan(token);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -207,15 +206,24 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
if (!confirm) return;
|
if (!confirm) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targets.length === 0)
|
if (targets.length === 0)
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||||
for (let target of targets) {
|
|
||||||
let damage = message.system.roll.total;
|
|
||||||
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
|
|
||||||
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
|
|
||||||
|
|
||||||
target.actor.takeDamage(damage, message.system.damage.damageType);
|
for (let target of targets) {
|
||||||
|
let damages = message.system.damage;
|
||||||
|
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) {
|
||||||
|
const mod = CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1;
|
||||||
|
Object.entries(damages).forEach((k,v) => {
|
||||||
|
let newTotal = 0;
|
||||||
|
v.forEach(part => {
|
||||||
|
v.total = Math.ceil(v.total * mod);
|
||||||
|
newTotal += v.total;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
target.actor.takeDamage(damages.roll);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -224,10 +232,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
const targets = Array.from(game.user.targets);
|
const targets = Array.from(game.user.targets);
|
||||||
|
|
||||||
if (targets.length === 0)
|
if (targets.length === 0)
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||||
|
|
||||||
for (var target of targets) {
|
for (var target of targets) {
|
||||||
target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]);
|
target.actor.takeHealing(message.system.roll);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,11 @@ export const healingTypes = {
|
||||||
id: 'armorStack',
|
id: 'armorStack',
|
||||||
label: 'DAGGERHEART.CONFIG.HealingType.armorStack.name',
|
label: 'DAGGERHEART.CONFIG.HealingType.armorStack.name',
|
||||||
abbreviation: 'DAGGERHEART.CONFIG.HealingType.armorStack.abbreviation'
|
abbreviation: 'DAGGERHEART.CONFIG.HealingType.armorStack.abbreviation'
|
||||||
|
},
|
||||||
|
fear: {
|
||||||
|
id: 'fear',
|
||||||
|
label: 'DAGGERHEART.CONFIG.HealingType.fear.name',
|
||||||
|
abbreviation: 'DAGGERHEART.CONFIG.HealingType.fear.abbreviation'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,32 @@ export class DHDamageField extends fields.SchemaField {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DHDamageData extends foundry.abstract.DataModel {
|
export class DHResourceData extends foundry.abstract.DataModel {
|
||||||
/** @override */
|
/** @override */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
|
applyTo: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.healingTypes,
|
||||||
|
required: true,
|
||||||
|
blank: false,
|
||||||
|
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.applyTo.label'
|
||||||
|
}),
|
||||||
|
resultBased: new fields.BooleanField({
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
|
||||||
|
}),
|
||||||
|
value: new fields.EmbeddedDataField(DHActionDiceData),
|
||||||
|
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DHDamageData extends DHResourceData {
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||||
type: new fields.SetField(
|
type: new fields.SetField(
|
||||||
new fields.StringField({
|
new fields.StringField({
|
||||||
|
|
@ -106,16 +128,9 @@ export class DHDamageData extends foundry.abstract.DataModel {
|
||||||
required: true
|
required: true
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
label: 'Type',
|
label: 'Type'
|
||||||
initial: 'physical'
|
|
||||||
}
|
}
|
||||||
),
|
)
|
||||||
resultBased: new fields.BooleanField({
|
}
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
|
|
||||||
}),
|
|
||||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
|
||||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { DHActionDiceData, DHActionRollData, DHDamageField } from './actionDice.mjs';
|
import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField, DHResourceData } from './actionDice.mjs';
|
||||||
import DhpActor from '../../documents/actor.mjs';
|
import DhpActor from '../../documents/actor.mjs';
|
||||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||||
|
|
||||||
|
|
@ -96,21 +96,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
onSave: new fields.BooleanField({ initial: false })
|
onSave: new fields.BooleanField({ initial: false })
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
healing: new fields.SchemaField({
|
healing: new fields.EmbeddedDataField(DHResourceData),
|
||||||
type: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.healingTypes,
|
|
||||||
required: true,
|
|
||||||
blank: false,
|
|
||||||
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
|
||||||
label: 'Healing'
|
|
||||||
}),
|
|
||||||
resultBased: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
|
|
||||||
}),
|
|
||||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
|
||||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
|
||||||
}),
|
|
||||||
beastform: new fields.SchemaField({
|
beastform: new fields.SchemaField({
|
||||||
tierAccess: new fields.SchemaField({
|
tierAccess: new fields.SchemaField({
|
||||||
exact: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
exact: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||||
|
|
@ -156,7 +142,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
static getSourceConfig(parent) {
|
static getSourceConfig(parent) {
|
||||||
const updateSource = {};
|
const updateSource = {};
|
||||||
updateSource.img ??= parent?.img ?? parent?.system?.img;
|
updateSource.img ??= parent?.img ?? parent?.system?.img;
|
||||||
if (parent?.type === 'weapon') {
|
if (parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
||||||
updateSource['damage'] = { includeBase: true };
|
updateSource['damage'] = { includeBase: true };
|
||||||
updateSource['range'] = parent?.system?.attack?.range;
|
updateSource['range'] = parent?.system?.attack?.range;
|
||||||
updateSource['roll'] = {
|
updateSource['roll'] = {
|
||||||
|
|
@ -177,6 +163,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
getRollData(data = {}) {
|
getRollData(data = {}) {
|
||||||
|
if(!this.actor) return null;
|
||||||
const actorData = this.actor.getRollData(false);
|
const actorData = this.actor.getRollData(false);
|
||||||
|
|
||||||
// Add Roll results to RollDatas
|
// Add Roll results to RollDatas
|
||||||
|
|
@ -191,6 +178,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event, ...args) {
|
async use(event, ...args) {
|
||||||
|
if(!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
|
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
|
||||||
// Prepare base Config
|
// Prepare base Config
|
||||||
const initConfig = this.initActionConfig(event);
|
const initConfig = this.initActionConfig(event);
|
||||||
|
|
@ -227,7 +216,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
// Display configuration window if necessary
|
// Display configuration window if necessary
|
||||||
// if (config.dialog?.configure && this.requireConfigurationDialog(config)) {
|
|
||||||
if (this.requireConfigurationDialog(config)) {
|
if (this.requireConfigurationDialog(config)) {
|
||||||
config = await D20RollDialog.configure(null, config);
|
config = await D20RollDialog.configure(null, config);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { setsEqual } from '../../helpers/utils.mjs';
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHDamageAction extends DHBaseAction {
|
export default class DHDamageAction extends DHBaseAction {
|
||||||
|
|
@ -18,28 +19,40 @@ export default class DHDamageAction extends DHBaseAction {
|
||||||
return formulaValue;
|
return formulaValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatFormulas(formulas, systemData) {
|
||||||
|
const formattedFormulas = [];
|
||||||
|
formulas.forEach(formula => {
|
||||||
|
if (isNaN(formula.formula)) formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
|
||||||
|
const same = formattedFormulas.find(f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo);
|
||||||
|
if(same)
|
||||||
|
same.formula += ` + ${formula.formula}`;
|
||||||
|
else
|
||||||
|
formattedFormulas.push(formula);
|
||||||
|
})
|
||||||
|
return formattedFormulas;
|
||||||
|
}
|
||||||
|
|
||||||
async rollDamage(event, data) {
|
async rollDamage(event, data) {
|
||||||
const systemData = data.system ?? data;
|
const systemData = data.system ?? data;
|
||||||
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '),
|
|
||||||
damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))];
|
let formulas = this.damage.parts.map(p => ({
|
||||||
|
formula: this.getFormulaValue(p, data).getFormula(this.actor),
|
||||||
|
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
||||||
|
applyTo: p.applyTo
|
||||||
|
}));
|
||||||
|
|
||||||
damageTypes = !damageTypes.length ? ['physical'] : damageTypes;
|
if(!formulas.length) return;
|
||||||
|
|
||||||
if (!formula || formula == '') return;
|
formulas = this.formatFormulas(formulas, systemData);
|
||||||
let roll = { formula: formula, total: formula },
|
|
||||||
bonusDamage = [];
|
|
||||||
|
|
||||||
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData));
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
|
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
|
||||||
roll: { formula },
|
roll: formulas,
|
||||||
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
isCritical: systemData.roll?.isCritical ?? false,
|
isCritical: systemData.roll?.isCritical ?? false,
|
||||||
source: systemData.source,
|
source: systemData.source,
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
damageTypes,
|
|
||||||
event
|
event
|
||||||
};
|
};
|
||||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||||
|
|
@ -50,10 +63,6 @@ export default class DHDamageAction extends DHBaseAction {
|
||||||
config.directDamage = true;
|
config.directDamage = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
return CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get modifiers() {
|
|
||||||
// return [];
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,25 +15,25 @@ export default class DHHealingAction extends DHBaseAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollHealing(event, data) {
|
async rollHealing(event, data) {
|
||||||
let formulaValue = this.getFormulaValue(data),
|
const systemData = data.system ?? data;
|
||||||
formula = formulaValue.getFormula(this.actor);
|
let formulas = [{
|
||||||
|
formula: this.getFormulaValue(data).getFormula(this.actor),
|
||||||
if (!formula || formula == '') return;
|
applyTo: this.healing.applyTo
|
||||||
let roll = { formula: formula, total: formula },
|
}];
|
||||||
bonusDamage = [];
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.healingRoll.title', {
|
title: game.i18n.format('DAGGERHEART.UI.Chat.healingRoll.title', {
|
||||||
healing: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[this.healing.type].label)
|
healing: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[this.healing.applyTo].label)
|
||||||
}),
|
}),
|
||||||
roll: { formula },
|
roll: formulas,
|
||||||
targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
|
targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
|
||||||
messageType: 'healing',
|
messageType: 'healing',
|
||||||
type: this.healing.type,
|
source: systemData.source,
|
||||||
|
data: this.getRollData(),
|
||||||
event
|
event
|
||||||
};
|
};
|
||||||
|
|
||||||
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
return CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
get chatTemplate() {
|
get chatTemplate() {
|
||||||
|
|
|
||||||
|
|
@ -170,11 +170,10 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
damageReduction: new fields.SchemaField({
|
damageReduction: new fields.SchemaField({
|
||||||
maxArmorMarked: new fields.SchemaField({
|
maxArmorMarked: new fields.SchemaField({
|
||||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
value: new fields.NumberField({
|
||||||
bonus: new fields.NumberField({
|
|
||||||
required: true,
|
required: true,
|
||||||
integer: true,
|
integer: true,
|
||||||
initial: 0,
|
initial: 1,
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
||||||
}),
|
}),
|
||||||
stressExtra: new fields.NumberField({
|
stressExtra: new fields.NumberField({
|
||||||
|
|
|
||||||
|
|
@ -140,28 +140,22 @@ export default class D20Roll extends DHRoll {
|
||||||
return modifiers;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
|
||||||
if (config.evaluate !== false) await roll.evaluate();
|
|
||||||
|
|
||||||
this.postEvaluate(roll, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
super.postEvaluate(roll, config);
|
const data = super.postEvaluate(roll, config);
|
||||||
if (config.targets?.length) {
|
if (config.targets?.length) {
|
||||||
config.targets.forEach(target => {
|
config.targets.forEach(target => {
|
||||||
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
||||||
target.hit = this.isCritical || roll.total >= difficulty;
|
target.hit = this.isCritical || roll.total >= difficulty;
|
||||||
});
|
});
|
||||||
} else if (config.roll.difficulty)
|
} else if (config.roll.difficulty)
|
||||||
config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||||
config.roll.advantage = {
|
data.advantage = {
|
||||||
type: config.roll.advantage,
|
type: config.roll.advantage,
|
||||||
dice: roll.dAdvantage?.denomination,
|
dice: roll.dAdvantage?.denomination,
|
||||||
value: roll.dAdvantage?.total
|
value: roll.dAdvantage?.total
|
||||||
};
|
};
|
||||||
config.roll.isCritical = roll.isCritical;
|
data.isCritical = roll.isCritical;
|
||||||
config.roll.extra = roll.dice
|
data.extra = roll.dice
|
||||||
.filter(d => !roll.baseTerms.includes(d))
|
.filter(d => !roll.baseTerms.includes(d))
|
||||||
.map(d => {
|
.map(d => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -169,7 +163,8 @@ export default class D20Roll extends DHRoll {
|
||||||
value: d.total
|
value: d.total
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
config.roll.modifierTotal = this.calculateTotalModifiers(roll);
|
data.modifierTotal = this.calculateTotalModifiers(roll);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFormula() {
|
resetFormula() {
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,24 @@ export default class DamageRoll extends DHRoll {
|
||||||
|
|
||||||
static DefaultDialog = DamageDialog;
|
static DefaultDialog = DamageDialog;
|
||||||
|
|
||||||
static async postEvaluate(roll, config = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
super.postEvaluate(roll, config);
|
if ( config.evaluate !== false ) {
|
||||||
config.roll.type = config.type;
|
for ( const roll of config.roll ) await roll.roll.evaluate();
|
||||||
config.roll.modifierTotal = this.calculateTotalModifiers(roll);
|
}
|
||||||
|
roll._evaluated = true;
|
||||||
|
const parts = config.roll.map(r => this.postEvaluate(r));
|
||||||
|
config.roll = this.unifyDamageRoll(parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static postEvaluate(roll, config = {}) {
|
||||||
|
return {
|
||||||
|
...roll,
|
||||||
|
...super.postEvaluate(roll.roll, config),
|
||||||
|
damageTypes: [...(roll.damageTypes ?? [])],
|
||||||
|
roll: roll.roll,
|
||||||
|
type: config.type,
|
||||||
|
modifierTotal: this.calculateTotalModifiers(roll.roll)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
|
|
@ -24,12 +38,50 @@ export default class DamageRoll extends DHRoll {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyBaseBonus() {
|
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) {
|
||||||
|
let formula, total;
|
||||||
|
const applyTo = new Set(rolls.flatMap(r => r.applyTo));
|
||||||
|
if(applyTo.size > 1) {
|
||||||
|
const data = {};
|
||||||
|
rolls.forEach(r => {
|
||||||
|
if(data[r.applyTo]) {
|
||||||
|
data[r.applyTo].formula += ` + ${r.formula}` ;
|
||||||
|
data[r.applyTo].total += r.total ;
|
||||||
|
} else {
|
||||||
|
data[r.applyTo] = {
|
||||||
|
formula: r.formula,
|
||||||
|
total: r.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
formula = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.formula}`, '');
|
||||||
|
total = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.total}`, '');
|
||||||
|
} else {
|
||||||
|
formula = rolls.map(r => r.formula).join(' + ');
|
||||||
|
total = rolls.reduce((a,c) => a + c.total, 0)
|
||||||
|
}
|
||||||
|
return {formula, total}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyBaseBonus(part) {
|
||||||
const modifiers = [],
|
const modifiers = [],
|
||||||
type = this.options.messageType ?? 'damage';
|
type = this.options.messageType ?? 'damage',
|
||||||
|
options = part ?? this.options;
|
||||||
|
|
||||||
modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`));
|
modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`));
|
||||||
this.options.damageTypes?.forEach(t => {
|
options.damageTypes?.forEach(t => {
|
||||||
modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`));
|
modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`));
|
||||||
});
|
});
|
||||||
const weapons = ['primaryWeapon', 'secondaryWeapon'];
|
const weapons = ['primaryWeapon', 'secondaryWeapon'];
|
||||||
|
|
@ -42,13 +94,36 @@ export default class DamageRoll extends DHRoll {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFormula(config) {
|
constructFormula(config) {
|
||||||
super.constructFormula(config);
|
this.options.roll.forEach( part => {
|
||||||
|
part.roll = new Roll(part.formula);
|
||||||
|
this.constructFormulaPart(config, part)
|
||||||
|
})
|
||||||
|
return this.options.roll;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.isCritical) {
|
constructFormulaPart(config, part) {
|
||||||
const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }),
|
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
||||||
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);
|
|
||||||
this.terms.push(...this.formatModifier(criticalBonus));
|
if(part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
part.modifiers = this.applyBaseBonus(part);
|
||||||
|
this.addModifiers(part);
|
||||||
|
part.modifiers?.forEach(m => {
|
||||||
|
part.roll.terms.push(...this.formatModifier(m.value));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return (this._formula = this.constructor.getFormula(this.terms));
|
|
||||||
|
if (part.extraFormula) {
|
||||||
|
part.roll.terms.push(
|
||||||
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
|
...this.constructor.parse(part.extraFormula, this.options.data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
const tmpRoll = Roll.fromTerms(part.roll.terms)._evaluateSync({ maximize: true }),
|
||||||
|
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);
|
||||||
|
part.roll.terms.push(...this.formatModifier(criticalBonus));
|
||||||
|
}
|
||||||
|
return (part.roll._formula = this.constructor.getFormula(part.roll.terms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export default class DHRoll extends Roll {
|
||||||
|
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
if (config.evaluate !== false) await roll.evaluate();
|
if (config.evaluate !== false) await roll.evaluate();
|
||||||
this.postEvaluate(roll, config);
|
config.roll = this.postEvaluate(roll, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
|
|
@ -57,25 +57,26 @@ export default class DHRoll extends Roll {
|
||||||
|
|
||||||
// Create Chat Message
|
// Create Chat Message
|
||||||
if (config.source?.message) {
|
if (config.source?.message) {
|
||||||
|
if(Object.values(config.roll)?.length) {
|
||||||
|
const pool = foundry.dice.terms.PoolTerm.fromRolls(Object.values(config.roll).flatMap(r => r.parts.map(p => p.roll)));
|
||||||
|
roll = Roll.fromTerms([pool]);
|
||||||
|
}
|
||||||
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||||
} else {
|
} else
|
||||||
config.message = await this.toMessage(roll, config);
|
config.message = await this.toMessage(roll, config);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
if (!config.roll) config.roll = {};
|
return {
|
||||||
config.roll.total = roll.total;
|
total: roll.total,
|
||||||
config.roll.formula = roll.formula;
|
formula: roll.formula,
|
||||||
config.roll.dice = [];
|
dice: roll.dice.map(d => ({
|
||||||
roll.dice.forEach(d => {
|
|
||||||
config.roll.dice.push({
|
|
||||||
dice: d.denomination,
|
dice: d.denomination,
|
||||||
total: d.total,
|
total: d.total,
|
||||||
formula: d.formula,
|
formula: d.formula,
|
||||||
results: d.results
|
results: d.results
|
||||||
});
|
}))
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async toMessage(roll, config) {
|
static async toMessage(roll, config) {
|
||||||
|
|
@ -118,8 +119,9 @@ export default class DHRoll extends Roll {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addModifiers() {
|
addModifiers(roll) {
|
||||||
this.options.roll.modifiers?.forEach(m => {
|
roll = roll ?? this.options.roll;
|
||||||
|
roll.modifiers?.forEach(m => {
|
||||||
this.terms.push(...this.formatModifier(m.value));
|
this.terms.push(...this.formatModifier(m.value));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,21 +161,21 @@ export default class DualityRoll extends D20Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
super.postEvaluate(roll, config);
|
const data = super.postEvaluate(roll, config);
|
||||||
|
|
||||||
config.roll.hope = {
|
data.hope = {
|
||||||
dice: roll.dHope.denomination,
|
dice: roll.dHope.denomination,
|
||||||
value: roll.dHope.total
|
value: roll.dHope.total
|
||||||
};
|
};
|
||||||
config.roll.fear = {
|
data.fear = {
|
||||||
dice: roll.dFear.denomination,
|
dice: roll.dFear.denomination,
|
||||||
value: roll.dFear.total
|
value: roll.dFear.total
|
||||||
};
|
};
|
||||||
config.roll.rally = {
|
data.rally = {
|
||||||
dice: roll.dRally?.denomination,
|
dice: roll.dRally?.denomination,
|
||||||
value: roll.dRally?.total
|
value: roll.dRally?.total
|
||||||
};
|
};
|
||||||
config.roll.result = {
|
data.result = {
|
||||||
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
|
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
|
||||||
total: roll.dHope.total + roll.dFear.total,
|
total: roll.dHope.total + roll.dFear.total,
|
||||||
label: roll.totalLabel
|
label: roll.totalLabel
|
||||||
|
|
@ -184,6 +184,8 @@ export default class DualityRoll extends D20Roll {
|
||||||
if(roll._rallyIndex && roll.data?.parent)
|
if(roll._rallyIndex && roll.data?.parent)
|
||||||
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
|
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
|
||||||
|
|
||||||
setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type);
|
setDiceSoNiceForDualityRoll(roll, data.advantage.type);
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -391,59 +391,70 @@ export default class DhpActor extends Actor {
|
||||||
return canUseArmor || canUseStress;
|
return canUseArmor || canUseStress;
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeDamage(baseDamage, type) {
|
async takeDamage(damages) {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, baseDamage, type) === false) return null;
|
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damages) === 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
type = !Array.isArray(type) ? [type] : type;
|
const updates = [];
|
||||||
|
|
||||||
const hpDamage = this.calculateDamage(baseDamage, type);
|
Object.entries(damages).forEach(([key, damage]) => {
|
||||||
|
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 (!hpDamage) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
|
||||||
|
|
||||||
const updates = [{ value: hpDamage, type: 'hitPoints' }];
|
if(!updates.length) return;
|
||||||
|
|
||||||
if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage, type)) {
|
const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
||||||
const armorStackResult = await this.owner.query('armorStack', {
|
if(hpDamage) {
|
||||||
actorId: this.uuid,
|
hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
|
||||||
damage: hpDamage,
|
if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
|
||||||
type: type
|
const armorStackResult = await this.owner.query('armorStack', {
|
||||||
});
|
actorId: this.uuid,
|
||||||
if (armorStackResult) {
|
damage: hpDamage.value,
|
||||||
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
|
type: [...hpDamage.damageTypes]
|
||||||
updates.find(u => u.type === 'hitPoints').value = modifiedDamage;
|
});
|
||||||
updates.push(
|
if (armorStackResult) {
|
||||||
...(armorSpent ? [{ value: armorSpent, key: 'armorStack' }] : []),
|
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
|
||||||
...(stressSpent ? [{ value: stressSpent, key: 'stress' }] : [])
|
updates.find(u => u.key === 'hitPoints').value = modifiedDamage;
|
||||||
);
|
updates.push(
|
||||||
|
...(armorSpent ? [{ value: armorSpent, key: 'armorStack' }] : []),
|
||||||
|
...(stressSpent ? [{ value: stressSpent, key: 'stress' }] : [])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updates.forEach( u =>
|
||||||
|
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);
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null;
|
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damages) === false) return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateDamage(baseDamage, type) {
|
calculateDamage(baseDamage, type) {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preCalculateDamage`, this, baseDamage, type) === false) return null;
|
|
||||||
|
|
||||||
/* if(this.system.resistance[type]?.immunity) return 0;
|
|
||||||
if(this.system.resistance[type]?.resistance) baseDamage = Math.ceil(baseDamage / 2); */
|
|
||||||
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);
|
||||||
|
|
||||||
// const flatReduction = this.system.resistance[type].reduction;
|
|
||||||
const flatReduction = this.getDamageTypeReduction(type);
|
const flatReduction = this.getDamageTypeReduction(type);
|
||||||
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
|
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
|
||||||
const hpDamage = this.convertDamageToThreshold(damage);
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, baseDamage, type) === false) return null;
|
return damage;
|
||||||
|
|
||||||
return hpDamage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canResist(type, resistance) {
|
canResist(type, resistance) {
|
||||||
|
|
@ -461,8 +472,13 @@ export default class DhpActor extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeHealing(resources) {
|
async takeHealing(resources) {
|
||||||
resources.forEach(r => (r.value *= -1));
|
const updates = Object.entries(resources).map(([key, value]) => (
|
||||||
await this.modifyResource(resources);
|
{
|
||||||
|
key: key,
|
||||||
|
value: !(key === 'fear' || this.system?.resources?.[key]?.isReversed === false) ? value.total * -1 : value.total
|
||||||
|
}
|
||||||
|
))
|
||||||
|
await this.modifyResource(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
async modifyResource(resources) {
|
async modifyResource(resources) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default class DHToken extends TokenDocument {
|
||||||
});
|
});
|
||||||
bars.sort((a, b) => a.label.compare(b.label));
|
bars.sort((a, b) => a.label.compare(b.label));
|
||||||
|
|
||||||
const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value'];
|
const invalidAttributes = ['gold', 'levelData', 'actions'];
|
||||||
const values = attributes.value.reduce((acc, v) => {
|
const values = attributes.value.reduce((acc, v) => {
|
||||||
const a = v.join('.');
|
const a = v.join('.');
|
||||||
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
|
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
|
||||||
|
|
|
||||||
|
|
@ -311,3 +311,15 @@ export const itemAbleRollParse = (value, actor, item) => {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const arraysEqual = (a, b) =>
|
||||||
|
a.length === b.length &&
|
||||||
|
[...new Set([...a, ...b])].every(
|
||||||
|
v => a.filter(e => e === v).length === b.filter(e => e === v).length
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setsEqual = (a, b) =>
|
||||||
|
a.size === b.size &&
|
||||||
|
[...a].every(
|
||||||
|
value => b.has(value)
|
||||||
|
);
|
||||||
|
|
@ -56,4 +56,15 @@
|
||||||
|
|
||||||
color: light-dark(@dark, @beige);
|
color: light-dark(@dark, @beige);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.damage-formula {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.damage-details {
|
||||||
|
font-style: italic;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@
|
||||||
height: 34px;
|
height: 34px;
|
||||||
.tags {
|
.tags {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
margin: 5px;
|
margin: 4px;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
.tag {
|
.tag {
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.damage-resource {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dice-total {
|
.dice-total {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,12 @@
|
||||||
{{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true classes="inline-child"}}
|
{{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true classes="inline-child"}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}}
|
<div class="nest-inputs">
|
||||||
|
{{formField ../fields.applyTo value=dmg.applyTo name=(concat ../path "damage.parts." realIndex ".applyTo") localize=true}}
|
||||||
|
{{#if (eq dmg.applyTo 'hitPoints')}}
|
||||||
|
{{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
{{#if ../horde}}
|
{{#if ../horde}}
|
||||||
<fieldset class="one-column">
|
<fieldset class="one-column">
|
||||||
<legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
|
<legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
|
||||||
|
|
@ -56,7 +61,12 @@
|
||||||
{{> formula fields=../../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}}
|
{{> formula fields=../../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}}
|
<div class="nest-inputs">
|
||||||
|
{{formField ../../fields.applyTo value=dmg.applyTo name=(concat "damage.parts." realIndex ".applyTo") localize=true}}
|
||||||
|
{{#if (eq dmg.applyTo 'hitPoints')}}
|
||||||
|
{{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
<input type="hidden" name="damage.parts.{{realIndex}}.base" value="{{dmg.base}}">
|
<input type="hidden" name="damage.parts.{{realIndex}}.base" value="{{dmg.base}}">
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{#unless dmg.base}}<div class="fas fa-trash" data-action="removeDamage" data-index="{{realIndex}}"></div>{{/unless}}
|
{{#unless dmg.base}}<div class="fas fa-trash" data-action="removeDamage" data-index="{{realIndex}}"></div>{{/unless}}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,41 @@
|
||||||
|
|
||||||
<fieldset class="action-category">
|
<fieldset class="one-column">
|
||||||
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
|
<legend>
|
||||||
<div>Healing</div>
|
{{localize "DAGGERHEART.GENERAL.healing"}}
|
||||||
</legend>
|
</legend>
|
||||||
<div class="action-category-data open">
|
{{#if (and (not @root.isNPC) @root.hasRoll)}}
|
||||||
<fieldset>
|
{{formField fields.resultBased value=source.resultBased name="healing.resultBased" localize=true classes="checkbox"}}
|
||||||
{{formField fields.type value=source.type name="healing.type" localize=true}}
|
{{/if}}
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll)}}
|
{{#if (and (not @root.isNPC) @root.hasRoll source.resultBased)}}
|
||||||
{{formField fields.resultBased value=source.resultBased name="healing.resultBased" localize=true}}
|
<div class="nest-inputs">
|
||||||
{{/if}}
|
<fieldset class="one-column">
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll source.resultBased)}}
|
<legend>
|
||||||
<fieldset>
|
<div>With Hope</div>
|
||||||
<legend>
|
</legend>
|
||||||
<div>With Hope</div>
|
|
||||||
</legend>
|
|
||||||
{{> formula fields=fields.value.fields source=source.value target="value"}}
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend>
|
|
||||||
<div>With Fear</div>
|
|
||||||
</legend>
|
|
||||||
{{> formula fields=fields.valueAlt.fields source=source.valueAlt target="valueAlt"}}
|
|
||||||
</fieldset>
|
|
||||||
{{else}}
|
|
||||||
{{> formula fields=fields.value.fields source=source.value target="value"}}
|
{{> formula fields=fields.value.fields source=source.value target="value"}}
|
||||||
{{/if}}
|
</fieldset>
|
||||||
</fieldset>
|
<fieldset class="one-column">
|
||||||
</div>
|
<legend>
|
||||||
|
<div>With Fear</div>
|
||||||
|
</legend>
|
||||||
|
{{> formula fields=fields.valueAlt.fields source=source.valueAlt target="valueAlt"}}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{> formula fields=fields.value.fields source=source.value target="value"}}
|
||||||
|
{{/if}}
|
||||||
|
{{formField fields.applyTo value=source.type name="healing.applyTo" localize=true}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
{{#*inline "formula"}}
|
{{#*inline "formula"}}
|
||||||
<div class="multi-display">
|
{{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat "healing." target ".custom.enabled") classes="checkbox"}}
|
||||||
{{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat "healing." target ".custom.enabled")}}
|
{{#if source.custom.enabled}}
|
||||||
{{#if source.custom.enabled}}
|
{{formField fields.custom.fields.formula value=source.custom.formula name=(concat "healing." target ".custom.formula") localize=true}}
|
||||||
{{formField fields.custom.fields.formula value=source.custom.formula name=(concat "healing." target ".custom.formula") localize=true}}
|
{{else}}
|
||||||
{{else}}
|
<div class="nest-inputs">
|
||||||
{{formField fields.multiplier value=source.multiplier name=(concat "healing." target ".multiplier") localize=true}}
|
{{formField fields.multiplier value=source.multiplier name=(concat "healing." target ".multiplier") localize=true}}
|
||||||
{{formField fields.dice value=source.dice name=(concat "healing." target ".dice")}}
|
{{formField fields.dice value=source.dice name=(concat "healing." target ".dice")}}
|
||||||
{{formField fields.bonus value=source.bonus name=(concat "healing." target ".bonus") localize=true}}
|
{{formField fields.bonus value=source.bonus name=(concat "healing." target ".bonus") localize=true}}
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
{{/if}}
|
||||||
{{/inline}}
|
{{/inline}}
|
||||||
|
|
@ -2,10 +2,26 @@
|
||||||
<header class="dialog-header">
|
<header class="dialog-header">
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
</header>
|
</header>
|
||||||
<span class="formula-label"><b>Formula:</b> {{@root.formula}}</span>
|
{{#each @root.formula}}
|
||||||
<div class="form-group">
|
<div class="damage-formula">
|
||||||
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="Situational Bonus">
|
<span class="damage-resource"><b>Formula:</b> {{roll.formula}}</span>
|
||||||
</div>
|
<span class="damage-details">
|
||||||
|
{{#with (lookup @root.config.GENERAL.healingTypes applyTo)}}
|
||||||
|
{{localize label}}
|
||||||
|
{{/with}}
|
||||||
|
{{#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="form-group">
|
||||||
|
<input type="text" value="{{extraFormula}}" name="roll.{{ @index }}.extraFormula" placeholder="Situational Bonus">
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
<div class="damage-section-controls">
|
<div class="damage-section-controls">
|
||||||
{{#if directDamage}}
|
{{#if directDamage}}
|
||||||
<select class="roll-mode-select" name="selectedRollMode">
|
<select class="roll-mode-select" name="selectedRollMode">
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,31 @@
|
||||||
<div class="dice-roll daggerheart chat roll" data-action="expandRoll">
|
<div class="dice-roll daggerheart chat roll" data-action="expandRoll">
|
||||||
<div class="dice-flavor">{{title}}</div>
|
<div class="dice-flavor">{{title}}</div>
|
||||||
<div class="dice-result">
|
<div class="dice-result">
|
||||||
<div class="dice-formula">{{roll.formula}}</div>
|
{{#each roll as | resource index | }}
|
||||||
<div class="dice-tooltip">
|
<div class="dice-formula">{{resource.formula}}</div>
|
||||||
<div class="wrapper">
|
<div class="dice-tooltip">
|
||||||
<section class="tooltip-part">
|
<div class="wrapper">
|
||||||
{{#each roll.dice}}
|
{{#each resource.parts}}
|
||||||
<div class="dice">
|
<section class="tooltip-part">
|
||||||
<header class="part-header flexrow">
|
<div class="dice">
|
||||||
<span class="part-formula">{{formula}}</span>
|
<header class="part-header flexrow">
|
||||||
<span class="part-total">{{total}}</span>
|
<span class="part-formula">{{formula}}</span>
|
||||||
</header>
|
<span class="part-total">{{total}}</span>
|
||||||
<ol class="dice-rolls">
|
</header>
|
||||||
{{#each results}}
|
<ol class="dice-rolls">
|
||||||
<li class="roll die {{../denomination}} min">{{result}}</li>
|
{{#each dice}}
|
||||||
{{/each}}
|
{{#each results}}
|
||||||
</ol>
|
<li class="roll die {{../dice}} min">{{result}}</li>
|
||||||
</div>
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</section>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="dice-total">{{resource.total}}</div>
|
||||||
<div class="dice-total">{{roll.total}}</div>
|
{{/each}}
|
||||||
<div class="flexrow">
|
<div class="flexrow">
|
||||||
<button class="healing-button"><span>{{localize "DAGGERHEART.UI.Chat.healingRoll.heal"}}</span></button>
|
<button class="healing-button"><span>{{localize "DAGGERHEART.UI.Chat.healingRoll.heal"}}</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,33 @@
|
||||||
<div class="dice-roll daggerheart chat roll" data-action="expandRoll">
|
<div class="dice-roll daggerheart chat roll" data-action="expandRoll">
|
||||||
{{#unless noTitle}}<div class="dice-flavor">{{damage.title}}</div>{{/unless}}
|
{{#unless noTitle}}<div class="dice-flavor">{{damage.title}}</div>{{/unless}}
|
||||||
<div class="dice-result">
|
<div class="dice-result">
|
||||||
<div class="dice-formula">{{damage.roll.formula}}</div>
|
{{#each damage.roll as | roll index | }}
|
||||||
<div class="dice-tooltip">
|
<div class="dice-flavor">{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}</div>
|
||||||
<div class="wrapper">
|
<div class="dice-formula">{{roll.formula}}</div>
|
||||||
<section class="tooltip-part">
|
<div class="dice-tooltip">
|
||||||
{{#each damage.roll.dice}}
|
<div class="wrapper">
|
||||||
<div class="dice">
|
{{#each roll.parts}}
|
||||||
<header class="part-header flexrow">
|
<section class="tooltip-part">
|
||||||
<span class="part-formula">{{formula}}</span>
|
<div class="dice">
|
||||||
<span class="part-total">{{total}}</span>
|
<header class="part-header flexrow">
|
||||||
</header>
|
<span class="part-formula">{{formula}}</span>
|
||||||
<ol class="dice-rolls">
|
<span class="part-total">{{total}}</span>
|
||||||
{{#each results}}
|
</header>
|
||||||
<li class="roll die {{../dice}} min">{{result}}</li>
|
<ol class="dice-rolls">
|
||||||
{{/each}}
|
{{#each dice}}
|
||||||
</ol>
|
{{#each results}}
|
||||||
</div>
|
<li class="roll die {{../dice}} min">{{result}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if damage.roll.modifierTotal}}<div class="duality-modifier">{{#if (gt damage.roll.modifierTotal 0)}}+{{/if}}{{damage.roll.modifierTotal}}</div>{{/if}}
|
{{/each}}
|
||||||
<div class="duality-result">Total: {{damage.roll.total}}</div>
|
</ol>
|
||||||
</section>
|
</div>
|
||||||
|
{{#if modifierTotal}}<div class="duality-modifier">{{#if (gt modifierTotal 0)}}+{{/if}}{{modifierTotal}}</div>{{/if}}
|
||||||
|
<div class="duality-result">Total: {{total}}</div>
|
||||||
|
</section>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="dice-total">{{roll.total}}</div>
|
||||||
<div class="dice-total">{{damage.roll.total}}</div>
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue