Working armor application

This commit is contained in:
WBHarry 2026-02-09 18:23:30 +01:00
parent 7036a53c71
commit 514e0260eb
9 changed files with 99 additions and 18 deletions

View file

@ -43,6 +43,7 @@ CONFIG.Item.dataModels = models.items.config;
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
CONFIG.ActiveEffect.dataModels = models.activeEffects.config; CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeTypes };
CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.Combat.documentClass = documents.DhpCombat;
CONFIG.Combat.dataModels = { base: models.DhCombat }; CONFIG.Combat.dataModels = { base: models.DhCombat };

View file

@ -34,6 +34,13 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
...super.PARTS ...super.PARTS
}; };
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
for (const element of htmlElement.querySelectorAll('.base-score-input'))
element.addEventListener('change', this.updateArmorEffect.bind(this));
}
/**@inheritdoc */ /**@inheritdoc */
async _preparePartContext(partId, context) { async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context); await super._preparePartContext(partId, context);
@ -41,12 +48,22 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
switch (partId) { switch (partId) {
case 'settings': case 'settings':
context.features = this.document.system.armorFeatures.map(x => x.value); context.features = this.document.system.armorFeatures.map(x => x.value);
context.armorScore = this.document.system.armorEffect?.system.armorData?.max;
break; break;
} }
return context; return context;
} }
async updateArmorEffect(event) {
const value = Number.parseInt(event.target.value);
const armorEffect = this.document.system.armorEffect;
if (Number.isNaN(value) || !armorEffect) return;
await armorEffect.system.updateArmorMax(value);
this.render();
}
/** /**
* Callback function used by `tagifyElement`. * Callback function used by `tagifyElement`.
* @param {Array<Object>} selectedOptions - The currently selected tag objects. * @param {Array<Object>} selectedOptions - The currently selected tag objects.

View file

@ -854,6 +854,11 @@ export const sceneRangeMeasurementSetting = {
}; };
export const activeEffectModes = { export const activeEffectModes = {
armor: {
id: 'armor',
priority: 20,
label: 'TYPES.ActiveEffect.armor'
},
custom: { custom: {
id: 'custom', id: 'custom',
priority: 0, priority: 0,

View file

@ -11,3 +11,7 @@ export const config = {
horde: HordeEffect, horde: HordeEffect,
armor: ArmorEffect armor: ArmorEffect
}; };
export const changeTypes = {
armor: ArmorEffect.armorChangeEffect
};

View file

@ -9,12 +9,11 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel
type: new fields.StringField({ type: new fields.StringField({
required: true, required: true,
blank: false, blank: false,
choices: CONFIG.DH.GENERAL.activeEffectModes, initial: CONFIG.DH.GENERAL.activeEffectModes.armor.id,
initial: CONFIG.DH.GENERAL.activeEffectModes.add.id,
validate: ArmorEffect.#validateType validate: ArmorEffect.#validateType
}), }),
phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }),
priority: new fields.NumberField(), priority: new fields.NumberField({ integer: true, initial: 20 }),
marked: new fields.NumberField({ marked: new fields.NumberField({
required: true, required: true,
integer: true, integer: true,
@ -34,13 +33,57 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel
}; };
} }
get armorData() {
if (this.changes.length !== 1) return { value: 0, max: 0 };
return { value: this.changes[0].value, max: this.changes[0].max };
}
async updateArmorMax(newMax) {
if (this.changes.length !== 1) return;
const newChanges = this.changes.map(change => ({
...change,
max: newMax,
marked: Math.min(change.marked, newMax)
}));
await this.parent.update({ 'system.changes': newChanges });
}
static applyChangeField(model, change, field) { static applyChangeField(model, change, field) {
return [model, change, field]; return [model, change, field];
} }
static armorChangeEffect = {
label: 'Armor',
defaultPriortiy: 20,
handler: (actor, change, _options, _field, replacementData) => {
game.system.api.documents.DhActiveEffect.applyChange(
actor,
{
...change,
key: 'system.armorScore.value',
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
value: change.value
},
replacementData
);
game.system.api.documents.DhActiveEffect.applyChange(
actor,
{
...change,
key: 'system.armorScore.max',
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
value: change.max
},
replacementData
);
return {};
},
render: null
};
prepareBaseData() { prepareBaseData() {
for (const change of this.changes) { for (const change of this.changes) {
change.key = 'system.armorScore.value'; change.key = 'system.armorScore';
change.value = Math.min(change.max - change.marked, change.max); change.value = Math.min(change.max - change.marked, change.max);
} }
} }
@ -52,13 +95,9 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel
* @throws {Error} An error if the type string is malformed * @throws {Error} An error if the type string is malformed
*/ */
static #validateType(type) { static #validateType(type) {
if (type.length < 3) throw new Error('must be at least three characters long'); if (type !== CONFIG.DH.GENERAL.activeEffectModes.armor.id)
if (!/^custom\.-?\d+$/.test(type) && !type.split('.').every(s => /^[a-z0-9]+$/i.test(s))) { throw new Error('An armor effect must have change.type "armor"');
throw new Error(
'A change type must either be a sequence of dot-delimited, alpha-numeric substrings or of the form' +
' "custom.{number}"'
);
}
return true; return true;
} }
} }

View file

@ -19,7 +19,6 @@ export default class DHArmor extends AttachableItem {
...super.defineSchema(), ...super.defineSchema(),
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }), tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }),
equipped: new fields.BooleanField({ initial: false }), equipped: new fields.BooleanField({ initial: false }),
baseScore: new fields.NumberField({ integer: true, initial: 0 }),
armorFeatures: new fields.ArrayField( armorFeatures: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.StringField({ value: new fields.StringField({
@ -54,6 +53,11 @@ export default class DHArmor extends AttachableItem {
); );
} }
get armorEffect() {
/* TODO: make armors only able to have on armor effect, or handle in some other way */
return this.parent.effects.find(x => x.type === 'armor');
}
/**@inheritdoc */ /**@inheritdoc */
async getDescriptionData() { async getDescriptionData() {
const baseDescription = this.description; const baseDescription = this.description;
@ -159,8 +163,9 @@ export default class DHArmor extends AttachableItem {
* @returns {string[]} An array of localized tag strings. * @returns {string[]} An array of localized tag strings.
*/ */
_getTags() { _getTags() {
const baseScore = this.armorEffect?.system.armorData?.value;
const tags = [ const tags = [
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`, `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${baseScore}`,
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}` `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}`
]; ];
@ -173,8 +178,10 @@ export default class DHArmor extends AttachableItem {
*/ */
_getLabels() { _getLabels() {
const labels = []; const labels = [];
if (this.baseScore) if (this.armorEffect)
labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`); labels.push(
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorEffect.system.armorData?.value}`
);
return labels; return labels;
} }

View file

@ -15,7 +15,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
} }
// Then apply the standard suppression rules // Then apply the standard suppression rules
if (['weapon', 'armor'].includes(this.parent?.type)) { if (['weapon', 'armor'].includes(this.parent?.type) && this.transfer) {
return !this.parent.system.equipped; return !this.parent.system.equipped;
} }

View file

@ -992,4 +992,8 @@ export default class DhpActor extends Actor {
return allTokens; return allTokens;
} }
applyActiveEffects(phase) {
super.applyActiveEffects(phase);
}
} }

View file

@ -7,8 +7,12 @@
<legend>{{localize tabs.settings.label}}</legend> <legend>{{localize tabs.settings.label}}</legend>
<span>{{localize "DAGGERHEART.GENERAL.Tiers.singular"}}</span> <span>{{localize "DAGGERHEART.GENERAL.Tiers.singular"}}</span>
{{formField systemFields.tier value=source.system.tier}} {{formField systemFields.tier value=source.system.tier}}
<span>{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}</span> {{#if this.armorScore includeZero=true}}
{{formField systemFields.baseScore value=source.system.baseScore}} <span>{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}</span>
<div class="form-fields">
<input type="text" data-dtype="Number" class="base-score-input" value="{{this.armorScore}}" />
</div>
{{/if}}
<span>{{localize "TYPES.Item.feature"}}</span> <span>{{localize "TYPES.Item.feature"}}</span>
<input type="text" class="features-input" value="{{features}}" /> <input type="text" class="features-input" value="{{features}}" />