mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-07-21 18:09:54 +02:00
Compare commits
18 commits
3828337472
...
6460077c94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6460077c94 | ||
|
|
3b68c6c895 | ||
|
|
c90c6afc19 | ||
|
|
4b651836ff | ||
|
|
c181a47cc2 | ||
|
|
d76b4bb707 | ||
|
|
79d6522614 | ||
|
|
0c2d257871 | ||
|
|
4974df16d0 | ||
|
|
3a5529f1dc | ||
|
|
02a73d774a | ||
|
|
450287e4d0 | ||
|
|
81e264a477 | ||
|
|
d3d9ddfb41 | ||
|
|
7b35feb36d | ||
|
|
6e0d0b4e2c | ||
|
|
3de9c2f909 | ||
|
|
3faf588e6c |
77 changed files with 1510 additions and 1002 deletions
|
|
@ -270,6 +270,10 @@ Hooks.on('i18nInit', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('setup', () => {
|
Hooks.on('setup', () => {
|
||||||
|
if (game.user.isGM) {
|
||||||
|
document.body.dataset.gm = true;
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG.statusEffects = [
|
CONFIG.statusEffects = [
|
||||||
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
||||||
...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({
|
...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({
|
||||||
|
|
|
||||||
12
lang/en.json
12
lang/en.json
|
|
@ -105,7 +105,8 @@
|
||||||
},
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"flatMultiplier": "Flat Multiplier"
|
"flatMultiplier": "Flat Multiplier",
|
||||||
|
"markResources": "Mark Resources"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"customFormula": "Custom Formula",
|
"customFormula": "Custom Formula",
|
||||||
|
|
@ -2245,7 +2246,7 @@
|
||||||
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
||||||
},
|
},
|
||||||
"magical": {
|
"magical": {
|
||||||
"label": "Daamge Reduction: Only Magical",
|
"label": "Damage Reduction: Only Magical",
|
||||||
"hint": "Armor can only be used to reduce magical damage"
|
"hint": "Armor can only be used to reduce magical damage"
|
||||||
},
|
},
|
||||||
"maxArmorMarkedBonus": "Max Armor Used",
|
"maxArmorMarkedBonus": "Max Armor Used",
|
||||||
|
|
@ -2483,7 +2484,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"
|
||||||
|
|
@ -2550,6 +2550,9 @@
|
||||||
},
|
},
|
||||||
"identifier": {
|
"identifier": {
|
||||||
"label": "Identifier"
|
"label": "Identifier"
|
||||||
|
},
|
||||||
|
"gmNotes": {
|
||||||
|
"label": "GM Notes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Ancestry": {
|
"Ancestry": {
|
||||||
|
|
@ -2565,6 +2568,9 @@
|
||||||
"severe": "Severe Threshold"
|
"severe": "Severe Threshold"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Base": {
|
||||||
|
"addGMNote": "Add GM Note"
|
||||||
|
},
|
||||||
"Beastform": {
|
"Beastform": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"beastformType": { "label": "Beastform Type" },
|
"beastformType": { "label": "Beastform Type" },
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -185,8 +185,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
if (result === undefined) return;
|
if (result === undefined) return;
|
||||||
|
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
|
|
@ -202,7 +200,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
img: this.selectedMove.img,
|
img: this.selectedMove.img,
|
||||||
description: game.i18n.localize(this.selectedMove.description),
|
description: game.i18n.localize(this.selectedMove.description),
|
||||||
result: result,
|
result: result,
|
||||||
open: autoExpandDescription ? 'open' : '',
|
|
||||||
showRiskItAllButton: this.showRiskItAllButton,
|
showRiskItAllButton: this.showRiskItAllButton,
|
||||||
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
||||||
riskItAllHope: this.riskItAllHope
|
riskItAllHope: this.riskItAllHope
|
||||||
|
|
|
||||||
|
|
@ -196,9 +196,6 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
||||||
.filter(x => x.uuid !== this.actor.uuid);
|
.filter(x => x.uuid !== this.actor.uuid);
|
||||||
|
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const msg = {
|
const msg = {
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
|
|
@ -219,8 +216,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
actor: { name: this.actor.name, img: this.actor.img },
|
actor: { name: this.actor.name, img: this.actor.img },
|
||||||
moves: moves,
|
moves: moves,
|
||||||
characters: characters,
|
characters: characters,
|
||||||
selfId: this.actor.uuid,
|
selfId: this.actor.uuid
|
||||||
open: autoExpandDescription ? 'open' : ''
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
|
|
|
||||||
|
|
@ -553,23 +553,27 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const { memberKey } = button.dataset;
|
const { memberKey } = button.dataset;
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members.${memberKey}.damageRollData.types`]:
|
[`system.tagTeam.members.${memberKey}.damageRollData`]: {
|
||||||
_replace({})
|
main: null,
|
||||||
|
resources: _replace({})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #rerollDamageDice(_, button) {
|
static async #rerollDamageDice(_, button) {
|
||||||
const { memberKey, damageKey, diceIndex, resultIndex } = 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];
|
||||||
await memberData.damageRollData.rerollDamageDie(damageKey, diceIndex, resultIndex);
|
await memberData.damageRollData.rerollDamageDie(isResource, damageKey, diceIndex, resultIndex);
|
||||||
|
|
||||||
|
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}.damageRollData.types`]: {
|
[updatePath]: updateValue.toJSON()
|
||||||
[damageKey]: memberData.damageRollData.types[damageKey].toJSON()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
);
|
);
|
||||||
|
|
@ -577,18 +581,18 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
async getCriticalDamage(origDamage) {
|
async getCriticalDamage(origDamage) {
|
||||||
const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
|
const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
|
||||||
for (let key in newDamage?.types ?? {}) {
|
if (newDamage?.main) {
|
||||||
const criticalDamage = await getCritDamageBonus(newDamage.types[key].formula);
|
const criticalDamage = await getCritDamageBonus(newDamage.main.formula);
|
||||||
if (!criticalDamage) continue;
|
if (criticalDamage) {
|
||||||
|
|
||||||
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
|
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
|
||||||
criticalTerm.evaluate();
|
criticalTerm.evaluate();
|
||||||
newDamage.types[key] = await Roll.fromTerms([
|
newDamage.main = await Roll.fromTerms([
|
||||||
...origDamage.types[key].terms,
|
...origDamage.main.terms,
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
criticalTerm
|
criticalTerm
|
||||||
]);
|
]);
|
||||||
newDamage.types[key].options = foundry.utils.deepClone(origDamage.types[key].options);
|
newDamage.main.options = foundry.utils.deepClone(origDamage.main.options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newDamage;
|
return newDamage;
|
||||||
|
|
@ -644,25 +648,47 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
? await this.getCriticalDamage(secondaryRoll.damageRollData)
|
? await this.getCriticalDamage(secondaryRoll.damageRollData)
|
||||||
: secondaryRoll.damageRollData;
|
: secondaryRoll.damageRollData;
|
||||||
if (mainRoll.damageRollData) {
|
if (mainRoll.damageRollData) {
|
||||||
for (const [key, damage] of Object.entries(secondaryDamage.types ?? {})) {
|
if (secondaryDamage.main) {
|
||||||
if (key in mainRoll.damageRollData.types) {
|
if (mainRoll.damageRollData.main) {
|
||||||
mainRoll.damageRollData.types[key] = Roll.fromTerms([
|
mainRoll.damageRollData.main = Roll.fromTerms([
|
||||||
...baseMainRoll.damageRollData.types[key].terms,
|
...baseMainRoll.damageRollData.main.terms,
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
...baseSecondaryRoll.damageRollData.types[key].terms
|
...baseSecondaryRoll.damageRollData.main.terms
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/* Joining the roll.options of both rolls */
|
/* Joining the roll.options of both rolls */
|
||||||
const joinedDamageTypes = new Set([
|
const joinedDamageTypes = new Set([
|
||||||
...baseMainRoll.damageRollData.types[key].options.damageTypes,
|
...baseMainRoll.damageRollData.main.options.damageTypes,
|
||||||
...baseSecondaryRoll.damageRollData.types[key].options.damageTypes
|
...baseSecondaryRoll.damageRollData.main.options.damageTypes
|
||||||
]);
|
]);
|
||||||
mainRoll.damageRollData.types[key].options = {
|
mainRoll.damageRollData.main.options = {
|
||||||
...baseMainRoll.damageRollData.types[key].options,
|
...baseMainRoll.damageRollData.main.options,
|
||||||
damageTypes: [...joinedDamageTypes]
|
damageTypes: [...joinedDamageTypes]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
mainRoll.damageRollData.types[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 {
|
||||||
|
|
@ -727,8 +753,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
...mainRoll.options,
|
...mainRoll.options,
|
||||||
damage: joinedRoll.damageRollData?.toJSON()
|
damage: joinedRoll.damageRollData?.toJSON()
|
||||||
};
|
};
|
||||||
for (const type of Object.keys(joinedRoll.damageRollData?.types ?? {})) {
|
|
||||||
systemData.damage.types[type] = joinedRoll.damageRollData.types[type].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'),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,53 +301,71 @@ 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.damageType'),
|
||||||
choices,
|
choices,
|
||||||
required: true
|
required: true
|
||||||
}).toFormGroup(
|
}).toFormGroup({}, {
|
||||||
{},
|
|
||||||
{
|
|
||||||
name: 'type',
|
name: 'type',
|
||||||
localize: true,
|
localize: true,
|
||||||
nameAttr: 'value',
|
nameAttr: 'value',
|
||||||
labelAttr: 'label'
|
labelAttr: 'label'
|
||||||
}
|
}).outerHTML;
|
||||||
).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')
|
/** @todo localize */
|
||||||
|
title: 'Add Damage'
|
||||||
},
|
},
|
||||||
position: { width: 300 }
|
position: { width: 300 }
|
||||||
});
|
});
|
||||||
|
|
@ -353,12 +373,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
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
addFeature: DHBaseItemSheet.#addFeature,
|
addFeature: DHBaseItemSheet.#addFeature,
|
||||||
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
||||||
addResource: DHBaseItemSheet.#addResource,
|
addResource: DHBaseItemSheet.#addResource,
|
||||||
removeResource: DHBaseItemSheet.#removeResource
|
removeResource: DHBaseItemSheet.#removeResource,
|
||||||
|
editGMNote: DHBaseItemSheet.#onEditGMNote
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{ dragSelector: null, dropSelector: '.drop-section' },
|
{ dragSelector: null, dropSelector: '.drop-section' },
|
||||||
|
|
@ -76,10 +77,16 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preparePartContext(partId, context, options) {
|
async _preparePartContext(partId, context, options) {
|
||||||
await super._preparePartContext(partId, context, options);
|
await super._preparePartContext(partId, context, options);
|
||||||
|
const TextEditor = foundry.applications.ux.TextEditor.implementation;
|
||||||
|
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'description':
|
case 'description':
|
||||||
context.enrichedDescription = await this.document.system.getEnrichedDescription();
|
context.enrichedDescription = await this.document.system.getEnrichedDescription({ gmNotes: false });
|
||||||
|
context.enrichedGMNotes = await TextEditor.implementation.enrichHTML(this.item.system.gmNotes, {
|
||||||
|
relativeTo: this.item,
|
||||||
|
rollData: this.item.getRollData(),
|
||||||
|
secrets: this.item.isOwner
|
||||||
|
})
|
||||||
break;
|
break;
|
||||||
case 'effects':
|
case 'effects':
|
||||||
await this._prepareEffectsContext(context, options);
|
await this._prepareEffectsContext(context, options);
|
||||||
|
|
@ -331,4 +338,45 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the Add GM Note button being pressed. This is only used when an item has no GM notes.
|
||||||
|
* Later edits to a GM note instead go through the normal editor toggle workflow.
|
||||||
|
* @this DHBaseItemSheet
|
||||||
|
*/
|
||||||
|
static #onEditGMNote() {
|
||||||
|
// Open the editor, which might be hidden. We remove the css class to hide temporarily
|
||||||
|
// so that menu auto resizing functions properly.
|
||||||
|
const editor = this.element.querySelector('prose-mirror[name="system.gmNotes"]');
|
||||||
|
const wasHidden = editor.classList.contains('hide-if-inactive');
|
||||||
|
editor.classList.remove('hide-if-inactive');
|
||||||
|
editor.open = true;
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (wasHidden) editor.classList.add('hide-if-inactive');
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
// Render an add gmnotes button if there are no set GM notes.
|
||||||
|
// We need to re-render on close since its possible to prosemirror to close *without* triggering a full re-render
|
||||||
|
if (game.user.isGM && !this.item.system.gmNotes) {
|
||||||
|
const description = this.element.querySelector('[name="system.description"]');
|
||||||
|
const addButton = () => {
|
||||||
|
if (description.querySelector('[data-action=editGMNote]')) return;
|
||||||
|
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.classList.add('icon', 'toggle', 'fa-regular', 'fa-note-medical');
|
||||||
|
button.dataset.action = 'editGMNote';
|
||||||
|
button.dataset.tooltip = 'DAGGERHEART.ITEMS.Base.addGMNote';
|
||||||
|
description.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
addButton();
|
||||||
|
description.addEventListener('close', () => addButton());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -251,15 +251,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, dice, result } = target.dataset;
|
const { isResource, damageType, dice, result } = target.dataset;
|
||||||
await message.system.damage.rerollDamageDie(damageType, dice, result);
|
await message.system.damage.rerollDamageDie(isResource, damageType, dice, result);
|
||||||
|
|
||||||
|
const updatePath = isResource ? `system.damage.resources.${damageType}` : 'system.damage.main';
|
||||||
|
const updateValue = isResource ?
|
||||||
|
message.system.damage.resources[damageType] : message.system.damage.main;
|
||||||
await message.update({
|
await message.update({
|
||||||
'system.damage.types': {
|
[updatePath]: updateValue.toJSON()
|
||||||
[damageType]: message.system.damage.types[damageType].toJSON()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} 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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat();
|
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled)
|
||||||
|
await this.toChat(null, config);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
@ -288,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,
|
||||||
|
|
@ -306,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
|
||||||
};
|
};
|
||||||
|
|
@ -429,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() {
|
||||||
|
|
@ -469,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,8 +84,7 @@ 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: {
|
||||||
|
|
@ -94,7 +93,6 @@ export default class DhpAdversary extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
experiences: new fields.TypedObjectField(
|
experiences: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,7 @@ 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: {
|
||||||
|
|
@ -117,7 +116,6 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||||
bonuses: new fields.SchemaField({
|
bonuses: new fields.SchemaField({
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -137,12 +137,17 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
if (!this.damage.active) return;
|
if (!this.damage.active) return;
|
||||||
|
|
||||||
const rerolls = [];
|
const rerolls = [];
|
||||||
const update = { system: { damage: { types: {} } } };
|
const update = { system: { damage: { main: null, resources: _replace({}) } } };
|
||||||
for (const key of Object.keys(this.damage.types)) {
|
if (this.damage.main) {
|
||||||
const type = this.damage.types[key];
|
const reroll = await this.damage.main.reroll();
|
||||||
const reroll = await type.reroll();
|
|
||||||
rerolls.push(reroll);
|
rerolls.push(reroll);
|
||||||
update.system.damage.types[key] = reroll.toJSON();
|
update.system.damage.main = reroll.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(this.damage.resources)) {
|
||||||
|
const reroll = await this.damage.resources[key].reroll();
|
||||||
|
rerolls.push(reroll);
|
||||||
|
update.system.damage.resources[key] = reroll.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
await triggerChatRollFx(rerolls);
|
await triggerChatRollFx(rerolls);
|
||||||
|
|
@ -188,22 +193,36 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
static migrateData(source) {
|
static migrateData(source) {
|
||||||
if (source.hasDamage && !source.damage.types) {
|
const { main, resources, ...flatDamageKeys } = source.damage ?? {};
|
||||||
source.damage = {
|
if (source.damage && !main && !resources) {
|
||||||
types: Object.keys(source.damage).reduce((acc, key) => {
|
source.damage.main = null;
|
||||||
|
source.damage.resources = {};
|
||||||
|
|
||||||
|
const getRoll = key => {
|
||||||
const damageData = source.damage[key];
|
const damageData = source.damage[key];
|
||||||
const oldRoll = damageData.parts[0]?.roll;
|
const oldRoll = damageData.parts[0]?.roll;
|
||||||
acc[key] = oldRoll ? {
|
return oldRoll ? JSON.stringify({
|
||||||
...oldRoll,
|
...oldRoll,
|
||||||
|
class: 'BaseRoll',
|
||||||
options: {
|
options: {
|
||||||
...oldRoll.options,
|
...oldRoll.options,
|
||||||
damageTypes: damageData.parts[0].damageTypes ?? []
|
damageTypes: damageData.parts[0].damageTypes ?? []
|
||||||
}
|
}
|
||||||
} : null;
|
}) : null;
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
return source;
|
||||||
|
|
|
||||||
|
|
@ -11,31 +11,31 @@ export class ChatDamageData extends foundry.abstract.DataModel {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
types: new fields.TypedObjectField(new fields.JSONField({validate: ChatDamageData.#validateRoll}))
|
main: new fields.JSONField({ nullable: true, validate: ChatDamageData.#validateRoll}),
|
||||||
|
resources: new fields.TypedObjectField(new fields.JSONField({validate: ChatDamageData.#validateRoll}))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get active() {
|
get active() {
|
||||||
return Boolean(Object.keys(this.types).length);
|
return !!this.main || Boolean(Object.keys(this.resources).length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static #validateRoll(rollJSON) {
|
static #validateRoll(rollJSON) {
|
||||||
|
if (rollJSON) {
|
||||||
const roll = JSON.parse(rollJSON);
|
const roll = JSON.parse(rollJSON);
|
||||||
if (!roll.evaluated) throw new Error('Roll objects added to ChatMessage documents must be evaluated');
|
if (!roll.evaluated) throw new Error('Roll objects added to ChatMessage documents must be evaluated');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_prepareRolls() {
|
_prepareRolls() {
|
||||||
for (const key of Object.keys(this.types)) {
|
this.main &&= Roll.fromData(this.main);
|
||||||
const type = this.types[key];
|
for (const key of Object.keys(this.resources)) {
|
||||||
try {
|
this.resources[key] = Roll.fromData(this.resources[key]);
|
||||||
this.types[key] = Roll.fromData(type);
|
|
||||||
this.types[key].options.modifierTotal = CONFIG.Dice.daggerheart.DHRoll.calculateTotalModifiers(type);
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async rerollDamageDie(damageType, dice, resultIndex) {
|
async rerollDamageDie(isResource, damageType, dice, resultIndex) {
|
||||||
const reroll = this.types[damageType];
|
const reroll = isResource ? this.resources[damageType] : this.main;
|
||||||
const rerollDice = reroll.dice[dice];
|
const rerollDice = reroll.dice[dice];
|
||||||
await rerollDice.rerollResult(resultIndex);
|
await rerollDice.rerollResult(resultIndex);
|
||||||
await reroll._evaluate();
|
await reroll._evaluate();
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
@ -111,17 +99,15 @@ export default class DamageField extends fields.SchemaField {
|
||||||
: actor.prototypeToken;
|
: actor.prototypeToken;
|
||||||
if (config.hasHealing)
|
if (config.hasHealing)
|
||||||
damagePromises.push(
|
damagePromises.push(
|
||||||
actor.takeHealing(config.damage.types).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.types);
|
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;
|
||||||
configDamage.hitPoints = configDamage.hitPoints.toJSON();
|
const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
||||||
configDamage.hitPoints.total = Math.ceil(
|
configDamage.main.total = Math.ceil(configDamage.main.total * multiplier * takenMultiplier);
|
||||||
configDamage.hitPoints.total * hpDamageMultiplier * hpDamageTakenMultiplier
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
damagePromises.push(
|
damagePromises.push(
|
||||||
|
|
@ -184,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(
|
||||||
|
|
@ -198,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,10 +269,18 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} the formula associated with this damage field
|
||||||
|
*/
|
||||||
getFormula() {
|
getFormula() {
|
||||||
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
if (this.custom.enabled) return this.custom.formula;
|
||||||
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
|
||||||
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`;
|
||||||
|
if (!multiplier) return String(this.bonus || 0);
|
||||||
|
|
||||||
|
const dice = `${multiplier ?? 1}${this.dice}`;
|
||||||
|
const sign = this.bonus < 0 ? ' - ' : ' + ';
|
||||||
|
return this.bonus ? `${dice} ${sign} ${Math.abs(this.bonus)}` : dice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,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,
|
||||||
|
|
@ -309,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,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
super(summonFields, options, context);
|
super(summonFields, options, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async execute() {
|
static async execute(config) {
|
||||||
if (!canvas.scene) {
|
if (!canvas.scene) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error'));
|
||||||
return;
|
return;
|
||||||
|
|
@ -36,6 +36,7 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
|
|
||||||
const rolls = [];
|
const rolls = [];
|
||||||
const summonData = [];
|
const summonData = [];
|
||||||
|
const chatMessageData = [];
|
||||||
for (const summon of this.summon) {
|
for (const summon of this.summon) {
|
||||||
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
||||||
await roll.evaluate();
|
await roll.evaluate();
|
||||||
|
|
@ -54,17 +55,18 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}`
|
tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chatMessageData.push({
|
||||||
|
data: actor,
|
||||||
|
quantity: countNumber
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rolls.length) await triggerChatRollFx(rolls);
|
if (rolls.length) await triggerChatRollFx(rolls);
|
||||||
|
|
||||||
this.actor.sheet?.minimize();
|
this.actor.sheet?.minimize();
|
||||||
DHSummonField.handleSummon(summonData, this.actor);
|
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: this.actor.token?.elevation });
|
||||||
}
|
this.actor.sheet?.maximize();
|
||||||
|
config.summonData = chatMessageData;
|
||||||
static async handleSummon(summonData, actionActor) {
|
|
||||||
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
|
|
||||||
|
|
||||||
return actionActor.sheet?.maximize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -269,10 +269,7 @@ export function ActionMixin(Base) {
|
||||||
return this.delete();
|
return this.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
async toChat(origin) {
|
async toChat(origin, config) {
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const systemData = {
|
const systemData = {
|
||||||
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
||||||
|
|
@ -282,7 +279,7 @@ export function ActionMixin(Base) {
|
||||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||||
areas: this.areas,
|
areas: this.areas,
|
||||||
summon: this.summon
|
summon: config?.summonData
|
||||||
},
|
},
|
||||||
source: {
|
source: {
|
||||||
actor: this.actor.uuid,
|
actor: this.actor.uuid,
|
||||||
|
|
@ -307,7 +304,7 @@ export function ActionMixin(Base) {
|
||||||
system: systemData,
|
system: systemData,
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||||
{ ...systemData, open: autoExpandDescription ? 'open' : '' }
|
systemData
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
daggerheart: {
|
daggerheart: {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,10 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
if (this.metadata.hasDescription) {
|
||||||
|
schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||||
|
schema.gmNotes = new fields.HTMLField({ required: true, nullable: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (this.metadata.hasResource) {
|
if (this.metadata.hasResource) {
|
||||||
schema.resource = new fields.SchemaField(
|
schema.resource = new fields.SchemaField(
|
||||||
|
|
@ -134,7 +137,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
/**
|
/**
|
||||||
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
|
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
|
||||||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||||
* @returns {string}
|
* @returns {Promise<{ prefix: string | null; value: string | null; suffix: string | null }>}
|
||||||
*/
|
*/
|
||||||
async getDescriptionData(_options) {
|
async getDescriptionData(_options) {
|
||||||
return { prefix: null, value: this.description, suffix: null };
|
return { prefix: null, value: this.description, suffix: null };
|
||||||
|
|
@ -143,16 +146,26 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
/**
|
/**
|
||||||
* Gets the enriched and augmented description for the item.
|
* Gets the enriched and augmented description for the item.
|
||||||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||||
* @returns {string}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
async getEnrichedDescription() {
|
async getEnrichedDescription({ gmNotes = true } = {}) {
|
||||||
if (!this.metadata.hasDescription) return '';
|
if (!this.metadata.hasDescription) return '';
|
||||||
|
|
||||||
const { prefix, value, suffix } = await this.getDescriptionData();
|
const { prefix, value, suffix } = await this.getDescriptionData();
|
||||||
const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
let fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
||||||
|
if (this.gmNotes && gmNotes) {
|
||||||
|
const gmNotesElement = document.createElement('section');
|
||||||
|
gmNotesElement.classList.add('gm-notes-section');
|
||||||
|
gmNotesElement.dataset.visibility = 'gm';
|
||||||
|
const header = document.createElement('header');
|
||||||
|
header.classList.add('gm-notes');
|
||||||
|
header.textContent = _loc('DAGGERHEART.ITEMS.FIELDS.gmNotes.label');
|
||||||
|
gmNotesElement.innerHTML = header.outerHTML + this.gmNotes;
|
||||||
|
fullDescription += gmNotesElement.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
||||||
relativeTo: this,
|
relativeTo: this.parent,
|
||||||
rollData: this.getRollData(),
|
rollData: this.getRollData(),
|
||||||
secrets: this.parent.isOwner
|
secrets: this.parent.isOwner
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,7 @@ 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',
|
||||||
|
|
@ -77,7 +76,6 @@ export default class DHWeapon extends AttachableItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
attack: new fields.SchemaField({
|
attack: new fields.SchemaField({
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,31 @@ 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 = {}) {
|
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) {
|
const evaluateRoll = async roll => {
|
||||||
await roll.roll.evaluate();
|
await roll.roll.evaluate();
|
||||||
roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] };
|
roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] };
|
||||||
|
return roll.roll;
|
||||||
|
}
|
||||||
|
|
||||||
if (!config.damage?.types) config.damage = { types: {} };
|
if (!config.damage) config.damage = { main: null, resources: {} };
|
||||||
config.damage.types[roll.applyTo] = roll.roll;
|
|
||||||
|
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;
|
roll._evaluated = true;
|
||||||
|
|
@ -36,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.types)
|
...(config.damage.main ? [config.damage.main] : []),
|
||||||
);
|
...Object.values(config.damage.resources)
|
||||||
|
]);
|
||||||
diceRolls.push(Roll.fromTerms([pool]));
|
diceRolls.push(Roll.fromTerms([pool]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +67,8 @@ export default class DamageRoll extends DHRoll {
|
||||||
if (config.source?.message) {
|
if (config.source?.message) {
|
||||||
chatMessage.update({ 'system.damage': {
|
chatMessage.update({ 'system.damage': {
|
||||||
...config.damage.toObject(),
|
...config.damage.toObject(),
|
||||||
types: config.damage.types
|
main: config.damage.main,
|
||||||
|
resources: config.damage.resources
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,11 +121,9 @@ 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);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
|
@ -125,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;
|
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
|
||||||
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
formulaData.roll = new Roll(Roll.replaceFormulaData(formulaData.formula, config.data));
|
||||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data);
|
||||||
for (const mod in config.modifiers) {
|
|
||||||
const modifier = config.modifiers[mod];
|
|
||||||
if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.extraFormula) {
|
if (formulaData.extraFormula) {
|
||||||
part.roll.terms.push(
|
formulaData.roll.terms.push(
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
...this.constructor.parse(part.extraFormula, this.options.data)
|
...this.constructor.parse(formulaData.extraFormula, this.options.data)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) {
|
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 */
|
||||||
|
for (const mod in config.modifiers) {
|
||||||
|
const modifier = config.modifiers[mod];
|
||||||
|
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(formulaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.damageOptions.groupAttack?.numAttackers > 1) {
|
||||||
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 */
|
formulaData.roll._formula = this.constructor.getFormula(formulaData.roll.terms);
|
||||||
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 formulaData;
|
||||||
}
|
|
||||||
return this.options.roll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
/* To Remove When Reaction System */
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@ export default class DHRoll extends BaseRoll {
|
||||||
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>}
|
||||||
|
|
@ -58,7 +62,7 @@ export default class DHRoll extends BaseRoll {
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -656,26 +656,23 @@ export default class DhpActor extends Actor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updates = [];
|
if (damages.main) {
|
||||||
|
damages.main.total = this.calculateDamage(damages.main.total, damages.main.damageTypes);
|
||||||
Object.entries(damages).forEach(([key, damage]) => {
|
}
|
||||||
if (key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
|
||||||
damage.total = this.calculateDamage(damage.total, damage.damageTypes);
|
|
||||||
const update = updates.find(u => u.key === key);
|
|
||||||
if (update) {
|
|
||||||
update.value += damage.total;
|
|
||||||
update.damageTypes.add(...new Set(damage.damageTypes));
|
|
||||||
} else updates.push({ value: damage.total, key, damageTypes: new Set(damage.damageTypes) });
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
|
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
|
||||||
|
|
||||||
if (!updates.length) return;
|
// Convert deducted resources to a record of updates. Return if nothing to do.
|
||||||
|
const updates = Object.entries(damages.resources).map(([key, damage]) => ({ key, value: damage.total }));
|
||||||
|
if (!updates.some(u => u.value) && !damages.main) return;
|
||||||
|
|
||||||
const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
if (damages.main) {
|
||||||
if (hpDamage?.value) {
|
const hpDamage = {
|
||||||
hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
|
value: this.convertDamageToThreshold(damages.main.total),
|
||||||
if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
|
damageTypes: new Set(damages.main.options.damageTypes),
|
||||||
|
key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id
|
||||||
|
};
|
||||||
|
if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.total, hpDamage.damageTypes)) {
|
||||||
const armorSlotResult = await this.owner.query(
|
const armorSlotResult = await this.owner.query(
|
||||||
'armorSlot',
|
'armorSlot',
|
||||||
{
|
{
|
||||||
|
|
@ -689,7 +686,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 });
|
||||||
}
|
}
|
||||||
|
|
@ -699,20 +696,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(
|
||||||
|
|
@ -728,11 +729,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);
|
||||||
|
|
||||||
|
|
@ -741,6 +740,28 @@ export default class DhpActor extends Actor {
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async takeHealing(healings) {
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, healings) === false) return null;
|
||||||
|
|
||||||
|
const updates = Object.entries(healings.resources).map(([key, damage]) => ({
|
||||||
|
key,
|
||||||
|
value: damage.total
|
||||||
|
}));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -765,30 +786,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]) => {
|
|
||||||
const update = updates.find(u => u.key === key);
|
|
||||||
if (update) update.value += healing.roll.total;
|
|
||||||
else updates.push({ value: healing.roll.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.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ import { emitGMUpdate, emitGMCreate, GMUpdateEvent } from '../systemRegistration
|
||||||
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
targetHook = null;
|
targetHook = null;
|
||||||
|
|
||||||
|
static #EXPAND_SECTIONS = [
|
||||||
|
{ selector: 'roll-section [data-action="expandRoll"]', key: 'roll' },
|
||||||
|
{ selector: 'damage-section', key: 'damage' },
|
||||||
|
{ selector: 'target-section', key: 'target' },
|
||||||
|
{ selector: 'description-section', key: 'desc' }
|
||||||
|
];
|
||||||
|
|
||||||
async renderHTML() {
|
async renderHTML() {
|
||||||
const actor = game.actors.get(this.speaker.actor);
|
const actor = game.actors.get(this.speaker.actor);
|
||||||
const actorData =
|
const actorData =
|
||||||
|
|
@ -89,23 +96,26 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check registered selectors and the main item section for expanding
|
||||||
|
// Preserving during re-render is handled by core foundry on anything with [data-action=expandRoll]
|
||||||
const autoExpandRoll = game.settings.get(
|
const autoExpandRoll = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
).expandRollMessage;
|
||||||
).expandRollMessage,
|
for (const { selector, key } of DhpChatMessage.#EXPAND_SECTIONS) {
|
||||||
rollSections = html.querySelectorAll('.roll-part'),
|
const elements = html.querySelectorAll(selector);
|
||||||
itemDesc = html.querySelector('.domain-card-move');
|
for (const element of elements) {
|
||||||
rollSections.forEach(s => {
|
element.classList.toggle('expanded', autoExpandRoll[key]);
|
||||||
if (s.classList.contains('roll-section')) {
|
}
|
||||||
const toExpand = s.querySelector('[data-action="expandRoll"]');
|
}
|
||||||
toExpand.classList.toggle('expanded', autoExpandRoll.roll);
|
|
||||||
} else if (s.classList.contains('damage-section'))
|
// Auto expand the item description. These are not preserved by foundry during re-renders
|
||||||
s.classList.toggle('expanded', autoExpandRoll.damage);
|
const itemDesc = html.querySelector('details');
|
||||||
else if (s.classList.contains('target-section')) s.classList.toggle('expanded', autoExpandRoll.target);
|
if (itemDesc) {
|
||||||
else if (s.classList.contains('description-section'))
|
const existing = document.querySelector(`.chat-message[data-message-id="${this.id}"] details`);
|
||||||
s.classList.toggle('expanded', autoExpandRoll.desc);
|
if (existing?.hasAttribute('open') ?? autoExpandRoll.desc) {
|
||||||
});
|
itemDesc.setAttribute('open', '');
|
||||||
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isAuthor && !this.speakerActor?.isOwner) {
|
if (!this.isAuthor && !this.speakerActor?.isOwner) {
|
||||||
|
|
|
||||||
|
|
@ -208,10 +208,7 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
tags: this._getTags()
|
tags: this._getTags()
|
||||||
},
|
},
|
||||||
actions: item.system.actionsList,
|
actions: item.system.actionsList,
|
||||||
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.system.description, {
|
description: await this.system.getEnrichedDescription()
|
||||||
relativeTo: this.parent,
|
|
||||||
rollData: this.parent?.getRollData() ?? {}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ export default class DhTokenManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await canvas.tokens.placeTokens(
|
const placedData = await canvas.tokens.placeTokens(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
...actor.prototypeToken.toObject(),
|
...(await actor.getTokenDocument()).toObject(),
|
||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
displayName: 50,
|
displayName: 50,
|
||||||
...tokenData
|
...tokenData
|
||||||
|
|
@ -30,6 +30,8 @@ export default class DhTokenManager {
|
||||||
],
|
],
|
||||||
{ create: false }
|
{ create: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return placedData?.[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,22 +48,24 @@ export default class DhTokenManager {
|
||||||
|
|
||||||
const createElevation = elevation ?? level.elevation.bottom;
|
const createElevation = elevation ?? level.elevation.bottom;
|
||||||
for (const tokenData of tokensData) {
|
for (const tokenData of tokensData) {
|
||||||
const previewTokens = await this.createPreview(tokenData.actor, {
|
const previewToken = await this.createPreview(tokenData.actor, {
|
||||||
name: tokenData.tokenPreviewName,
|
name: tokenData.tokenPreviewName,
|
||||||
level: game.user.viewedLevel,
|
level: game.user.viewedLevel,
|
||||||
elevation: createElevation,
|
elevation: createElevation,
|
||||||
flags: { daggerheart: { createPlacement: true } }
|
flags: { daggerheart: { createPlacement: true } }
|
||||||
});
|
});
|
||||||
if (!previewTokens?.length) return null;
|
if (!previewToken) return null;
|
||||||
|
|
||||||
await canvas.scene.createEmbeddedDocuments(
|
const finalTokenData = {
|
||||||
'Token',
|
...previewToken.toObject(),
|
||||||
previewTokens.map(x => ({
|
|
||||||
...x.toObject(),
|
|
||||||
name: tokenData.actor.prototypeToken.name,
|
name: tokenData.actor.prototypeToken.name,
|
||||||
displayName: tokenData.actor.prototypeToken.displayName,
|
displayName: tokenData.actor.prototypeToken.displayName,
|
||||||
flags: tokenData.actor.prototypeToken.flags
|
flags: tokenData.actor.prototypeToken.flags
|
||||||
})),
|
};
|
||||||
|
|
||||||
|
await canvas.scene.createEmbeddedDocuments(
|
||||||
|
'Token',
|
||||||
|
[finalTokenData],
|
||||||
{ controlObject: true, parent: canvas.scene }
|
{ controlObject: true, parent: canvas.scene }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,55 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
#wide = false;
|
#wide = false;
|
||||||
#bordered = false;
|
#bordered = false;
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
async activate(element, options = {}) {
|
async activate(element, options = {}) {
|
||||||
const { TextEditor } = foundry.applications.ux;
|
this.#wide = false;
|
||||||
|
this.#bordered = false;
|
||||||
|
|
||||||
|
const isMacro = document.getElementById('action-bar').contains(element);
|
||||||
|
const macro = isMacro ? game.macros.get(game.user.hotbar[Number(element.dataset.slot)] ?? null) : null;
|
||||||
|
const macroItemUuid = macro?.type === 'script' ? macro.command.match(/await game\.system\.api\.applications\.ui\.DhHotbar\.useItem\("([^"]+)"\);/)?.[1] : null;
|
||||||
|
if (macroItemUuid && await fromUuid(macroItemUuid, { strict: false })) {
|
||||||
|
element.dataset.tooltip = `#item#${macroItemUuid}`;
|
||||||
|
options.direction = this.constructor.TOOLTIP_DIRECTIONS.UP;
|
||||||
|
}
|
||||||
|
|
||||||
let html = options.html;
|
let html = options.html;
|
||||||
if (element.dataset.tooltip?.startsWith('#battlepoints#')) {
|
const key = element.dataset.tooltip?.match(/^#([\w-]+)#/)?.[1];
|
||||||
|
switch (key) {
|
||||||
|
case 'battlepoints':
|
||||||
|
return this.#activateBattlepoints(element, options);
|
||||||
|
case 'effect-display':
|
||||||
|
html = await this.#activateEffectDisplay(element, options);
|
||||||
|
break;
|
||||||
|
case 'item':
|
||||||
|
html = await this.#activateItem(element, options);
|
||||||
|
break;
|
||||||
|
case 'attack':
|
||||||
|
html = await this.#activateAttack(element, options);
|
||||||
|
break;
|
||||||
|
case 'shortRest':
|
||||||
|
case 'longRest':
|
||||||
|
html = await this.#activateRest(element, options);
|
||||||
|
break;
|
||||||
|
case 'advantage':
|
||||||
|
case 'disadvantage':
|
||||||
|
html = await this.#activateAdvantageDisadvantage(element, options);
|
||||||
|
break;
|
||||||
|
case 'deathMove':
|
||||||
|
html = await this.#activateDeathMove(element, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.noOffset = options.noOffset;
|
||||||
|
super.activate(element, { ...options, html });
|
||||||
|
}
|
||||||
|
|
||||||
|
async #activateBattlepoints(element, options) {
|
||||||
this.#wide = true;
|
this.#wide = true;
|
||||||
this.#bordered = true;
|
this.#bordered = true;
|
||||||
|
|
||||||
html = await this.getBattlepointHTML(element.dataset.combatId);
|
const html = await this.getBattlepointHTML(element.dataset.combatId);
|
||||||
options.direction = this._determineItemTooltipDirection(element);
|
options.direction = this._determineItemTooltipDirection(element);
|
||||||
super.activate(element, { ...options, html: html });
|
super.activate(element, { ...options, html: html });
|
||||||
|
|
||||||
|
|
@ -20,13 +60,9 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => {
|
lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => {
|
||||||
element.addEventListener('input', this.toggleModifier.bind(this));
|
element.addEventListener('input', this.toggleModifier.bind(this));
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.#wide = false;
|
|
||||||
this.#bordered = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.dataset.tooltip === '#effect-display#') {
|
async #activateEffectDisplay(element, options) {
|
||||||
this.#bordered = true;
|
this.#bordered = true;
|
||||||
let effect = {};
|
let effect = {};
|
||||||
if (element.dataset.uuid) {
|
if (element.dataset.uuid) {
|
||||||
|
|
@ -75,7 +111,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/effect-display.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/effect-display.hbs`,
|
||||||
{
|
{
|
||||||
effect
|
effect
|
||||||
|
|
@ -84,11 +120,11 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
this.tooltip.innerHTML = html;
|
||||||
options.direction = this._determineItemTooltipDirection(element);
|
options.direction = this._determineItemTooltipDirection(element);
|
||||||
} else {
|
|
||||||
this.#bordered = false;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.dataset.tooltip?.startsWith('#item#')) {
|
async #activateItem(element, options) {
|
||||||
const itemUuid = element.dataset.tooltip.slice(6);
|
const itemUuid = element.dataset.tooltip.slice(6);
|
||||||
const item = await foundry.utils.fromUuid(itemUuid);
|
const item = await foundry.utils.fromUuid(itemUuid);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
|
@ -97,7 +133,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
await this.enrichText(item);
|
await this.enrichText(item);
|
||||||
|
|
||||||
const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
|
const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
||||||
{
|
{
|
||||||
item: item,
|
item: item,
|
||||||
|
|
@ -108,17 +144,20 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
);
|
);
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
this.tooltip.innerHTML = html;
|
||||||
options.direction = this._determineItemTooltipDirection(element);
|
options.direction ??= this._determineItemTooltipDirection(element);
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const attack = element.dataset.tooltip?.startsWith('#attack#');
|
return null;
|
||||||
if (attack) {
|
}
|
||||||
|
|
||||||
|
async #activateAttack(element, options) {
|
||||||
const actorUuid = element.dataset.tooltip.slice(8);
|
const actorUuid = element.dataset.tooltip.slice(8);
|
||||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||||
const attack = actor.system.attack;
|
const attack = actor.system.attack;
|
||||||
|
|
||||||
const description = await TextEditor.enrichHTML(attack.description);
|
const description = await foundry.applications.ux.TextEditor.enrichHTML(attack.description);
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/attack.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/attack.hbs`,
|
||||||
{
|
{
|
||||||
attack: attack,
|
attack: attack,
|
||||||
|
|
@ -129,19 +168,57 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
);
|
);
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
this.tooltip.innerHTML = html;
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortRest = element.dataset.tooltip?.startsWith('#shortRest#');
|
async #activateAdvantageDisadvantage(element, options) {
|
||||||
const longRest = element.dataset.tooltip?.startsWith('#longRest#');
|
const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#');
|
||||||
if (shortRest || longRest) {
|
const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14);
|
||||||
const key = element.dataset.tooltip.slice(shortRest ? 11 : 10);
|
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||||
|
|
||||||
|
if (actor) {
|
||||||
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/advantage.hbs`,
|
||||||
|
{
|
||||||
|
sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.tooltip.innerHTML = html;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #activateDeathMove(element, options) {
|
||||||
|
const name = element.dataset.deathName;
|
||||||
|
const img = element.dataset.deathImg;
|
||||||
|
const description = element.dataset.deathDescription;
|
||||||
|
|
||||||
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/death-move.hbs`,
|
||||||
|
{
|
||||||
|
move: { name: name, img: img, description: description }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.tooltip.innerHTML = html;
|
||||||
|
options.direction = this._determineItemTooltipDirection(
|
||||||
|
element,
|
||||||
|
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
||||||
|
);
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #activateRest(element, options) {
|
||||||
|
const isShortRest = element.dataset.tooltip?.startsWith('#shortRest#');
|
||||||
|
const key = element.dataset.tooltip.slice(isShortRest ? 11 : 10);
|
||||||
const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[
|
const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[
|
||||||
element.dataset.restType
|
element.dataset.restType
|
||||||
].moves;
|
].moves;
|
||||||
const move = moves[key];
|
const move = moves[key];
|
||||||
const description = await TextEditor.enrichHTML(move.description);
|
const description = await foundry.applications.ux.TextEditor.enrichHTML(move.description);
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
|
||||||
{
|
{
|
||||||
move: move,
|
move: move,
|
||||||
|
|
@ -154,49 +231,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
element,
|
element,
|
||||||
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
||||||
);
|
);
|
||||||
}
|
return html;
|
||||||
|
|
||||||
const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#');
|
|
||||||
const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#');
|
|
||||||
if (isAdvantage || isDisadvantage) {
|
|
||||||
const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14);
|
|
||||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
|
||||||
|
|
||||||
if (actor) {
|
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
`systems/daggerheart/templates/ui/tooltip/advantage.hbs`,
|
|
||||||
{
|
|
||||||
sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deathMove = element.dataset.tooltip?.startsWith('#deathMove#');
|
|
||||||
if (deathMove) {
|
|
||||||
const name = element.dataset.deathName;
|
|
||||||
const img = element.dataset.deathImg;
|
|
||||||
const description = element.dataset.deathDescription;
|
|
||||||
|
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
`systems/daggerheart/templates/ui/tooltip/death-move.hbs`,
|
|
||||||
{
|
|
||||||
move: { name: name, img: img, description: description }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
|
||||||
options.direction = this._determineItemTooltipDirection(
|
|
||||||
element,
|
|
||||||
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.noOffset = options.noOffset;
|
|
||||||
super.activate(element, { ...options, html: html });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setAnchor(direction) {
|
_setAnchor(direction) {
|
||||||
|
|
|
||||||
|
|
@ -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++) {
|
||||||
|
|
@ -15,7 +16,7 @@ export class Migration_2_5_2 extends MigrationHandlerBase {
|
||||||
const srdEffect = srdItem?.effects.find(x => x.name === effectSource.name);
|
const srdEffect = srdItem?.effects.find(x => x.name === effectSource.name);
|
||||||
if (change.type === 'custom') {
|
if (change.type === 'custom') {
|
||||||
const srdChange = srdEffect ? srdEffect.system.changes[i] : null;
|
const srdChange = srdEffect ? srdEffect.system.changes[i] : null;
|
||||||
if (
|
if (srdChange &&
|
||||||
change.key === srdChange.key &&
|
change.key === srdChange.key &&
|
||||||
change.value === srdChange.value &&
|
change.value === srdChange.value &&
|
||||||
change.type !== srdChange.type
|
change.type !== srdChange.type
|
||||||
|
|
|
||||||
|
|
@ -391,7 +391,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "p6V4k4yMwJ1UPZMz",
|
"_id": "p6V4k4yMwJ1UPZMz",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.sRn4bqerfARvhgSV]{Minor Chaos Elemental} drawn to the echoes of violence and discord. They appear within Far range of a chosen PC and immediately take the spotlight.</p><section id=\"secret-iCbXJnMeqDXXOc8n\" class=\"secret\"><p><em>What color does the grass turn as the elemental appears? How does the chaos warp insects and small wildlife within the grove?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,9 @@
|
||||||
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
||||||
"anchorX": 0.5,
|
"anchorX": 0.5,
|
||||||
"anchorY": 0.5,
|
"anchorY": 0.5,
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"fit": "contain",
|
"fit": "contain",
|
||||||
"scaleX": 1,
|
"scaleX": 1,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"rotation": 0,
|
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"alphaThreshold": 0.75
|
"alphaThreshold": 0.75
|
||||||
},
|
},
|
||||||
|
|
@ -91,7 +88,7 @@
|
||||||
"saturation": 0,
|
"saturation": 0,
|
||||||
"contrast": 0
|
"contrast": 0
|
||||||
},
|
},
|
||||||
"detectionModes": [],
|
"detectionModes": {},
|
||||||
"occludable": {
|
"occludable": {
|
||||||
"radius": 0
|
"radius": 0
|
||||||
},
|
},
|
||||||
|
|
@ -117,7 +114,8 @@
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"randomImg": false,
|
"randomImg": false,
|
||||||
"appendNumber": false,
|
"appendNumber": false,
|
||||||
"prependAdjective": false
|
"prependAdjective": false,
|
||||||
|
"depth": 1
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -156,7 +154,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "6DKa1Pm605HpChPd",
|
"_id": "6DKa1Pm605HpChPd",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When a PC starts the ambush on unsuspecting adversaries, you lose 2 Fear and the first attack roll a PC makes has advantage.</p><section class=\"secret\" id=\"secret-WV3oLdq2IoLoSZqe\"><p><em>What are the adversaries in the middle of doing when the ambush starts? How does this impact their approach to the fight?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "r5JN5oFYL5DC6Qqw",
|
"_id": "r5JN5oFYL5DC6Qqw",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When an adversary is defeated, you can <strong>spend a Fear</strong> to have a stray attack from a siege weapon hit a point on the battlefield. All targets within Very Close range of that point must make an Agility Reaction Roll.</p><ul><li><p>Targets who fail take <strong>3d8+3</strong> physical or magic damage and must mark a Stress.</p></li><li><p>Targets who succeed must mark a Stress.</p></li></ul><section id=\"secret-qe1DM7HUSefRAplk\" class=\"secret\"><p><em>What debris is scattered by the attack? What is broken by the strike that can’t be easily mended?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,9 @@
|
||||||
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
||||||
"anchorX": 0.5,
|
"anchorX": 0.5,
|
||||||
"anchorY": 0.5,
|
"anchorY": 0.5,
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"fit": "contain",
|
"fit": "contain",
|
||||||
"scaleX": 1,
|
"scaleX": 1,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"rotation": 0,
|
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"alphaThreshold": 0.75
|
"alphaThreshold": 0.75
|
||||||
},
|
},
|
||||||
|
|
@ -101,7 +98,7 @@
|
||||||
"saturation": 0,
|
"saturation": 0,
|
||||||
"contrast": 0
|
"contrast": 0
|
||||||
},
|
},
|
||||||
"detectionModes": [],
|
"detectionModes": {},
|
||||||
"occludable": {
|
"occludable": {
|
||||||
"radius": 0
|
"radius": 0
|
||||||
},
|
},
|
||||||
|
|
@ -127,7 +124,8 @@
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"randomImg": false,
|
"randomImg": false,
|
||||||
"appendNumber": false,
|
"appendNumber": false,
|
||||||
"prependAdjective": false
|
"prependAdjective": false,
|
||||||
|
"depth": 1
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -204,7 +202,7 @@
|
||||||
"type": "damage",
|
"type": "damage",
|
||||||
"_id": "p1UiGEiGyl6r7PrA",
|
"_id": "p1UiGEiGyl6r7PrA",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Previous climbers left behind large metal rods that climbers can use to aid their ascent. If a PC using the pitons fails an action roll to climb, they can <strong>mark a Stress</strong> instead of ticking the countdown up.</p><section class=\"secret\" id=\"secret-fWHkzBQAxCLDmV6H\"><p><em>What do the shape and material of these pitons tell you about the previous climbers? How far apart are they from one another?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -435,7 +433,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "M8MfD2qBfYCwNKvH",
|
"_id": "M8MfD2qBfYCwNKvH",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Spend a Fear to have a PC’s handhold fail, plummeting them toward the ground. If they aren’t saved on the next action, they hit the ground and tick up the countdown by 2. The PC takes <strong>1d12</strong> physical damage if the countdown is between 8 and 12, <strong>2d12</strong> between 4 and 7, and <strong>3d12</strong> at 3 or lower.</p><section class=\"secret\" id=\"secret-ufZ6ifPWxU4MkKow\"><p><em>How can you tell many others have fallen here before? What lives in these walls that might try to scare adventurers into falling for an easy meal?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,9 @@
|
||||||
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
||||||
"anchorX": 0.5,
|
"anchorX": 0.5,
|
||||||
"anchorY": 0.5,
|
"anchorY": 0.5,
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"fit": "contain",
|
"fit": "contain",
|
||||||
"scaleX": 1,
|
"scaleX": 1,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"rotation": 0,
|
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"alphaThreshold": 0.75
|
"alphaThreshold": 0.75
|
||||||
},
|
},
|
||||||
|
|
@ -101,7 +98,7 @@
|
||||||
"saturation": 0,
|
"saturation": 0,
|
||||||
"contrast": 0
|
"contrast": 0
|
||||||
},
|
},
|
||||||
"detectionModes": [],
|
"detectionModes": {},
|
||||||
"occludable": {
|
"occludable": {
|
||||||
"radius": 0
|
"radius": 0
|
||||||
},
|
},
|
||||||
|
|
@ -127,7 +124,8 @@
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"randomImg": false,
|
"randomImg": false,
|
||||||
"appendNumber": false,
|
"appendNumber": false,
|
||||||
"prependAdjective": false
|
"prependAdjective": false,
|
||||||
|
"depth": 1
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -236,7 +234,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "EATw4ZkcuGeDfgLZ",
|
"_id": "EATw4ZkcuGeDfgLZ",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>A portion of the ritual’s power is diverted into a cult member to fight off interlopers. Choose one adversary to become Imbued with terrible magic until the scene ends or they’re defeated. An Imbued adversary immediately takes the spotlight and gains one of the following benefits, or all three if you <strong>spend a Fear</strong>:</p><ul><li><p>They gain advantage on all attacks.</p></li><li><p>They deal an extra <strong>1d10</strong> damage on a successful attack.</p></li><li><p>They gain the following feature: <strong>Relentless (2) - Passive</strong>. This adversary can be spotlighted up to two times per GM turn. Spend Fear as usual to spotlight them.</p></li></ul><section class=\"secret\" id=\"secret-5wFIFufzFAPrIDaq\"><p><em>How does the enemy change in appearance? What fears do their blows bring to the surface?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -283,13 +281,10 @@
|
||||||
},
|
},
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"value": null,
|
||||||
"combat": null,
|
"units": "seconds",
|
||||||
"seconds": null,
|
"expiry": null,
|
||||||
"rounds": null,
|
"expired": false
|
||||||
"turns": null,
|
|
||||||
"startRound": null,
|
|
||||||
"startTurn": null
|
|
||||||
},
|
},
|
||||||
"description": "<p>An Imbued adversary immediately takes the spotlight and gains one of the following benefits, or all three if you <strong>spend a Fear</strong>:</p><ul><li><p>They gain advantage on all attacks.</p></li><li><p>They deal an extra <strong>1d10</strong> damage on a successful attack.</p></li><li><p>They gain the following feature: <strong>Relentless (2) - Passive</strong>. This adversary can be spotlighted up to two times per GM turn. Spend Fear as usual to spotlight them.</p></li></ul><section class=\"secret\" id=\"secret-YOHA68Na64uXDAxu\"><p><em>How does the enemy change in appearance? What fears do their blows bring to the surface?</em></p></section>",
|
"description": "<p>An Imbued adversary immediately takes the spotlight and gains one of the following benefits, or all three if you <strong>spend a Fear</strong>:</p><ul><li><p>They gain advantage on all attacks.</p></li><li><p>They deal an extra <strong>1d10</strong> damage on a successful attack.</p></li><li><p>They gain the following feature: <strong>Relentless (2) - Passive</strong>. This adversary can be spotlighted up to two times per GM turn. Spend Fear as usual to spotlight them.</p></li></ul><section class=\"secret\" id=\"secret-YOHA68Na64uXDAxu\"><p><em>How does the enemy change in appearance? What fears do their blows bring to the surface?</em></p></section>",
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
|
|
@ -299,6 +294,9 @@
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null
|
"compendiumSource": null
|
||||||
},
|
},
|
||||||
|
"start": null,
|
||||||
|
"showIcon": 1,
|
||||||
|
"folder": null,
|
||||||
"_key": "!actors.items.effects!QAXXiOKBDmCTauHD.0Rgqw1kUPeJ11ldd.dYQBQq1xIysM0qLo"
|
"_key": "!actors.items.effects!QAXXiOKBDmCTauHD.0Rgqw1kUPeJ11ldd.dYQBQq1xIysM0qLo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -326,13 +324,10 @@
|
||||||
},
|
},
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"value": null,
|
||||||
"combat": null,
|
"units": "seconds",
|
||||||
"seconds": null,
|
"expiry": null,
|
||||||
"rounds": null,
|
"expired": false
|
||||||
"turns": null,
|
|
||||||
"startRound": null,
|
|
||||||
"startTurn": null
|
|
||||||
},
|
},
|
||||||
"description": "",
|
"description": "",
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
|
|
@ -342,6 +337,9 @@
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null
|
"compendiumSource": null
|
||||||
},
|
},
|
||||||
|
"start": null,
|
||||||
|
"showIcon": 1,
|
||||||
|
"folder": null,
|
||||||
"_key": "!actors.items.effects!QAXXiOKBDmCTauHD.0Rgqw1kUPeJ11ldd.Hxw5lXE77bGzuaOu"
|
"_key": "!actors.items.effects!QAXXiOKBDmCTauHD.0Rgqw1kUPeJ11ldd.Hxw5lXE77bGzuaOu"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,9 @@
|
||||||
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
||||||
"anchorX": 0.5,
|
"anchorX": 0.5,
|
||||||
"anchorY": 0.5,
|
"anchorY": 0.5,
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"fit": "contain",
|
"fit": "contain",
|
||||||
"scaleX": 1,
|
"scaleX": 1,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"rotation": 0,
|
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"alphaThreshold": 0.75
|
"alphaThreshold": 0.75
|
||||||
},
|
},
|
||||||
|
|
@ -100,7 +97,7 @@
|
||||||
"saturation": 0,
|
"saturation": 0,
|
||||||
"contrast": 0
|
"contrast": 0
|
||||||
},
|
},
|
||||||
"detectionModes": [],
|
"detectionModes": {},
|
||||||
"occludable": {
|
"occludable": {
|
||||||
"radius": 0
|
"radius": 0
|
||||||
},
|
},
|
||||||
|
|
@ -126,7 +123,8 @@
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"randomImg": false,
|
"randomImg": false,
|
||||||
"appendNumber": false,
|
"appendNumber": false,
|
||||||
"prependAdjective": false
|
"prependAdjective": false,
|
||||||
|
"depth": 1
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +138,7 @@
|
||||||
"type": "healing",
|
"type": "healing",
|
||||||
"_id": "uLCoTKa7Jn2HaRqR",
|
"_id": "uLCoTKa7Jn2HaRqR",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>A PC who takes a rest in the Hallowed Temple automatically clears all HP.</p><section class=\"secret\" id=\"secret-vN4UFTYIQW8nd2mC\"><p><em>What does the incense smell like? What kinds of songs do the acolytes sing?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -337,7 +335,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "pJVipg7CbA9CB0Um",
|
"_id": "pJVipg7CbA9CB0Um",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When the PCs have trespassed, blasphemed, or offended the clergy, you can <strong>spend a Fear</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.r1mbfSSwKWdcFdAU]{High Seraph} and [[/r 1d4]] @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guard} within Close range of the senior priest to reinforce their will.</p><section class=\"secret\" id=\"secret-tG902NbtYpyK0NFi\"><p><em>What symbols or icons do they bear that signal they are anointed agents of the divinity? Who leads the group and what led them to this calling?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,9 @@
|
||||||
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
||||||
"anchorX": 0.5,
|
"anchorX": 0.5,
|
||||||
"anchorY": 0.5,
|
"anchorY": 0.5,
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"fit": "contain",
|
"fit": "contain",
|
||||||
"scaleX": 1,
|
"scaleX": 1,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"rotation": 0,
|
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"alphaThreshold": 0.75
|
"alphaThreshold": 0.75
|
||||||
},
|
},
|
||||||
|
|
@ -100,7 +97,7 @@
|
||||||
"saturation": 0,
|
"saturation": 0,
|
||||||
"contrast": 0
|
"contrast": 0
|
||||||
},
|
},
|
||||||
"detectionModes": [],
|
"detectionModes": {},
|
||||||
"occludable": {
|
"occludable": {
|
||||||
"radius": 0
|
"radius": 0
|
||||||
},
|
},
|
||||||
|
|
@ -126,7 +123,8 @@
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"randomImg": false,
|
"randomImg": false,
|
||||||
"appendNumber": false,
|
"appendNumber": false,
|
||||||
"prependAdjective": false
|
"prependAdjective": false,
|
||||||
|
"depth": 1
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -282,7 +280,7 @@
|
||||||
"type": "countdown",
|
"type": "countdown",
|
||||||
"_id": "VhqZKDA4032i8zY3",
|
"_id": "VhqZKDA4032i8zY3",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to manifest the echo of a past disaster that ravaged the city. Activate a <em>Progress Countdown (5)</em> as the disaster replays around the PCs. To complete the countdown and escape the catastrophe, the PCs must overcome threats such as rampaging fires, stampeding civilians, collapsing buildings, or crumbling streets, while recalling history and finding clues to escape the inevitable.</p><section class=\"secret\" id=\"secret-2Ou4kbqfCwCgsG5X\"><p><em>Is this the disaster that led the city to be abandoned? What is known about this disaster and how could that help the PCs escape?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"originItem": {
|
"originItem": {
|
||||||
"type": "itemCollection"
|
"type": "itemCollection"
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,9 @@
|
||||||
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
|
||||||
"anchorX": 0.5,
|
"anchorX": 0.5,
|
||||||
"anchorY": 0.5,
|
"anchorY": 0.5,
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"fit": "contain",
|
"fit": "contain",
|
||||||
"scaleX": 1,
|
"scaleX": 1,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"rotation": 0,
|
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"alphaThreshold": 0.75
|
"alphaThreshold": 0.75
|
||||||
},
|
},
|
||||||
|
|
@ -102,7 +99,7 @@
|
||||||
"saturation": 0,
|
"saturation": 0,
|
||||||
"contrast": 0
|
"contrast": 0
|
||||||
},
|
},
|
||||||
"detectionModes": [],
|
"detectionModes": {},
|
||||||
"occludable": {
|
"occludable": {
|
||||||
"radius": 0
|
"radius": 0
|
||||||
},
|
},
|
||||||
|
|
@ -128,7 +125,8 @@
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"randomImg": false,
|
"randomImg": false,
|
||||||
"appendNumber": false,
|
"appendNumber": false,
|
||||||
"prependAdjective": false
|
"prependAdjective": false,
|
||||||
|
"depth": 1
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -297,7 +295,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "9ipckCFMz9DVw8ab",
|
"_id": "9ipckCFMz9DVw8ab",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to tick down a long-term countdown related to the empire’s agenda by [[/r 1d4]]. If this triggers the countdown, a proclamation related to the agenda is announced at court as the plan is executed.</p><section class=\"secret\" id=\"secret-ThoGDjazxfPoUvJS\"><p><em>What display of power or transfer of wealth was needed to expedite this plan? Whose lives were disrupted or upended to make this happen?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@
|
||||||
"type": "damage",
|
"type": "damage",
|
||||||
"_id": "jVY198vniaTSlgsX",
|
"_id": "jVY198vniaTSlgsX",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>A feature or action that clears HP requires spending a Hope to use. If it already costs Hope, a PC must spend an additional Hope.</p><section class=\"secret\" id=\"secret-Ls4rMO6N9zzuR2Ib\"><p><em>What does it feel like to try to heal in a place so antithetical to life?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -240,7 +240,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "M1mOwi4Limw2hRwL",
|
"_id": "M1mOwi4Limw2hRwL",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>All targets within Close range of a point you choose in this environment must succeed on an Agility Reaction Roll or take <strong>4d8+8</strong> physical damage from skeletal shrapnel as part of the ossuary detonates around them.</p><section id=\"secret-xjKFOtdFFWAgx2Cv\" class=\"secret\"><p><em>What ancient skeletal architecture is destroyed? What bones stick in your armor?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -421,7 +421,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "hFeTdiHWeCYkb8Hg",
|
"_id": "hFeTdiHWeCYkb8Hg",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to summon [[/r 1d6]] @UUID[Compendium.daggerheart.adversaries.Actor.gP3fWTLzSFnpA8EJ]{Rotted Zombie}, two @UUID[Compendium.daggerheart.adversaries.Actor.CP6iRfHdyFWniTHY]{Perfected Zombie}, or a @UUID[Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp]{Zombie Legion}, who appear at Close range of a chosen PC.</p><section class=\"secret\" id=\"secret-Du2785Ih29RVrHpG\"><p><em>Who were these people before they became the necromancer’s pawns? What vestiges of those lives remain for the heroes to see?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "1giAFbu3tGqXwi8g",
|
"_id": "1giAFbu3tGqXwi8g",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> as a mage from one side uses large-scale destructive magic. Pick a point on the battlefield within Very Far range of the mage. All targets within Close range of that point must make an Agility Reaction Roll. Targets who fail take <strong>3d12+8</strong> magic damage and must mark a Stress.</p><section id=\"secret-jbI9lwrzP59CTRUt\" class=\"secret\"><p><em>What form does the attack take—fireball raining acid a storm of blades? What tactical objective is this attack meant to accomplish and what comes next?</em></p></section>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@
|
||||||
.chat-log .chat-message {
|
.chat-log .chat-message {
|
||||||
background-image: url('../assets/parchments/dh-parchment-light.png');
|
background-image: url('../assets/parchments/dh-parchment-light.png');
|
||||||
|
|
||||||
.message-header .message-header-metadata .message-metadata,
|
.message-header .message-header-main .message-metadata,
|
||||||
.message-header .message-header-main .message-sub-header-container {
|
.message-header .message-header-main .name {
|
||||||
color: @dark;
|
color: @dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-header .message-header-main .message-sub-header-container h4 {
|
.message-header .message-header-main h4 {
|
||||||
color: @dark-blue;
|
color: @dark-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,27 +42,12 @@
|
||||||
|
|
||||||
.message-header {
|
.message-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
.message-header-metadata {
|
|
||||||
flex: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.message-metadata {
|
|
||||||
font-family: @font-body;
|
|
||||||
color: @beige;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-header-main {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex: 1;
|
padding: 8px;
|
||||||
overflow: hidden;
|
align-items: center;
|
||||||
|
|
||||||
|
.portrait {
|
||||||
|
flex: 0 0 auto;
|
||||||
.actor-img {
|
.actor-img {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
|
@ -70,13 +55,20 @@
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: top center;
|
object-position: top center;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message-sub-header-container {
|
.message-header-main {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
justify-content: space-between;
|
column-gap: 4px;
|
||||||
color: @beige;
|
row-gap: 2px;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template:
|
||||||
|
"title metadata"
|
||||||
|
"subtitle subtitle";
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: var(--font-size-16);
|
font-size: var(--font-size-16);
|
||||||
|
|
@ -84,6 +76,39 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-family: @font-subtitle;
|
font-family: @font-subtitle;
|
||||||
color: @golden;
|
color: @golden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-metadata {
|
||||||
|
font-family: @font-body;
|
||||||
|
color: @beige;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-items: baseline;
|
||||||
|
.message-timestamp {
|
||||||
|
font-size: var(--font-size-11);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
grid-area: subtitle;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: @beige;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: baseline;
|
||||||
|
line-height: 1;
|
||||||
|
.name {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.whisper-to {
|
||||||
|
color: @color-text-subtle;
|
||||||
|
flex: 0;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -595,7 +595,86 @@
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
color: light-dark(#14142599, #efe6d850);
|
color: light-dark(#14142599, #efe6d850);
|
||||||
font-size: var(--font-size-12);
|
font-size: var(--font-size-12);
|
||||||
padding-left: 3px;
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.gm-notes-section {
|
||||||
|
padding-bottom: var(--spacer-4);
|
||||||
|
header.gm-notes + p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header.gm-notes {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: " ";
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 1px solid var(--color-dark-6);
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
mask-image: linear-gradient(270deg, black 0%, black calc(100% - 10px), transparent 100%);
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
mask-image: linear-gradient(270deg, transparent 0%, black 10px, black 100%);
|
||||||
|
}
|
||||||
|
margin-top: var(--spacer-8);
|
||||||
|
margin-bottom: var(--spacer-4);
|
||||||
|
font-size: var(--font-size-11);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
secret-block {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
/** A buffer to make the hover behavior work a bit better. The bottom in the button needs to compensate */
|
||||||
|
@buffer: 8px;
|
||||||
|
margin-top: -@buffer;
|
||||||
|
padding-top: @buffer;
|
||||||
|
|
||||||
|
button.reveal {
|
||||||
|
--button-size: 1rem;
|
||||||
|
height: var(--button-size);
|
||||||
|
position: absolute;
|
||||||
|
margin: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: min-content;
|
||||||
|
padding: 1px 8px 0 8px;
|
||||||
|
bottom: calc(100% - 0.4375rem - 1px);
|
||||||
|
|
||||||
|
background-color: var(--dh-window-button-color-bg); // todo: find a better var name
|
||||||
|
border-color: var(--color-secret-border);
|
||||||
|
color: var(--dh-window-button-color-text);
|
||||||
|
font-size: var(--font-size-10);
|
||||||
|
user-select: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover button.reveal {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element inside a secret-block.
|
||||||
|
* This is separate since during prosemirror editing, the secret-block container does not exist.
|
||||||
|
*/
|
||||||
|
section.secret {
|
||||||
|
--color-secret-bg: @red-10;
|
||||||
|
--color-revealed-bg: @green-10;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -817,4 +896,8 @@
|
||||||
right: 2px;
|
right: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gm-notes {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
.sheet.daggerheart.dh-style.item {
|
.sheet.daggerheart.dh-style.item {
|
||||||
.tab.features {
|
.tab.features {
|
||||||
padding: 0 10px;
|
padding: 7px 10px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
.feature-list {
|
.feature-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -111,3 +111,7 @@ body.theme-light,
|
||||||
.themed.theme-light {
|
.themed.theme-light {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body:not([data-gm=true]) [data-visibility="gm"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -163,37 +163,7 @@
|
||||||
}
|
}
|
||||||
.inventory-description {
|
.inventory-description {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
.typography();
|
||||||
h1 {
|
|
||||||
font-size: var(--font-size-32);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: var(--font-size-28);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: var(--font-size-20);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-size: var(--font-size-16);
|
|
||||||
color: @beige;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin: 1rem 0;
|
|
||||||
padding: 0 0 0 1.25rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: disc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.item-resources {
|
.item-resources {
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
.application.sheet.daggerheart.dh-style {
|
.application.sheet.daggerheart.dh-style {
|
||||||
|
--portrait-size: 150px;
|
||||||
|
|
||||||
.item-sheet-header {
|
.item-sheet-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.profile {
|
.profile {
|
||||||
height: 150px;
|
height: var(--portrait-size);
|
||||||
width: 150px;
|
width: var(--portrait-size);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-right: 1px solid light-dark(@dark-blue, @golden);
|
border-right: 1px solid light-dark(@dark-blue, @golden);
|
||||||
border-bottom: 1px solid light-dark(@dark-blue, @golden);
|
border-bottom: 1px solid light-dark(@dark-blue, @golden);
|
||||||
|
|
@ -34,14 +36,18 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
|
||||||
.item-name input[type='text'] {
|
.item-name {
|
||||||
font-size: var(--font-size-32);
|
display: flex;
|
||||||
height: 42px;
|
flex-direction: column;
|
||||||
|
margin: 10px 10px 0 10px;
|
||||||
|
input[type='text'] {
|
||||||
|
font-size: var(--font-size-30);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 90%;
|
width: 100%;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
&:hover[type='text'],
|
&:hover[type='text'],
|
||||||
&:focus[type='text'] {
|
&:focus[type='text'] {
|
||||||
|
|
@ -49,6 +55,7 @@
|
||||||
outline: 2px solid light-dark(@dark-blue, @golden);
|
outline: 2px solid light-dark(@dark-blue, @golden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-description {
|
.item-description {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -14,36 +14,7 @@
|
||||||
}
|
}
|
||||||
.editor-content {
|
.editor-content {
|
||||||
.with-scroll-shadows();
|
.with-scroll-shadows();
|
||||||
h1 {
|
.typography();
|
||||||
font-size: var(--font-size-32);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: var(--font-size-28);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: var(--font-size-20);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-size: var(--font-size-16);
|
|
||||||
color: light-dark(@dark, @beige);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin: 1rem 0;
|
|
||||||
padding: 0 0 0 1.25rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: disc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Fixes centering and makes it not render over scrollbar
|
// Fixes centering and makes it not render over scrollbar
|
||||||
&:hover button.toggle:enabled {
|
&:hover button.toggle:enabled {
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ body.game:is(.performance-low, .noblur) {
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: light-dark(#e8e6e3, @deep-black);
|
background: var(--dh-window-button-color-bg);
|
||||||
color: light-dark(@dark-blue, @beige);
|
color: var(--dh-window-button-color-text);
|
||||||
border: 1px solid light-dark(@dark-blue, transparent);
|
border: 1px solid light-dark(@dark-blue, transparent);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,80 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: hidden !important;
|
overflow: hidden;
|
||||||
padding-top: 10px;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
prose-mirror.active + .artist-attribution {
|
.description-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 12px 16px 4px 16px;
|
||||||
|
.with-scroll-shadows();
|
||||||
|
prose-mirror {
|
||||||
|
button.toggle {
|
||||||
|
top: 0px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
button[data-action=editGMNote] {
|
||||||
|
right: calc(var(--button-size) + 4px);
|
||||||
|
}
|
||||||
|
&.inactive {
|
||||||
|
height: unset!important;
|
||||||
|
overflow: unset;
|
||||||
|
.editor-content {
|
||||||
|
position: relative;
|
||||||
|
overflow: unset;
|
||||||
|
|
||||||
|
// Allows content links to peek out
|
||||||
|
margin-top: -4px;
|
||||||
|
padding: 4px 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
--min-height: 250px;
|
||||||
|
padding: 8px 0 0 16px;
|
||||||
|
button[data-action=editGMNote] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.editor-content {
|
||||||
|
padding-right: 16px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Hide editors that are empty when inactive if we need them to be */
|
||||||
|
prose-mirror.inactive.hide-if-inactive {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:has(prose-mirror.active) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
/** Description should fill available room (with overriden exceptions) */
|
||||||
|
prose-mirror[name="system.description"] {
|
||||||
|
flex: 1 0;
|
||||||
|
}
|
||||||
|
&:has(prose-mirror[name="system.gmNotes"]:not(.hide-if-inactive)) {
|
||||||
|
prose-mirror.inactive {
|
||||||
|
--min-height: 3rem;
|
||||||
|
&[name="system.description"] {
|
||||||
|
flex: 0 0;
|
||||||
|
}
|
||||||
|
&[name="system.gmNotes"] {
|
||||||
|
flex: 1 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hide other elements if an editor is open */
|
||||||
|
&:has(prose-mirror.active) {
|
||||||
|
prose-mirror.inactive,
|
||||||
|
header.gm-notes,
|
||||||
|
.artist-attribution {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,6 @@
|
||||||
padding: 8px 0 0 16px;
|
padding: 8px 0 0 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-attribution {
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-section {
|
.search-section {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
.application.sheet.daggerheart.dh-style.beastform {
|
.application.sheet.daggerheart.dh-style.beastform {
|
||||||
|
--portrait-size: 130px;
|
||||||
|
|
||||||
.settings.tab {
|
.settings.tab {
|
||||||
.advantage-on-section {
|
.advantage-on-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -9,4 +11,11 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tab.features.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px;
|
||||||
|
.stable-scroll-container();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,9 @@
|
||||||
@import '../../utils/fonts.less';
|
@import '../../utils/fonts.less';
|
||||||
|
|
||||||
.application.sheet.daggerheart.dh-style.feature {
|
.application.sheet.daggerheart.dh-style.feature {
|
||||||
.item-sheet-header {
|
--portrait-size: 130px;
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.profile {
|
|
||||||
height: 130px;
|
|
||||||
width: 130px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
section.tab {
|
section.tab {
|
||||||
height: 400px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
@import './item-sheet-shared.less';
|
||||||
@import './beastform.less';
|
@import './beastform.less';
|
||||||
@import './class.less';
|
@import './class.less';
|
||||||
@import './domain-card.less';
|
@import './domain-card.less';
|
||||||
@import './feature.less';
|
@import './feature.less';
|
||||||
@import './heritage.less';
|
@import './heritage.less';
|
||||||
@import './item-sheet-shared.less';
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.application.sheet.daggerheart.dh-style.item {
|
.item.daggerheart.dh-style:where(.application.sheet) {
|
||||||
&.minimized {
|
&.minimized {
|
||||||
.attribution-header-label {
|
.attribution-header-label {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -14,4 +14,22 @@
|
||||||
button.plain.inline-control {
|
button.plain.inline-control {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-navigation {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default tab stylings */
|
||||||
|
.tab.active {
|
||||||
|
padding-top: 8px;
|
||||||
|
.with-scroll-shadows();
|
||||||
|
|
||||||
|
&.effects {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px;
|
||||||
|
.stable-scroll-container();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
padding: 8px;
|
padding: 0;
|
||||||
|
margin: 8px;
|
||||||
|
.typography();
|
||||||
}
|
}
|
||||||
|
|
||||||
.ability-card-footer {
|
.ability-card-footer {
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,8 @@
|
||||||
--dh-input-color-text: @dark;
|
--dh-input-color-text: @dark;
|
||||||
--dh-trait-color-bg: #b1afb6;
|
--dh-trait-color-bg: #b1afb6;
|
||||||
--dh-trait-color-border: #8e8d96;
|
--dh-trait-color-border: #8e8d96;
|
||||||
|
--dh-window-button-color-bg: #e8e6e3;
|
||||||
|
--dh-window-button-color-text: @dark-blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@scope (.theme-dark) to (.themed) {
|
@scope (.theme-dark) to (.themed) {
|
||||||
|
|
@ -124,6 +126,8 @@
|
||||||
--dh-input-color-text: @beige;
|
--dh-input-color-text: @beige;
|
||||||
--dh-trait-color-bg: #50433F;
|
--dh-trait-color-bg: #50433F;
|
||||||
--dh-trait-color-border: #927952;
|
--dh-trait-color-border: #927952;
|
||||||
|
--dh-window-button-color-bg: @deep-black;
|
||||||
|
--dh-window-button-color-text: @beige;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -204,3 +204,37 @@
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
.with-scroll-shadows();
|
.with-scroll-shadows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Typography stylings for most longform text, usually item descriptions */
|
||||||
|
.typography() {
|
||||||
|
h1 {
|
||||||
|
font-size: var(--font-size-32);
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: var(--font-size-28);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: var(--font-size-20);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: var(--font-size-16);
|
||||||
|
color: light-dark(@dark, @beige);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0 0 0 1.25rem;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
24
system.json
24
system.json
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "daggerheart",
|
"id": "daggerheart",
|
||||||
"title": "Daggerheart",
|
"title": "Daggerheart",
|
||||||
"description": "An unofficial implementation of the Daggerheart system",
|
"description": "An unofficial implementation of the Daggerheart system",
|
||||||
"version": "2.5.2",
|
"version": "2.5.4",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "14.364",
|
"minimum": "14.364",
|
||||||
"verified": "14.364",
|
"verified": "14.364",
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"url": "https://github.com/Foundryborne/daggerheart",
|
"url": "https://github.com/Foundryborne/daggerheart",
|
||||||
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
|
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
|
||||||
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.5.2/system.zip",
|
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.5.4/system.zip",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "WBHarry"
|
"name": "WBHarry"
|
||||||
|
|
@ -256,34 +256,34 @@
|
||||||
},
|
},
|
||||||
"Item": {
|
"Item": {
|
||||||
"ancestry": {
|
"ancestry": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"community": {
|
"community": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"class": {
|
"class": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"subclass": {
|
"subclass": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"feature": {
|
"feature": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"domainCard": {
|
"domainCard": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"loot": {
|
"loot": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"consumable": {
|
"consumable": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"weapon": {
|
"weapon": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"armor": {
|
"armor": {
|
||||||
"htmlFields": ["description"]
|
"htmlFields": ["description", "gmNotes"]
|
||||||
},
|
},
|
||||||
"beastform": {}
|
"beastform": {}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
<legend class="with-icon">
|
||||||
|
{{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=@root.source.damage.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}}
|
||||||
|
</fieldset>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#unless (eq path 'system.attack.')}}
|
||||||
|
{{! In the future, consider allowing this even on NPCs}}
|
||||||
<fieldset class="one-column">
|
<fieldset class="one-column">
|
||||||
<legend class="with-icon">
|
<legend class="with-icon">
|
||||||
{{#if (eq @root.source.type 'healing')}}
|
{{#if (eq @root.source.type 'healing')}}
|
||||||
{{localize "DAGGERHEART.GENERAL.healing"}}
|
{{localize "DAGGERHEART.GENERAL.healing"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{localize "DAGGERHEART.GENERAL.damage"}}
|
{{localize "DAGGERHEART.ACTIONS.Config.damage.markResources"}}
|
||||||
{{/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}}
|
{{#unless @root.allDamageTypesUsed}}<a data-action="addDamageResource"><i class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
|
||||||
</legend>
|
</legend>
|
||||||
|
{{#each source.resources as |dmg key|}}
|
||||||
<div class="nest-inputs">
|
<div class="nest-inputs">
|
||||||
{{#if @root.hasBaseDamage}}
|
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column">
|
||||||
{{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 --}}
|
|
||||||
{{#each source.parts as |dmg key|}}
|
|
||||||
<div class="nest-inputs">
|
|
||||||
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column{{#if ../path}} no-style{{/if}}">
|
|
||||||
<legend class="with-icon">
|
<legend class="with-icon">
|
||||||
{{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}}
|
{{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}}
|
||||||
{{#unless (or dmg.base ../path)}}
|
{{#unless (or dmg.base ../path)}}
|
||||||
<a data-action="removeDamage" data-key="{{dmg.applyTo}}"><i class="fas fa-trash"></i></a>
|
<a data-action="removeDamageResource" data-key="{{key}}"><i class="fas fa-trash"></i></a>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</legend>
|
</legend>
|
||||||
|
{{> damageData damage=dmg fields=../fields.resources.element.fields basePath=(concat ../path "damage.resources." dmg.applyTo)}}
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}}
|
|
||||||
{{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
|
|
||||||
<div class="nest-inputs">
|
|
||||||
<fieldset class="one-column">
|
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}</legend>
|
|
||||||
{{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}}
|
|
||||||
</fieldset>
|
|
||||||
<fieldset class="one-column">
|
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}</legend>
|
|
||||||
{{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path}}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
{{> 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>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
{{#*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}}
|
||||||
|
|
||||||
|
{{#*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}}
|
||||||
|
|
@ -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,7 +1,15 @@
|
||||||
{{#each damage.types as |roll 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>{{roll.total}}</span>
|
<span>{{roll.total}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -9,7 +17,7 @@
|
||||||
{{#each roll.dice}}
|
{{#each roll.dice}}
|
||||||
{{#each results as |result index|}}
|
{{#each results as |result index|}}
|
||||||
{{#if result.active}}
|
{{#if result.active}}
|
||||||
<a class="roll-dice" data-action="rerollDamageDice" data-member-key="{{../../../key}}" data-damage-key="{{@../../key}}" data-dice-index="{{@../key}}" data-result-index="{{index}}">
|
<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}}">
|
||||||
<span class="dice-label">{{result.result}}</span>
|
<span class="dice-label">{{result.result}}</span>
|
||||||
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" ../denomination ".svg"}}" />
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" ../denomination ".svg"}}" />
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -25,4 +33,4 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/inline}}
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -3,8 +3,22 @@
|
||||||
data-tab='{{tabs.description.id}}'
|
data-tab='{{tabs.description.id}}'
|
||||||
data-group='{{tabs.description.group}}'
|
data-group='{{tabs.description.group}}'
|
||||||
>
|
>
|
||||||
|
<div class="description-section">
|
||||||
{{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}}
|
{{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}}
|
||||||
|
{{#if (and systemFields.gmNotes @root.user.isGM)}}
|
||||||
|
<section class="gm-notes-section">
|
||||||
|
{{#if enrichedGMNotes}}
|
||||||
|
<header class="gm-notes">{{localize "DAGGERHEART.ITEMS.FIELDS.gmNotes.label"}}</header>
|
||||||
|
{{/if}}
|
||||||
|
<prose-mirror
|
||||||
|
name="system.gmNotes"
|
||||||
|
{{#unless enrichedGMNotes}}class="hide-if-inactive"{{/unless}}
|
||||||
|
toggled="true"
|
||||||
|
value="{{document.system.gmNotes}}"
|
||||||
|
>{{{enrichedGMNotes}}}</prose-mirror>
|
||||||
|
</section>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
{{#if (and showAttribution document.system.attribution.artist)}}
|
{{#if (and showAttribution document.system.attribution.artist)}}
|
||||||
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>
|
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
||||||
|
|
@ -18,25 +18,25 @@
|
||||||
</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>
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@
|
||||||
{{#each action.summon}}
|
{{#each action.summon}}
|
||||||
<div class="summon-container">
|
<div class="summon-container">
|
||||||
<div class="summon-label-container">
|
<div class="summon-label-container">
|
||||||
<img src="{{this.actor.img}}" />
|
<img src="{{this.data.img}}" />
|
||||||
<label>{{this.actor.name}}</label>
|
<label>{{this.data.name}}</label>
|
||||||
</div>
|
</div>
|
||||||
<span># {{this.rolledCount}}</span>
|
<span># {{this.quantity}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,18 @@
|
||||||
<li class="chat-message message flexcol {{cssClass}}" data-message-id="{{message._id}}"
|
<li class="chat-message message flexcol {{cssClass}}" data-message-id="{{message._id}}"
|
||||||
{{#if borderColor}}style="border-color:{{borderColor}}"{{/if}}>
|
{{#if borderColor}}style="border-color:{{borderColor}}"{{/if}}>
|
||||||
<header class="message-header flexrow">
|
<header class="message-header flexrow">
|
||||||
<div class="message-header-main">
|
<div class="portrait">
|
||||||
<img class="actor-img" src="{{actor.img}}" />
|
<img class="actor-img" src="{{actor.img}}" />
|
||||||
<div class="message-sub-header-container">
|
</div>
|
||||||
|
<div class="message-header-main">
|
||||||
{{#if message.title}}
|
{{#if message.title}}
|
||||||
<h4>{{message.title}}</h4>
|
<h4>{{message.title}}</h4>
|
||||||
<div>{{alias}} {{#if author.isGM}}(GM){{/if}}</div>
|
{{else if (not actor.name)}}
|
||||||
{{else}}
|
|
||||||
{{#unless actor.name}}
|
|
||||||
<h4>{{author.name}}</h4>
|
<h4>{{author.name}}</h4>
|
||||||
{{else}}
|
{{else}}
|
||||||
<h4>{{alias}}</h4>
|
<h4>{{alias}}</h4>
|
||||||
<div>{{author.name}}</div>
|
|
||||||
{{/unless}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="message-header-metadata">
|
|
||||||
<span class="message-metadata">
|
<span class="message-metadata">
|
||||||
<time class="message-timestamp">{{timeSince message.timestamp}}</time>
|
<time class="message-timestamp">{{timeSince message.timestamp}}</time>
|
||||||
{{#if canDelete}}
|
{{#if canDelete}}
|
||||||
|
|
@ -32,10 +27,20 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<div class="subtitle">
|
||||||
|
<div class="name">
|
||||||
|
{{#if message.title}}
|
||||||
|
{{alias}} {{#if author.isGM}}(GM){{/if}}
|
||||||
|
{{else if (not actor.name)}}
|
||||||
|
{{author.name}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#if isWhisper}}
|
{{#if isWhisper}}
|
||||||
<span class="whisper-to">{{localize 'CHAT.To'}}: {{whisperTo}}</span>
|
<span class="whisper-to">{{localize 'CHAT.To'}}: {{whisperTo}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
{{{message.content}}}
|
{{{message.content}}}
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,43 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="roll-part-extra on-reduced">
|
<div class="roll-part-extra on-reduced">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{{#each damage.types as | roll index | }}
|
{{#if damage.main}}
|
||||||
<div class="roll-formula">{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}: {{roll.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.types as | roll index | }}
|
{{#if damage.main}}
|
||||||
|
{{> damage label=(localize "DAGGERHEART.GENERAL.damage") roll=damage.main isDirect=isDirect }}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#each damage.resources as | roll index | }}
|
||||||
|
{{> damage
|
||||||
|
label=(ifThen ../hasHealing (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')) (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')))
|
||||||
|
roll=roll
|
||||||
|
isResource=true
|
||||||
|
}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#*inline "formula"}}
|
||||||
|
<div class="roll-formula">{{label}}: {{roll.total}}</div>
|
||||||
|
{{/inline}}
|
||||||
|
|
||||||
|
{{#*inline "damage"}}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
{{#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}}
|
{{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>
|
</legend>
|
||||||
|
|
||||||
{{#if (and (not @root.hasHealing) roll.options.damageTypes.length)}}
|
{{#if (and (not @root.hasHealing) roll.options.damageTypes.length)}}
|
||||||
<label class="roll-part-header"><span>
|
<label class="roll-part-header"><span>
|
||||||
{{#each roll.options.damageTypes}}
|
{{#each roll.options.damageTypes}}
|
||||||
|
|
@ -40,7 +63,7 @@
|
||||||
<div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
|
<div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
|
||||||
<div
|
<div
|
||||||
class="dice reroll-button {{../denomination}}"
|
class="dice reroll-button {{../denomination}}"
|
||||||
data-die-index="0" data-type="damage" data-damage-type="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}"
|
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}}
|
{{#if hasRerolls}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerolled"}}"></i>{{/if}}
|
||||||
{{result}}
|
{{result}}
|
||||||
|
|
@ -62,8 +85,4 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/each}}
|
{{/inline}}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -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