Fix conflict

This commit is contained in:
Dapoolp 2025-08-05 21:16:13 +02:00
commit 3d1be5fa22
487 changed files with 16301 additions and 1974 deletions

View file

@ -1,5 +1,6 @@
import { abilities } from '../../config/actorConfig.mjs';
import { burden } from '../../config/generalConfig.mjs';
import { createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -550,34 +551,46 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
}
};
await this.character.createEmbeddedDocuments('Item', [ancestry]);
await this.character.createEmbeddedDocuments('Item', [this.setup.community]);
await this.character.createEmbeddedDocuments('Item', [this.setup.class]);
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
await this.character.createEmbeddedDocuments('Item', Object.values(this.setup.domainCards));
if (this.equipment.armor.uuid)
await this.character.createEmbeddedDocuments('Item', [
{ ...this.equipment.armor, system: { ...this.equipment.armor.system, equipped: true } }
]);
if (this.equipment.primaryWeapon.uuid)
await this.character.createEmbeddedDocuments('Item', [
{ ...this.equipment.primaryWeapon, system: { ...this.equipment.primaryWeapon.system, equipped: true } }
]);
if (this.equipment.secondaryWeapon.uuid)
await this.character.createEmbeddedDocuments('Item', [
{
...this.equipment.secondaryWeapon,
system: { ...this.equipment.secondaryWeapon.system, equipped: true }
}
]);
if (this.equipment.inventory.choiceA.uuid)
await this.character.createEmbeddedDocuments('Item', [this.equipment.inventory.choiceA]);
if (this.equipment.inventory.choiceB.uuid)
await this.character.createEmbeddedDocuments('Item', [this.equipment.inventory.choiceB]);
await createEmbeddedItemWithEffects(this.character, ancestry);
await createEmbeddedItemWithEffects(this.character, this.setup.community);
await createEmbeddedItemWithEffects(this.character, this.setup.class);
await createEmbeddedItemWithEffects(this.character, this.setup.subclass);
await this.character.createEmbeddedDocuments(
'Item',
this.setup.class.system.inventory.take.filter(x => x)
Object.values(this.setup.domainCards).map(x => ({
...x,
effects: x.effects?.map(effect => effect.toObject())
}))
);
if (this.equipment.armor.uuid)
await createEmbeddedItemWithEffects(this.character, this.equipment.armor, {
...this.equipment.armor,
system: { ...this.equipment.armor.system, equipped: true }
});
if (this.equipment.primaryWeapon.uuid)
await createEmbeddedItemWithEffects(this.character, this.equipment.primaryWeapon, {
...this.equipment.primaryWeapon,
system: { ...this.equipment.primaryWeapon.system, equipped: true }
});
if (this.equipment.secondaryWeapon.uuid)
await createEmbeddedItemWithEffects(this.character, this.equipment.secondaryWeapon, {
...this.equipment.secondaryWeapon,
system: { ...this.equipment.secondaryWeapon.system, equipped: true }
});
if (this.equipment.inventory.choiceA.uuid)
await createEmbeddedItemWithEffects(this.character, this.equipment.inventory.choiceA);
if (this.equipment.inventory.choiceB.uuid)
await createEmbeddedItemWithEffects(this.character, this.equipment.inventory.choiceB);
await this.character.createEmbeddedDocuments(
'Item',
this.setup.class.system.inventory.take
.filter(x => x)
.map(x => ({
...x,
effects: x.effects?.map(effect => effect.toObject())
}))
);
await this.character.update({

View file

@ -10,14 +10,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.reject = reject;
this.actor = actor;
this.damage = damage;
this.rulesDefault = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Automation
).damageReductionRulesDefault;
this.rulesOn = [CONFIG.DH.GENERAL.ruleChoice.on.id, CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id].includes(
this.rulesDefault
);
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
const maxArmorMarks = canApplyArmor
? Math.min(
actor.system.armorScore - actor.system.armor.system.marks.value,
actor.system.rules.damageReduction.maxArmorMarked.value
)
: 0;
const availableArmor = actor.system.armorScore - actor.system.armor.system.marks.value;
const maxArmorMarks = canApplyArmor ? availableArmor : 0;
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
acc[foundry.utils.randomID()] = { selected: false };
@ -42,6 +46,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
acc[damage] = {
cost: dr.cost,
selected: false,
any: key === 'any',
from: getDamageLabel(damage),
to: getDamageLabel(damage - 1)
};
@ -51,16 +56,28 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
},
null
);
this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce(
(acc, key) => {
if (actor.system.rules.damageReduction.thresholdImmunities[key])
acc[damageKeyToNumber(key)] = game.i18n.format(`DAGGERHEART.GENERAL.DamageThresholds.with`, {
threshold: game.i18n.localize(`DAGGERHEART.GENERAL.DamageThresholds.${key}`)
});
return acc;
},
{}
);
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'views', 'damage-reduction'],
position: {
width: 240,
width: 280,
height: 'auto'
},
actions: {
toggleRules: this.toggleRules,
setMarks: this.setMarks,
useStressReduction: this.useStressReduction,
takeDamage: this.takeDamage
@ -89,6 +106,12 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.rulesOn = this.rulesOn;
context.rulesToggleable = [
CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id
].includes(this.rulesDefault);
context.thresholdImmunities = this.thresholdImmunities;
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
this.getDamageInfo();
@ -110,12 +133,22 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
}
: null;
context.marks = this.marks;
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
context.marks = {
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
const mark = this.marks.armor[key];
if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark;
return acc;
}, {}),
stress: this.marks.stress
};
context.availableStressReductions = this.availableStressReductions;
context.damage = getDamageLabel(this.damage);
context.reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null;
context.currentDamage = context.reducedDamage ?? context.damage;
context.currentDamageNr = currentDamage;
return context;
}
@ -136,22 +169,48 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
const armorMarkReduction =
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
const currentDamage = this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length;
let currentDamage = Math.max(
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
0
);
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
};
static toggleRules() {
this.rulesOn = !this.rulesOn;
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
this.marks = {
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
const mark = this.marks.armor[key];
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
acc[key] = { ...mark, selected: keepSelectValue ? mark.selected : false };
return acc;
}, {}),
stress: this.marks.stress
};
this.render();
}
static setMarks(_, target) {
const currentMark = this.marks[target.dataset.type][target.dataset.key];
const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo();
if (!currentMark.selected && currentDamage === 0) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
return;
}
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
return;
if (this.rulesOn) {
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
return;
}
}
if (currentMark.selected) {

View file

@ -24,6 +24,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].longMoves
}
};
this.refreshables = this.getRefreshables();
}
get title() {
@ -81,11 +83,56 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
context.shortRestMoves = this.nrChoices.shortRest.max > 0 ? this.moveData.shortRest : null;
context.longRestMoves = this.nrChoices.longRest.max > 0 ? this.moveData.longRest : null;
context.refreshables = this.refreshables;
context.disabledDowntime = shortRestMovesSelected === 0 && longRestMovesSelected === 0;
return context;
}
getRefreshables() {
const actionItems = this.actor.items.reduce((acc, x) => {
if (x.system.actions) {
const recoverable = x.system.actions.reduce((acc, action) => {
if (action.uses.recovery && (action.uses.recovery === 'shortRest') === this.shortrest) {
acc.push({
title: x.name,
name: action.name,
uuid: action.uuid
});
}
return acc;
}, []);
if (recoverable) {
acc.push(...recoverable);
}
}
return acc;
}, []);
const resourceItems = this.actor.items.reduce((acc, x) => {
if (
x.system.resource &&
x.system.resource.type &&
(x.system.resource.recovery === 'shortRest') === this.shortrest
) {
acc.push({
title: game.i18n.localize(`TYPES.Item.${x.type}`),
name: x.name,
uuid: x.uuid
});
}
return acc;
}, []);
return {
actionItems,
resourceItems
};
}
static selectMove(_, target) {
const { category, move } = target.dataset;
@ -172,11 +219,24 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
}
}
// We can close the window when all moves are taken
// We can close the window and refresh resources when all moves are taken
if (
this.nrChoices.shortRest.taken >= this.nrChoices.shortRest.max &&
this.nrChoices.longRest.taken >= this.nrChoices.longRest.max
) {
for (var data of this.refreshables.actionItems) {
const action = await foundry.utils.fromUuid(data.uuid);
await action.parent.parent.update({ [`system.actions.${action.id}.uses.value`]: action.uses.max ?? 1 });
}
for (var data of this.refreshables.resourceItems) {
const feature = await foundry.utils.fromUuid(data.uuid);
const increasing =
feature.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
const resetValue = increasing ? 0 : (feature.system.resource.max ?? 0);
await feature.update({ 'system.resource.value': resetValue });
}
this.close();
} else {
this.render();

View file

@ -130,7 +130,7 @@ export default function DHApplicationMixin(Base) {
docs.push(doc);
}
docs.filter(doc => doc).map(doc => (doc.apps[this.id] = this));
docs.filter(doc => doc).forEach(doc => (doc.apps[this.id] = this));
if (!!this.options.contextMenus.length) this._createContextMenus();
}

View file

@ -80,10 +80,17 @@ export default class ClassSheet extends DHBaseItemSheet {
'inventory.choiceB'
];
paths.forEach(path => {
const docs = [].concat(foundry.utils.getProperty(this.document, `system.${path}`) ?? []);
docs.forEach(doc => (doc.apps[this.id] = this));
});
for (let path of paths) {
const docDatas = [].concat(foundry.utils.getProperty(this.document, `system.${path}`) ?? []);
const docs = [];
for (var docData of docDatas) {
const doc = await foundry.utils.fromUuid(docData.uuid);
docs.push(doc);
}
docs.filter(doc => doc).forEach(doc => (doc.apps[this.id] = this));
}
}
/**@inheritdoc */

View file

@ -55,6 +55,24 @@ export const abilities = {
}
};
export const scrollingTextResource = {
hitPoints: {
label: 'DAGGERHEART.GENERAL.HitPoints.plural',
reversed: true
},
stress: {
label: 'DAGGERHEART.GENERAL.stress',
reversed: true
},
hope: {
label: 'DAGGERHEART.GENERAL.hope'
},
armor: {
label: 'DAGGERHEART.GENERAL.armor',
reversed: true
}
};
export const featureProperties = {
agility: {
name: 'DAGGERHEART.CONFIG.Traits.agility.name',

View file

@ -2,6 +2,25 @@ export const compendiumJournals = {
welcome: 'Compendium.daggerheart.journals.JournalEntry.g7NhKvwltwafmMyR'
};
export const ruleChoice = {
on: {
id: 'on',
label: 'DAGGERHEART.CONFIG.RuleChoice.on'
},
of: {
id: 'off',
label: 'DAGGERHEART.CONFIG.RuleChoice.off'
},
onWithToggle: {
id: 'onWithToggle',
label: 'DAGGERHEART.CONFIG.RuleChoice.onWithToggle'
},
offWithToggle: {
id: 'offWithToggle',
label: 'DAGGERHEART.CONFIG.RuleChoice.offWithToggle'
}
};
export const range = {
self: {
id: 'self',
@ -129,10 +148,10 @@ export const healingTypes = {
label: 'DAGGERHEART.CONFIG.HealingType.hope.name',
abbreviation: 'DAGGERHEART.CONFIG.HealingType.hope.abbreviation'
},
armorSlot: {
id: 'armorSlot',
label: 'DAGGERHEART.CONFIG.HealingType.armorSlot.name',
abbreviation: 'DAGGERHEART.CONFIG.HealingType.armorSlot.abbreviation'
armor: {
id: 'armor',
label: 'DAGGERHEART.CONFIG.HealingType.armor.name',
abbreviation: 'DAGGERHEART.CONFIG.HealingType.armor.abbreviation'
},
fear: {
id: 'fear',
@ -190,14 +209,21 @@ export const defaultRestOptions = {
img: 'icons/magic/life/cross-worn-green.webp',
actionType: 'action',
chatDisplay: false,
healing: {
applyTo: healingTypes.hitPoints.id,
value: {
custom: {
enabled: true,
formula: '1d4 + @tier'
target: {
type: 'self'
},
damage: {
parts: [
{
applyTo: healingTypes.hitPoints.id,
value: {
custom: {
enabled: true,
formula: '1d4 + @tier'
}
}
}
}
]
}
}
}
@ -216,14 +242,21 @@ export const defaultRestOptions = {
img: 'icons/magic/perception/eye-ringed-green.webp',
actionType: 'action',
chatDisplay: false,
healing: {
applyTo: healingTypes.stress.id,
value: {
custom: {
enabled: true,
formula: '1d4 + @tier'
target: {
type: 'self'
},
damage: {
parts: [
{
applyTo: healingTypes.stress.id,
value: {
custom: {
enabled: true,
formula: '1d4 + @tier'
}
}
}
}
]
}
}
}
@ -242,14 +275,21 @@ export const defaultRestOptions = {
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
actionType: 'action',
chatDisplay: false,
healing: {
applyTo: healingTypes.armorSlot.id,
value: {
custom: {
enabled: true,
formula: '1d4 + @tier'
target: {
type: 'self'
},
damage: {
parts: [
{
applyTo: healingTypes.armor.id,
value: {
custom: {
enabled: true,
formula: '1d4 + @tier'
}
}
}
}
]
}
}
}
@ -278,14 +318,21 @@ export const defaultRestOptions = {
img: 'icons/magic/life/cross-worn-green.webp',
actionType: 'action',
chatDisplay: false,
healing: {
applyTo: healingTypes.hitPoints.id,
value: {
custom: {
enabled: true,
formula: '@system.resources.hitPoints.max'
target: {
type: 'self'
},
damage: {
parts: [
{
applyTo: healingTypes.hitPoints.id,
value: {
custom: {
enabled: true,
formula: '@system.resources.hitPoints.max'
}
}
}
}
]
}
}
}
@ -304,14 +351,21 @@ export const defaultRestOptions = {
img: 'icons/magic/perception/eye-ringed-green.webp',
actionType: 'action',
chatDisplay: false,
healing: {
applyTo: healingTypes.stress.id,
value: {
custom: {
enabled: true,
formula: '@system.resources.stress.max'
target: {
type: 'self'
},
damage: {
parts: [
{
applyTo: healingTypes.stress.id,
value: {
custom: {
enabled: true,
formula: '@system.resources.stress.max'
}
}
}
}
]
}
}
}
@ -330,14 +384,21 @@ export const defaultRestOptions = {
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
actionType: 'action',
chatDisplay: false,
healing: {
applyTo: healingTypes.armorSlot.id,
value: {
custom: {
enabled: true,
formula: '@system.armorScore'
target: {
type: 'self'
},
damage: {
parts: [
{
applyTo: healingTypes.armor.id,
value: {
custom: {
enabled: true,
formula: '@system.armorScore'
}
}
}
}
]
}
}
}
@ -432,8 +493,20 @@ export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
const getPreset = async (type, faces) => {
const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
if (!system.modelLoaded) {
if (!system) {
ui.notifications.error(
game.i18n.format('DAGGERHEART.UI.Notifications.noDiceSystem', {
system: game.dice3d.DiceFactory.systems.get(type.system).name,
faces: faces
})
);
return;
}
if (system.modelFile && !system.modelLoaded) {
await system.loadModel(game.dice3d.DiceFactory.loaderGLTF);
} else {
await system.loadTextures();
}
return {

View file

@ -675,7 +675,7 @@ export const weaponFeatures = {
},
cost: [
{
type: 'armorSlot',
type: 'armor',
value: 1
}
],
@ -886,14 +886,21 @@ export const weaponFeatures = {
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
img: 'icons/magic/life/cross-beam-green.webp',
healing: {
type: 'health',
value: {
custom: {
enabled: true,
formula: '1'
target: {
type: 'self'
},
damage: {
parts: [
{
applyTo: 'hitPoints',
value: {
custom: {
enabled: true,
formula: 1
}
}
}
}
]
}
}
]
@ -1473,6 +1480,17 @@ export const itemResourceTypes = {
}
};
export const itemResourceProgression = {
increasing: {
id: 'increasing',
label: 'DAGGERHEART.CONFIG.ItemResourceProgression.increasing'
},
decreasing: {
id: 'decreasing',
label: 'DAGGERHEART.CONFIG.ItemResourceProgression.decreasing'
}
};
export const beastformTypes = {
normal: {
id: 'normal',

View file

@ -53,8 +53,8 @@ export default class DhpAdversary extends BaseDataActor {
})
}),
resources: new fields.SchemaField({
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
}),
attack: new ActionField({
initial: {
@ -105,6 +105,13 @@ export default class DhpAdversary extends BaseDataActor {
};
}
/* -------------------------------------------- */
/**@inheritdoc */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/actors/dragon-head.svg';
/* -------------------------------------------- */
get attackBonus() {
return this.attack.roll.bonus;
}

View file

@ -1,4 +1,5 @@
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
import { createScrollText, getScrollTextData } from '../../helpers/utils.mjs';
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
new foundry.data.fields.SchemaField({
@ -69,6 +70,16 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
return schema;
}
/* -------------------------------------------- */
/**
* The default icon used for newly created Actors documents
* @type {string}
*/
static DEFAULT_ICON = null;
/* -------------------------------------------- */
/**
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
* @param {object} [options] - Options which modify the getRollData method.
@ -78,4 +89,28 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
const data = { ...this };
return data;
}
async _preUpdate(changes, options, userId) {
const allowed = await super._preUpdate(changes, options, userId);
if (allowed === false) return;
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
if (changes.system?.resources && autoSettings.resourceScrollTexts) {
const textData = Object.keys(changes.system.resources).reduce((acc, key) => {
const resource = changes.system.resources[key];
if (resource.value !== undefined && resource.value !== this.resources[key].value) {
acc.push(getScrollTextData(this.resources, resource, key));
}
return acc;
}, []);
options.scrollingTextData = textData;
}
}
_onUpdate(changes, options, userId) {
super._onUpdate(changes, options, userId);
createScrollText(this.parent, options.scrollingTextData);
}
}

View file

@ -7,8 +7,10 @@ import { ActionField } from '../fields/actionField.mjs';
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
export default class DhCharacter extends BaseDataActor {
/**@override */
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
/**@inheritdoc */
static get metadata() {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.character',
@ -18,6 +20,7 @@ export default class DhCharacter extends BaseDataActor {
});
}
/**@inheritdoc */
static defineSchema() {
const fields = foundry.data.fields;
@ -25,13 +28,14 @@ export default class DhCharacter extends BaseDataActor {
...super.defineSchema(),
resources: new fields.SchemaField({
hitPoints: resourceField(
0,
0,
'DAGGERHEART.GENERAL.HitPoints.plural',
true,
'DAGGERHEART.ACTORS.Character.maxHPBonus'
),
stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
hope: resourceField(6, 2, 'DAGGERHEART.GENERAL.hope')
}),
traits: new fields.SchemaField({
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
@ -239,7 +243,8 @@ export default class DhCharacter extends BaseDataActor {
stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor')
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
}),
increasePerArmorMark: new fields.NumberField({
integer: true,
@ -248,7 +253,11 @@ export default class DhCharacter extends BaseDataActor {
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}),
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false })
physical: new fields.BooleanField({ initial: false }),
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
disabledArmor: new fields.BooleanField({ intial: false })
}),
attack: new fields.SchemaField({
damage: new fields.SchemaField({
@ -289,11 +298,16 @@ export default class DhCharacter extends BaseDataActor {
*/
flipMinDiceValue: new fields.BooleanField({ intial: false })
}),
runeWard: new fields.BooleanField({ initial: false })
runeWard: new fields.BooleanField({ initial: false }),
burden: new fields.SchemaField({
ignore: new fields.BooleanField()
})
})
};
}
/* -------------------------------------------- */
get tier() {
const currentLevel = this.levelData.level.current;
return currentLevel === 1

View file

@ -9,6 +9,7 @@ import { resourceField, bonusField } from '../fields/actorField.mjs';
export default class DhCompanion extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
/**@inheritdoc */
static get metadata() {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.companion',
@ -18,6 +19,7 @@ export default class DhCompanion extends BaseDataActor {
});
}
/**@inheritdoc */
static defineSchema() {
const fields = foundry.data.fields;
@ -25,7 +27,7 @@ export default class DhCompanion extends BaseDataActor {
...super.defineSchema(),
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
resources: new fields.SchemaField({
stress: resourceField(3, 'DAGGERHEART.GENERAL.stress', true),
stress: resourceField(3, 0, 'DAGGERHEART.GENERAL.stress', true),
hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' })
}),
evasion: new fields.NumberField({
@ -87,6 +89,13 @@ export default class DhCompanion extends BaseDataActor {
};
}
/* -------------------------------------------- */
/**@inheritdoc */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/actors/capybara.svg';
/* -------------------------------------------- */
get proficiency() {
return this.partner?.system?.proficiency ?? 1;
}

View file

@ -3,8 +3,10 @@ import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayFie
import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs';
export default class DhEnvironment extends BaseDataActor {
/**@override */
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment'];
/**@inheritdoc */
static get metadata() {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.environment',
@ -14,6 +16,7 @@ export default class DhEnvironment extends BaseDataActor {
});
}
/**@inheritdoc */
static defineSchema() {
const fields = foundry.data.fields;
return {
@ -37,6 +40,13 @@ export default class DhEnvironment extends BaseDataActor {
};
}
/* -------------------------------------------- */
/**@inheritdoc */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/actors/forest.svg';
/* -------------------------------------------- */
get features() {
return this.parent.items.filter(x => x.type === 'feature');
}

View file

@ -103,7 +103,7 @@ class DhCountdown extends foundry.abstract.DataModel {
required: true,
choices: CONFIG.DH.GENERAL.countdownTypes,
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label'
label: 'DAGGERHEART.GENERAL.type'
}),
label: new fields.StringField({
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.label.label'

View file

@ -6,9 +6,9 @@ const attributeField = label =>
tierMarked: new fields.BooleanField({ initial: false })
});
const resourceField = (max = 0, label, reverse = false, maxLabel) =>
const resourceField = (max = 0, initial = 0, label, reverse = false, maxLabel) =>
new fields.SchemaField({
value: new fields.NumberField({ initial: 0, min: 0, integer: true, label }),
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
max: new fields.NumberField({
initial: max,
integer: true,

View file

@ -19,10 +19,26 @@ export default class DHAncestry extends BaseDataItem {
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/family-tree.svg';
/* -------------------------------------------- */
/**
* Gets the primary feature.
* @type {foundry.documents.Item|null} Returns the item of the first feature with type "primary" or null if none is found.
*/
get primaryFeature() {
return this.features.find(x => x.type === CONFIG.DH.ITEM.featureSubTypes.primary)?.item;
}
/**
* Gets the secondary feature.
* @type {foundry.documents.Item|null} Returns the item of the first feature with type "secondary" or null if none is found.
*/
get secondaryFeature() {
return this.features.find(x => x.type === CONFIG.DH.ITEM.featureSubTypes.secondary)?.item;
}

View file

@ -42,12 +42,20 @@ export default class DHArmor extends AttachableItem {
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/chest-armor.svg';
/* -------------------------------------------- */
get customActions() {
return this.actions.filter(
action => !this.armorFeatures.some(feature => feature.actionIds.includes(action.id))
);
}
/**@inheritdoc */
async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
@ -68,7 +76,7 @@ export default class DHArmor extends AttachableItem {
return acc;
}, {});
for (var feature of added) {
for (const feature of added) {
const featureData = armorFeatures[feature.value];
if (featureData.effects?.length > 0) {
const embeddedItems = await this.parent.createEmbeddedDocuments(

View file

@ -8,7 +8,7 @@
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
*/
import { addLinkedItemsDiff, updateLinkedItemApps } from '../../helpers/utils.mjs';
import { addLinkedItemsDiff, createScrollText, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
import { ActionsField } from '../fields/actionField.mjs';
import FormulaField from '../fields/formulaField.mjs';
@ -56,6 +56,11 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
initial: null,
nullable: true
}),
progression: new fields.StringField({
required: true,
choices: CONFIG.DH.ITEM.itemResourceProgression,
initial: CONFIG.DH.ITEM.itemResourceProgression.increasing.id
}),
diceStates: new fields.TypedObjectField(
new fields.SchemaField({
value: new fields.NumberField({ integer: true, initial: 1, min: 1 }),
@ -79,6 +84,16 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
return schema;
}
/* -------------------------------------------- */
/**
* The default icon used for newly created Item documents
* @type {string}
*/
static DEFAULT_ICON = null;
/* -------------------------------------------- */
/**
* Convenient access to the item's actor, if it exists.
* @returns {foundry.documents.Actor | null}
@ -178,11 +193,20 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
if (allowed === false) return false;
addLinkedItemsDiff(changed.system?.features, this.features, options);
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
const armorChanged =
changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value;
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
const armorData = getScrollTextData(this.parent.parent.system.resources, changed.system.marks, 'armor');
options.scrollingTextData = [armorData];
}
}
_onUpdate(changed, options, userId) {
super._onUpdate(changed, options, userId);
updateLinkedItemApps(options, this.parent.sheet);
createScrollText(this.parent?.parent, options.scrollingTextData);
}
}

View file

@ -81,6 +81,13 @@ export default class DHBeastform extends BaseDataItem {
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/wolf-head.svg';
/* -------------------------------------------- */
async _preCreate() {
if (!this.actor) return;

View file

@ -53,6 +53,13 @@ export default class DHClass extends BaseDataItem {
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/laurel-crown.svg';
/* -------------------------------------------- */
get hopeFeatures() {
return this.features.filter(x => x.type === CONFIG.DH.ITEM.featureSubTypes.hope).map(x => x.item);
}

View file

@ -13,10 +13,15 @@ export default class DHCommunity extends BaseDataItem {
/** @inheritDoc */
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/village.svg';
}

View file

@ -22,4 +22,10 @@ export default class DHConsumable extends BaseDataItem {
consumeOnUse: new fields.BooleanField({ initial: false })
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/round-potion.svg';
}

View file

@ -33,18 +33,26 @@ export default class DHDomainCard extends BaseDataItem {
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/card-play.svg';
/* -------------------------------------------- */
/**@inheritdoc */
async _preCreate(data, options, user) {
const allowed = await super._preCreate(data, options, user);
if (allowed === false) return;
if (this.actor?.type === 'character') {
if (!this.actor.system.class.value) {
const actorClasses = this.actor.items.filter(x => x.type === 'class');
if (!actorClasses.length) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noClassSelected'));
return false;
}
if (!this.actor.system.domains.find(x => x === this.domain)) {
if (!actorClasses.some(c => c.system.domains.find(x => x === this.domain))) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.lacksDomain'));
return false;
}

View file

@ -13,6 +13,13 @@ export default class DHFeature extends BaseDataItem {
});
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/stars-stack.svg';
/* -------------------------------------------- */
/** @inheritDoc */
static defineSchema() {
const fields = foundry.data.fields;

View file

@ -19,4 +19,11 @@ export default class DHLoot extends BaseDataItem {
...super.defineSchema()
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/open-treasure-chest.svg';
/* -------------------------------------------- */
}

View file

@ -29,6 +29,13 @@ export default class DHSubclass extends BaseDataItem {
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/laurels.svg';
/* -------------------------------------------- */
get foundationFeatures() {
return this.features.filter(x => x.type === CONFIG.DH.ITEM.featureSubTypes.foundation).map(x => x.item);
}
@ -43,6 +50,7 @@ export default class DHSubclass extends BaseDataItem {
async _preCreate(data, options, user) {
if (this.actor?.type === 'character') {
const dataUuid = data.uuid ?? data._stats?.compendiumSource ?? `Item.${data._id}`;
if (this.actor.system.class.subclass) {
if (this.actor.system.multiclass.subclass) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));
@ -54,7 +62,7 @@ export default class DHSubclass extends BaseDataItem {
return false;
}
if (multiclass.system.subclasses.every(x => x.uuid !== (data.uuid ?? `Item.${data._id}`))) {
if (multiclass.system.subclasses.every(x => x.uuid !== dataUuid)) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInMulticlass')
);
@ -69,7 +77,7 @@ export default class DHSubclass extends BaseDataItem {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass'));
return false;
}
if (actorClass.system.subclasses.every(x => x.uuid !== (data.uuid ?? `Item.${data._id}`))) {
if (actorClass.system.subclasses.every(x => x.uuid !== dataUuid)) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
return false;
}

View file

@ -80,6 +80,13 @@ export default class DHWeapon extends AttachableItem {
};
}
/* -------------------------------------------- */
/**@override */
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/battered-axe.svg';
/* -------------------------------------------- */
get actionsList() {
return [this.attack, ...this.actions];
}

View file

@ -34,6 +34,17 @@ export default class DhAutomation extends foundry.abstract.DataModel {
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.effects.rangeDependent.label'
})
}),
damageReductionRulesDefault: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.ruleChoice,
initial: CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.damageReductionRulesDefault.label'
}),
resourceScrollTexts: new fields.BooleanField({
required: true,
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
})
};
}

View file

@ -5,3 +5,4 @@ export { default as DhActiveEffect } from './activeEffect.mjs';
export { default as DhChatMessage } from './chatMessage.mjs';
export { default as DhToken } from './token.mjs';
export { default as DhTooltipManager } from './tooltipManager.mjs';
export { default as DhTemplateManager } from './templateManager.mjs';

View file

@ -147,4 +147,32 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
cls.create(msg);
}
prepareDerivedData() {
/* Preventing subclass features from transferring to actor if they do not have the right subclass advancement */
if (this.parent?.type === 'feature') {
const origSubclassParent = this.parent.system.originItemType === 'subclass';
if (origSubclassParent) {
const subclass = this.parent.parent.items.find(
x =>
x.type === 'subclass' &&
x.system.isMulticlass === (this.parent.system.identifier === 'multiclass')
);
if (subclass) {
const featureState = subclass.system.featureState;
const featureType = subclass
? (subclass.system.features.find(x => x.item?.uuid === this.parent.uuid)?.type ?? null)
: null;
if (
(featureType === CONFIG.DH.ITEM.featureSubTypes.specialization && featureState < 2) ||
(featureType === CONFIG.DH.ITEM.featureSubTypes.mastery && featureState < 3)
) {
this.transfer = false;
}
}
}
}
}
}

View file

@ -22,6 +22,23 @@ export default class DhpActor extends Actor {
return this.system.metadata.isNPC;
}
/* -------------------------------------------- */
/**@inheritdoc */
static getDefaultArtwork(actorData) {
const { type } = actorData;
const Model = CONFIG.Actor.dataModels[type];
const img = Model.DEFAULT_ICON ?? this.DEFAULT_ICON;
return {
img,
texture: {
src: img
}
};
}
/* -------------------------------------------- */
/** @inheritDoc */
getEmbeddedDocument(embeddedName, id, options) {
let doc;
@ -39,6 +56,7 @@ export default class DhpActor extends Actor {
return doc;
}
/**@inheritdoc */
async _preCreate(data, options, user) {
if ((await super._preCreate(data, options, user)) === false) return false;
@ -455,6 +473,7 @@ export default class DhpActor extends Actor {
return ActiveEffect.implementation.create(effect, { parent: this, keepId: true });
}
/**@inheritdoc */
getRollData() {
const rollData = super.getRollData();
rollData.system = this.system.getRollData();
@ -464,14 +483,17 @@ export default class DhpActor extends Actor {
}
#canReduceDamage(hpDamage, type) {
const { stressDamageReduction, disabledArmor } = this.system.rules.damageReduction;
if (disabledArmor) return false;
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
const canUseArmor =
this.system.armor &&
this.system.armor.system.marks.value < this.system.armorScore &&
type.every(t => this.system.armorApplicableDamageTypes[t] === true);
const canUseStress = Object.keys(this.system.rules.damageReduction.stressDamageReduction).reduce((acc, x) => {
const rule = this.system.rules.damageReduction.stressDamageReduction[x];
const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => {
const rule = stressDamageReduction[x];
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
return acc;
}, false);
@ -537,8 +559,8 @@ export default class DhpActor extends Actor {
updates.forEach(
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);
@ -584,9 +606,9 @@ export default class DhpActor extends Actor {
updates.forEach(
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);

View file

@ -40,6 +40,8 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
elements.forEach(e => {
const uuid = e.dataset.permId,
document = fromUuidSync(uuid);
if (!document) return;
e.setAttribute('data-view-perm', document.testUserPermission(game.user, 'OBSERVER'));
e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER'));
});
@ -68,7 +70,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
getTargetList() {
const targets = this.system.hitTargets;
return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor.uuid === target.actorId));
return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId));
}
async onDamage(event) {
@ -88,7 +90,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
if (targets.length === 0)
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
for (let target of targets) {
let damages = foundry.utils.deepClone(this.system.damage);
if (
@ -139,9 +141,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
}
consumeOnSuccess() {
if(!this.system.successConsumed && !this.system.targetSelection) {
if (!this.system.successConsumed && !this.system.targetSelection) {
const action = this.system.action;
if(action) action.consume(this.system, true);
if (action) action.consume(this.system, true);
}
}
}

View file

@ -74,8 +74,8 @@ export default class DHItem extends foundry.documents.Item {
isInventoryItem === true
? 'Inventory Items' //TODO localize
: isInventoryItem === false
? 'Character Items' //TODO localize
: 'Other'; //TODO localize
? 'Character Items' //TODO localize
: 'Other'; //TODO localize
return { value: type, label, group };
}
@ -112,12 +112,25 @@ export default class DHItem extends foundry.documents.Item {
* Generate a localized label array for this item.
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/
getLabels() {
_getLabels() {
const labels = [];
if (this.system.getLabels) labels.push(...this.system.getLabels());
if (this.system._getLabels) labels.push(...this.system._getLabels());
return labels;
}
/* -------------------------------------------- */
/**@inheritdoc */
static getDefaultArtwork(itemData) {
const { type } = itemData;
const Model = CONFIG.Item.dataModels[type];
const img = Model.DEFAULT_ICON ?? this.DEFAULT_ICON;
return { img };
}
/* -------------------------------------------- */
async use(event) {
const actions = new Set(this.system.actionsList);
if (actions?.size) {
@ -139,10 +152,10 @@ export default class DHItem extends foundry.documents.Item {
this.type === 'ancestry'
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.ancestryTitle')
: this.type === 'community'
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.communityTitle')
: this.type === 'feature'
? game.i18n.localize('TYPES.Item.feature')
: game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'),
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.communityTitle')
: this.type === 'feature'
? game.i18n.localize('TYPES.Item.feature')
: game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'),
origin: origin,
img: this.img,
item: {

View file

@ -0,0 +1,99 @@
/**
* A singleton class that handles preview templates.
*/
export default class DhTemplateManager {
#activePreview;
/**
* Create a template preview, deactivating any existing ones.
* @param {object} data
*/
async createPreview(data) {
const template = await canvas.templates._createPreview(data, { renderSheet: false });
this.#activePreview = {
document: template.document,
object: template,
origin: { x: template.document.x, y: template.document.y }
};
this.#activePreview.events = {
contextmenu: this.#cancelTemplate.bind(this),
mousedown: this.#confirmTemplate.bind(this),
mousemove: this.#onDragMouseMove.bind(this),
wheel: this.#onMouseWheel.bind(this)
};
canvas.stage.on('mousemove', this.#activePreview.events.mousemove);
canvas.stage.on('mousedown', this.#activePreview.events.mousedown);
canvas.app.view.addEventListener('wheel', this.#activePreview.events.wheel, true);
canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu);
}
/**
* Handles the movement of the temlate preview on mousedrag.
* @param {mousemove Event} event
*/
#onDragMouseMove(event) {
event.stopPropagation();
const { moveTime, object } = this.#activePreview;
const update = {};
const now = Date.now();
if (now - (moveTime || 0) <= 16) return;
this.#activePreview.moveTime = now;
let cursor = event.getLocalPosition(canvas.templates);
Object.assign(update, canvas.grid.getCenterPoint(cursor));
object.document.updateSource(update);
object.renderFlags.set({ refresh: true });
}
/**
* Handles the rotation of the preview template on scrolling.
* @param {wheel Event} event
*/
#onMouseWheel(event) {
if (!event.shiftKey) return;
event.stopPropagation();
event.preventDefault();
const { moveTime, object } = this.#activePreview;
const now = Date.now();
if (now - (moveTime || 0) <= 16) return;
this.#activePreview.moveTime = now;
object.document.updateSource({
direction: object.document.direction + event.deltaY * 0.2
});
object.renderFlags.set({ refresh: true });
}
/**
* Cancels the preview template on right-click.
* @param {contextmenu Event} event
*/
#cancelTemplate(event) {
const { mousemove, mousedown, contextmenu } = this.#activePreview.events;
canvas.templates._onDragLeftCancel(event);
canvas.stage.off('mousemove', mousemove);
canvas.stage.off('mousedown', mousedown);
canvas.app.view.removeEventListener('contextmenu', contextmenu);
}
/**
* Creates a real MeasuredTemplate at the preview location and cancels the preview.
* @param {click Event} event
*/
#confirmTemplate(event) {
event.stopPropagation();
canvas.scene.createEmbeddedDocuments('MeasuredTemplate', [this.#activePreview.document.toObject()]);
this.#cancelTemplate(event);
}
}

View file

@ -59,14 +59,14 @@ export const renderMeasuredTemplate = async event => {
const distance = type === CONFIG.DH.GENERAL.templateTypes.EMANATION ? baseDistance + 2.5 : baseDistance;
const { width, height } = game.canvas.scene.dimensions;
canvas.scene.createEmbeddedDocuments('MeasuredTemplate', [
{
x: width / 2,
y: height / 2,
t: usedType,
distance: distance,
width: type === CONST.MEASURED_TEMPLATE_TYPES.RAY ? 5 : undefined,
angle: angle
}
]);
const data = {
x: width / 2,
y: height / 2,
t: usedType,
distance: distance,
width: type === CONST.MEASURED_TEMPLATE_TYPES.RAY ? 5 : undefined,
angle: angle
};
CONFIG.ux.TemplateManager.createPreview(data);
};

View file

@ -324,3 +324,48 @@ export const arraysEqual = (a, b) =>
[...new Set([...a, ...b])].every(v => a.filter(e => e === v).length === b.filter(e => e === v).length);
export const setsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));
export function getScrollTextData(resources, resource, key) {
const { reversed, label } = CONFIG.DH.ACTOR.scrollingTextResource[key];
const { BOTTOM, TOP } = CONST.TEXT_ANCHOR_POINTS;
const increased = resources[key].value < resource.value;
const value = -1 * (resources[key].value - resource.value);
const text = `${game.i18n.localize(label)} ${value.signedString()}`;
const stroke = increased ? (reversed ? 0xffffff : 0x000000) : reversed ? 0x000000 : 0xffffff;
const fill = increased ? (reversed ? 0x0032b1 : 0xffe760) : reversed ? 0xffe760 : 0x0032b1;
const direction = increased ? (reversed ? BOTTOM : TOP) : reversed ? TOP : BOTTOM;
return { text, stroke, fill, direction };
}
export function createScrollText(actor, optionsData) {
if (actor && optionsData?.length) {
actor.getDependentTokens().forEach(token => {
optionsData.forEach(data => {
const { text, ...options } = data;
canvas.interface.createScrollingText(token.getCenterPoint(), data.text, {
duration: 2000,
distance: token.h,
jitter: 0,
...options
});
});
});
}
}
export async function createEmbeddedItemWithEffects(actor, baseData, update) {
const data = baseData.uuid.startsWith('Compendium') ? await foundry.utils.fromUuid(baseData.uuid) : baseData;
const [doc] = await actor.createEmbeddedDocuments('Item', [
{
...(update ?? data),
id: data.id,
uuid: data.uuid,
effects: data.effects?.map(effect => effect.toObject())
}
]);
return doc;
}