mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
[V14] 1354 - Armor Effect (#1652)
* Initial * progress * Working armor application * . * Added a updateArmorValue function that updates armoreffects according to an auto order * . * Added createDialog * . * Updated Armor SRD * . * Fixed character sheet armor update * Updated itemconfig * Actions now use createDialog for effects * . * . * Fixed ArmorEffect max being a string * Fixed SRD armor effects * Finally finished the migration ._. * SRD finalization * Added ArmoreEffect.armorInteraction option * Added ArmorManagement menu * Fixed DamageReductionDialog * Fixed ArmorManagement pip syle * feat: add style to armors tooltip, add a style to make armor slot label more clear that was a button and add a tooltip location * . * Removed tooltip on manageArmor * Fixes * Fixed Downtime armor repair * Removed ArmorScore from character data model and instead adding it in basePrep * [Feature] ArmorEffect reworked into ChangeType on BaseEffect (#1739) * Initial * . * Single armor rework start * More fixes * Fixed DamageReductionDialog * Removed last traces of ArmorEffect * . * Corrected the SRD to use base effects again * Removed bare bones armor item * [V14] Refactor ArmorChange schema and fix some bugs (#1742) * Refactor ArmorChange schema and fix some bugs * Add current back to schema * Fixed so changing armor values and taking damage works again * Fixed so that scrolltexts for armor changes work again * Removed old marks on armor.system * Restored damageReductionDialog armorScore.value * Use toggle for css class addition/removal * Fix armor change type choices * Added ArmorChange DamageThresholds --------- Co-authored-by: WBHarry <williambjrklund@gmail.com> * [V14] Armor System ArmorScore (#1744) * Readded so that armor items have their system defined armor instead of using an ActiveEffect * Consolidate armor source retrieval * Fix regression with updating armor when sources are disabled * Simplify armor pip update * Use helper in damage reduction dialog * . * Corrected SRD Armor Items --------- Co-authored-by: Carlos Fernandez <cfern1990@gmail.com> * Updated migrations * Migrations are now not horrible =D --------- Co-authored-by: Murilo Brito <dev.murilobrito@gmail.com> Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com> Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
This commit is contained in:
parent
a3f515cf6d
commit
ef53a7c561
94 changed files with 1961 additions and 545 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import BaseEffect from './baseEffect.mjs';
|
||||
import BeastformEffect from './beastformEffect.mjs';
|
||||
import HordeEffect from './hordeEffect.mjs';
|
||||
export { changeTypes, changeEffects } from './changeTypes/_module.mjs';
|
||||
|
||||
export { BaseEffect, BeastformEffect, HordeEffect };
|
||||
|
||||
|
|
|
|||
|
|
@ -12,26 +12,41 @@
|
|||
* "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 { getScrollTextData } from '../../helpers/utils.mjs';
|
||||
import { changeTypes } from './_module.mjs';
|
||||
|
||||
export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
const baseChanges = Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, type) => {
|
||||
r[type] = new fields.SchemaField({
|
||||
key: new fields.StringField({ required: true }),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
choices: [type],
|
||||
initial: type,
|
||||
validate: BaseEffect.#validateType
|
||||
}),
|
||||
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()
|
||||
});
|
||||
return r;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
changes: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
key: new fields.StringField({ required: true }),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
blank: false,
|
||||
choices: CONFIG.DH.GENERAL.activeEffectModes,
|
||||
initial: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||
validate: BaseEffect.#validateType
|
||||
}),
|
||||
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()
|
||||
})
|
||||
new fields.TypedSchemaField(
|
||||
{ ...changeTypes, ...baseChanges },
|
||||
{ initial: baseChanges.add.getInitialValue() }
|
||||
)
|
||||
),
|
||||
duration: new fields.SchemaField({
|
||||
type: new fields.StringField({
|
||||
|
|
@ -86,6 +101,23 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
return true;
|
||||
}
|
||||
|
||||
get isSuppressed() {
|
||||
for (const change of this.changes) {
|
||||
if (change.isSuppressed) 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.getArmorData();
|
||||
}
|
||||
|
||||
static getDefaultObject() {
|
||||
return {
|
||||
name: 'New Effect',
|
||||
|
|
@ -105,4 +137,31 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
async _preUpdate(changed, options, userId) {
|
||||
const allowed = await super._preUpdate(changed, options, userId);
|
||||
if (allowed === false) return false;
|
||||
|
||||
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||
if (
|
||||
autoSettings.resourceScrollTexts &&
|
||||
this.parent.actor?.type === 'character' &&
|
||||
this.parent.actor.system.resources.armor
|
||||
) {
|
||||
const newArmorTotal = (changed.system?.changes ?? []).reduce((acc, change) => {
|
||||
if (change.type === 'armor') acc += change.value.current;
|
||||
return acc;
|
||||
}, this.parent.actor.system.armor?.system?.armor?.current ?? 0);
|
||||
|
||||
const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor');
|
||||
options.scrollingTextData = [armorData];
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdate(changed, options, userId) {
|
||||
super._onUpdate(changed, options, userId);
|
||||
|
||||
if (this.parent.actor && options.scrollingTextData)
|
||||
this.parent.actor.queueScrollText(options.scrollingTextData);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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
|
||||
};
|
||||
206
module/data/activeEffect/changeTypes/armor.mjs
Normal file
206
module/data/activeEffect/changeTypes/armor.mjs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import { itemAbleRollParse } from '../../../helpers/utils.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class ArmorChange extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
return {
|
||||
type: new fields.StringField({ required: true, choices: ['armor'], initial: 'armor' }),
|
||||
priority: new fields.NumberField(),
|
||||
phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }),
|
||||
value: new fields.SchemaField({
|
||||
current: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
|
||||
max: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: '1',
|
||||
label: 'DAGGERHEART.GENERAL.max'
|
||||
}),
|
||||
damageThresholds: new fields.SchemaField(
|
||||
{
|
||||
major: new fields.StringField({
|
||||
initial: '0',
|
||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
||||
}),
|
||||
severe: new fields.StringField({
|
||||
initial: '0',
|
||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||
})
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
interaction: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.activeEffectArmorInteraction,
|
||||
initial: CONFIG.DH.GENERAL.activeEffectArmorInteraction.none.id,
|
||||
label: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.label',
|
||||
hint: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.hint'
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static changeEffect = {
|
||||
label: 'Armor',
|
||||
defaultPriority: 20,
|
||||
handler: (actor, change, _options, _field, replacementData) => {
|
||||
const parsedMax = itemAbleRollParse(change.value.max, actor, change.effect.parent);
|
||||
game.system.api.documents.DhActiveEffect.applyChange(
|
||||
actor,
|
||||
{
|
||||
...change,
|
||||
key: 'system.armorScore.value',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||
value: change.value.current
|
||||
},
|
||||
replacementData
|
||||
);
|
||||
game.system.api.documents.DhActiveEffect.applyChange(
|
||||
actor,
|
||||
{
|
||||
...change,
|
||||
key: 'system.armorScore.max',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||
value: parsedMax
|
||||
},
|
||||
replacementData
|
||||
);
|
||||
|
||||
if (change.value.damageThresholds) {
|
||||
const getThresholdValue = value => {
|
||||
const parsed = itemAbleRollParse(value, actor, change.effect.parent);
|
||||
const roll = new Roll(parsed).evaluateSync();
|
||||
return roll ? (roll.isDeterministic ? roll.total : null) : null;
|
||||
};
|
||||
const major = getThresholdValue(change.value.damageThresholds.major);
|
||||
const severe = getThresholdValue(change.value.damageThresholds.severe);
|
||||
|
||||
if (major) {
|
||||
game.system.api.documents.DhActiveEffect.applyChange(
|
||||
actor,
|
||||
{
|
||||
...change,
|
||||
key: 'system.damageThresholds.major',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
||||
priority: 50,
|
||||
value: major
|
||||
},
|
||||
replacementData
|
||||
);
|
||||
}
|
||||
|
||||
if (severe) {
|
||||
game.system.api.documents.DhActiveEffect.applyChange(
|
||||
actor,
|
||||
{
|
||||
...change,
|
||||
key: 'system.damageThresholds.severe',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
||||
priority: 50,
|
||||
value: severe
|
||||
},
|
||||
replacementData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
render: null
|
||||
};
|
||||
|
||||
get isSuppressed() {
|
||||
switch (this.value.interaction) {
|
||||
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() {
|
||||
return {
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.armor.id,
|
||||
value: {
|
||||
current: 0,
|
||||
max: 0
|
||||
},
|
||||
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: [ArmorChange.getInitialValue()]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
|
||||
getArmorData() {
|
||||
const actor = this.parent.parent?.actor?.type === 'character' ? this.parent.parent.actor : null;
|
||||
const maxParse = actor ? itemAbleRollParse(this.value.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 {
|
||||
current: this.value.current,
|
||||
max: maxEvaluated ?? this.value.max
|
||||
};
|
||||
}
|
||||
|
||||
async updateArmorMax(newMax) {
|
||||
const newChanges = [
|
||||
...this.parent.changes.map(change => ({
|
||||
...change,
|
||||
value:
|
||||
change.type === 'armor'
|
||||
? {
|
||||
...change.value,
|
||||
current: Math.min(change.value.current, newMax),
|
||||
max: newMax
|
||||
}
|
||||
: change.value
|
||||
}))
|
||||
];
|
||||
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