mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-22 07:23:37 +02:00
[Feature] ArmorEffect reworked into ChangeType on BaseEffect (#1739)
* Initial * . * Single armor rework start * More fixes * Fixed DamageReductionDialog * Removed last traces of ArmorEffect * .
This commit is contained in:
parent
0b5de79ca8
commit
b5e0bb7c27
26 changed files with 339 additions and 416 deletions
|
|
@ -1,17 +1,12 @@
|
|||
import BaseEffect from './baseEffect.mjs';
|
||||
import BeastformEffect from './beastformEffect.mjs';
|
||||
import HordeEffect from './hordeEffect.mjs';
|
||||
import ArmorEffect from './armorEffect.mjs';
|
||||
export { changeTypes, changeEffects } from './changeTypes/_module.mjs';
|
||||
|
||||
export { BaseEffect, BeastformEffect, HordeEffect, ArmorEffect };
|
||||
export { BaseEffect, BeastformEffect, HordeEffect };
|
||||
|
||||
export const config = {
|
||||
base: BaseEffect,
|
||||
beastform: BeastformEffect,
|
||||
horde: HordeEffect,
|
||||
armor: ArmorEffect
|
||||
};
|
||||
|
||||
export const changeTypes = {
|
||||
armor: ArmorEffect.armorChangeEffect
|
||||
horde: HordeEffect
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,244 +0,0 @@
|
|||
import { getScrollTextData, itemAbleRollParse } from '../../helpers/utils.mjs';
|
||||
|
||||
/**
|
||||
* ArmorEffects are ActiveEffects that have a static changes field of length 1. It includes current and maximum armor.
|
||||
* When applied to a character, it adds to their currently marked and maximum armor.
|
||||
*/
|
||||
export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
changes: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
key: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 'system.armorScore'
|
||||
}),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
blank: false,
|
||||
initial: CONFIG.DH.GENERAL.activeEffectModes.armor.id,
|
||||
validate: ArmorEffect.#validateType
|
||||
}),
|
||||
phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }),
|
||||
priority: new fields.NumberField({ integer: true, initial: 20 }),
|
||||
value: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.value'
|
||||
}),
|
||||
max: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: '1',
|
||||
label: 'DAGGERHEART.GENERAL.max'
|
||||
})
|
||||
}),
|
||||
{
|
||||
initial: [
|
||||
{
|
||||
key: 'system.armorScore',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.armor.id,
|
||||
phase: 'initial',
|
||||
priority: 20,
|
||||
value: 0,
|
||||
max: '1'
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
armorInteraction: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.activeEffectArmorInteraction,
|
||||
initial: CONFIG.DH.GENERAL.activeEffectArmorInteraction.none.id,
|
||||
label: 'DAGGERHEART.EFFECTS.Armor.FIELDS.armorInteraction.label',
|
||||
hint: 'DAGGERHEART.EFFECTS.Armor.FIELDS.armorInteraction.hint'
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
get isSuppressed() {
|
||||
if (this.parent.actor?.type !== 'character') return false;
|
||||
|
||||
switch (this.armorInteraction) {
|
||||
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id:
|
||||
return !this.parent.actor.system.armor;
|
||||
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.inactive.id:
|
||||
return Boolean(this.parent.actor.system.armor);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Type Functions */
|
||||
|
||||
/**
|
||||
* Validate that an {@link EffectChangeData#type} string is well-formed.
|
||||
* @param {string} type The string to be validated
|
||||
* @returns {true}
|
||||
* @throws {Error} An error if the type string is malformed
|
||||
*/
|
||||
static #validateType(type) {
|
||||
if (type !== CONFIG.DH.GENERAL.activeEffectModes.armor.id)
|
||||
throw new Error('An armor effect must have change.type "armor"');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
/* Helpers */
|
||||
|
||||
get armorChange() {
|
||||
if (this.changes.length !== 1)
|
||||
throw new Error('Unexpected error. An armor effect should have a changes field of length 1.');
|
||||
|
||||
const actor = this.parent.actor?.type === 'character' ? this.parent.actor : null;
|
||||
const changeData = this.changes[0];
|
||||
const maxParse = actor ? itemAbleRollParse(changeData.max, actor, this.parent.parent) : null;
|
||||
const maxRoll = maxParse ? new Roll(maxParse).evaluateSync() : null;
|
||||
const maxEvaluated = maxRoll ? (maxRoll.isDeterministic ? maxRoll.total : null) : null;
|
||||
|
||||
return {
|
||||
...changeData,
|
||||
max: maxEvaluated ?? changeData.max
|
||||
};
|
||||
}
|
||||
|
||||
get armorData() {
|
||||
return { value: this.armorChange.value, max: this.armorChange.max };
|
||||
}
|
||||
|
||||
async updateArmorMax(newMax) {
|
||||
const { effect, ...baseChange } = this.armorChange;
|
||||
const newChanges = [
|
||||
{
|
||||
...baseChange,
|
||||
max: newMax,
|
||||
value: Math.min(this.armorChange.value, newMax)
|
||||
}
|
||||
];
|
||||
await this.parent.update({ 'system.changes': newChanges });
|
||||
}
|
||||
|
||||
static orderEffectsForAutoChange(armorEffects, increasing) {
|
||||
const getEffectWeight = effect => {
|
||||
switch (effect.parent.type) {
|
||||
case 'class':
|
||||
case 'subclass':
|
||||
case 'ancestry':
|
||||
case 'community':
|
||||
case 'feature':
|
||||
case 'domainCard':
|
||||
return 2;
|
||||
case 'armor':
|
||||
return 3;
|
||||
case 'loot':
|
||||
case 'consumable':
|
||||
return 4;
|
||||
case 'weapon':
|
||||
return 5;
|
||||
case 'character':
|
||||
return 6;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
return armorEffects
|
||||
.filter(x => !x.disabled && !x.isSuppressed)
|
||||
.sort((a, b) =>
|
||||
increasing ? getEffectWeight(b) - getEffectWeight(a) : getEffectWeight(a) - getEffectWeight(b)
|
||||
);
|
||||
}
|
||||
|
||||
/* Overrides */
|
||||
|
||||
static getDefaultObject() {
|
||||
return {
|
||||
key: 'system.armorScore',
|
||||
type: 'armor',
|
||||
name: game.i18n.localize('DAGGERHEART.EFFECTS.Armor.newArmorEffect'),
|
||||
img: 'icons/equipment/chest/breastplate-helmet-metal.webp'
|
||||
};
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (changes.system?.changes) {
|
||||
const changesChanged = changes.system.changes.length !== this.changes.length;
|
||||
if (changesChanged) {
|
||||
ui.notifications.error(
|
||||
game.i18n.localize('DAGGERHEART.UI.Notifications.cannotAlterArmorEffectChanges')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (changes.system.changes.length === 1) {
|
||||
if (changes.system.changes[0].type !== CONFIG.DH.GENERAL.activeEffectModes.armor.id) {
|
||||
ui.notifications.error(
|
||||
game.i18n.localize('DAGGERHEART.UI.Notifications.cannotAlterArmorEffectType')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (changes.system.changes[0].key !== 'system.armorScore') {
|
||||
ui.notifications.error(
|
||||
game.i18n.localize('DAGGERHEART.UI.Notifications.cannotAlterArmorEffectKey')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
changes.system.changes[0].value !== this.armorChange.value &&
|
||||
this.parent.actor?.type === 'character'
|
||||
) {
|
||||
options.scrollingTextData = [
|
||||
getScrollTextData(this.parent.actor, changes.system.changes[0], 'armor')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdate(changes, options, userId) {
|
||||
super._onUpdate(changes, options, userId);
|
||||
|
||||
if (options.scrollingTextData && this.parent.actor?.type === 'character')
|
||||
this.parent.actor.queueScrollText(options.scrollingTextData);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@
|
|||
* "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility)
|
||||
*/
|
||||
|
||||
import { changeTypes } from './_module.mjs';
|
||||
|
||||
export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -30,7 +32,8 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
}),
|
||||
value: new fields.AnyField({ required: true, nullable: true, serializable: true, initial: '' }),
|
||||
phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }),
|
||||
priority: new fields.NumberField()
|
||||
priority: new fields.NumberField(),
|
||||
typeData: new fields.TypedSchemaField(changeTypes, { nullable: true, initial: null })
|
||||
})
|
||||
),
|
||||
duration: new fields.SchemaField({
|
||||
|
|
@ -86,6 +89,17 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
return true;
|
||||
}
|
||||
|
||||
get armorChange() {
|
||||
return this.changes.find(x => x.type === CONFIG.DH.GENERAL.activeEffectModes.armor.id);
|
||||
}
|
||||
|
||||
get armorData() {
|
||||
const armorChange = this.armorChange;
|
||||
if (!armorChange) return null;
|
||||
|
||||
return armorChange.typeData.getArmorData(armorChange);
|
||||
}
|
||||
|
||||
static getDefaultObject() {
|
||||
return {
|
||||
name: 'New Effect',
|
||||
|
|
|
|||
9
module/data/activeEffect/changeTypes/_module.mjs
Normal file
9
module/data/activeEffect/changeTypes/_module.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import Armor from './armor.mjs';
|
||||
|
||||
export const changeEffects = {
|
||||
armor: Armor.changeEffect
|
||||
};
|
||||
|
||||
export const changeTypes = {
|
||||
armor: Armor
|
||||
};
|
||||
145
module/data/activeEffect/changeTypes/armor.mjs
Normal file
145
module/data/activeEffect/changeTypes/armor.mjs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import { itemAbleRollParse } from '../../../helpers/utils.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class Armor extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
return {
|
||||
type: new fields.StringField({ required: true, initial: 'armor', blank: false }),
|
||||
max: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: '1',
|
||||
label: 'DAGGERHEART.GENERAL.max'
|
||||
}),
|
||||
armorInteraction: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.activeEffectArmorInteraction,
|
||||
initial: CONFIG.DH.GENERAL.activeEffectArmorInteraction.none.id,
|
||||
label: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.armorInteraction.label',
|
||||
hint: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.armorInteraction.hint'
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static changeEffect = {
|
||||
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.typeData.max
|
||||
},
|
||||
replacementData
|
||||
);
|
||||
return {};
|
||||
},
|
||||
render: null
|
||||
};
|
||||
|
||||
get isSuppressed() {
|
||||
switch (this.armorInteraction) {
|
||||
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id:
|
||||
return !this.parent.parent?.actor.system.armor;
|
||||
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.inactive.id:
|
||||
return Boolean(this.parent.parent?.actor.system.armor);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static getInitialValue(locked) {
|
||||
return {
|
||||
key: 'Armor',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.armor.id,
|
||||
value: 0,
|
||||
typeData: {
|
||||
type: 'armor',
|
||||
max: 0,
|
||||
locked
|
||||
},
|
||||
phase: 'initial',
|
||||
priority: 20
|
||||
};
|
||||
}
|
||||
|
||||
static getDefaultArmorEffect() {
|
||||
return {
|
||||
name: game.i18n.localize('DAGGERHEART.EFFECTS.ChangeTypes.armor.newArmorEffect'),
|
||||
img: 'icons/equipment/chest/breastplate-helmet-metal.webp',
|
||||
system: {
|
||||
changes: [Armor.getInitialValue(true)]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
|
||||
getArmorData(parentChange) {
|
||||
const actor = this.parent.parent?.actor?.type === 'character' ? this.parent.parent.actor : null;
|
||||
const maxParse = actor ? itemAbleRollParse(this.max, actor, this.parent.parent.parent) : null;
|
||||
const maxRoll = maxParse ? new Roll(maxParse).evaluateSync() : null;
|
||||
const maxEvaluated = maxRoll ? (maxRoll.isDeterministic ? maxRoll.total : null) : null;
|
||||
|
||||
return {
|
||||
value: parentChange.value,
|
||||
max: maxEvaluated ?? this.max
|
||||
};
|
||||
}
|
||||
|
||||
async updateArmorMax(newMax) {
|
||||
const newChanges = [
|
||||
...this.parent.changes.map(change => ({
|
||||
...change,
|
||||
value: change.type === 'armor' ? Math.min(change.value, newMax) : change.value,
|
||||
typeData: change.type === 'armor' ? { ...change.typeData, max: newMax } : change.typeData
|
||||
}))
|
||||
];
|
||||
await this.parent.parent.update({ 'system.changes': newChanges });
|
||||
}
|
||||
|
||||
static orderEffectsForAutoChange(armorEffects, increasing) {
|
||||
const getEffectWeight = effect => {
|
||||
switch (effect.parent.type) {
|
||||
case 'class':
|
||||
case 'subclass':
|
||||
case 'ancestry':
|
||||
case 'community':
|
||||
case 'feature':
|
||||
case 'domainCard':
|
||||
return 2;
|
||||
case 'armor':
|
||||
return 3;
|
||||
case 'loot':
|
||||
case 'consumable':
|
||||
return 4;
|
||||
case 'weapon':
|
||||
return 5;
|
||||
case 'character':
|
||||
return 6;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
return armorEffects
|
||||
.filter(x => !x.disabled && !x.isSuppressed)
|
||||
.sort((a, b) =>
|
||||
increasing ? getEffectWeight(b) - getEffectWeight(a) : getEffectWeight(a) - getEffectWeight(b)
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue