Merged with development

This commit is contained in:
WBHarry 2025-08-23 18:17:32 +02:00
commit bd76e22e8d
1096 changed files with 11080 additions and 5102 deletions

View file

@ -163,7 +163,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
const hasRoll = this.getUseHasRoll(byPass);
return {
event,
title: `${this.item.name}: ${this.name}`,
title: `${this.item.name}: ${game.i18n.localize(this.name)}`,
source: {
item: this.item._id,
action: this._id,
@ -209,15 +209,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
async consume(config, successCost = false) {
const actor= this.actor.system.partner ?? this.actor,
const actor = this.actor.system.partner ?? this.actor,
usefulResources = {
...foundry.utils.deepClone(actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
...foundry.utils.deepClone(actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
for (var cost of config.costs) {
if (cost.keyIsID) {

View file

@ -10,7 +10,8 @@ export default class DhpAdversary extends BaseDataActor {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.adversary',
type: 'adversary',
settingSheet: DHAdversarySettings
settingSheet: DHAdversarySettings,
hasAttribution: true
});
}
@ -26,7 +27,7 @@ export default class DhpAdversary extends BaseDataActor {
}),
type: new fields.StringField({
required: true,
choices: CONFIG.DH.ACTOR.adversaryTypes,
choices: CONFIG.DH.ACTOR.allAdversaryTypes,
initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id
}),
motivesAndTactics: new fields.StringField(),

View file

@ -1,5 +1,5 @@
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
import { createScrollText, getScrollTextData } from '../../helpers/utils.mjs';
import { getScrollTextData } from '../../helpers/utils.mjs';
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
new foundry.data.fields.SchemaField({
@ -39,7 +39,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
type: 'base',
isNPC: true,
settingSheet: null,
hasResistances: true
hasResistances: true,
hasAttribution: false
};
}
@ -53,6 +54,13 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
const fields = foundry.data.fields;
const schema = {};
if (this.metadata.hasAttribution) {
schema.attribution = new fields.SchemaField({
source: new fields.StringField(),
page: new fields.NumberField(),
artist: new fields.StringField()
});
}
if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true });
if (this.metadata.hasResistances)
schema.resistance = new fields.SchemaField({
@ -78,6 +86,13 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
*/
static DEFAULT_ICON = null;
get attributionLabel() {
if (!this.attribution) return;
const { source, page } = this.attribution;
return [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
}
/* -------------------------------------------- */
/**
@ -133,6 +148,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
_onUpdate(changes, options, userId) {
super._onUpdate(changes, options, userId);
createScrollText(this.parent, options.scrollingTextData);
if (options.scrollingTextData) this.parent.queueScrollText(options.scrollingTextData);
}
}

View file

@ -95,7 +95,7 @@ export default class DhCharacter extends BaseDataActor {
}),
attack: new ActionField({
initial: {
name: 'Unarmed Attack',
name: 'DAGGERHEART.GENERAL.unarmedAttack',
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
_id: foundry.utils.randomID(),
systemPath: 'attack',
@ -444,16 +444,12 @@ export default class DhCharacter extends BaseDataActor {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
if (this.class.subclass) {
const subclassState = this.class.subclass.system.featureState;
const subclass =
item.system.identifier === 'multiclass' ? this.multiclass.subclass : this.class.subclass;
const featureType = subclass
? (subclass.system.features.find(x => x.item?.uuid === item.uuid)?.type ?? null)
: null;
if (
featureType === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(featureType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
(featureType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
subclassState >= 2) ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) {
subclassFeatures.push(item);
}

View file

@ -12,7 +12,8 @@ export default class DhEnvironment extends BaseDataActor {
label: 'TYPES.Actor.environment',
type: 'environment',
settingSheet: DHEnvironmentSettings,
hasResistances: false
hasResistances: false,
hasAttribution: true
});
}

View file

@ -25,7 +25,7 @@ export default class CostField extends fields.ArrayField {
config.costs = CostField.calcCosts.call(this, costs);
const hasCost = CostField.hasCost.call(this, config.costs);
if (config.isFastForward && !hasCost)
return ui.notifications.warn("You don't have the resources to use that action.");
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
return hasCost;
}

View file

@ -23,14 +23,22 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
multiplier: new fields.StringField({
choices: CONFIG.DH.GENERAL.multiplierTypes,
initial: 'prof',
label: 'Multiplier'
label: 'DAGGERHEART.ACTIONS.Config.damage.multiplier'
}),
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
dice: new fields.StringField({ choices: CONFIG.DH.GENERAL.diceTypes, initial: 'd6', label: 'Dice' }),
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
flatMultiplier: new fields.NumberField({
nullable: true,
initial: 1,
label: 'DAGGERHEART.ACTIONS.Config.damage.flatMultiplier'
}),
dice: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceTypes,
initial: 'd6',
label: 'DAGGERHEART.GENERAL.Dice.single'
}),
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'DAGGERHEART.GENERAL.bonus' }),
custom: new fields.SchemaField({
enabled: new fields.BooleanField({ label: 'Custom Formula' }),
formula: new FormulaField({ label: 'Formula', initial: '' })
enabled: new fields.BooleanField({ label: 'DAGGERHEART.ACTIONS.Config.general.customFormula' }),
formula: new FormulaField({ label: 'DAGGERHEART.ACTIONS.Config.general.formula', initial: '' })
})
};
}

View file

@ -25,7 +25,8 @@ export default class UsesField extends fields.SchemaField {
if (uses && !uses.value) uses.value = 0;
config.uses = uses;
const hasUses = UsesField.hasUses.call(this, config.uses);
if (config.isFastForward && !hasUses) return ui.notifications.warn("That action doesn't have remaining uses.");
if (config.isFastForward && !hasUses)
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
return hasUses;
}

View file

@ -26,7 +26,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
hasResource: false,
isQuantifiable: false,
isInventoryItem: false,
hasActions: false
hasActions: false,
hasAttribution: true
};
}
@ -37,7 +38,13 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
/** @inheritDoc */
static defineSchema() {
const schema = {};
const schema = {
attribution: new fields.SchemaField({
source: new fields.StringField(),
page: new fields.NumberField(),
artist: new fields.StringField()
})
};
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
@ -110,6 +117,13 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
return [];
}
get attributionLabel() {
if (!this.attribution) return;
const { source, page } = this.attribution;
return [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
}
/**
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
* @param {object} [options] - Options which modify the getRollData method.
@ -144,50 +158,30 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
}
if (this.actor && this.actor.type === 'character' && this.features) {
const featureUpdates = {};
const features = [];
for (let f of this.features) {
const fBase = f.item ?? f;
const feature = fBase.system ? fBase : await foundry.utils.fromUuid(fBase.uuid);
const createData = foundry.utils.mergeObject(
feature.toObject(),
{
system: {
originItemType: this.parent.type,
originId: data._id,
identifier: this.isMulticlass ? 'multiclass' : null
}
},
{ inplace: false }
const multiclass = this.isMulticlass ? 'multiclass' : null;
features.push(
foundry.utils.mergeObject(
feature.toObject(),
{
_stats: { compendiumSource: fBase.uuid },
system: {
originItemType: this.parent.type,
identifier: multiclass ?? (f.item ? f.type : null)
}
},
{ inplace: false }
)
);
const [doc] = await this.actor.createEmbeddedDocuments('Item', [createData]);
if (!featureUpdates.features)
featureUpdates.features = this.features.map(x => (x.item ? { ...x, item: x.item.uuid } : x.uuid));
if (f.item) {
const existingFeature = featureUpdates.features.find(x => x.item === f.item.uuid);
existingFeature.item = doc.uuid;
} else {
const replaceIndex = featureUpdates.features.findIndex(x => x === f.uuid);
featureUpdates.features.splice(replaceIndex, 1, doc.uuid);
}
}
await this.updateSource(featureUpdates);
await this.actor.createEmbeddedDocuments('Item', features);
}
}
async _preDelete() {
if (!this.actor || this.actor.type !== 'character') return;
const items = this.actor.items.filter(item => item.system.originId === this.parent.id);
if (items.length > 0)
await this.actor.deleteEmbeddedDocuments(
'Item',
items.map(x => x.id)
);
}
async _preUpdate(changed, options, userId) {
const allowed = await super._preUpdate(changed, options, userId);
if (allowed === false) return false;
@ -207,6 +201,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
super._onUpdate(changed, options, userId);
updateLinkedItemApps(options, this.parent.sheet);
createScrollText(this.parent?.parent, options.scrollingTextData);
if (this.parent?.parent && options.scrollingTextData)
this.parent.parent.queueScrollText(options.scrollingTextData);
}
}

View file

@ -1,5 +1,4 @@
import BaseDataItem from './base.mjs';
import { ActionField, ActionsField } from '../fields/actionField.mjs';
export default class DHFeature extends BaseDataItem {
/** @inheritDoc */
@ -30,24 +29,7 @@ export default class DHFeature extends BaseDataItem {
nullable: true,
initial: null
}),
originId: new fields.StringField({ nullable: true, initial: null }),
identifier: new fields.StringField()
};
}
get spellcastingModifier() {
let traitValue = 0;
if (this.actor && this.originId && ['class', 'subclass'].includes(this.originItemType)) {
if (this.originItemType === 'subclass') {
traitValue =
this.actor.system.traits[this.actor.items.get(this.originId).system.spellcastingTrait]?.value ?? 0;
} else {
const { value: multiclass, subclass } = this.actor.system.multiclass;
const selectedSubclass = multiclass?.id === this.originId ? subclass : this.actor.system.class.subclass;
traitValue = this.actor.system.traits[selectedSubclass.system.spellcastingTrait]?.value ?? 0;
}
}
return traitValue;
}
}

View file

@ -199,8 +199,8 @@ export default class DHWeapon extends AttachableItem {
];
for (const { value, type } of attack.damage.parts) {
const parts = [value.dice];
if (value.bonus) parts.push(value.bonus.signedString());
const parts = value.custom.enabled ? [game.i18n.localize('DAGGERHEART.GENERAL.custom')] : [value.dice];
if (!value.custom.enabled && value.bonus) parts.push(value.bonus.signedString());
if (type.size > 0) {
const typeTags = Array.from(type)

View file

@ -89,6 +89,11 @@ export default class DhAppearance extends foundry.abstract.DataModel {
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.expandRollMessageTarget.label'
})
}),
hideAttribution: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.hideAttribution.label'
})
};
}

View file

@ -108,6 +108,13 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
}),
description: new fields.HTMLField()
})
),
adversaryTypes: new fields.TypedObjectField(
new fields.SchemaField({
id: new fields.StringField({ required: true }),
label: new fields.StringField({ required: true, label: 'DAGGERHEART.GENERAL.label' }),
description: new fields.StringField()
})
)
};
}