mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-07-22 02:19:54 +02:00
Merge branch 'main' into feature/granular-action-outcomes
This commit is contained in:
commit
2e9dede678
207 changed files with 2666 additions and 1798 deletions
|
|
@ -79,8 +79,8 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
const str = damageString
|
||||
? damageString
|
||||
: game.i18n.format('DAGGERHEART.GENERAL.missingX', {
|
||||
x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
|
||||
});
|
||||
x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
|
||||
});
|
||||
|
||||
const icons = Array.from(type)
|
||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return {};
|
||||
}
|
||||
|
||||
get hasDescription() {
|
||||
return Boolean(this.description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
|
||||
*
|
||||
|
|
@ -144,8 +148,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return this.item instanceof DhpActor
|
||||
? this.item
|
||||
: this.item?.parent instanceof DhpActor
|
||||
? this.item.parent
|
||||
: null;
|
||||
? this.item.parent
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -223,12 +227,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @returns {object}
|
||||
*/
|
||||
async use(event, configOptions = {}) {
|
||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||
if (!this.actor) throw new Error('An Action can\'t be used outside of an Actor context.');
|
||||
|
||||
let config = this.prepareConfig(event, configOptions);
|
||||
if (!config) return;
|
||||
|
||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
||||
config.effects =
|
||||
await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(this.actor, this.item);
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||
|
||||
|
|
@ -300,17 +305,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
|
||||
const groupAttackTokens = this.damage.groupAttack
|
||||
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
|
||||
this.actor.id,
|
||||
this.damage.groupAttack
|
||||
)
|
||||
this.actor.id,
|
||||
this.damage.groupAttack
|
||||
)
|
||||
: null;
|
||||
|
||||
config.damageOptions = {
|
||||
groupAttack: this.damage.groupAttack
|
||||
? {
|
||||
numAttackers: Math.max(groupAttackTokens.length, 1),
|
||||
range: this.damage.groupAttack
|
||||
}
|
||||
numAttackers: Math.max(groupAttackTokens.length, 1),
|
||||
range: this.damage.groupAttack
|
||||
}
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
|
@ -333,27 +338,45 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the all potentially applicable effects on the actor
|
||||
* Get the all potentially applicable effects on the actor for the action's RollDialog
|
||||
* @param {DHActor} actor The actor performing the action
|
||||
* @param {DHItem|DhActor} effectParent The parent of the effect
|
||||
* @returns {DhActiveEffect[]}
|
||||
*/
|
||||
static async getEffects(actor, effectParent) {
|
||||
static async getActionRelevantEffects(actor, effectParent) {
|
||||
if (!actor) return [];
|
||||
|
||||
return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).filter(
|
||||
effect => {
|
||||
/* Effects on weapons only ever apply for the weapon itself */
|
||||
if (effect.parent.type === 'weapon') {
|
||||
/* Unless they're secondary - then they apply only to other primary weapons */
|
||||
if (effect.parent.system.secondary) {
|
||||
if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false;
|
||||
} else if (effectParent?.id !== effect.parent.id) return false;
|
||||
}
|
||||
// Changes on weapon effects are not typically only applicable to show in the roll dialog for the weapon itself
|
||||
// The exemptions to this rule are listed below
|
||||
const weaponTransferredEffectKeys = [
|
||||
'system.bonuses.roll.spellcast.bonus'
|
||||
];
|
||||
|
||||
return !effect.isSuppressed;
|
||||
const results = [];
|
||||
const applicableEffects = await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true });
|
||||
for (const effect of [...applicableEffects].filter(e => !e.isSuppressed)) {
|
||||
if (effect.parent.type === 'weapon') {
|
||||
// Effects on weapons only ever apply for the weapon itself (with a few exceptions)
|
||||
const restricted =
|
||||
effect.parent.system.secondary
|
||||
// Secondary applies only to other primary weapons
|
||||
? effectParent?.type !== 'weapon' || effectParent?.system.secondary
|
||||
// Primary only applies to itself
|
||||
: effectParent?.id !== effect.parent.id;
|
||||
if (restricted) {
|
||||
const sourceChanges = effect._source.system.changes;
|
||||
const changes = sourceChanges.filter(x => weaponTransferredEffectKeys.includes(x.key));
|
||||
if (changes.length) {
|
||||
results.push(effect.clone({ 'system.changes': changes }));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
results.push(effect);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -428,7 +451,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
|
||||
static migrateData(source) {
|
||||
if (source.damage?.parts && Array.isArray(source.damage.parts)) {
|
||||
let hitPointsExists = source.damage.parts.some(x => x.applyTo === 'hitPoints');
|
||||
source.damage.parts = source.damage.parts.reduce((acc, part) => {
|
||||
if (!part.applyTo && hitPointsExists) return acc;
|
||||
|
||||
if (!part.applyTo) {
|
||||
hitPointsExists = true;
|
||||
part.applyTo = 'hitPoints';
|
||||
}
|
||||
|
||||
acc[part.applyTo] = part;
|
||||
return acc;
|
||||
}, {});
|
||||
|
|
@ -459,8 +490,7 @@ export class ResourceUpdateMap extends Map {
|
|||
} else if (!existing?.clear) {
|
||||
this.set(resource.key, {
|
||||
...existing,
|
||||
value: existing.value + (resource.value ?? 0),
|
||||
total: existing.total + (resource.total ?? 0)
|
||||
value: existing.value + (resource.value ?? 0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,13 +90,13 @@ export default class BeastformEffect extends BaseEffect {
|
|||
...baseUpdate,
|
||||
x,
|
||||
y,
|
||||
'texture': {
|
||||
texture: {
|
||||
enabled: this.characterTokenData.usesDynamicToken,
|
||||
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg,
|
||||
scaleX: this.characterTokenData.tokenSize.scale,
|
||||
scaleY: this.characterTokenData.tokenSize.scale
|
||||
},
|
||||
'ring': {
|
||||
ring: {
|
||||
subject: {
|
||||
texture:
|
||||
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg
|
||||
|
|
|
|||
|
|
@ -166,10 +166,10 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
|||
value:
|
||||
change.type === 'armor'
|
||||
? {
|
||||
...change.value,
|
||||
current: Math.min(change.value.current, newMax),
|
||||
max: newMax
|
||||
}
|
||||
...change.value,
|
||||
current: Math.min(change.value.current, newMax),
|
||||
max: newMax
|
||||
}
|
||||
: change.value
|
||||
}))
|
||||
];
|
||||
|
|
|
|||
14
module/data/actor/_types.d.ts
vendored
Normal file
14
module/data/actor/_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import DhpActor from '../../documents/actor.mjs'
|
||||
import DhCharacter from './character.mjs';
|
||||
|
||||
declare module './base.mjs' {
|
||||
export default interface BaseDataActor {
|
||||
parent: DhpActor<this>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module './companion.mjs' {
|
||||
export default interface DhCompanion {
|
||||
partner: DhpActor<DhCharacter>;
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +87,7 @@ export default class DhpAdversary extends DhCreature {
|
|||
parts: {
|
||||
hitPoints: {
|
||||
type: ['physical'],
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
multiplier: 'flat'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
|
||||
import DHItem from '../../documents/item.mjs';
|
||||
import { createShallowProxy, getScrollTextData } from '../../helpers/utils.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
/**
|
||||
* @import DHItem from '../../documents/item.mjs';
|
||||
* @import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
|
||||
*/
|
||||
|
||||
/** Function to generate resistance fields for damage types */
|
||||
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
||||
new fields.SchemaField({
|
||||
resistance: new fields.BooleanField({
|
||||
|
|
@ -96,6 +100,8 @@ export const commonActorRules = (extendedData = { damageReduction: {}, attack: {
|
|||
* @property {Boolean} isNPC - This data model represents a NPC?
|
||||
* @property {typeof DHBaseActorSettings} settingSheet - The sheet class used to render the settings UI for this actor type.
|
||||
*/
|
||||
|
||||
/** Base actor type data model for all actors in Daggerheart */
|
||||
export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||
/** @returns {ActorDataModelMetadata}*/
|
||||
static get metadata() {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,18 @@ export default class DhCharacter extends DhCreature {
|
|||
core: new fields.BooleanField({ initial: false })
|
||||
})
|
||||
),
|
||||
gold: new GoldField(),
|
||||
gold: new GoldField({
|
||||
initial: () => {
|
||||
const homebrew = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
|
||||
const { coins, handfuls, bags, chests } = homebrew.currency;
|
||||
return {
|
||||
coins: coins.enabled ? coins.initialAmount : 0,
|
||||
handfuls: handfuls.enabled ? handfuls.initialAmount : 0,
|
||||
bags: bags.enabled ? bags.initialAmount : 0,
|
||||
chests: chests.enabled ? chests.initialAmount : 0
|
||||
};
|
||||
}
|
||||
}),
|
||||
scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }),
|
||||
biography: new fields.SchemaField({
|
||||
background: new fields.HTMLField(),
|
||||
|
|
@ -96,6 +107,7 @@ export default class DhCharacter extends DhCreature {
|
|||
parts: {
|
||||
hitPoints: {
|
||||
type: ['physical'],
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
|
|
@ -304,7 +316,12 @@ export default class DhCharacter extends DhCreature {
|
|||
label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice'
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
/** Accumulated armor score from all sources */
|
||||
armorScore: new fields.SchemaField({
|
||||
value: new fields.NumberField(),
|
||||
max: new fields.NumberField()
|
||||
}, { persisted: false })
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -315,8 +332,8 @@ export default class DhCharacter extends DhCreature {
|
|||
return currentLevel === 1
|
||||
? 1
|
||||
: Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find(
|
||||
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
|
||||
).tier;
|
||||
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
|
||||
).tier;
|
||||
}
|
||||
|
||||
get ancestry() {
|
||||
|
|
@ -400,8 +417,8 @@ export default class DhCharacter extends DhCreature {
|
|||
|
||||
get domainCards() {
|
||||
const domainCards = this.parent.items.filter(x => x.type === 'domainCard');
|
||||
const loadout = domainCards.filter(x => !x.system.inVault);
|
||||
const vault = domainCards.filter(x => x.system.inVault);
|
||||
const loadout = domainCards.filter(x => !x.system.inVault).sort((a, b) => a.sort - b.sort);
|
||||
const vault = domainCards.filter(x => x.system.inVault).sort((a, b) => a.sort - b.sort);
|
||||
|
||||
return {
|
||||
loadout: loadout,
|
||||
|
|
@ -520,20 +537,20 @@ export default class DhCharacter extends DhCreature {
|
|||
|
||||
if (armorSource.type === 'armor') {
|
||||
armorUpdates[armorSource.parent.id].updates.push({
|
||||
'_id': armorSource.id,
|
||||
_id: armorSource.id,
|
||||
'system.armor.current': armorSource.system.armor.current + usedArmorChange
|
||||
});
|
||||
} else {
|
||||
effectUpdates[armorSource.parent.id].updates.push({
|
||||
'_id': armorSource.id,
|
||||
_id: armorSource.id,
|
||||
'system.changes': armorSource.system.changes.map(change => ({
|
||||
...change,
|
||||
value:
|
||||
change.type === 'armor'
|
||||
? {
|
||||
...change.value,
|
||||
current: armorSource.system.armorChange.value.current + usedArmorChange
|
||||
}
|
||||
...change.value,
|
||||
current: armorSource.system.armorChange.value.current + usedArmorChange
|
||||
}
|
||||
: change.value
|
||||
}))
|
||||
});
|
||||
|
|
@ -621,21 +638,21 @@ export default class DhCharacter extends DhCreature {
|
|||
},
|
||||
...(multiclassFeatures.length
|
||||
? {
|
||||
multiclassFeatures: {
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`,
|
||||
type: 'multiclass',
|
||||
values: multiclassFeatures
|
||||
}
|
||||
}
|
||||
multiclassFeatures: {
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`,
|
||||
type: 'multiclass',
|
||||
values: multiclassFeatures
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(multiclassSubclassFeatures.length
|
||||
? {
|
||||
multiclassSubclassFeatures: {
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`,
|
||||
type: 'multiclassSubclass',
|
||||
values: multiclassSubclassFeatures
|
||||
}
|
||||
}
|
||||
multiclassSubclassFeatures: {
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`,
|
||||
type: 'multiclassSubclass',
|
||||
values: multiclassSubclassFeatures
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
companionFeatures: {
|
||||
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionFeatures'),
|
||||
|
|
@ -659,8 +676,8 @@ export default class DhCharacter extends DhCreature {
|
|||
(this.primaryWeapon && this.secondaryWeapon)
|
||||
? burden.twoHanded.value
|
||||
: this.primaryWeapon || this.secondaryWeapon
|
||||
? burden.oneHanded.value
|
||||
: null;
|
||||
? burden.oneHanded.value
|
||||
: null;
|
||||
}
|
||||
|
||||
get deathMoveViable() {
|
||||
|
|
@ -726,8 +743,8 @@ export default class DhCharacter extends DhCreature {
|
|||
currentLevel === 1
|
||||
? null
|
||||
: Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find(
|
||||
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
|
||||
).tier;
|
||||
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
|
||||
).tier;
|
||||
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto) {
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ export default class DhCompanion extends DhCreature {
|
|||
parts: {
|
||||
hitPoints: {
|
||||
type: ['physical'],
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
dice: 'd6',
|
||||
multiplier: 'prof'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs';
|
||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
||||
import { parseInlineParams } from '../../enrichers/parser.mjs';
|
||||
|
||||
export function getTierAdjustedAdversary(source, tier) {
|
||||
const currentTier = source.tier ?? 1;
|
||||
|
|
@ -60,8 +61,8 @@ export function getTierAdjustedAdversary(source, tier) {
|
|||
const descriptionFormulas = [];
|
||||
for (const withDescription of [item.system, ...Object.values(item.system.actions)]) {
|
||||
withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => {
|
||||
const { value: formula } = parseInlineParams(inner);
|
||||
if (!formula || !type) return match;
|
||||
const { value: formula } = parseInlineParams(inner, { first: 'value' });
|
||||
if (!formula) return match;
|
||||
|
||||
try {
|
||||
const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula;
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ export class DhCompanionLevelup extends foundry.abstract.DataModel {
|
|||
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
|
||||
const experiences = initialAchievements.experience
|
||||
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: initialAchievements.experience.modifier
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
acc[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: initialAchievements.experience.modifier
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
: {};
|
||||
|
||||
const currentChoices = pcLevelData.levelups[i]?.selections?.length;
|
||||
|
|
@ -302,9 +302,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel {
|
|||
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
|
||||
domainCards: levelData.achievements?.domainCards
|
||||
? levelData.achievements.domainCards.reduce((acc, card, index) => {
|
||||
acc[index] = { ...card };
|
||||
return acc;
|
||||
}, {})
|
||||
acc[index] = { ...card };
|
||||
return acc;
|
||||
}, {})
|
||||
: (achievements.domainCards ?? {}),
|
||||
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,54 @@ export default class DhCountdowns extends foundry.abstract.DataModel {
|
|||
})
|
||||
};
|
||||
}
|
||||
|
||||
handleChange() {
|
||||
const previousCountdowns = foundry.ui.countdowns.previousCountdownData;
|
||||
const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => {
|
||||
const previousCountdown = previousCountdowns[key];
|
||||
if (!previousCountdown || (previousCountdown.progress.current !== countdown.progress.current)) {
|
||||
acc.push(key);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
for (const countdownKey of changedCountdowns)
|
||||
foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey);
|
||||
}
|
||||
|
||||
static migrateData(source) {
|
||||
const migrateOldCountdowns = (data, type) => {
|
||||
for (const key of Object.keys(data.countdowns)) {
|
||||
const countdown = data.countdowns[key];
|
||||
source.countdowns[key] = {
|
||||
...countdown,
|
||||
type: type,
|
||||
ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => {
|
||||
acc[key] =
|
||||
countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type;
|
||||
return acc;
|
||||
}, {}),
|
||||
progress: {
|
||||
...countdown.progress,
|
||||
type: countdown.progress.type.value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
source[type] = null;
|
||||
};
|
||||
|
||||
if (source.narrative) {
|
||||
migrateOldCountdowns(source.narrative, 'narrative');
|
||||
}
|
||||
|
||||
if (source.encounter) {
|
||||
migrateOldCountdowns(source.encounter, 'encounter');
|
||||
}
|
||||
|
||||
return super.migrateData(source);
|
||||
}
|
||||
}
|
||||
|
||||
export class DhCountdown extends foundry.abstract.DataModel {
|
||||
|
|
@ -21,7 +69,8 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
|||
return {
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.countdownBaseTypes,
|
||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownTypes.encounter.id,
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
}),
|
||||
name: new fields.StringField({
|
||||
|
|
@ -77,15 +126,15 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
|||
static defaultCountdown(type, playerHidden) {
|
||||
const ownership = playerHidden
|
||||
? game.users.reduce((acc, user) => {
|
||||
if (!user.isGM) {
|
||||
acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE;
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
if (!user.isGM) {
|
||||
acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE;
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
type: type ?? CONFIG.DH.GENERAL.countdownBaseTypes.narrative.id,
|
||||
type: type ?? CONFIG.DH.GENERAL.countdownTypes.encounter.id,
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'),
|
||||
img: 'icons/magic/time/hourglass-yellow-green.webp',
|
||||
ownership: ownership,
|
||||
|
|
@ -102,8 +151,8 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
|||
value: user.isGM
|
||||
? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
|
||||
: this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1
|
||||
? this.ownership.players[user.id].type
|
||||
: this.ownership.default,
|
||||
? this.ownership.players[user.id].type
|
||||
: this.ownership.default,
|
||||
isGM: user.isGM
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@ export default class BeastformField extends fields.SchemaField {
|
|||
baseSize === 'custom'
|
||||
? 'custom'
|
||||
: (Object.keys(CONFIG.DH.ACTOR.tokenSize).find(
|
||||
x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1
|
||||
) ?? baseSize);
|
||||
x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1
|
||||
) ?? baseSize);
|
||||
formData.system.tokenSize = {
|
||||
...evolvedData.form.system.tokenSize,
|
||||
size: evolvedSize
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ export default class CostField extends fields.ArrayField {
|
|||
c.key === 'fear'
|
||||
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||
: resources[c.key].isReversed
|
||||
? resources[c.key].max - resources[c.key].value
|
||||
: resources[c.key].value;
|
||||
? resources[c.key].max - resources[c.key].value
|
||||
: resources[c.key].value;
|
||||
if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step);
|
||||
return c;
|
||||
});
|
||||
|
|
@ -149,8 +149,8 @@ export default class CostField extends fields.ArrayField {
|
|||
!resources[c.key]
|
||||
? a
|
||||
: a && resources[c.key].isReversed
|
||||
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
|
||||
: resources[c.key]?.value >= (c.total ?? c.value),
|
||||
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
|
||||
: resources[c.key]?.value >= (c.total ?? c.value),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ export default class CountdownField extends fields.ArrayField {
|
|||
...game.system.api.data.countdowns.DhCountdown.defineSchema(),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.countdownBaseTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id,
|
||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownTypes.encounter.id,
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
}),
|
||||
name: new fields.StringField({
|
||||
|
|
@ -87,11 +87,11 @@ export default class CountdownField extends fields.ArrayField {
|
|||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
||||
countdownSetting.toObject()
|
||||
),
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.Countdown }
|
||||
});
|
||||
);
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.Countdown }
|
||||
});
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||
},
|
||||
data,
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@ export default class EffectsField extends fields.ArrayField {
|
|||
token: messageToken,
|
||||
conditionImmunities: Object.values(conditionImmunities).some(x => x)
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', {
|
||||
immunities: Object.keys(conditionImmunities)
|
||||
.filter(x => conditionImmunities[x])
|
||||
.map(x => game.i18n.localize(conditions[x].name))
|
||||
.join(', ')
|
||||
})
|
||||
immunities: Object.keys(conditionImmunities)
|
||||
.filter(x => conditionImmunities[x])
|
||||
.map(x => game.i18n.localize(conditions[x].name))
|
||||
.join(', ')
|
||||
})
|
||||
: null
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -69,11 +69,11 @@ export default class SaveField extends fields.SchemaField {
|
|||
game.user === actor.owner
|
||||
? SaveField.rollSave.call(this, actor, event)
|
||||
: actor.owner.query('reactionRoll', {
|
||||
actionId: this.uuid,
|
||||
actorId: actor.uuid,
|
||||
event,
|
||||
message
|
||||
});
|
||||
actionId: this.uuid,
|
||||
actorId: actor.uuid,
|
||||
event,
|
||||
message
|
||||
});
|
||||
const result = await rollSave;
|
||||
await SaveField.updateSaveMessage.call(this, result, message, target.id);
|
||||
subResolve();
|
||||
|
|
@ -97,8 +97,8 @@ export default class SaveField extends fields.SchemaField {
|
|||
const title = actor.isNPC
|
||||
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
|
||||
: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.save.trait]?.label)
|
||||
}),
|
||||
ability: game.i18n.localize(abilities[this.save.trait]?.label)
|
||||
}),
|
||||
rollConfig = {
|
||||
event,
|
||||
title,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs';
|
||||
import { getWorldActor, itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs';
|
||||
import FormulaField from '../formulaField.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -42,7 +42,7 @@ export default class DHSummonField extends fields.ArrayField {
|
|||
const count = roll.total;
|
||||
if (!roll.isDeterministic) rolls.push(roll);
|
||||
|
||||
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
||||
const actor = await getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
||||
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
||||
summon.actor = actor.toObject();
|
||||
|
||||
|
|
@ -62,19 +62,6 @@ export default class DHSummonField extends fields.ArrayField {
|
|||
DHSummonField.handleSummon(summonData, this.actor);
|
||||
}
|
||||
|
||||
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */
|
||||
static async getWorldActor(baseActor) {
|
||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
||||
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
||||
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
||||
if (worldActorCopy) return worldActorCopy;
|
||||
|
||||
return await game.system.api.documents.DhpActor.create(baseActor.toObject());
|
||||
}
|
||||
|
||||
return baseActor;
|
||||
}
|
||||
|
||||
static async handleSummon(summonData, actionActor) {
|
||||
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
|
||||
|
||||
|
|
|
|||
|
|
@ -116,13 +116,16 @@ class ResourcesField extends fields.TypedObjectField {
|
|||
}
|
||||
|
||||
class GoldField extends fields.SchemaField {
|
||||
constructor() {
|
||||
super({
|
||||
coins: new fields.NumberField({ initial: 0, integer: true }),
|
||||
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||
});
|
||||
constructor(options = {}) {
|
||||
super(
|
||||
{
|
||||
coins: new fields.NumberField({ initial: 0, integer: true }),
|
||||
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default class FormulaField extends foundry.data.fields.StringField {
|
|||
let roll = null;
|
||||
try {
|
||||
roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, '1'));
|
||||
} catch (_) {
|
||||
} catch {
|
||||
roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, 'd6'));
|
||||
}
|
||||
roll.evaluateSync({ strict: false });
|
||||
|
|
|
|||
8
module/data/item/_types.d.ts
vendored
Normal file
8
module/data/item/_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import DHItem from '../../documents/item.mjs';
|
||||
|
||||
declare module './base.mjs' {
|
||||
export default interface BaseDataItem {
|
||||
parent: DHItem<this>;
|
||||
actor: DhpActor;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import ItemLinkFields from '../../data/fields/itemLinkFields.mjs';
|
||||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHAncestry extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -45,6 +45,10 @@ export default class DHAncestry extends BaseDataItem {
|
|||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
// Preload all ancestry features for acquisition from the cache
|
||||
// todo: make feature acquisition async and replace feature helpers for methods
|
||||
await fromUuids(this._source.features.map(f => f.item));
|
||||
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ export default class DHArmor extends AttachableItem {
|
|||
);
|
||||
}
|
||||
|
||||
get itemFeatures() {
|
||||
return this.armorFeatures;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
|
@ -169,8 +173,4 @@ export default class DHArmor extends AttachableItem {
|
|||
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`];
|
||||
return labels;
|
||||
}
|
||||
|
||||
get itemFeatures() {
|
||||
return this.armorFeatures;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,8 +208,8 @@ export default class DHBeastform extends BaseDataItem {
|
|||
const autoTokenSize =
|
||||
this.tokenSize.size !== 'custom'
|
||||
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[
|
||||
this.tokenSize.size
|
||||
]
|
||||
this.tokenSize.size
|
||||
]
|
||||
: null;
|
||||
const width = autoTokenSize ?? this.tokenSize.width;
|
||||
const height = autoTokenSize ?? this.tokenSize.height;
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ export default class DHClass extends BaseDataItem {
|
|||
}
|
||||
|
||||
async fetchSubclasses() {
|
||||
const uuids = [this.parent.uuid, this.parent._stats?.compendiumSource].filter(u => !!u);
|
||||
const subclasses = game.items.filter(x => x.type === 'subclass' && uuids.includes(x.system.linkedClass));
|
||||
const sourceUuid = this.parent.sourceUuid;
|
||||
const subclasses = game.items.filter(x => x.type === 'subclass' && x.system.linkedClass === sourceUuid);
|
||||
for (const pack of game.packs) {
|
||||
const packIds = [];
|
||||
const indexes = await pack.getIndex({ fields: ['system.linkedClass'] });
|
||||
for (const index of indexes) {
|
||||
if (index.type !== 'subclass') continue;
|
||||
if (!uuids.includes(index.system?.linkedClass)) continue;
|
||||
if (index.system?.linkedClass !== sourceUuid) continue;
|
||||
if (subclasses.find(x => x.uuid === index.uuid)) continue;
|
||||
packIds.push(index._id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
||||
|
|
@ -27,6 +27,10 @@ export default class DHCommunity extends BaseDataItem {
|
|||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
// Preload all community features for acquisition from the cache
|
||||
// todo: make feature acquisition async and replace feature helpers for methods
|
||||
await fromUuids(this._source.features);
|
||||
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
|
|
|
|||
|
|
@ -70,9 +70,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
return false;
|
||||
}
|
||||
|
||||
const match = [multiclass, actorClass].find(
|
||||
c => c && (c._stats.compendiumSource ?? c.uuid) === this.linkedClass
|
||||
);
|
||||
const match = [multiclass, actorClass].find(c => c && c.sourceUuid === this.linkedClass);
|
||||
if (!match) {
|
||||
const key = multiclass ? 'subclassNotInMulticlass' : 'subclassNotInClass';
|
||||
ui.notifications.warn(`DAGGERHEART.UI.Notifications.${key}`, { localize: true });
|
||||
|
|
@ -91,7 +89,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label)
|
||||
: null;
|
||||
|
||||
// Preload all class features for acquisition from the cache
|
||||
// Preload all subclass features for acquisition from the cache
|
||||
// todo: make feature acquisition async and replace feature helpers for methods
|
||||
await fromUuids(this._source.features.map(f => f.item));
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ export default class DHWeapon extends AttachableItem {
|
|||
);
|
||||
}
|
||||
|
||||
get itemFeatures() {
|
||||
return this.weaponFeatures;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
|
@ -269,8 +273,4 @@ export default class DHWeapon extends AttachableItem {
|
|||
|
||||
return labels;
|
||||
}
|
||||
|
||||
get itemFeatures() {
|
||||
return this.weaponFeatures;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
|
||||
const experiences = initialAchievements.experience
|
||||
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: initialAchievements.experience.modifier
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
acc[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: initialAchievements.experience.modifier
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
: {};
|
||||
|
||||
const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => {
|
||||
|
|
@ -298,9 +298,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel {
|
|||
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
|
||||
domainCards: levelData.achievements?.domainCards
|
||||
? levelData.achievements.domainCards.reduce((acc, card, index) => {
|
||||
acc[index] = { ...card };
|
||||
return acc;
|
||||
}, {})
|
||||
acc[index] = { ...card };
|
||||
return acc;
|
||||
}, {})
|
||||
: (achievements.domainCards ?? {}),
|
||||
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
|
||||
},
|
||||
|
|
|
|||
|
|
@ -149,12 +149,15 @@ export default class RegisteredTriggers extends Map {
|
|||
|
||||
const result = await command(...args);
|
||||
if (result?.updates?.length) updates.push(...result.updates);
|
||||
} catch (_) {
|
||||
} catch {
|
||||
const item = await foundry.utils.fromUuid(itemUuid);
|
||||
const triggerName = game.i18n.localize(triggerData.label);
|
||||
ui.notifications.error(
|
||||
console.error(
|
||||
game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', {
|
||||
trigger: triggerName,
|
||||
actor: currentActor?.name
|
||||
actor: currentActor?.name,
|
||||
item: item?.name ?? '<Missing Item>',
|
||||
itemUuid: item?.uuid ?? '<Missing UUID>'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { defaultRestOptions } from '../../config/generalConfig.mjs';
|
|||
import { resetAndRerenderActors } from '../../helpers/utils.mjs';
|
||||
import { ActionsField } from '../fields/actionField.mjs';
|
||||
|
||||
const currencyField = (initial, label, icon) =>
|
||||
const currencyField = (initial, label, icon, initialAmount = 0) =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }),
|
||||
label: new foundry.data.fields.StringField({
|
||||
|
|
@ -10,7 +10,14 @@ const currencyField = (initial, label, icon) =>
|
|||
initial,
|
||||
label
|
||||
}),
|
||||
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
|
||||
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon }),
|
||||
initialAmount: new foundry.data.fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: initialAmount,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.initialAmount'
|
||||
})
|
||||
});
|
||||
|
||||
const restMoveField = () =>
|
||||
|
|
@ -108,7 +115,8 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
handfuls: currencyField(
|
||||
'Handfuls',
|
||||
'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName',
|
||||
'fa-solid fa-coins'
|
||||
'fa-solid fa-coins',
|
||||
1
|
||||
),
|
||||
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName', 'fa-solid fa-sack'),
|
||||
chests: currencyField(
|
||||
|
|
@ -193,6 +201,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
for (const type of ['coins', 'handfuls', 'bags', 'chests']) {
|
||||
const initial = this.schema.fields.currency.fields[type].getInitialValue();
|
||||
source.currency[type] = foundry.utils.mergeObject(initial, source.currency[type], { inplace: false });
|
||||
source.currency[type].initialAmount ??= 0;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue