mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 22:46:12 +01:00
Merged with main
This commit is contained in:
commit
2123ae1965
177 changed files with 2677 additions and 657 deletions
|
|
@ -3,6 +3,7 @@ export { default as DhCombatant } from './combatant.mjs';
|
|||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||
export { default as DhRollTable } from './rollTable.mjs';
|
||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
export * as actions from './action/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -34,6 +34,20 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
};
|
||||
}
|
||||
|
||||
get damageFormula() {
|
||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
||||
if (!hitPointsPart) return '0';
|
||||
|
||||
return hitPointsPart.value.getFormula();
|
||||
}
|
||||
|
||||
get altDamageFormula() {
|
||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
||||
if (!hitPointsPart) return '0';
|
||||
|
||||
return hitPointsPart.valueAlt.getFormula();
|
||||
}
|
||||
|
||||
async use(event, options) {
|
||||
const result = await super.use(event, options);
|
||||
if (!result.message) return;
|
||||
|
|
|
|||
|
|
@ -114,9 +114,24 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* Return Item the action is attached too.
|
||||
*/
|
||||
get item() {
|
||||
if (!this.parent.parent && this.systemPath)
|
||||
return foundry.utils.getProperty(this.parent, this.systemPath).get(this.id);
|
||||
|
||||
return this.parent.parent;
|
||||
}
|
||||
|
||||
get applyEffects() {
|
||||
if (this.item.systemPath) {
|
||||
const itemEffectIds = this.item.effects.map(x => x._id);
|
||||
const movePathSplit = this.item.systemPath.split('.');
|
||||
movePathSplit.pop();
|
||||
const move = foundry.utils.getProperty(this.parent, movePathSplit.join('.'));
|
||||
return new Collection(itemEffectIds.map(id => [id, move.effects.find(x => x.id === id)]));
|
||||
}
|
||||
|
||||
return this.item.effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first Actor parent found.
|
||||
*/
|
||||
|
|
@ -125,7 +140,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
? this.item
|
||||
: this.item?.parent instanceof DhpActor
|
||||
? this.item.parent
|
||||
: this.item?.actor;
|
||||
: null;
|
||||
}
|
||||
|
||||
static getRollType(parent) {
|
||||
|
|
@ -214,7 +229,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||
|
||||
if (this.chatDisplay) await this.toChat();
|
||||
if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -225,9 +240,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @returns {object}
|
||||
*/
|
||||
prepareBaseConfig(event) {
|
||||
const isActor = this.item instanceof CONFIG.Actor.documentClass;
|
||||
const actionTitle = game.i18n.localize(this.name);
|
||||
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
|
||||
|
||||
const config = {
|
||||
event,
|
||||
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
|
||||
title: `${itemTitle}${actionTitle}`,
|
||||
source: {
|
||||
item: this.item._id,
|
||||
originItem: this.originItem,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||
import { commonActorRules } from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs';
|
||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
||||
|
||||
export default class DhpAdversary extends BaseDataActor {
|
||||
export default class DhpAdversary extends DhCreature {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
||||
|
||||
static get metadata() {
|
||||
|
|
@ -40,7 +43,14 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hordeHp'
|
||||
}),
|
||||
criticalThreshold: new fields.NumberField({ required: true, integer: true, min: 1, max: 20, initial: 20 }),
|
||||
criticalThreshold: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 1,
|
||||
max: 20,
|
||||
initial: 20,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.criticalThreshold'
|
||||
}),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
major: new fields.NumberField({
|
||||
required: true,
|
||||
|
|
@ -180,6 +190,10 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.attack.roll.isStandardAttack = true;
|
||||
}
|
||||
|
||||
_getTags() {
|
||||
const tags = [
|
||||
game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`),
|
||||
|
|
@ -188,4 +202,211 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
];
|
||||
return tags;
|
||||
}
|
||||
|
||||
/** Returns source data for this actor adjusted to a new tier, which can be used to create a new actor. */
|
||||
adjustForTier(tier) {
|
||||
const source = this.parent.toObject(true);
|
||||
|
||||
/** @type {(2 | 3 | 4)[]} */
|
||||
const tiers = new Array(Math.abs(tier - this.tier))
|
||||
.fill(0)
|
||||
.map((_, idx) => idx + Math.min(tier, this.tier) + 1);
|
||||
if (tier < this.tier) tiers.reverse();
|
||||
const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard];
|
||||
const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] }));
|
||||
|
||||
// Apply simple tier changes
|
||||
const scale = tier > this.tier ? 1 : -1;
|
||||
for (const entry of tierEntries) {
|
||||
source.system.difficulty += scale * entry.difficulty;
|
||||
source.system.damageThresholds.major += scale * entry.majorThreshold;
|
||||
source.system.damageThresholds.severe += scale * entry.severeThreshold;
|
||||
source.system.resources.hitPoints.max += scale * entry.hp;
|
||||
source.system.resources.stress.max += scale * entry.stress;
|
||||
source.system.attack.roll.bonus += scale * entry.attack;
|
||||
}
|
||||
|
||||
// Get the mean and standard deviation of expected damage in the previous and new tier
|
||||
// The data we have is for attack scaling, but we reuse this for action scaling later
|
||||
const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic;
|
||||
const damageMeta = {
|
||||
currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] },
|
||||
newDamageRange: { tier, ...expectedDamageData[tier] },
|
||||
type: 'attack'
|
||||
};
|
||||
|
||||
// Update damage of base attack
|
||||
try {
|
||||
this.#adjustActionDamage(source.system.attack, damageMeta);
|
||||
} catch (err) {
|
||||
ui.notifications.warn('Failed to convert attack damage of adversary');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Update damage of each item action, making sure to also update the description if possible
|
||||
const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g;
|
||||
for (const item of source.items) {
|
||||
// Replace damage inlines with new formulas
|
||||
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;
|
||||
|
||||
try {
|
||||
const adjusted = this.#calculateAdjustedDamage(formula, { ...damageMeta, type: 'action' });
|
||||
const newFormula = [
|
||||
adjusted.diceQuantity ? `${adjusted.diceQuantity}d${adjusted.faces}` : null,
|
||||
adjusted.bonus
|
||||
]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
return match.replace(formula, newFormula);
|
||||
} catch {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update damage in item actions
|
||||
for (const action of Object.values(item.system.actions)) {
|
||||
if (!action.damage) continue;
|
||||
|
||||
// Parse damage, and convert all formula matches in the descriptions to the new damage
|
||||
try {
|
||||
const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' });
|
||||
for (const { previousFormula, formula } of Object.values(result)) {
|
||||
const oldFormulaRegexp = new RegExp(
|
||||
previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?')
|
||||
);
|
||||
item.system.description = item.system.description.replace(oldFormulaRegexp, formula);
|
||||
action.description = action.description.replace(oldFormulaRegexp, formula);
|
||||
}
|
||||
} catch (err) {
|
||||
ui.notifications.warn(`Failed to convert action damage for item ${item.name}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally set the tier of the source data, now that everything is complete
|
||||
source.system.tier = tier;
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a damage object to a new damage range
|
||||
* @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term
|
||||
* @throws error if the formula is the wrong type
|
||||
*/
|
||||
#calculateAdjustedDamage(formula, { currentDamageRange, newDamageRange, type }) {
|
||||
const terms = parseTermsFromSimpleFormula(formula);
|
||||
const flatTerms = terms.filter(t => t.diceQuantity === 0);
|
||||
const diceTerms = terms.filter(t => t.diceQuantity > 0);
|
||||
if (flatTerms.length > 1 || diceTerms.length > 1) {
|
||||
throw new Error('invalid formula for conversion');
|
||||
}
|
||||
const value = {
|
||||
...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }),
|
||||
bonus: flatTerms[0]?.bonus ?? 0
|
||||
};
|
||||
const previousExpected = calculateExpectedValue(value);
|
||||
if (previousExpected === 0) return value; // nothing to do
|
||||
|
||||
const dieSizes = [4, 6, 8, 10, 12, 20];
|
||||
const steps = newDamageRange.tier - currentDamageRange.tier;
|
||||
const increasing = steps > 0;
|
||||
const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation;
|
||||
const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation);
|
||||
|
||||
// If this was just a flat number, convert to the expected damage and exit
|
||||
if (value.diceQuantity === 0) {
|
||||
value.bonus = Math.round(expected);
|
||||
return value;
|
||||
}
|
||||
|
||||
const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1;
|
||||
const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 });
|
||||
|
||||
// Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die
|
||||
const baseOverages = Math.floor(value.bonus / getExpectedDie());
|
||||
|
||||
// Prestep. Change number of dice for attacks, bump up/down for actions
|
||||
// We never bump up to d20, though we might bump down from it
|
||||
if (type === 'attack') {
|
||||
const minimum = increasing ? value.diceQuantity : 0;
|
||||
value.diceQuantity = Math.max(minimum, newDamageRange.tier);
|
||||
} else {
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)];
|
||||
}
|
||||
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
|
||||
// Attempt to handle negative values.
|
||||
// If we can do it with only step downs, do so. Otherwise remove tier dice, and try again
|
||||
if (value.bonus < 0) {
|
||||
let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
|
||||
// If step downs alone don't suffice, change the flat modifier, then calculate steps required again
|
||||
// If this isn't sufficient, the result will be slightly off. This is unlikely to happen
|
||||
if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) {
|
||||
value.diceQuantity -= increasing ? 1 : Math.abs(steps);
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
if (value.bonus >= 0) return value; // complete
|
||||
}
|
||||
|
||||
stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)];
|
||||
value.bonus = Math.max(0, Math.round(expected - getBaseAverage()));
|
||||
}
|
||||
|
||||
// If value is really high, we add a number of dice based on the number of overages
|
||||
// This attempts to preserve a similar amount of variance when increasing an action
|
||||
const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages;
|
||||
if (type !== 'attack' && increasing && overagesToRemove > 0) {
|
||||
value.diceQuantity += overagesToRemove;
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates damage to reflect a specific value.
|
||||
* @throws if damage structure is invalid for conversion
|
||||
* @returns the converted formula and value as a simplified term
|
||||
*/
|
||||
#adjustActionDamage(action, damageMeta) {
|
||||
// The current algorithm only returns a value if there is a single damage part
|
||||
const hpDamageParts = action.damage.parts.filter(d => d.applyTo === 'hitPoints');
|
||||
if (hpDamageParts.length !== 1) throw new Error('incorrect number of hp parts');
|
||||
|
||||
const result = {};
|
||||
for (const property of ['value', 'valueAlt']) {
|
||||
const data = hpDamageParts[0][property];
|
||||
const previousFormula = data.custom.enabled
|
||||
? data.custom.formula
|
||||
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
const value = this.#calculateAdjustedDamage(previousFormula, damageMeta);
|
||||
const formula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
if (value.diceQuantity) {
|
||||
data.custom.enabled = false;
|
||||
data.bonus = value.bonus;
|
||||
data.dice = `d${value.faces}`;
|
||||
data.flatMultiplier = value.diceQuantity;
|
||||
} else if (!value.diceQuantity) {
|
||||
data.custom.enabled = true;
|
||||
data.custom.formula = formula;
|
||||
}
|
||||
|
||||
result[property] = { previousFormula, formula, value };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,40 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
|||
/* Common rules applying to Characters and Adversaries */
|
||||
export const commonActorRules = (extendedData = { damageReduction: {}, attack: { damage: {} } }) => ({
|
||||
conditionImmunities: new fields.SchemaField({
|
||||
hidden: new fields.BooleanField({ initial: false }),
|
||||
restrained: new fields.BooleanField({ initial: false }),
|
||||
vulnerable: new fields.BooleanField({ initial: false })
|
||||
hidden: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.hidden'
|
||||
}),
|
||||
restrained: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.restrained'
|
||||
}),
|
||||
vulnerable: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
||||
})
|
||||
}),
|
||||
damageReduction: new fields.SchemaField({
|
||||
thresholdImmunities: new fields.SchemaField({
|
||||
minor: new fields.BooleanField({ initial: false })
|
||||
minor: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.thresholdImmunities.minor.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.thresholdImmunities.minor.hint'
|
||||
})
|
||||
}),
|
||||
reduceSeverity: new fields.SchemaField({
|
||||
magical: new fields.NumberField({ initial: 0, min: 0 }),
|
||||
physical: new fields.NumberField({ initial: 0, min: 0 })
|
||||
magical: new fields.NumberField({
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.magical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.magical.hint'
|
||||
}),
|
||||
physical: new fields.NumberField({
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.physical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.physical.hint'
|
||||
})
|
||||
}),
|
||||
...(extendedData.damageReduction ?? {})
|
||||
}),
|
||||
|
|
@ -49,12 +72,16 @@ export const commonActorRules = (extendedData = { damageReduction: {}, attack: {
|
|||
hpDamageMultiplier: new fields.NumberField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 1
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Attack.hpDamageMultiplier.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Attack.hpDamageMultiplier.hint'
|
||||
}),
|
||||
hpDamageTakenMultiplier: new fields.NumberField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 1
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Attack.hpDamageTakenMultiplier.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Attack.hpDamageTakenMultiplier.hint'
|
||||
}),
|
||||
...(extendedData.attack?.damage ?? {})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { burden } from '../../config/generalConfig.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||
import { commonActorRules } from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
|
||||
export default class DhCharacter extends BaseDataActor {
|
||||
export default class DhCharacter extends DhCreature {
|
||||
/**@override */
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||
|
||||
|
|
@ -36,14 +37,18 @@ export default class DhCharacter extends BaseDataActor {
|
|||
'DAGGERHEART.ACTORS.Character.maxHPBonus'
|
||||
),
|
||||
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: new fields.SchemaField({
|
||||
value: new fields.NumberField({
|
||||
initial: 2,
|
||||
min: 0,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hope'
|
||||
})
|
||||
})
|
||||
hope: new fields.SchemaField(
|
||||
{
|
||||
value: new fields.NumberField({
|
||||
initial: 2,
|
||||
min: 0,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hope'
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: false })
|
||||
},
|
||||
{ label: 'DAGGERHEART.GENERAL.hope' }
|
||||
)
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||
|
|
@ -128,14 +133,6 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
}),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||
}),
|
||||
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
roll: new fields.SchemaField({
|
||||
|
|
@ -222,8 +219,16 @@ export default class DhCharacter extends BaseDataActor {
|
|||
rules: new fields.SchemaField({
|
||||
...commonActorRules({
|
||||
damageReduction: {
|
||||
magical: new fields.BooleanField({ initial: false }),
|
||||
physical: new fields.BooleanField({ initial: false }),
|
||||
magical: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.magical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.magical.hint'
|
||||
}),
|
||||
physical: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.physical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.physical.hint'
|
||||
}),
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({
|
||||
required: true,
|
||||
|
|
@ -253,7 +258,10 @@ export default class DhCharacter extends BaseDataActor {
|
|||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||
}),
|
||||
disabledArmor: new fields.BooleanField({ intial: false })
|
||||
disabledArmor: new fields.BooleanField({
|
||||
intial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.disabledArmor.label'
|
||||
})
|
||||
},
|
||||
attack: {
|
||||
damage: {
|
||||
|
|
@ -301,12 +309,14 @@ export default class DhCharacter extends BaseDataActor {
|
|||
label: 'DAGGERHEART.ACTORS.Character.defaultFearDice'
|
||||
})
|
||||
}),
|
||||
runeWard: new fields.BooleanField({ initial: false }),
|
||||
burden: new fields.SchemaField({
|
||||
ignore: new fields.BooleanField()
|
||||
ignore: new fields.BooleanField({ label: 'DAGGERHEART.ACTORS.Character.burden.ignore.label' })
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
guaranteedCritical: new fields.BooleanField()
|
||||
guaranteedCritical: new fields.BooleanField({
|
||||
label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.hint'
|
||||
})
|
||||
})
|
||||
}),
|
||||
sidebarFavorites: new ForeignDocumentUUIDArrayField({ type: 'Item' })
|
||||
|
|
@ -640,7 +650,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
};
|
||||
|
||||
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
||||
this.resources.hope.max = globalHopeMax - this.scars;
|
||||
this.resources.hope.max = globalHopeMax;
|
||||
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
||||
|
||||
/* Companion Related Data */
|
||||
|
|
@ -664,6 +674,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
|
||||
this.resources.hope.max -= this.scars;
|
||||
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
|
|
@ -6,7 +6,7 @@ import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
|||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhCompanion extends BaseDataActor {
|
||||
export default class DhCompanion extends DhCreature {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
||||
|
||||
/**@inheritdoc */
|
||||
|
|
@ -53,9 +53,18 @@ export default class DhCompanion extends BaseDataActor {
|
|||
),
|
||||
rules: new fields.SchemaField({
|
||||
conditionImmunities: new fields.SchemaField({
|
||||
hidden: new fields.BooleanField({ initial: false }),
|
||||
restrained: new fields.BooleanField({ initial: false }),
|
||||
vulnerable: new fields.BooleanField({ initial: false })
|
||||
hidden: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.hidden'
|
||||
}),
|
||||
restrained: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.restrained'
|
||||
}),
|
||||
vulnerable: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
||||
})
|
||||
})
|
||||
}),
|
||||
attack: new ActionField({
|
||||
|
|
|
|||
20
module/data/actor/creature.mjs
Normal file
20
module/data/actor/creature.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
|
||||
export default class DhCreature extends BaseDataActor {
|
||||
/**@inheritdoc */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||
}),
|
||||
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
static defineSchema() {
|
||||
return {
|
||||
title: new fields.StringField(),
|
||||
actionDescription: new fields.HTMLField(),
|
||||
roll: new fields.ObjectField(),
|
||||
targets: targetsField(),
|
||||
hasRoll: new fields.BooleanField({ initial: false }),
|
||||
|
|
|
|||
36
module/data/compendiumBrowserSettings.mjs
Normal file
36
module/data/compendiumBrowserSettings.mjs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export default class CompendiumBrowserSettings extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
excludedSources: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
excludedDocumentTypes: new fields.ArrayField(
|
||||
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
|
||||
)
|
||||
})
|
||||
),
|
||||
excludedPacks: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
excludedDocumentTypes: new fields.ArrayField(
|
||||
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
isEntryExcluded(item) {
|
||||
const pack = game.packs.get(item.pack);
|
||||
if (!pack) return false;
|
||||
|
||||
const packageName = pack.metadata.packageType === 'world' ? 'world' : pack.metadata.packageName;
|
||||
const excludedSourceData = this.excludedSources[packageName];
|
||||
if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
||||
|
||||
const excludedPackData = this.excludedPacks[item.pack];
|
||||
if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,8 @@ export default class DamageField extends fields.SchemaField {
|
|||
|
||||
const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
|
||||
if (!damageResult) return false;
|
||||
if (damageResult.actionChatMessageHandled) config.actionChatMessageHandled = true;
|
||||
|
||||
config.damage = damageResult.damage;
|
||||
config.message ??= damageConfig.message;
|
||||
}
|
||||
|
|
@ -107,8 +109,8 @@ export default class DamageField extends fields.SchemaField {
|
|||
);
|
||||
else {
|
||||
const configDamage = foundry.utils.deepClone(config.damage);
|
||||
const hpDamageMultiplier = config.actionActor?.system.rules.attack.damage.hpDamageMultiplier ?? 1;
|
||||
const hpDamageTakenMultiplier = actor.system.rules.attack.damage.hpDamageTakenMultiplier;
|
||||
const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
||||
const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
||||
if (configDamage.hitPoints) {
|
||||
for (const part of configDamage.hitPoints.parts) {
|
||||
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
||||
|
|
@ -163,7 +165,8 @@ export default class DamageField extends fields.SchemaField {
|
|||
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
|
||||
|
||||
const isAdversary = this.actor.type === 'adversary';
|
||||
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||
const isHorde = this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id;
|
||||
if (isAdversary && isHorde && this.roll?.isStandardAttack) {
|
||||
const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
|
||||
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
});
|
||||
|
||||
effects.forEach(async e => {
|
||||
const effect = this.item.effects.get(e._id);
|
||||
const effect = (this.item.applyEffects ?? this.item.effects).get(e._id);
|
||||
if (!token.actor || !effect) return;
|
||||
await EffectsField.applyEffect(effect, token.actor);
|
||||
});
|
||||
|
|
@ -96,7 +96,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/effectSummary.hbs',
|
||||
{
|
||||
effects: this.effects.map(e => this.item.effects.get(e._id)),
|
||||
effects: this.effects.map(e => (this.item.applyEffects ?? this.item.effects).get(e._id)),
|
||||
targets: messageTargets
|
||||
}
|
||||
)
|
||||
|
|
@ -123,7 +123,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
|
||||
// Otherwise, create a new effect on the target
|
||||
const effectData = foundry.utils.mergeObject({
|
||||
...effect.toObject(),
|
||||
...(effect.toObject?.() ?? effect),
|
||||
disabled: false,
|
||||
transfer: false,
|
||||
origin: effect.uuid
|
||||
|
|
|
|||
|
|
@ -152,7 +152,9 @@ export function ActionMixin(Base) {
|
|||
}
|
||||
|
||||
get uuid() {
|
||||
return `${this.item.uuid}.${this.documentName}.${this.id}`;
|
||||
const isItem = this.item instanceof game.system.api.documents.DHItem;
|
||||
const isActor = this.item instanceof game.system.api.documents.DhpActor;
|
||||
return isItem || isActor ? `${this.item.uuid}.${this.documentName}.${this.id}` : null;
|
||||
}
|
||||
|
||||
get sheet() {
|
||||
|
|
@ -260,6 +262,9 @@ export function ActionMixin(Base) {
|
|||
}
|
||||
|
||||
async toChat(origin) {
|
||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||
.expandRollMessage?.desc;
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
||||
|
|
@ -288,7 +293,7 @@ export function ActionMixin(Base) {
|
|||
system: systemData,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||
systemData
|
||||
{ ...systemData, open: autoExpandDescription ? 'open' : '' }
|
||||
),
|
||||
flags: {
|
||||
daggerheart: {
|
||||
|
|
|
|||
|
|
@ -7,16 +7,20 @@ const attributeField = label =>
|
|||
});
|
||||
|
||||
const resourceField = (max = 0, initial = 0, label, reverse = false, maxLabel) =>
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
|
||||
max: new fields.NumberField({
|
||||
initial: max,
|
||||
integer: true,
|
||||
label:
|
||||
maxLabel ?? game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: reverse })
|
||||
});
|
||||
new fields.SchemaField(
|
||||
{
|
||||
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
|
||||
max: new fields.NumberField({
|
||||
initial: max,
|
||||
integer: true,
|
||||
label:
|
||||
maxLabel ??
|
||||
game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: reverse })
|
||||
},
|
||||
{ label }
|
||||
);
|
||||
|
||||
const stressDamageReductionRule = localizationPath =>
|
||||
new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import ItemLinkFields from '../../data/fields/itemLinkFields.mjs';
|
||||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHAncestry extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -19,7 +20,6 @@ export default class DHAncestry extends BaseDataItem {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**@override */
|
||||
|
|
@ -42,4 +42,18 @@ export default class DHAncestry extends BaseDataItem {
|
|||
get secondaryFeature() {
|
||||
return this.features.find(x => x.type === CONFIG.DH.ITEM.featureSubTypes.secondary)?.item;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/description.hbs',
|
||||
{ label: 'DAGGERHEART.ITEMS.Ancestry.featuresLabel', features }
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,11 +59,10 @@ export default class DHArmor extends AttachableItem {
|
|||
const baseDescription = this.description;
|
||||
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
||||
const features = this.armorFeatures.map(x => allFeatures[x.value]);
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
|
||||
{ features }
|
||||
{ item: this.parent, features }
|
||||
);
|
||||
|
||||
return { prefix, value: baseDescription, suffix: null };
|
||||
|
|
|
|||
|
|
@ -253,4 +253,20 @@ export default class DHBeastform extends BaseDataItem {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onCreate(_data, _options, userId) {
|
||||
if (!this.actor && game.user.id === userId) {
|
||||
const hasBeastformEffect = this.parent.effects.some(x => x.type === 'beastform');
|
||||
if (!hasBeastformEffect)
|
||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
type: 'beastform',
|
||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp'
|
||||
}
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs';
|
|||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||
import { addLinkedItemsDiff, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
import { addLinkedItemsDiff, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHClass extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -163,4 +163,56 @@ export default class DHClass extends BaseDataItem {
|
|||
|
||||
updateLinkedItemApps(options, this.parent.sheet);
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
||||
const getDomainLabel = domain => {
|
||||
const data = CONFIG.DH.DOMAIN.allDomains()[domain];
|
||||
return data ? game.i18n.localize(data.label) : '';
|
||||
};
|
||||
let domainsLabel = '';
|
||||
if (this.domains.length) {
|
||||
if (this.domains.length === 1) domainsLabel = getDomainLabel(this.domains[0]);
|
||||
else {
|
||||
const firstDomains = this.domains
|
||||
.slice(0, this.domains.length - 1)
|
||||
.map(getDomainLabel)
|
||||
.join(', ');
|
||||
const lastDomain = getDomainLabel(this.domains[this.domains.length - 1]);
|
||||
domainsLabel = game.i18n.format('DAGGERHEART.GENERAL.thingsAndThing', {
|
||||
things: firstDomains,
|
||||
thing: lastDomain
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const classItems = [];
|
||||
for (const itemData of this.inventory.choiceB) {
|
||||
const linkData = [
|
||||
undefined,
|
||||
'UUID', // type
|
||||
itemData.uuid // target
|
||||
];
|
||||
const contentLink = await foundry.applications.ux.TextEditor.implementation._createContentLink(linkData);
|
||||
classItems.push(contentLink.outerHTML);
|
||||
}
|
||||
|
||||
const hopeFeatures = await getFeaturesHTMLData(this.hopeFeatures);
|
||||
const classFeatures = await getFeaturesHTMLData(this.classFeatures);
|
||||
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/class/description.hbs',
|
||||
{
|
||||
class: this.parent,
|
||||
domains: domainsLabel,
|
||||
classItems,
|
||||
hopeFeatures,
|
||||
classFeatures
|
||||
}
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
||||
|
|
@ -24,4 +25,17 @@ export default class DHCommunity extends BaseDataItem {
|
|||
/**@override */
|
||||
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/village.svg';
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/description.hbs',
|
||||
{ label: 'DAGGERHEART.ITEMS.Community.featuresLabel', features }
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,8 +94,10 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!this.actor.system.loadoutSlot.available) {
|
||||
if (!this.actor.system.loadoutSlot.available && !this.loadoutIgnore) {
|
||||
data.system.inVault = true;
|
||||
await this.updateSource({ inVault: true });
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
|
@ -89,4 +90,28 @@ export default class DHSubclass extends BaseDataItem {
|
|||
const allowed = await super._preCreate(data, options, user);
|
||||
if (allowed === false) return;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
||||
const spellcastTrait = this.spellcastingTrait
|
||||
? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label)
|
||||
: null;
|
||||
const foundationFeatures = await getFeaturesHTMLData(this.foundationFeatures);
|
||||
const specializationFeatures = await getFeaturesHTMLData(this.specializationFeatures);
|
||||
const masteryFeatures = await getFeaturesHTMLData(this.masteryFeatures);
|
||||
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/subclass/description.hbs',
|
||||
{
|
||||
spellcastTrait,
|
||||
foundationFeatures,
|
||||
specializationFeatures,
|
||||
masteryFeatures
|
||||
}
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,13 +113,26 @@ export default class DHWeapon extends AttachableItem {
|
|||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
||||
const tier = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`);
|
||||
const trait = game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.attack.roll.trait].label);
|
||||
const range = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${this.attack.range}.name`);
|
||||
const damage = Roll.replaceFormulaData(this.attack.damageFormula, this.parent.parent ?? this.parent);
|
||||
const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label);
|
||||
|
||||
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]);
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
||||
{ features }
|
||||
{
|
||||
features,
|
||||
tier,
|
||||
trait,
|
||||
range,
|
||||
damage,
|
||||
burden
|
||||
}
|
||||
);
|
||||
|
||||
return { prefix, value: baseDescription, suffix: null };
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ export default class DhLevelData extends foundry.abstract.DataModel {
|
|||
|
||||
return {
|
||||
level: new fields.SchemaField({
|
||||
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
current: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.currentLevel'
|
||||
}),
|
||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false }))
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -37,11 +37,30 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
extendEnvironmentDescriptions: new BooleanField(),
|
||||
extendItemDescriptions: new BooleanField(),
|
||||
expandRollMessage: new SchemaField({
|
||||
desc: new BooleanField(),
|
||||
desc: new BooleanField({ initial: true }),
|
||||
roll: new BooleanField(),
|
||||
damage: new BooleanField(),
|
||||
target: new BooleanField()
|
||||
}),
|
||||
showTokenDistance: new StringField({
|
||||
required: true,
|
||||
choices: {
|
||||
always: {
|
||||
value: 'always',
|
||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.always'
|
||||
},
|
||||
encounters: {
|
||||
value: 'encounters',
|
||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.encounters'
|
||||
},
|
||||
never: {
|
||||
value: 'never',
|
||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.never'
|
||||
}
|
||||
},
|
||||
nullable: false,
|
||||
initial: 'always'
|
||||
}),
|
||||
hideAttribution: new BooleanField(),
|
||||
showGenericStatusEffects: new BooleanField({ initial: true })
|
||||
};
|
||||
|
|
|
|||
|
|
@ -55,6 +55,23 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
|
||||
}),
|
||||
deathMoveAutomation: new fields.SchemaField({
|
||||
avoidDeath: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name'
|
||||
}),
|
||||
riskItAll: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.CONFIG.DeathMoves.riskItAll.name'
|
||||
}),
|
||||
blazeOfGlory: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'
|
||||
})
|
||||
}),
|
||||
defeated: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({
|
||||
required: true,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,20 @@ const currencyField = (initial, label, icon) =>
|
|||
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
|
||||
});
|
||||
|
||||
const restMoveField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
name: new foundry.data.fields.StringField({ required: true }),
|
||||
icon: new foundry.data.fields.StringField({ required: true }),
|
||||
img: new foundry.data.fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new foundry.data.fields.HTMLField(),
|
||||
actions: new ActionsField(),
|
||||
effects: new foundry.data.fields.ArrayField(new foundry.data.fields.ObjectField())
|
||||
});
|
||||
|
||||
export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -105,37 +119,11 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
restMoves: new fields.SchemaField({
|
||||
longRest: new fields.SchemaField({
|
||||
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
||||
moves: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
icon: new fields.StringField({ required: true }),
|
||||
img: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new fields.HTMLField(),
|
||||
actions: new ActionsField()
|
||||
}),
|
||||
{ initial: defaultRestOptions.longRest() }
|
||||
)
|
||||
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.longRest() })
|
||||
}),
|
||||
shortRest: new fields.SchemaField({
|
||||
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
||||
moves: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
icon: new fields.StringField({ required: true }),
|
||||
img: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new fields.HTMLField(),
|
||||
actions: new ActionsField()
|
||||
}),
|
||||
{ initial: defaultRestOptions.shortRest() }
|
||||
)
|
||||
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.shortRest() })
|
||||
})
|
||||
}),
|
||||
domains: new fields.TypedObjectField(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue